GeometricObject

From XNAWiki
Jump to: navigation, search


This is a class for XNA Game Studio 4.0 to assist in building 3D geometry at runtime. The class is generic and requires an IVertexType type that is a struct. You create the object with a GraphicsDevice and call Begin to start constructing the object, passing in the type of primitives to render. Then you use the AddVertex, AddVertices, AddIndex, and AddIndices methods to add the data to the object. When you are complete, call End to end the construction and create the VertexBuffer and IndexBuffer. Then you can call Draw on the object, passing in any effect, to render the object.

Here's a brief sample showing how one could build a skybox with the class:

skybox = new GeometricObject<VertexPositionTexture>(GraphicsDevice);
skybox.Begin(PrimitiveType.TriangleList);
 
const float s = 50f;
Vector3[] pos = new[]
{
  new Vector3(-s, s, -s),
  new Vector3(s, s, -s),
  new Vector3(-s, s, s),
  new Vector3(s, s, s),
  new Vector3(-s, -s, -s),
  new Vector3(s, -s, -s),
  new Vector3(-s, -s, s),
  new Vector3(s, -s, s),
};
 
// add the vertices that map to our 4098x3072 skybox texture
skybox.AddVertex(new VertexPositionTexture(pos[0], new Vector2(2048f / 4098f, 0)));
skybox.AddVertex(new VertexPositionTexture(pos[1], new Vector2(3072f / 4098f, 0)));
skybox.AddVertex(new VertexPositionTexture(pos[1], new Vector2(0, 1024f / 3072f)));
skybox.AddVertex(new VertexPositionTexture(pos[0], new Vector2(1024f / 4098f, 1024f / 3072f)));
skybox.AddVertex(new VertexPositionTexture(pos[2], new Vector2(2048f / 4098f, 1024f / 3072f)));
skybox.AddVertex(new VertexPositionTexture(pos[3], new Vector2(3072f / 4098f, 1024f / 3072f)));
skybox.AddVertex(new VertexPositionTexture(pos[1], new Vector2(4098f / 4098f, 1024f / 3072f)));
skybox.AddVertex(new VertexPositionTexture(pos[5], new Vector2(0, 2048f / 3072f)));
skybox.AddVertex(new VertexPositionTexture(pos[4], new Vector2(1024f / 4098f, 2048f / 3072f)));
skybox.AddVertex(new VertexPositionTexture(pos[6], new Vector2(2048f / 4098f, 2048f / 3072f)));
skybox.AddVertex(new VertexPositionTexture(pos[7], new Vector2(3072f / 4098f, 2048f / 3072f)));
skybox.AddVertex(new VertexPositionTexture(pos[5], new Vector2(4098f / 4098f, 2048f / 3072f)));
skybox.AddVertex(new VertexPositionTexture(pos[4], new Vector2(2048f / 4098f, 3072f / 3072f)));
skybox.AddVertex(new VertexPositionTexture(pos[5], new Vector2(3072f / 4098f, 3072f / 3072f)));
 
// top
skybox.AddIndices(0, 4, 1);
skybox.AddIndices(1, 4, 5);
 
// left
skybox.AddIndices(2, 7, 3);
skybox.AddIndices(3, 7, 8);
 
// front
skybox.AddIndices(3, 8, 4);
skybox.AddIndices(4, 8, 9);
 
// right
skybox.AddIndices(4, 9, 5);
skybox.AddIndices(5, 9, 10);
 
// back
skybox.AddIndices(5, 10, 6);
skybox.AddIndices(6, 10, 11);
 
// bottom
skybox.AddIndices(9, 12, 10);
skybox.AddIndices(10, 12, 13);
 
skybox.End();

And here's the full source for the class:

/// <summary>
/// Wraps a VertexBuffer and IndexBuffer into a constructable object.
/// </summary>
/// <typeparam name="T">The type of vertex the object will use.</typeparam>
public class GeometricObject<T> where T : struct, IVertexType
{
  // are we in the middle of a Begin/End pair?
  private bool inConstruction = false;
 
  // lists for construction
  private List<T> vertexList = new List<T>();
  private List<ushort> indexList = new List<ushort>();
 
  // our graphics device
  private GraphicsDevice graphicsDevice;
 
  // buffers for drawing
  private VertexBuffer vertexBuffer;
  private IndexBuffer indexBuffer;
 
  // details about what we're drawing
  private PrimitiveType primitiveType;
  private int vertexCount;
  private int indexCount;
  private int primitiveCount;
 
  public GeometricObject(GraphicsDevice graphicsDevice)
  {
    this.graphicsDevice = graphicsDevice;
  }
 
