using System; using System.Collections.Generic; using System.Linq; using Chernobyl.Collections.Generic.Event; using Chernobyl.Dependency; using Chernobyl.Graphics.Polygon; using Chernobyl.Graphics.Polygon.Buffers; using Chernobyl.Readiness; using Microsoft.Xna.Framework.Graphics; namespace Chernobyl.Graphics.Xna.Controllers { /// /// An interface for working with the XNA mesh. /// public interface IXnaMesh : IBuffer { /// /// The XNA vertex buffer used to hold the mesh data. /// VertexBuffer XnaVertexBuffer { get; set; } } /// /// Representation of an XNA mesh (AKA vertex buffer). /// /// The type of data that is going to be stored in /// the buffer. This should be a struct that defines what data meshes /// contain using the attribute . public class XnaMesh : XnaBuffer, IXnaMesh where TData : struct { /// /// Initializes a new instance of the class. /// /// The instance that gives out services for use /// by this type and takes services from this type for use by other systems. /// The mesh element data to place in the mesh. public XnaMesh(IEventCollection services, TData[] meshElements) { Services = services; _initialMeshElements = meshElements; _count = (uint)meshElements.Length; services.Inject(this); } /// /// Gets the number of elements in this buffer. /// public override uint Count { get { return _count; } } /// /// Gets the size of this entire buffer in bytes. /// public override uint SizeInBytes { get { return (uint)XnaVertexBuffer.SizeInBytes; } } /// /// Sets the VertexDeclaration and XNA vertex buffer of the mesh /// elements on the XNA graphics device. /// public override void Apply() { GraphicsDevice.VertexDeclaration = MeshElementInfo.VertexDeclaration; GraphicsDevice.Vertices[0].SetSource(XnaVertexBuffer, 0, (int)MeshElementInfo.SizeInBytes); } /// /// Passes this XNA buffer to the render passed in so the render takes /// into account the data of the buffer. /// /// The render that should know about this buffer. public override void SetRender(XnaRender render) { render.Mesh = this; } /// /// Disposes of the XNA vertex buffer used by this mesh. /// public override void Dispose() { if (XnaVertexBuffer != null) XnaVertexBuffer.Dispose(); } /// /// Gets the data in the buffer. /// /// The array that is to be filled with the data in the buffer. /// The index in this buffer to start pulling the data from. /// The number of elements to pull from the buffer. /// Thrown if this /// has not been fully configured (i.e /// is false). Listen to the event /// to know when this is ready. public override void Get(out TData[] dataOut, uint startIndex, uint elementCount) { if (IsReady == false) throw new NotReadyException("The IBuffer data cannot be set " + "because the XnaMesh has not been fully configured."); if (elementCount == 0) throw new ArgumentException("The parameter \"elementCount\" is zero. You must copy over at least 1 element.", "elementCount"); dataOut = new TData[elementCount]; XnaVertexBuffer.GetData(dataOut, (int)startIndex, (int)elementCount); } /// /// The vertex buffer used to hold the mesh data. /// public VertexBuffer XnaVertexBuffer { get; set; } /// /// The graphics device used by this mesh element. /// [Inject] public GraphicsDevice GraphicsDevice { private get { return _graphicsDevice; } set { // If we are resetting the GraphicsDevice we'll need to grab a // copy of the mesh elements so that we can recreate the VertexBuffer // with the new GraphicsDevice if (XnaVertexBuffer != null) { _initialMeshElements = new TData[Count]; XnaVertexBuffer.GetData(_initialMeshElements); } _graphicsDevice = value; // create a vertex declaration Information info = GenerateInformation(DataType, Services); MeshElementInfo = info; // if the XNA vertex buffer was already created, dispose of it // to make room for the new one if (XnaVertexBuffer != null) XnaVertexBuffer.Dispose(); XnaVertexBuffer = new VertexBuffer(GraphicsDevice, (int)(MeshElementInfo.SizeInBytes * Count), BufferUsage.None); XnaVertexBuffer.SetData(_initialMeshElements); // We no longer need this copy of the indices. _initialMeshElements = null; IsReady = true; } } /// /// Information about the mesh element this buffer is using. /// public Information MeshElementInfo { get; set; } /// /// Generates the XNA specific mesh element information. /// /// The type of the mesh element to generate /// the information for. /// The instance that gives out services for use /// by this type and takes services from this type for use by other systems. /// The generated information. Information GenerateInformation(Type meshElementType, IEventCollection services) { IDictionary informationStore = services.OfType>().First(); // create the mesh element information, if it needs to be recreated. Information info = null; if (informationStore.TryGetValue(meshElementType, out info) == false) { // get information on the mesh element MeshElement.Information meshElementInformation = MeshElement.GenerateInformation(meshElementType); if (meshElementInformation == null) throw new Exception("The mesh element type " + meshElementType.FullName + "was not attributed with MeshElementComponents. " + "Please attribute this type before attempting to use it in a Mesh."); // generate the vertex elements List vertexElements = new List(meshElementInformation.Components.Count); foreach (MeshElementComponent component in meshElementInformation.Components) { // find the vertex element format VertexElementFormat format = VertexElementFormat.Unused; switch (component.Format) { case MeshElementFormat.Single: format = VertexElementFormat.Single; break; case MeshElementFormat.Vector2: format = VertexElementFormat.Vector2; break; case MeshElementFormat.Vector3: format = VertexElementFormat.Vector3; break; case MeshElementFormat.Vector4: format = VertexElementFormat.Vector4; break; case MeshElementFormat.Color: format = VertexElementFormat.Color; break; default: throw new Exception("Unknown MeshElementFormat in MeshElementType \"" + meshElementType.FullName + "\""); } // find the vertex element usage VertexElementUsage usage = VertexElementUsage.Position; switch (component.Usage) { case MeshElementUsage.Binormal: usage = VertexElementUsage.Binormal; break; case MeshElementUsage.Color: usage = VertexElementUsage.Color; break; case MeshElementUsage.Depth: usage = VertexElementUsage.Depth; break; case MeshElementUsage.Normal: usage = VertexElementUsage.Normal; break; case MeshElementUsage.PointSize: usage = VertexElementUsage.PointSize; break; case MeshElementUsage.Vertex: usage = VertexElementUsage.Position; break; case MeshElementUsage.Tangent: usage = VertexElementUsage.Tangent; break; case MeshElementUsage.TextureCoordinate: usage = VertexElementUsage.TextureCoordinate; break; default: throw new Exception("Unknown MeshElementUsage in MeshElementType \"" + meshElementType.FullName + "\""); } vertexElements.Add(new VertexElement(component.StreamIndex, component.Offset, format, VertexElementMethod.Default, usage, component.UsageIndex)); } info = new Information(new VertexDeclaration(GraphicsDevice, vertexElements.ToArray()), meshElementInformation); informationStore.Add(new KeyValuePair(meshElementType, info)); } return info; } /// /// The instance that gives out services for use by this type and takes /// services from this type for use by other systems. /// IEventCollection Services { get; set; } /// /// The mesh elements that this instance originally received in its /// constructor or null if they have been removed following initialization. /// TData[] _initialMeshElements; /// /// The backing field to . /// uint _count; /// /// The backing field to . /// GraphicsDevice _graphicsDevice; } /// /// Holds XNA specific MeshElement information along with /// normal MeshElement information provided by /// . /// public class Information : MeshElement.Information { /// /// Constructor. /// /// The vertex declaration for the mesh element. /// The information about the mesh element. public Information(VertexDeclaration vertexDecl, MeshElement.Information info) : base(info.Components, info.SizeInBytes) { VertexDeclaration = vertexDecl; } /// /// The vertex declaration for a single MeshElement. /// public VertexDeclaration VertexDeclaration { get; set; } } }