#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
}
}