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