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