Generic Mesh
From XNAWiki
Much of this is designed for XNA 3.1, but I don't see why it couldn't be ported to XNA 4.0 with minimum modification.
Required supporting code.
VertexElementAttribute - used to create vertex declarations by marking up a vertex structure with VertexElement attributes.
/// <summary>
/// An attribute to use inside of vertex structures to mark the data that will be streamed to
/// the video card at draw time. The static methods provide a means of finding or building the
/// VertexElements array and VertexDeclaration depending on the vertex structure's contents.
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
public sealed class VertexElementAttribute : Attribute
{
static Dictionary<Type, VertexElement[]> ms_cachedElements = new Dictionary<Type, VertexElement[]>();
static Dictionary<Type, VertexElementFormat> ms_format = new Dictionary<Type, VertexElementFormat>();
static VertexElementAttribute()
{
// 32 bit floating point types
ms_format[typeof(float)] = VertexElementFormat.Single;
ms_format[typeof(Vector2)] = VertexElementFormat.Vector2;
ms_format[typeof(Vector3)] = VertexElementFormat.Vector3;
ms_format[typeof(Vector4)] = VertexElementFormat.Vector4;
// 16 bit floating point types
ms_format[typeof(HalfVector2)] = VertexElementFormat.HalfVector2;
ms_format[typeof(HalfVector4)] = VertexElementFormat.HalfVector4;
// 64 bpp unsigned normalized
ms_format[typeof(Rgba64)] = VertexElementFormat.Rgba64;
// 32 bpp unsigned normalized
ms_format[typeof(Color)] = VertexElementFormat.Color;
ms_format[typeof(Rgba32)] = VertexElementFormat.Rgba32;
ms_format[typeof(Rg32)] = VertexElementFormat.Rg32;
// signed normalized
ms_format[typeof(NormalizedShort2)] = VertexElementFormat.NormalizedShort2;
ms_format[typeof(NormalizedShort4)] = VertexElementFormat.NormalizedShort4;
ms_format[typeof(Normalized101010)] = VertexElementFormat.Normalized101010;
// signed integer
ms_format[typeof(Short2)] = VertexElementFormat.Short2;
ms_format[typeof(Short4)] = VertexElementFormat.Short4;
// unsigned integer
ms_format[typeof(Byte4)] = VertexElementFormat.Byte4;
ms_format[typeof(UInt101010)] = VertexElementFormat.UInt101010;
}
/// <summary>
/// Creates the vertex elements for a given vertex type which either impliments
/// VertexElementAttributes or contains a VertexElements field/property.
/// </summary>
public static VertexElement[] GetVertexElements(Type vertexType)
{
// the type must be a value type for it to be accepted by a vertex buffer
if (!vertexType.IsValueType)
throw new ArgumentException("Only value types (structs) are valid to be " +
"placed within a vertex buffer.",
"vertexType");
// if the type is already cached, pull it from the cache
if (ms_cachedElements.ContainsKey(vertexType))
return ms_cachedElements[vertexType];
else
{
// check if the type has a VertexElements array defined, if so cache it so that
// it will not need to be reflected next time
FieldInfo fieldVertexElements = vertexType.GetField("VertexElements");
if (fieldVertexElements != null)
{
ms_cachedElements[vertexType] = (VertexElement[])fieldVertexElements.GetValue(null);
return ms_cachedElements[vertexType];
}
PropertyInfo propVertexElements = vertexType.GetProperty("VertexElements");
if (propVertexElements != null)
{
ms_cachedElements[vertexType] = (VertexElement[])propVertexElements.GetValue(null, null);
return ms_cachedElements[vertexType];
}
}
// if it gets this far build the vertex elements for the type based on attributes
List<VertexElementAttribute> objAttributes = new List<VertexElementAttribute>();
GetVertexAttributes(vertexType, objAttributes);
// need the attribute mark-ups to do anything with the data at this point
if (objAttributes.Count < 1)
throw new ArgumentException("The type provided does not expose a VertexElements " +
"static field/property, nor does it have any fields " +
"marked with the VertexElementAttribute."
, "vertexType");
// the following is to calculate the usage index, example POSITION0, POSITION1, etc...
List<VertexElement> elements = new List<VertexElement>();
Dictionary<VertexElementUsage, int> usages = new Dictionary<VertexElementUsage, int>();
for (int a = 0; a < objAttributes.Count; a++)
{
// if the element usage counter doen't appear then add it as 0 index
if (!usages.ContainsKey(objAttributes[a].Usage))
usages.Add(objAttributes[a].Usage, 0);
// create the vertex element and increment the usage counter for it
elements.Add(new VertexElement((short)objAttributes[a].Stream, (short)objAttributes[a].Offset,
objAttributes[a].Format, objAttributes[a].Method,
objAttributes[a].Usage, (byte)usages[objAttributes[a].Usage]++));
}
// cache the data gathered here
VertexElement[] elementArray = elements.ToArray();
ms_cachedElements.Add(vertexType, elementArray);
return elementArray;
}
/// <summary>
/// Creates the vertex declaration for a given vertex type which either impliments
/// VertexElementAttributes or contains a VertexElements field/property.
/// </summary>
public static VertexDeclaration CreateVertexDeclaration(GraphicsDevice device, Type vertexType)
{
return new VertexDeclaration(device, GetVertexElements(vertexType));
}
static VertexElementAttribute[] GetVertexAttributes(VertexElement[] elements)
{
VertexElementAttribute[] attributes = new VertexElementAttribute[elements.Length];
for (int i = 0; i < elements.Length; i++)
{
attributes[i] = new VertexElementAttribute(elements[i].VertexElementUsage)
{
Format = elements[i].VertexElementFormat,
Method = elements[i].VertexElementMethod,
Offset = elements[i].Offset,
Stream = elements[i].Stream,
Usage = elements[i].VertexElementUsage
};
}
return attributes;
}
static void GetVertexAttributes(Type vertexType, List<VertexElementAttribute> objAttributes)
{
FieldInfo[] fields = vertexType.GetFields(BindingFlags.NonPublic |
BindingFlags.Public |
BindingFlags.Instance);
for (int f = 0; f < fields.Length; f++)
{
// attempt to retrieve the vertex element attribute from the current field
object[] attributes = fields[f].GetCustomAttributes(typeof(VertexElementAttribute), false);
// if the open and close markers aren't -1 then it's a property link,
// unroll the property to find the element attribute data
int openIndex = fields[f].Name.IndexOf('<'),
closeIndex = fields[f].Name.IndexOf('>');
if (openIndex != -1 && closeIndex != -1)
{
string propertyName = fields[f].Name.Substring(openIndex + 1, closeIndex - openIndex - 1);
PropertyInfo property = vertexType.GetProperty(propertyName, fields[f].FieldType);
if (property != null)
attributes = property.GetCustomAttributes(typeof(VertexElementAttribute), false);
}
// if the length of the attributes is less than 1, then this field doesn't have the VertexElementAttribute
if (attributes.Length > 0)
{
// the following is to map Unused formats into their respective proper formats,
// otherwise the byte offsets wont match up and wreck havok on the gpu streamers
VertexElementAttribute vertexElementAttribute = (VertexElementAttribute)attributes[0];
if (vertexElementAttribute.Format == VertexElementFormat.Unused &&
ms_format.ContainsKey(fields[f].FieldType))
vertexElementAttribute.Format = ms_format[fields[f].FieldType];
vertexElementAttribute.Offset = Marshal.OffsetOf(vertexType, fields[f].Name).ToInt32();
objAttributes.Add(vertexElementAttribute);
}
else
{
// recurse into sub-types
Type fieldType = fields[f].FieldType;
if (ms_cachedElements.ContainsKey(fieldType))
objAttributes.AddRange(GetVertexAttributes(ms_cachedElements[fieldType]));
else if (fieldType.IsValueType && !fieldType.IsPrimitive &&
fieldType.Namespace != null &&
!fieldType.Namespace.StartsWith("System.") &&
!fieldType.Namespace.StartsWith("Microsoft."))
GetVertexAttributes(fieldType, objAttributes);
}
}
}
/// <summary>
/// Stream number or index of use.
/// </summary>
public int Stream { get; set; }
/// <summary>
/// Offset in bytes from the start of the stream to this element's position.
/// </summary>
public int Offset { get; set; }
/// <summary>
/// The method that the video card will use to interpolate over the vertices.
/// </summary>
public VertexElementMethod Method { get; set; }
/// <summary>
/// The format of data that will be read by the attribute
/// </summary>
public VertexElementFormat Format { get; set; }
/// <summary>
/// The format of data that will be output by the attribute
/// </summary>
public VertexElementUsage Usage { get; set; }
/// <summary>
/// Create a new VertexElementAttribute.
/// </summary>
/// <param name="usage">The format of data that will be read by the attribute.</param>
public VertexElementAttribute(VertexElementUsage usage)
{
Usage = usage;
Format = VertexElementFormat.Unused;
}
/// <summary>
/// Create a new VertexElementAttribute.
/// </summary>
/// <param name="usage">The format of data that will be read by the attribute.</param>
/// <param name="format">The format of data that will be output by the attribute.</param>
public VertexElementAttribute(VertexElementUsage usage, VertexElementFormat format)
{
Usage = usage;
Format = format;
}
}RenderOperation - a handy base class for establishing common render functionality.
/// <summary>
/// A delegate used for per-pass callbacks from a render operation.
/// </summary>
/// <param name="pass">The effect pass that will be performed after the callback.</param>
/// <param name="renderOp">The render operation that is being drawn.</param>
public delegate void EffectPassCallback(EffectPass pass, RenderOperation renderOp);
/// <summary>
/// An abstract class which handles the basic function structure for an object which can be rendered.
/// </summary>
abstract public class RenderOperation : IDisposable
{
/// <summary>
/// Load graphical content this render operation requires.
/// </summary>
/// <param name="device"></param>
abstract public void LoadContent(GraphicsDevice device);
/// <summary>
/// Unload any graphical content this render operation has loaded.
/// </summary>
abstract public void UnloadContent();
/// <summary>
/// Called before this render operation draws into the buffers to ensure all preparations
/// for rendering are completed successfully.
/// </summary>
/// <param name="device"></param>
/// <returns>True if it's ok to perform the render, false if any problems were encountered
/// that will prevent this object from being rendered properly.</returns>
abstract protected bool PreRender(GraphicsDevice device);
/// <summary>
/// Called when this render operation should draw it's contents.
/// </summary>
/// <param name="device"></param>
abstract protected void Render(GraphicsDevice device);
/// <summary>
/// Called after rendering has completed.
/// </summary>
/// <param name="device"></param>
abstract protected void PostRender(GraphicsDevice device);
/// <summary>
/// Draw the contents of this render operation onto the current render target.
/// </summary>
/// <param name="device"></param>
public void Draw(GraphicsDevice device)
{
try
{
if (PreRender(device))
{
Render(device);
PostRender(device);
}
}
catch (Exception e) { throw e; }
}
/// <summary>
/// Draw the contents of this render operation onto the current render target.
/// </summary>
/// <param name="effect"></param>
public void Draw(Effect effect)
{
Draw(effect, SaveStateMode.None);
}
/// <summary>
/// Draw the contents of this render operation onto the current render target.
/// </summary>
/// <param name="effect"></param>
/// <param name="saveStateMode"></param>
public void Draw(Effect effect, SaveStateMode saveStateMode)
{
try
{
if (PreRender(effect.GraphicsDevice))
{
effect.Begin(saveStateMode);
for (int i = 0; i < effect.CurrentTechnique.Passes.Count; i++)
{
effect.CurrentTechnique.Passes[i].Begin();
Render(effect.GraphicsDevice);
effect.CurrentTechnique.Passes[i].End();
}
effect.End();
PostRender(effect.GraphicsDevice);
}
}
catch (Exception e) { throw e; }
}
/// <summary>
/// Draw the contents of this render operation onto the current render target.
/// </summary>
/// <param name="effect"></param>
/// <param name="callback"></param>
public void Draw(Effect effect, EffectPassCallback callback)
{
Draw(effect, SaveStateMode.None, callback);
}
/// <summary>
/// Draw the contents of this render operation onto the current render target.
/// </summary>
/// <param name="effect"></param>
/// <param name="saveStateMode"></param>
/// <param name="callback"></param>
public void Draw(Effect effect, SaveStateMode saveStateMode, EffectPassCallback callback)
{
try
{
if (PreRender(effect.GraphicsDevice))
{
effect.Begin(saveStateMode);
for (int i = 0; i < effect.CurrentTechnique.Passes.Count; i++)
{
callback(effect.CurrentTechnique.Passes[i], this);
effect.CurrentTechnique.Passes[i].Begin();
Render(effect.GraphicsDevice);
effect.CurrentTechnique.Passes[i].End();
}
effect.End();
PostRender(effect.GraphicsDevice);
}
}
catch (Exception e) { throw e; }
}
#region IDisposable Members
~RenderOperation()
{
Dispose(false);
}
/// <summary>
/// Dispose of this render operation and all of the data it has loaded.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Called when this render operation is being disposed of.
/// </summary>
/// <param name="disposing">Should managed resources be disposed of as well?</param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
UnloadContent();
}
}
#endregion
}Mesh<VertexType> generic class, for non-indexed meshes. Primitive count will be determined automatically based on the vertex count and primitive type.
/// <summary>
/// A basic mesh which provides a wrapper around the vertex data required for drawing a
/// 3D object.
/// </summary>
/// <typeparam name="VertexType">A structure which either exposes a VertexElements static array,
/// or has fields marked with VertexElementAttributes.</typeparam>
public class Mesh<VertexType> : RenderOperation
where VertexType : struct
{
protected VertexType[] m_vertexData;
protected VertexDeclaration m_vertexDeclaration;
protected VertexBuffer m_vertexBuffer;
protected PrimitiveType m_primitiveType;
protected int m_vertexStride, m_vertexCount, m_primitiveCount;
/// <summary>
/// Gets the type of primitives contained in this mesh.
/// </summary>
public PrimitiveType PrimitiveType { get { return m_primitiveType; } }
/// <summary>
/// Gets the number of vertices contained in this mesh.
/// </summary>
public int VertexCount { get { return m_vertexCount; } }
/// <summary>
/// Gets the number of primitives contained in this mesh.
/// </summary>
public int PrimitiveCount { get { return m_primitiveCount; } }
/// <summary>
/// Should a copy of the mesh data be stored in system memory even after the buffer(s) have
/// been populated with that data?
/// (default = false)
/// </summary>
public bool ShadowCopy;
/// <summary>
/// Create a new Mesh.
/// </summary>
/// <param name="primitiveType">Type of primitives stored in the mesh.</param>
/// <param name="vertexData">Data that will be placed in the vertex buffer.</param>
public Mesh(PrimitiveType primitiveType, VertexType[] vertexData)
{
m_primitiveType = primitiveType;
if (vertexData == null)
throw new ArgumentNullException("vertexData");
m_vertexData = vertexData;
m_vertexCount = m_vertexData.Length;
m_vertexStride = VertexDeclaration.GetVertexStrideSize(VertexElementAttribute.GetVertexElements(typeof(VertexType)), 0);
switch (m_primitiveType)
{
case PrimitiveType.TriangleList: m_primitiveCount = m_vertexCount / 3; break;
case PrimitiveType.TriangleStrip: m_primitiveCount = m_vertexCount - 2; break;
case PrimitiveType.TriangleFan: m_primitiveCount = (m_vertexCount - 1) / 2; break;
case PrimitiveType.LineList: m_primitiveCount = m_vertexCount / 2; break;
case PrimitiveType.LineStrip: m_primitiveCount = m_vertexCount - 1; break;
case PrimitiveType.PointList: m_primitiveCount = m_vertexCount; break;
default: m_primitiveCount = 0; break;
}
}
/// <summary>
/// Sets the vertex data into the vertex buffer if one has been created or into system
/// memory for later loading if no vertex buffer has been created yet.
/// </summary>
/// <param name="vertexData">An array equivalent in size to the vertex allocation when
/// this instance was created.</param>
public virtual void SetVertexData(VertexType[] vertexData)
{
if (vertexData.Length != m_vertexCount)
throw new ArgumentOutOfRangeException("vertexData", "The array provided is either " +
"too large or too small to fit within the " +
"vertex buffer.");
if (m_vertexBuffer == null || ShadowCopy)
m_vertexData = vertexData;
if (m_vertexBuffer != null)
{
m_vertexBuffer.SetData<VertexType>(vertexData);
if (!ShadowCopy)
vertexData = null;
}
}
public override void LoadContent(GraphicsDevice device)
{
if (m_vertexDeclaration == null || m_vertexDeclaration.IsDisposed)
{
m_vertexDeclaration = VertexElementAttribute.CreateVertexDeclaration(device, typeof(VertexType));
m_vertexStride = m_vertexDeclaration.GetVertexStrideSize(0);
}
if (m_vertexBuffer == null || m_vertexBuffer.IsDisposed)
{
m_vertexBuffer = new VertexBuffer(device, m_vertexStride * m_vertexCount, BufferUsage.WriteOnly);
SetVertexData(m_vertexData);
}
}
public override void UnloadContent()
{
if (m_vertexDeclaration != null)
{
m_vertexDeclaration.Dispose();
m_vertexDeclaration = null;
}
if (m_vertexBuffer != null)
{
if (!m_vertexBuffer.IsDisposed && ShadowCopy)
m_vertexBuffer.GetData<VertexType>(m_vertexData);
m_vertexBuffer.Dispose();
m_vertexBuffer = null;
}
}
protected override bool PreRender(GraphicsDevice device)
{
device.VertexDeclaration = m_vertexDeclaration;
device.Vertices[0].SetSource(m_vertexBuffer, 0, m_vertexStride);
return true;
}
protected override void Render(GraphicsDevice device)
{
device.DrawPrimitives(m_primitiveType, 0, m_primitiveCount);
}
protected override void PostRender(GraphicsDevice device)
{
device.VertexDeclaration = null;
device.Vertices[0].SetSource(null, 0, 0);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
m_vertexData = null;
}
}
}Mesh<VertexType, IndexType> - same as above, but for indexed meshes instead. This time primitive count is determined based on the index count and primitive type.
/// <summary>
/// A basic mesh which provides a wrapper around the vertex and index data required for
/// drawing a 3D object.
/// </summary>
/// <typeparam name="VertexType">A structure which either exposes a VertexElements static array,
/// or has fields marked with VertexElementAttributes.</typeparam>
/// <typeparam name="IndexType">A integral type used for integers, to save memory this should
/// be ushort if there is less than or equal to ushort.MaxValue of vertices.</typeparam>
public class Mesh<VertexType, IndexType> : Mesh<VertexType>
where VertexType : struct
where IndexType : struct
{
protected IndexType[] m_indexData;
protected IndexBuffer m_indexBuffer;
protected int m_indexCount;
/// <summary>
/// Gets the number of indicies contained in this mesh.
/// </summary>
public int IndexCount { get { return m_indexCount; } }
/// <summary>
/// Create a new Mesh.
/// </summary>
/// <param name="primitiveType">Type of primitives stored in the mesh.</param>
/// <param name="vertexData">Data that will be placed in the vertex buffer.</param>
/// <param name="indexData">(Optional) Data that will be placed in the vertex buffer.</param>
public Mesh(PrimitiveType primitiveType, VertexType[] vertexData, IndexType[] indexData)
: base(primitiveType, vertexData)
{
if (indexData == null)
throw new ArgumentNullException("indexData");
Type indexType = typeof(IndexType);
if (indexType != typeof(uint) && indexType != typeof(int) &&
indexType != typeof(ushort) && indexType != typeof(short))
throw new ArgumentException("Valid index types are: uint, int, ushort, and short.",
"IndexType");
m_indexData = indexData;
m_indexCount = m_indexData.Length;
switch (m_primitiveType)
{
case PrimitiveType.TriangleList: m_primitiveCount = m_indexCount / 3; break;
case PrimitiveType.TriangleStrip: m_primitiveCount = m_indexCount - 2; break;
case PrimitiveType.TriangleFan: m_primitiveCount = (m_indexCount - 1) / 2; break;
case PrimitiveType.LineList: m_primitiveCount = m_indexCount / 2; break;
case PrimitiveType.LineStrip: m_primitiveCount = m_indexCount - 1; break;
case PrimitiveType.PointList: m_primitiveCount = m_indexCount; break;
default: m_primitiveCount = 0; break;
}
}
/// <summary>
/// Sets the index data into the index buffer if one has been created or into system
/// memory for later loading if no index buffer has been created yet.
/// </summary>
/// <param name="indexData">An array equivalent in size to the index allocation when
/// this instance was created.</param>
public virtual void SetIndexData(IndexType[] indexData)
{
if (indexData.Length != m_indexCount)
throw new ArgumentOutOfRangeException("indexData", "The array provided is either " +
"too large or too small to fit within the " +
"index buffer.");
if (m_indexBuffer == null || ShadowCopy)
m_indexData = indexData;
if (m_indexBuffer != null)
{
m_indexBuffer.SetData<IndexType>(indexData);
if (!ShadowCopy)
m_indexData = null;
}
}
public override void LoadContent(GraphicsDevice device)
{
base.LoadContent(device);
if ((m_indexBuffer == null || m_indexBuffer.IsDisposed) && m_indexData != null)
{
m_indexBuffer = new IndexBuffer(device, typeof(IndexType), m_indexCount, BufferUsage.WriteOnly);
SetIndexData(m_indexData);
}
}
public override void UnloadContent()
{
base.UnloadContent();
if (m_indexBuffer != null)
{
if (!m_indexBuffer.IsDisposed && ShadowCopy)
m_indexBuffer.GetData<IndexType>(m_indexData);
m_indexBuffer.Dispose();
m_indexBuffer = null;
}
}
protected override bool PreRender(GraphicsDevice device)
{
bool result = base.PreRender(device);
device.Indices = m_indexBuffer;
return result && true;
}
protected override void Render(GraphicsDevice device)
{
device.DrawIndexedPrimitives(m_primitiveType,
0, 0, m_vertexCount,
0, m_primitiveCount);
}
protected override void PostRender(GraphicsDevice device)
{
base.PostRender(device);
device.Indices = null;
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
m_indexData = null;
}
}
}