using System; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework; using System.Linq; namespace TrueCraft.Client.Rendering { /// /// Represents an indexed collection of data that can be rendered. /// public class Mesh : IDisposable { /// /// The maximum number of submeshes stored in a single mesh. /// public const int SubmeshLimit = 16; // Used for synchronous access to the graphics device. private static readonly object _syncLock = new object(); private TrueCraftGame _game; private GraphicsDevice _graphicsDevice; protected VertexBuffer _vertices; // ChunkMesh uses these but external classes shouldn't, so I've made them protected. protected IndexBuffer[] _indices; private bool _recalculateBounds; // Whether this mesh should recalculate its bounding box when changed. /// /// Gets or sets the vertices in this mesh. /// public VertexPositionNormalColorTexture[] Vertices { set { if (_vertices != null) _vertices.Dispose(); _game.PendingMainThreadActions.Add(() => { _vertices = new VertexBuffer(_graphicsDevice, VertexPositionNormalColorTexture.VertexDeclaration, (value.Length + 1), BufferUsage.WriteOnly); _vertices.SetData(value); }); if (_recalculateBounds) BoundingBox = RecalculateBounds(value); } } /// /// Gets the bounding box for this mesh. /// public BoundingBox BoundingBox { get; private set; } /// /// Gets whether this mesh is disposed of. /// public bool IsDisposed { get; private set; } /// /// Creates a new mesh. /// /// The graphics device to store the mesh on. /// The number of submeshes in the mesh. /// Whether the mesh should recalculate its bounding box when changed. public Mesh(TrueCraftGame game, int submeshes = 1, bool recalculateBounds = true) { if ((submeshes < 0) || (submeshes >= Mesh.SubmeshLimit)) throw new ArgumentOutOfRangeException(); _game = game; _graphicsDevice = game.GraphicsDevice; _indices = new IndexBuffer[submeshes]; _recalculateBounds = recalculateBounds; } /// /// Creates a new mesh. /// /// The graphics device to store the mesh on. /// The vertices in the mesh. /// The number of submeshes in the mesh. /// Whether the mesh should recalculate its bounding box when changed. public Mesh(TrueCraftGame game, VertexPositionNormalColorTexture[] vertices, int submeshes = 1, bool recalculateBounds = true) : this(game, submeshes, recalculateBounds) { Vertices = vertices; } /// /// Creates a new mesh. /// /// The graphics device to store the mesh on. /// The vertices in the mesh. /// The first (and only) submesh in the mesh. /// Whether the mesh should recalculate its bounding box when changed. public Mesh(TrueCraftGame game, VertexPositionNormalColorTexture[] vertices, int[] indices, bool recalculateBounds = true) : this(game, 1, recalculateBounds) { Vertices = vertices; SetSubmesh(0, indices); } /// /// Sets a submesh in this mesh. /// /// The submesh index. /// The indices for the submesh. public void SetSubmesh(int index, int[] indices) { if ((index < 0) || (index > _indices.Length)) throw new ArgumentOutOfRangeException(); lock (_syncLock) { if (_indices[index] != null) _indices[index].Dispose(); _game.PendingMainThreadActions.Add(() => { _indices[index] = new IndexBuffer(_graphicsDevice, typeof(int), (indices.Length + 1), BufferUsage.WriteOnly); _indices[index].SetData(indices); }); } } /// /// Draws this mesh using the specified effect. /// /// The effect to use. public void Draw(Effect effect) { if (effect == null) throw new ArgumentException(); for (int i = 0; i < _indices.Length; i++) Draw(effect, i); } /// /// Draws a submesh in this mesh using the specified effect. /// /// The effect to use. /// The submesh index. public void Draw(Effect effect, int index) { if (effect == null) throw new ArgumentException(); if ((index < 0) || (index > _indices.Length)) throw new ArgumentOutOfRangeException(); if (_vertices == null || _vertices.IsDisposed || _indices[index] == null || _indices[index].IsDisposed || _indices[index].IndexCount < 3) return; // Invalid state for rendering, just return. effect.GraphicsDevice.SetVertexBuffer(_vertices); effect.GraphicsDevice.Indices = _indices[index]; foreach (var pass in effect.CurrentTechnique.Passes) { pass.Apply(); effect.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, _indices[index].IndexCount, 0, _indices[index].IndexCount / 3); } } /// /// Returns the total vertices in this mesh. /// /// public int GetTotalVertices() { if (_vertices == null) return 0; lock (_syncLock) return _vertices.VertexCount; } /// /// Returns the total indices for all the submeshes in this mesh. /// /// public int GetTotalIndices() { lock (_syncLock) { int sum = 0; foreach (var element in _indices) sum += (element != null) ? element.IndexCount : 0; return sum; } } /// /// Recalculates the bounding box for this mesh. /// /// The vertices in this mesh. /// protected virtual BoundingBox RecalculateBounds(VertexPositionNormalColorTexture[] vertices) { return new BoundingBox( vertices.Select(v => v.Position).OrderBy(v => v.Length()).First(), vertices.Select(v => v.Position).OrderByDescending(v => v.Length()).First()); } /// /// Disposes of this mesh. /// public void Dispose() { if (IsDisposed) return; Dispose(true); GC.SuppressFinalize(this); } /// /// Disposes of this mesh. /// /// Whether Dispose() called the method. protected virtual void Dispose(bool disposing) { if (disposing) { _graphicsDevice = null; // Lose the reference to our graphics device. if (_vertices != null) _vertices.Dispose(); foreach (var element in _indices) { if (element != null) element.Dispose(); } } } /// /// Finalizes this mesh. /// ~Mesh() { Dispose(false); } } }