  /// <summary>
  /// Begins the construction of the object.
  /// </summary>
  /// <param name="primitiveType">The type of primitives to be drawn.</param>
  public void Begin(PrimitiveType primitiveType)
  {
    if (inConstruction)
      throw new InvalidOperationException("Cannot Begin until End has been called.");
 
    // clear our lists
    vertexList.Clear();
    indexList.Clear();
 
    // dipose the old buffers
    if (vertexBuffer != null)
      vertexBuffer.Dispose();
    if (indexBuffer != null)
      indexBuffer.Dispose();
 
    // store the primitive type
    this.primitiveType = primitiveType;
 
    // flag us as in construction
    inConstruction = true;
  }
 
  /// <summary>
  /// Ends the construction of the object.
  /// </summary>
  public void End()
  {
    if (!inConstruction)
      throw new InvalidOperationException("Cannot End until Begin has been called.");
    if (vertexList.Count == 0)
      throw new InvalidOperationException("The primitive has no vertices.");
    if (indexList.Count == 0)
      throw new InvalidOperationException("The primitive has no indices.");
 
    // get our counts
    vertexCount = vertexList.Count;
    indexCount = indexList.Count;
 
    // get our primitive count
    switch (primitiveType)
    {
      case PrimitiveType.LineList:
        primitiveCount = indexCount / 2;
        break;
      case PrimitiveType.LineStrip:
        primitiveCount = indexCount - 1;
        break;
      case PrimitiveType.TriangleList:
        primitiveCount = indexCount / 3;
        break;
      case PrimitiveType.TriangleStrip:
        primitiveCount = indexCount - 2;
        break;
    }
 
    // create our buffers
    vertexBuffer = new VertexBuffer(graphicsDevice, typeof(T), vertexCount, BufferUsage.WriteOnly);
    vertexBuffer.SetData(vertexList.ToArray());
 
    indexBuffer = new IndexBuffer(graphicsDevice, IndexElementSize.SixteenBits, indexCount, BufferUsage.WriteOnly);
    indexBuffer.SetData(indexList.ToArray());
 
    // we're out of construction mode
    inConstruction = false;
  }
 
  /// <summary>
  /// Adds a vertex to the object.
  /// </summary>
  /// <remarks>
  /// Vertices can only be added in between a Begin and End pair.
  /// </remarks>
  /// <param name="vertex">The vertex to add.</param>
  public void AddVertex(T vertex)
  {
    if (!inConstruction)
      throw new InvalidOperationException("Cannot AddVertex outside of a Begin/End pair.");
    vertexList.Add(vertex);
  }
 
  /// <summary>
  /// Adds vertices to the object.
  /// </summary>
  /// <remarks>
  /// Vertices can only be added in between a Begin and End pair.
  /// </remarks>
  /// <param name="vertices">The vertices to add.</param>
  public void AddVertices(params T[] vertices)
  {
    if (!inConstruction)
      throw new InvalidOperationException("Cannot AddVertices outside of a Begin/End pair.");
    vertexList.AddRange(vertices);
  }
 
  /// <summary>
  /// Adds an index to the object.
  /// </summary>
  /// <remarks>
  /// Indices can only be added in between a Begin and End pair.
  /// </remarks>
  /// <param name="index">The index to add.</param>
  public void AddIndex(ushort index)
  {
    if (!inConstruction)
      throw new InvalidOperationException("Cannot AddIndex outside of a Begin/End pair.");
    indexList.Add(index);
  }
 
  /// <summary>
  /// Adds indices to the object.
  /// </summary>
  /// <remarks>
  /// Indices can only be added in between a Begin and End pair.
  /// </remarks>
  /// <param name="indices">The indices to add.</param>
  public void AddIndices(params ushort[] indices)
  {
    if (!inConstruction)
      throw new InvalidOperationException("Cannot AddIndices outside of a Begin/End pair.");
    indexList.AddRange(indices);
  }
 
  /// <summary>
  /// Draws the object with the given effect.
  /// </summary>
  /// <remarks>
  /// Objects must be constructed using a Begin/End pair and the Add* methods
  /// before you can call Draw.
  /// 
  /// The object does not set any values on the effect so all matrices and other
  /// parameters should be set before calling Draw.
  /// </remarks>
  /// <param name="effect">The effect with which to draw the object.</param>
  public void Draw(Effect effect)
  {
    if (inConstruction)
      throw new InvalidOperationException("Cannot Draw in between a Begin/End pair.");
    if (vertexBuffer == null || indexBuffer == null)
      throw new InvalidOperationException("Cannot Draw until the primitive is constructed.");
 
    // set our buffers
    graphicsDevice.SetVertexBuffer(vertexBuffer);
    graphicsDevice.Indices = indexBuffer;
 
    // draw the primitives with the effect
    foreach (EffectPass pass in effect.CurrentTechnique.Passes)
    {
      pass.Apply();
      graphicsDevice.DrawIndexedPrimitives(primitiveType, 0, 0, vertexCount, 0, primitiveCount);
    }
  }
}