#region Copyright /* -------------------------------------------------------------------------------- This source file is part of Xenocide by Project Xenocide Team For the latest info on Xenocide, see http://www.projectxenocide.com/ This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 2.5 License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/2.5/ or send a letter to Creative Commons, 543 Howard Street, 5th Floor, San Francisco, California, 94105, USA. -------------------------------------------------------------------------------- */ /* * @file StatisticsScene.cs - based on FacilityScene.cs * @date Created: 2007/12/29 * @author File creator: Oded Coster * @author Credits: dteviot */ #endregion #region Using Statements using System; using System.Collections.Generic; using System.Text; using System.Diagnostics; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Content; using ProjectXenocide.Model.Geoscape.Outposts; using ProjectXenocide.Utils; using ProjectXenocide.Model; using ProjectXenocide.UI.Scenes.Facility; #endregion namespace ProjectXenocide.UI.Scenes.Statistics { /// /// Shows the statistics for Xcorps in a graph /// public class StatisticsScene : IDisposable { /// /// Constructor /// /// Month we are in /// The selected series to render public StatisticsScene(int currentMonth, IList selectedSerieses) { thisMonth = currentMonth; dataset = selectedSerieses; this.cameraPosition = ComputeCameraPosition(); } /// /// Implement IDisposable /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Implement IDisposable /// /// false when called from a finalizer protected virtual void Dispose(bool disposing) { if (disposing) { if (basicEffect != null) { basicEffect.Dispose(); basicEffect = null; } if (spriteBatch != null) { spriteBatch.Dispose(); spriteBatch = null; } if (content != null) { content.Dispose(); content = null; } if (grid != null) { grid.Dispose(); grid = null; } if (graph != null) { graph.Dispose(); graph = null; } } } /// /// Load the graphic content of the scene /// /// the display public void LoadContent(GraphicsDevice device) { { basicEffect = new BasicEffect(device, null); grid.LoadContent(device, new Grid(gridCellsWidth, gridCellsHeight)); spriteBatch = new SpriteBatch(Xenocide.Instance.GraphicsDevice); font = content.Load(@"Content\SpriteFont1"); } } /// /// Render scene /// /// Device to use for render /// Where to draw the scene on the display [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods", Justification = "Will throw exception if device is null")] public void Draw(GraphicsDevice device, CeGui.Rect sceneWindow) { if ((null == graph) && (0 < GetMaxVisibleValue(dataset))) { graph = new LineMesh(); graph.LoadContent(device, new Graph(thisMonth, dataset, gridCellsHeight)); } // only draw in area we've been told to Viewport oldview = device.Viewport; device.Viewport = CalcViewportForSceneWindow(sceneWindow, device.Viewport); basicEffect.Projection = GetProjectionMatrix(AspectRatio); Matrix viewMatrix = Matrix.CreateLookAt( cameraPosition, Vector3.Zero, Vector3.Forward ); basicEffect.View = viewMatrix; // draw the grid grid.ConfigureEffect(basicEffect); grid.Draw(device, basicEffect); // draw the graph (only if there is something to draw) if (GetMaxVisibleValue(dataset) > 0) { graph.Draw(device, basicEffect); } // restore viewport device.Viewport = oldview; // write out the axis lables spriteBatch.Begin(SpriteBlendMode.AlphaBlend); LabelVerticalAxis(); LabelHorizontalAxis(); spriteBatch.End(); } /// /// Compute the projection matrix for the scene /// /// window's aspect ratio /// The calculated projection matrix private static Matrix GetProjectionMatrix(float aspectRatio) { return Matrix.CreatePerspectiveFieldOfView( ViewAngle, aspectRatio, nearClipPlane, farClipPlane); } /// /// Deterime where camera is located. /// Its above center of graph, just high enough to see all the graph /// /// Position for the camera private static Vector3 ComputeCameraPosition() { // assumes aspect ratio is 1.0. // problem is, at this point in time, it's not known. float oposite = MathHelper.Max(gridCellsHeight, gridCellsWidth) * 0.5f; float adjacent = (float)(oposite / Math.Tan(ViewAngle * 0.5)); return new Vector3(0.0f, adjacent + 1.0f, 0.0f); } /// /// convert Window's co-ordinates to viewport co-ordinates /// /// Window co-ords to translate /// The current viewport /// Viewport co-ordinates private Viewport CalcViewportForSceneWindow(CeGui.Rect windowCoords, Viewport viewport) { int fullHeight = viewport.Height; int fullWidth = viewport.Width; viewport.X = (int)(fullWidth * windowCoords.Left); viewport.Y = (int)(fullHeight * windowCoords.Top); viewport.Width = (int)(fullWidth * windowCoords.Width); viewport.Height = (int)(fullHeight * windowCoords.Height); // compute the aspect ratio while we're about it aspectRatio = (float)viewport.Width / (float)viewport.Height; return viewport; } /// /// Write out month names on the "x" axis /// private void LabelHorizontalAxis() { int month = thisMonth + 1; int startX = 85; for (int i = 0; i < 12; ++i) { string output = Util.LoadString(monthNames[month]); Vector2 pos = font.MeasureString(output); pos.Y = 500; pos.X = startX + (43 * i); spriteBatch.DrawString(font, output, pos, Color.Purple); month++; month %= 12; } } /// /// Write out the current values on the "y" axis /// private void LabelVerticalAxis() { // draw scale of graph near y axis, using calculated max int cellStep = GetMaxVisibleValue(dataset) / 10; for (int i = 0; i < 10; ++i) { string output = Util.ToString(i * cellStep); Vector2 pos = font.MeasureString(output); pos.X = 90 - pos.X; pos.Y = 486 - (i * 43); spriteBatch.DrawString(font, output, pos, Color.Purple); } } /// /// Finds the maximum value within all visible serieses /// /// The maximum value public static int GetMaxVisibleValue(IList data) { int maxValue = 0; foreach (Series series in data) { if (series.Show) { for (int i = 0; i < 12; i++) { if (maxValue < series.ScaledData(i)) { maxValue = series.ScaledData(i); } } } } return maxValue; } #region Fields /// /// The position of the camera, in polar co-ordinates. /// at current time, camera is fixed /// private Vector3 cameraPosition; /// /// The viewport's aspect ratio /// protected float AspectRatio { get { return aspectRatio; } } /// /// The viewport's aspect ratio /// private float aspectRatio; /// /// The basic effect used for rendering /// private BasicEffect basicEffect; /// /// Graph that shows the current series /// private LineMesh graph = new LineMesh(); /// /// Grid on which graph is superimposed /// private LineMesh grid = new LineMesh(); /// /// Used to draw the sprites /// private SpriteBatch spriteBatch; /// /// Font to draw all text in the graph /// private SpriteFont font; /// /// Needed so that we can load the sprite font /// private ContentManager content = new ContentManager(Xenocide.Instance.Services); /// /// The names of the months (used as graph identifiers) /// private static readonly String[] monthNames = { "SCREEN_STATISTICS_COLUMN_JANUARY", "SCREEN_STATISTICS_COLUMN_FEBRUARY", "SCREEN_STATISTICS_COLUMN_MARCH", "SCREEN_STATISTICS_COLUMN_APRIL", "SCREEN_STATISTICS_COLUMN_MAY", "SCREEN_STATISTICS_COLUMN_JUNE", "SCREEN_STATISTICS_COLUMN_JULY", "SCREEN_STATISTICS_COLUMN_AUGUST", "SCREEN_STATISTICS_COLUMN_SEPTEMBER", "SCREEN_STATISTICS_COLUMN_OCTOBER", "SCREEN_STATISTICS_COLUMN_NOVEMBER", "SCREEN_STATISTICS_COLUMN_DECEMBER" }; /// /// The current month /// private int thisMonth; /// /// The dataset used for rendering the graph /// public IList DataSet { get { return dataset; } set { dataset = value; graph = null; } } /// /// The set of data to graph /// private IList dataset; /// ///Size of grid (in cells) along width of display /// private const int gridCellsWidth = 11; /// /// Size of grid (in cells) along height of display /// private const int gridCellsHeight = 9; #endregion #region Constant definitions /// /// Used in constructing viewing fustrum /// private const float nearClipPlane = 0.1f; /// /// Used in constructing viewing fustrum /// private const float farClipPlane = 20.0f; /// /// Used in constructing viewing fustrum /// private const float ViewAngle = (float)Math.PI / 4.0f; // 45 degres #endregion } }