#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 TerrainMesh.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.Battlescape;
#endregion
namespace ProjectXenocide.UI.Scenes.Battlescape
{
///
/// The 3D model of the landscape that combatants are going to fight on
///
public class TerrainMesh : IDisposable
{
#region IDisposable
///
/// 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)
{
DisposeBuffers();
if (basicEffectVertexDeclaration != null)
{
basicEffectVertexDeclaration.Dispose();
basicEffectVertexDeclaration = null;
}
}
}
#endregion IDisposable
///
/// Load/create the graphic resources needed by the terrain
///
/// the display
/// the terrain to build a mesh for
public void LoadContent(GraphicsDevice device, Terrain terrain)
{
textureAtlas = TextureAtlas.DefaultAtlas(device);
ConstructMesh(device, terrain);
basicEffectVertexDeclaration = new VertexDeclaration(
device, VertexPositionNormalTexture.VertexElements);
}
///
/// Set up a BasicEffect for drawing the mesh
///
/// effect to use to draw the mesh
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods",
Justification = "will throw if effect is null")]
public void ConfigureEffect(BasicEffect effect)
{
effect.DiffuseColor = new Vector3(1.0f, 1.0f, 1.0f);
effect.Texture = textureAtlas.Bitmap;
effect.TextureEnabled = true;
}
///
/// Draw the terrain on the device
///
/// Device to render the terrain
/// effect to use to draw the terrain
/// topmost level of terrain to draw
public void Draw(GraphicsDevice device, Effect effect, int topLevel)
{
device.VertexDeclaration = basicEffectVertexDeclaration;
for (int i = 0; i <= topLevel; ++i)
{
if (0 < numQuads[i])
{
device.Vertices[0].SetSource(vertexBuffers[i], 0, VertexPositionNormalTexture.SizeInBytes);
device.Indices = indexBuffers[i];
effect.Begin();
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
pass.Begin();
device.DrawIndexedPrimitives(
PrimitiveType.TriangleList,
0,
0,
numQuads[i] * 4,
0,
numQuads[i] * 2
);
pass.End();
}
effect.End();
}
}
}
///
/// Build the mesh we're going to draw on the display
///
/// Device to render the terrain
/// the terrain to build a mesh for
public void ConstructMesh(GraphicsDevice device, Terrain terrain)
{
numQuads.Clear();
DisposeBuffers();
List verts = new List();
List indexes = new List();
for (int y = 0; y < terrain.Levels; ++y)
{
verts.Clear();
indexes.Clear();
for (int z = 0; z < terrain.Length; ++z)
{
for (int x = 0; x < terrain.Width; ++x)
{
Vector3 pos = new Vector3(x, y, z);
// don't draw hidden cells
if (0 != (terrain.CellProperty(pos) & CellProperties.Seen))
{
for (int side = (int)Side.North; side <= (int)Side.Bottom; ++side)
{
AddFaceToMesh(pos, side, verts, indexes);
}
}
}
}
ConstructVertexBuffer(device, verts);
ConstructIndexBuffer(device, indexes);
}
}
///
/// Add a quad that represents the specifed face of specified cell to the mesh
///
/// position of cell
/// face of cell
/// the mesh's vertices
/// the mesh's indices
private void AddFaceToMesh(Vector3 pos, int side, List verts,
List indexes)
{
Terrain terrain = Xenocide.GameState.Battlescape.Terrain;
int textureId = terrain.GetFaceTexture(pos, (Side)side);
if (0 != textureId)
{
TextureAtlas.Coord uv = textureAtlas.TextureCoords[textureId];
// note when drawing south or east faces, need to flip texture because we're looking at back side of quad
// and if drawing ground, adjust height
switch ((Side)side)
{
case Side.East:
case Side.South:
uv.FlipHorizontal();
break;
case Side.Bottom:
pos += new Vector3(0, terrain.GroundHeight(pos), 0);
break;
}
verts.Add(MakeVertex(pos + offset[side, 0], normals[side], uv.LeftBottom));
verts.Add(MakeVertex(pos + offset[side, 1], normals[side], uv.LeftTop));
verts.Add(MakeVertex(pos + offset[side, 2], normals[side], uv.RightTop));
verts.Add(MakeVertex(pos + offset[side, 3], normals[side], uv.RightBottom));
BindVerticesToQuad(verts, indexes);
}
}
///
/// Construct a vertex element
///
/// position of vertex, in cell space
/// vertex's normal
/// vertex's uv texture co-ords
/// vertex element
private static VertexPositionNormalTexture MakeVertex(Vector3 pos, Vector3 normal, Vector2 texture)
{
return new VertexPositionNormalTexture(BattlescapeScene.CellToWorld(pos), normal, texture);
}
///
/// Bind the last 4 vertices in the supplied list into a quad
///
/// the quads must have been added in clockwise winding order
///
///
private static void BindVerticesToQuad(List verts, List indexes)
{
int i = (verts.Count - 4);
indexes.Add(i);
indexes.Add(++i);
indexes.Add(++i);
indexes.Add(i);
indexes.Add(++i);
indexes.Add(i - 3);
}
///
/// build vertex buffer used to draw the mesh
///
private void ConstructVertexBuffer(GraphicsDevice device, List verts)
{
numQuads.Add(verts.Count / 4);
if (0 < verts.Count)
{
vertexBuffers.Add(new VertexBuffer(
device,
VertexPositionNormalTexture.SizeInBytes * verts.Count,
BufferUsage.None
));
vertexBuffers[vertexBuffers.Count - 1].SetData(verts.ToArray());
}
else
{
vertexBuffers.Add(null);
}
}
///
/// Build indexed triangle list used to draw the mesh
///
private void ConstructIndexBuffer(GraphicsDevice device, List indexes)
{
if (0 < indexes.Count)
{
indexBuffers.Add(new IndexBuffer(
device,
sizeof(int) * indexes.Count,
BufferUsage.None,
IndexElementSize.ThirtyTwoBits
));
indexBuffers[indexBuffers.Count - 1].SetData(indexes.ToArray());
}
else
{
indexBuffers.Add(null);
}
}
/// Release the buffers cleanly
private void DisposeBuffers()
{
for (int i = 0; i < indexBuffers.Count; ++i)
{
if (null != indexBuffers[i])
{
indexBuffers[i].Dispose();
}
}
for (int i = 0; i < vertexBuffers.Count; ++i)
{
if (null != vertexBuffers[i])
{
vertexBuffers[i].Dispose();
}
}
indexBuffers.Clear();
vertexBuffers.Clear();
}
#region Fields
///
/// the triangles making up the meshs for each level
///
private List indexBuffers = new List();
///
/// The vertices making up the meshs for each level
///
private List vertexBuffers = new List();
///
/// The textures we will paint onto the mesh
///
private TextureAtlas textureAtlas;
///
/// Used to tell the Graphics system what the structure of the vertices in memory
///
private VertexDeclaration basicEffectVertexDeclaration;
///
/// Number of quads in each mesh
///
private List numQuads = new List();
///
/// Normal for each of the faces
///
private static readonly Vector3[] normals =
{
new Vector3(0, 0, 1), // north wall
new Vector3(-1, 0, 0), // east wall
new Vector3(1, 0, 0), // west wall
new Vector3(0, 0, -1), // south wall
new Vector3(0, 1, 0) // ground
};
///
/// The co-ordinates of the vertices of each face, relative to the cell's bottom north west corner
///
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1814:PreferJaggedArraysOverMultidimensional", MessageId = "Member",
Justification = "FxCop false positive")]
private static readonly Vector3[,] offset =
{
{ new Vector3(0, 0, 0), new Vector3(0, 1, 0), new Vector3(1, 1, 0), new Vector3(1, 0, 0) }, // north
{ new Vector3(1, 0, 0), new Vector3(1, 1, 0), new Vector3(1, 1, 1), new Vector3(1, 0, 1) }, // east
{ new Vector3(0, 0, 1), new Vector3(0, 1, 1), new Vector3(0, 1, 0), new Vector3(0, 0, 0) }, // west
{ new Vector3(1, 0, 1), new Vector3(1, 1, 1), new Vector3(0, 1, 1), new Vector3(0, 0, 1) }, // south
{ new Vector3(0, 0, 1), new Vector3(0, 0, 0), new Vector3(1, 0, 0), new Vector3(1, 0, 1) } // ground
};
#endregion Fields
}
}