#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2009 Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
You should have received a copy of the IBM Common Public
License along with this library
*/
#endregion
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Nuclex.Graphics {
///
/// Builds vertex declarations from vertex structures
///
///
/// Based on ideas from Michael Popoloski's article on gamedev.net:
/// http://www.gamedev.net/reference/programming/features/xnaVertexElement/
///
public static class VertexDeclarationHelper {
/// Combines two vertex element list into one single list
/// First vertex element list that will be merged
/// Second vertex element list that will be merged
/// The combined vertex element list from both inputs
///
///
/// No intelligence is applied to avoid duplicates or to adjust the usage index
/// of individual vertex elements. This method simply serves as a helper to merge
/// two vertex element lists from two structures that are used in seperate
/// vertex streams (but require a single vertex declaration containing the elements
/// of both streams).
///
///
///
/// This example shows how two vertex structures, each used in a different
/// vertex buffer, can be merged into a single vertex declaration that fetches
/// vertices from both vertex buffers, the positions from stream 0 and
/// the texture coordinates from stream 1
///
/// struct PositionVertex {
/// [VertexElement(VertexElementUsage.Position)]
/// public Vector3 Position;
/// }
/// struct TextureCoordinateVertex {
/// [VertexElement(VertexElementUsage.TextureCoordinate)]
/// public Vector2 TextureCoordinate;
/// }
///
/// private VertexDeclaration buildVertexDeclaration() {
/// VertexDeclaration declaration = new VertexDeclaration(
/// graphicsDevice,
/// VertexDeclarationHelper.Combine(
/// VertexDeclarationHelper.BuildElementList<PositionVertex>(0),
/// VertexDeclarationHelper.BuildElementList<TextureCoordinateVertex>(1)
/// )
/// );
/// }
///
///
///
///
public static VertexElement[] Combine(VertexElement[] left, VertexElement[] right) {
// Determine the total length the resulting array will have. If one of the arguments
// is null, this line will intentionally trigger the NullReferenceException
int totalLength = left.Length + right.Length;
// Merge the two arrays
VertexElement[] combined = new VertexElement[totalLength];
Array.Copy(left, combined, left.Length);
Array.Copy(right, 0, combined, left.Length, right.Length);
// Done, no further processing required
return combined;
}
///
/// Builds a vertex element list that can be used to construct a vertex declaration
/// from a vertex structure that has the vertex element attributes applied to it
///
///
/// Vertex structure with vertex element attributes applied to it
///
///
/// A vertex element list that can be used to create a new vertex declaration matching
/// the provided vertex structure
///
public static VertexElement[] BuildElementList() where VertexType : struct {
FieldInfo[] fields = getFields();
int fieldOffset = 0;
int elementCount = 0;
// Set up an array for the vertex elements and fill it with the data we
// gather directly from the vertex structure
VertexElement[] elements = new VertexElement[fields.Length];
for(int index = 0; index < fields.Length; ++index) {
// Find out whether this field is used by the vertex shader. If so, add
// it to the elements list so it ends up in the vertex shader.
VertexElementAttribute attribute = getVertexElementAttribute(fields[index]);
if(attribute != null) {
buildVertexElement(fields[index], attribute, ref elements[elementCount]);
#if !(XBOX360 || WINDOWS_PHONE)
fieldOffset = Marshal.OffsetOf(typeof(VertexType), fields[index].Name).ToInt32();
#endif
elements[elementCount].Offset = (short)fieldOffset;
++elementCount;
}
#if XBOX360
fieldOffset += Marshal.SizeOf(fields[index].FieldType);
#endif
}
// If there isn't a single vertex element, this type would be completely useless
// as a vertex. Probably the user forgot to add the VertexElementAttribute.
if(elementCount == 0) {
throw new InvalidOperationException(
"No fields had the VertexElementAttribute assigned to them."
);
}
Array.Resize(ref elements, elementCount);
return elements;
}
/// Obtains the stride value for a vertex
///
/// Vertex structure the stride value will be obtained for
///
/// The stride value for the specified vertex structure
public static int GetStride() where VertexType : struct {
FieldInfo[] fields = getFields();
int fieldOffset = 0;
for(int index = 0; index < fields.Length; ++index) {
fieldOffset += Marshal.SizeOf(fields[index].FieldType);
}
return fieldOffset;
}
/// Retrieves the fields declared in a vertex structure
/// Type the fields will be retrieved from
/// The list of fields declared in the provided structure
private static FieldInfo[] getFields() where VertexType : struct {
Type vertexType = typeof(VertexType);
// Obtain a list of all the fields (object member variables) in the vertex type
FieldInfo[] fields = vertexType.GetFields(
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic
);
if(fields.Length == 0) {
throw new InvalidOperationException("Specified vertex type has no fields");
}
return fields;
}
/// Builds a vertex element from an attributed field in a structure
///
/// Reflected data on the field for which a vertex element will be built
///
/// Vertex eelement attribute assigned to the field
///
/// Output parameter the newly built vertex element is stored in
///
private static void buildVertexElement(
FieldInfo fieldInfo, VertexElementAttribute attribute, ref VertexElement element
) {
element.VertexElementUsage = attribute.Usage;
element.UsageIndex = attribute.UsageIndex;
// Was an explicit data type provided for this field?
if(attribute.FormatProvided == true) {
element.VertexElementFormat = attribute.Format;
} else { // Nope, try to auto-detect the data type
if(fieldInfo.FieldType == typeof(Vector2)) {
element.VertexElementFormat = VertexElementFormat.Vector2;
} else if(fieldInfo.FieldType == typeof(Vector3)) {
element.VertexElementFormat = VertexElementFormat.Vector3;
} else if(fieldInfo.FieldType == typeof(Vector4)) {
element.VertexElementFormat = VertexElementFormat.Vector4;
} else if(fieldInfo.FieldType == typeof(Color)) {
element.VertexElementFormat = VertexElementFormat.Color;
} else if(fieldInfo.FieldType == typeof(float)) {
element.VertexElementFormat = VertexElementFormat.Single;
} else if(fieldInfo.FieldType == typeof(int)) {
element.VertexElementFormat = VertexElementFormat.Short4;
} else if(fieldInfo.FieldType == typeof(short)) {
element.VertexElementFormat = VertexElementFormat.Short2;
} else { // No success in auto-detection, give up
throw new InvalidOperationException(
"Unrecognized field type, please specify vertex format explicitly"
);
}
}
}
///
/// Retrieves the vertex element attribute assigned to a field in a structure
///
///
/// Informations about the vertex element field the attribute is retrieved for
///
/// The vertex element attribute of the requested field
private static VertexElementAttribute getVertexElementAttribute(FieldInfo fieldInfo) {
object[] attributes = fieldInfo.GetCustomAttributes(
typeof(VertexElementAttribute), false
);
// The docs state that if the requested attribute has not been applied to the field,
// an array of length 0 will be returned.
if(attributes.Length == 0) {
return null;
}
return (VertexElementAttribute)attributes[0];
}
}
} // namespace Nuclex.Graphics