#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 BattlescapeScene.cs * @date Created: 2008/01/01 * @author File creator: David Teviotdale * @author Credits: none */ #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; using ProjectXenocide.Model.Battlescape; using ProjectXenocide.Model.Battlescape.Combatants; using ProjectXenocide.UI.Scenes.Common; using ProjectXenocide.UI.Scenes.Battlescape; #endregion namespace ProjectXenocide.UI.Scenes.Battlescape { /// /// This is the 3D scene that appears on the Battlescape screen /// public class BattlescapeScene : IDisposable { /// /// Constructor /// public BattlescapeScene() { } /// /// 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 (content != null) { content.Dispose(); content = null; } if (terrainMesh != null) { terrainMesh.Dispose(); terrainMesh = null; } if (cellCursor != null) { cellCursor.Dispose(); cellCursor = null; } if (pathLine != null) { pathLine.Dispose(); pathLine = null; } } } /// /// Load the graphic content of the scene /// /// device to render to /// the battlescape [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods", Justification="Will throw if scape is null")] public void LoadContent(GraphicsDevice device, Battle scape) { { battlescape = scape; basicEffect = new BasicEffect(device, null); pathLine.LoadContent(device, pathBuilder); cellCursor.LoadContent(device, new CellCursorLineMeshBuilder(wantedCellCursorColor)); terrainMesh.LoadContent(device, battlescape.Terrain); combatantMeshes.LoadContent(content, battlescape); projectileMesh.LoadContent(content); facility.LoadContent(content); } } /// /// Perform processing which updates the scene /// /// snapshot of timing values public void Update(GameTime gameTime) { camera.Update(gameTime); } /// /// Render the scene to the display /// /// device to render to /// child window to render scene to /// topmost level of terrain to draw /// cell of terrian the cursor is over [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods", Justification="will throw if device is null")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2233:OperationsShouldNotOverflow", MessageId = "topLevel*8", Justification="value won't overflow")] public void Draw(GraphicsDevice device, CeGui.Rect sceneWindow, int topLevel, Vector3 cursorPosition) { // we're only interested in cell it's over cursorPosition = RoundToCell(cursorPosition); // only draw in area we've been told to Viewport oldview = device.Viewport; device.Viewport = CalcViewportForSceneWindow(sceneWindow, device.Viewport); SetupBasicEffect(device); // draw suggested path for selected unit pathLine.ConfigureEffect(basicEffect); DrawPath(device); // Draw Terrain terrainMesh.ConfigureEffect(basicEffect); basicEffect.VertexColorEnabled = false; terrainMesh.Draw(device, basicEffect, topLevel); // facility.Draw(basicEffect); // Draw combatants DrawCombatants(); // Draw projectile (if there is one) if (null != battlescape.Trajectory) { projectileMesh.Draw(battlescape.Trajectory, basicEffect); } // Draw Cell Cursor //... Calculate World transform to put the cell Cursor at the right place cellCursor.ConfigureEffect(basicEffect); DrawCellCursor(device, topLevel, cursorPosition); // ToDo: Draw everything else // restore viewport device.Viewport = oldview; } /// /// Convert a position in the viewport to cell location on battlescape /// /// The position in the viewport (in relative co-ords) /// level of the battlescape to find intersection at /// corresponding position, or undefined if not on Battlescape public Vector3 WindowToBattlescapeCell(System.Drawing.PointF coords, int level) { // convert screen position to point on near clipping plane in view space float x = TanViewAngle * (coords.X - 0.5f) * aspectRatio; float y = TanViewAngle * (0.5f - coords.Y); Vector3 viewSpace = new Vector3(x, y, -1); // convert vector into worldspace Vector3 direction = Vector3.TransformNormal(viewSpace, Matrix.Invert(camera.View)); direction.Normalize(); // if we're looking upwards, there is no intercept if (-0.01 < direction.Y) { return new Vector3(float.NaN, float.NaN, float.NaN); } // and project to level Vector3 intercept = camera.Position; float scale = ((level * LevelHeight) - intercept.Y) / direction.Y; intercept += (direction * scale); intercept.Y = level; return intercept; } /// /// Move the camera /// /// relative displacement public void MoveCamera(Vector3 delta) { camera.Position += delta; } /// /// Convert a position in cell co-ordinates to "world" co-ordinates /// /// position in "Cell" co-ordiantes /// position in "world" co-ordinates public static Vector3 CellToWorld(Vector3 v) { // Basically, each level of battlescape is 2.5 units high return new Vector3(v.X, v.Y * LevelHeight, v.Z); } /// /// Put basic effect into correct state to render the scene /// /// device to render to private void SetupBasicEffect(GraphicsDevice device) { basicEffect.View = camera.View; basicEffect.World = Matrix.Identity; basicEffect.DiffuseColor = new Vector3(1.0f, 1.0f, 1.0f); basicEffect.Projection = Matrix.CreatePerspectiveFieldOfView(viewAngle, aspectRatio, 1.0f, 100.0f); device.RenderState.CullMode = CullMode.CullCounterClockwiseFace; device.RenderState.DepthBufferEnable = true; device.RenderState.DepthBufferWriteEnable = true; } /// /// 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; } /// /// Convert vector's components into integers (give us a cell index) /// /// vector to process /// Rounded vecor public static Vector3 RoundToCell(Vector3 v) { return new Vector3((float)Math.Truncate(v.X), (float)Math.Round(v.Y), (float)Math.Truncate(v.Z)); } /// /// Draw the combatants /// private void DrawCombatants() { foreach (Team team in battlescape.Teams) { foreach (Combatant combatant in team.Combatants) { if (InViewingFustrum(combatant.Position)) { bool visible = IsVisibleToXCorp(combatant); if (visible || Xenocide.StaticTables.StartSettings.Cheats.ShowAllAliens) { combatantMeshes.Draw(combatant, basicEffect, visible); } } } } } /// Is combatant currently visible to the X-Corp forces? /// to check /// true if visible private static bool IsVisibleToXCorp(Combatant combatant) { return (combatant.TeamId != Team.Aliens) || (0 != combatant.OponentsViewing); } /// Checks if a cell is within the volume being drawn on the display /// position of cell, in cell co-ords /// true if cell and it's contents should be drawn on screen [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "position", Justification = "ToDo: function needs implementation")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "ToDo: function needs implementation")] private bool InViewingFustrum(Vector3 position) { //ToDo: implement return true; } /// Draw selected combatant's proposed path on battlescape /// device to render to private void DrawPath(GraphicsDevice device) { if (showPath) { if (pathBuilder.Dirty) { pathLine.BuildMesh(device, pathBuilder); } pathLine.Draw(device, basicEffect); } } /// Draw selected combatant's proposed path on battlescape /// device to render to /// topmost level of terrain to draw /// cell of terrian the cursor is over private void DrawCellCursor(GraphicsDevice device, int topLevel, Vector3 cursorPosition) { if (wantedCellCursorColor != currentCellCursorColor) { cellCursor.BuildMesh(device, new CellCursorLineMeshBuilder(wantedCellCursorColor)); currentCellCursorColor = wantedCellCursorColor; } basicEffect.World = Matrix.CreateTranslation(CellToWorld(cursorPosition)); cellCursor.Draw(device, basicEffect, 12 + (topLevel * 8)); } #region Fields /// Each "cell" of the battlescape is 2.5 units high (and 1 unit wide and long) public const float LevelHeight = 2.5f; /// Draw a Path on the screen? public bool ShowPath { get { return showPath; } set { showPath = value; } } /// Show pathfinder output public PathMeshBuilder PathMeshBuilder { get { return pathBuilder; } } /// Color we want to draw the CellCursor with public Color WantedCellCursorColor { get { return wantedCellCursorColor; } set { wantedCellCursorColor = value; } } /// Bullet (if any) in flight public ProjectileMesh ProjectileMesh { get { return projectileMesh; } } /// Aspect ratio of the window showing this scene private float aspectRatio; /// Used to define projection matrix private const float viewAngle = MathHelper.PiOver4; /// Used to convert from Screen space to view space, for picking private static readonly float TanViewAngle = (float)(Math.Tan(viewAngle * 0.5) * 2.0); /// Used to render the scene private BasicEffect basicEffect; /// The terrain private TerrainMesh terrainMesh = new TerrainMesh(); /// Camera used to view the scene private Camera camera = new Camera(); /// Show pathfinder output private LineMesh pathLine = new LineMesh(); /// Show pathfinder output private PathMeshBuilder pathBuilder = new PathMeshBuilder(); /// Draw a Path on the screen? private bool showPath; /// Simple cube, marking the cell the mouse cursor is currently over private LineMesh cellCursor = new LineMesh(); /// The battlescape private Battle battlescape; /// The 3D models for the combatants private CombatantMeshes combatantMeshes = new CombatantMeshes(); /// Will want to unload the content when we close this screen private ContentManager content = new ContentManager(Xenocide.Instance.Services); /// Color we want to draw the CellCursor with private Color wantedCellCursorColor = Color.Blue; /// Color we the CellCursor is currently set up as private Color currentCellCursorColor = Color.Blue; /// Bullet (if any) in flight private ProjectileMesh projectileMesh = new ProjectileMesh(); /// 3D model of X-Corp facility to draw on battlescape private FacilityMesh facility = new FacilityMesh(); #endregion Fields } }