using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace ShapeTest
{
///
/// Defines a custom vertex format structure that contains position and normal data.
///
struct VertexPositionNormal
{
///
/// The vertex position.
///
public Vector3 Position;
///
/// The vertex normal.
///
public Vector3 Normal;
///
/// Initializes a new instance of the VertexPositionNormal class.
///
/// The position of the vertex.
/// The vertex normal.
public VertexPositionNormal(Vector3 position, Vector3 normal)
{
Position = position;
Normal = normal;
}
///
/// An array of two vertex elements describing the position and normal of this vertex .
///
public static VertexElement[] VertexElements =
{
new VertexElement(0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0),
new VertexElement(0, sizeof(float)*3, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Normal, 0),
};
///
/// Gets the size of the VertexPositionNormal class.
///
public static int SizeInBytes = sizeof(float) * 3 * 2;
}
///
/// Defines a simple 3D shape, such as a box, sphere, or cylinder.
///
public class Shape3D
{
#region Properties
///
/// The number of vertices making up the shape.
///
public int VertexCount { get; private set; }
///
/// The number of triangle making up the shape.
///
public int TriangleCount { get; private set; }
///
/// The buffer of vertices making up the shape.
///
public VertexBuffer VertexBuffer { get; private set; }
///
/// The buffer of indices making up the shape, if it has one.
///
public IndexBuffer IndexBuffer { get; private set; }
///
/// The declaration of the type of vertices making up the shape.
///
public VertexDeclaration VertexDeclaration { get; private set; }
///
/// The byte size of each vertex making up the shape.
///
public int VertexSizeInBytes { get; private set; }
#endregion
///
/// Initializes a new instance of a .
/// Private so that a shape can only be instantiated from the Create methods.
///
private Shape3D()
{
VertexCount = 0;
TriangleCount = 0;
VertexSizeInBytes = 0;
VertexBuffer = null;
IndexBuffer = null;
VertexDeclaration = null;
}
///
/// Draws the shape.
///
/// The device to use to draw the shape.
public void Draw(GraphicsDevice device)
{
device.VertexDeclaration = VertexDeclaration;
device.Vertices[0].SetSource(VertexBuffer, 0, VertexSizeInBytes);
device.Indices = IndexBuffer;
if (IndexBuffer != null)
device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, VertexCount, 0, TriangleCount);
else
device.DrawPrimitives(PrimitiveType.TriangleList, 0, TriangleCount);
}
#region Static Methods
///
/// Creates a filled with vertices forming a box.
///
/// Always draw using a non-indexed TriangleList of 12 primitives.
/// The that is associated with the created box.
/// Width of the box, along the x-axis.
/// Height of the box, along the y-axis.
/// Depth of the box, along the z-axis.
public static Shape3D CreateTexturedBox(GraphicsDevice device, float width, float height, float depth)
{
Shape3D shape = new Shape3D();
// Because the box is centered at the origin, need to divide by two to find the + and - offsets
width = width / 2.0f;
height = height / 2.0f;
depth = depth / 2.0f;
VertexPositionNormalTexture[] cubeVertices = new VertexPositionNormalTexture[36];
Vector3 topLeftFront = new Vector3(-width, height, depth);
Vector3 bottomLeftFront = new Vector3(-width, -height, depth);
Vector3 topRightFront = new Vector3(width, height, depth);
Vector3 bottomRightFront = new Vector3(width, -height, depth);
Vector3 topLeftBack = new Vector3(-width, height, -depth);
Vector3 topRightBack = new Vector3(width, height, -depth);
Vector3 bottomLeftBack = new Vector3(-width, -height, -depth);
Vector3 bottomRightBack = new Vector3(width, -height, -depth);
Vector2 textureTopLeft = new Vector2(0.0f, 0.0f);
Vector2 textureTopRight = new Vector2(1.0f, 0.0f);
Vector2 textureBottomLeft = new Vector2(0.0f, 1.0f);
Vector2 textureBottomRight = new Vector2(1.0f, 1.0f);
Vector3 frontNormal = new Vector3(0.0f, 0.0f, 1.0f);
Vector3 backNormal = new Vector3(0.0f, 0.0f, -1.0f);
Vector3 topNormal = new Vector3(0.0f, 1.0f, 0.0f);
Vector3 bottomNormal = new Vector3(0.0f, -1.0f, 0.0f);
Vector3 leftNormal = new Vector3(-1.0f, 0.0f, 0.0f);
Vector3 rightNormal = new Vector3(1.0f, 0.0f, 0.0f);
// Front face.
cubeVertices[1] = new VertexPositionNormalTexture(topLeftFront, frontNormal, textureTopLeft);
cubeVertices[0] = new VertexPositionNormalTexture(bottomLeftFront, frontNormal, textureBottomLeft);
cubeVertices[2] = new VertexPositionNormalTexture(topRightFront, frontNormal, textureTopRight);
cubeVertices[4] = new VertexPositionNormalTexture(bottomLeftFront, frontNormal, textureBottomLeft);
cubeVertices[3] = new VertexPositionNormalTexture(bottomRightFront, frontNormal, textureBottomRight);
cubeVertices[5] = new VertexPositionNormalTexture(topRightFront, frontNormal, textureTopRight);
// Back face.
cubeVertices[7] = new VertexPositionNormalTexture(topLeftBack, backNormal, textureTopRight);
cubeVertices[6] = new VertexPositionNormalTexture(topRightBack, backNormal, textureTopLeft);
cubeVertices[8] = new VertexPositionNormalTexture(bottomLeftBack, backNormal, textureBottomRight);
cubeVertices[10] = new VertexPositionNormalTexture(bottomLeftBack, backNormal, textureBottomRight);
cubeVertices[9] = new VertexPositionNormalTexture(topRightBack, backNormal, textureTopLeft);
cubeVertices[11] = new VertexPositionNormalTexture(bottomRightBack, backNormal, textureBottomLeft);
// Top face.
cubeVertices[13] = new VertexPositionNormalTexture(topLeftFront, topNormal, textureBottomLeft);
cubeVertices[12] = new VertexPositionNormalTexture(topRightBack, topNormal, textureTopRight);
cubeVertices[14] = new VertexPositionNormalTexture(topLeftBack, topNormal, textureTopLeft);
cubeVertices[16] = new VertexPositionNormalTexture(topLeftFront, topNormal, textureBottomLeft);
cubeVertices[15] = new VertexPositionNormalTexture(topRightFront, topNormal, textureBottomRight);
cubeVertices[17] = new VertexPositionNormalTexture(topRightBack, topNormal, textureTopRight);
// Bottom face.
cubeVertices[19] = new VertexPositionNormalTexture(bottomLeftFront, bottomNormal, textureTopLeft);
cubeVertices[18] = new VertexPositionNormalTexture(bottomLeftBack, bottomNormal, textureBottomLeft);
cubeVertices[20] = new VertexPositionNormalTexture(bottomRightBack, bottomNormal, textureBottomRight);
cubeVertices[22] = new VertexPositionNormalTexture(bottomLeftFront, bottomNormal, textureTopLeft);
cubeVertices[21] = new VertexPositionNormalTexture(bottomRightBack, bottomNormal, textureBottomRight);
cubeVertices[23] = new VertexPositionNormalTexture(bottomRightFront, bottomNormal, textureTopRight);
// Left face.
cubeVertices[25] = new VertexPositionNormalTexture(topLeftFront, leftNormal, textureTopRight);
cubeVertices[24] = new VertexPositionNormalTexture(bottomLeftBack, leftNormal, textureBottomLeft);
cubeVertices[26] = new VertexPositionNormalTexture(bottomLeftFront, leftNormal, textureBottomRight);
cubeVertices[28] = new VertexPositionNormalTexture(topLeftBack, leftNormal, textureTopLeft);
cubeVertices[27] = new VertexPositionNormalTexture(bottomLeftBack, leftNormal, textureBottomLeft);
cubeVertices[29] = new VertexPositionNormalTexture(topLeftFront, leftNormal, textureTopRight);
// Right face.
cubeVertices[31] = new VertexPositionNormalTexture(topRightFront, rightNormal, textureTopLeft);
cubeVertices[30] = new VertexPositionNormalTexture(bottomRightFront, rightNormal, textureBottomLeft);
cubeVertices[32] = new VertexPositionNormalTexture(bottomRightBack, rightNormal, textureBottomRight);
cubeVertices[34] = new VertexPositionNormalTexture(topRightBack, rightNormal, textureTopRight);
cubeVertices[33] = new VertexPositionNormalTexture(topRightFront, rightNormal, textureTopLeft);
cubeVertices[35] = new VertexPositionNormalTexture(bottomRightBack, rightNormal, textureBottomRight);
// Create the actual vertex buffer
shape.VertexBuffer = new VertexBuffer(device, VertexPositionNormalTexture.SizeInBytes * cubeVertices.Length, BufferUsage.None);
shape.VertexBuffer.SetData(cubeVertices);
shape.VertexCount = 36;
shape.TriangleCount = 12;
shape.VertexDeclaration = new VertexDeclaration(device, VertexPositionNormalTexture.VertexElements);
shape.VertexSizeInBytes = VertexPositionNormalTexture.SizeInBytes;
return shape;
}
///
/// Creates a filled with vertices forming a box.
///
/// Always draw using a non-indexed TriangleList of 12 primitives.
/// The that is associated with the created box.
/// Width of the box, along the x-axis.
/// Height of the box, along the y-axis.
/// Depth of the box, along the z-axis.
public static Shape3D CreateBox(GraphicsDevice device, float width, float height, float depth)
{
Shape3D shape = new Shape3D();
// Because the box is centered at the origin, need to divide by two to find the + and - offsets
width = width / 2.0f;
height = height / 2.0f;
depth = depth / 2.0f;
VertexPositionNormal[] cubeVertices = new VertexPositionNormal[36];
Vector3 topLeftFront = new Vector3(-width, height, depth);
Vector3 bottomLeftFront = new Vector3(-width, -height, depth);
Vector3 topRightFront = new Vector3(width, height, depth);
Vector3 bottomRightFront = new Vector3(width, -height, depth);
Vector3 topLeftBack = new Vector3(-width, height, -depth);
Vector3 topRightBack = new Vector3(width, height, -depth);
Vector3 bottomLeftBack = new Vector3(-width, -height, -depth);
Vector3 bottomRightBack = new Vector3(width, -height, -depth);
Vector3 frontNormal = new Vector3(0.0f, 0.0f, 1.0f);
Vector3 backNormal = new Vector3(0.0f, 0.0f, -1.0f);
Vector3 topNormal = new Vector3(0.0f, 1.0f, 0.0f);
Vector3 bottomNormal = new Vector3(0.0f, -1.0f, 0.0f);
Vector3 leftNormal = new Vector3(-1.0f, 0.0f, 0.0f);
Vector3 rightNormal = new Vector3(1.0f, 0.0f, 0.0f);
// Front face.
cubeVertices[1] = new VertexPositionNormal(topLeftFront, frontNormal);
cubeVertices[0] = new VertexPositionNormal(bottomLeftFront, frontNormal);
cubeVertices[2] = new VertexPositionNormal(topRightFront, frontNormal);
cubeVertices[4] = new VertexPositionNormal(bottomLeftFront, frontNormal);
cubeVertices[3] = new VertexPositionNormal(bottomRightFront, frontNormal);
cubeVertices[5] = new VertexPositionNormal(topRightFront, frontNormal);
// Back face.
cubeVertices[7] = new VertexPositionNormal(topLeftBack, backNormal);
cubeVertices[6] = new VertexPositionNormal(topRightBack, backNormal);
cubeVertices[8] = new VertexPositionNormal(bottomLeftBack, backNormal);
cubeVertices[10] = new VertexPositionNormal(bottomLeftBack, backNormal);
cubeVertices[9] = new VertexPositionNormal(topRightBack, backNormal);
cubeVertices[11] = new VertexPositionNormal(bottomRightBack, backNormal);
// Top face.
cubeVertices[13] = new VertexPositionNormal(topLeftFront, topNormal);
cubeVertices[12] = new VertexPositionNormal(topRightBack, topNormal);
cubeVertices[14] = new VertexPositionNormal(topLeftBack, topNormal);
cubeVertices[16] = new VertexPositionNormal(topLeftFront, topNormal);
cubeVertices[15] = new VertexPositionNormal(topRightFront, topNormal);
cubeVertices[17] = new VertexPositionNormal(topRightBack, topNormal);
// Bottom face.
cubeVertices[19] = new VertexPositionNormal(bottomLeftFront, bottomNormal);
cubeVertices[18] = new VertexPositionNormal(bottomLeftBack, bottomNormal);
cubeVertices[20] = new VertexPositionNormal(bottomRightBack, bottomNormal);
cubeVertices[22] = new VertexPositionNormal(bottomLeftFront, bottomNormal);
cubeVertices[21] = new VertexPositionNormal(bottomRightBack, bottomNormal);
cubeVertices[23] = new VertexPositionNormal(bottomRightFront, bottomNormal);
// Left face.
cubeVertices[25] = new VertexPositionNormal(topLeftFront, leftNormal);
cubeVertices[24] = new VertexPositionNormal(bottomLeftBack, leftNormal);
cubeVertices[26] = new VertexPositionNormal(bottomLeftFront, leftNormal);
cubeVertices[28] = new VertexPositionNormal(topLeftBack, leftNormal);
cubeVertices[27] = new VertexPositionNormal(bottomLeftBack, leftNormal);
cubeVertices[29] = new VertexPositionNormal(topLeftFront, leftNormal);
// Right face.
cubeVertices[31] = new VertexPositionNormal(topRightFront, rightNormal);
cubeVertices[30] = new VertexPositionNormal(bottomRightFront, rightNormal);
cubeVertices[32] = new VertexPositionNormal(bottomRightBack, rightNormal);
cubeVertices[34] = new VertexPositionNormal(topRightBack, rightNormal);
cubeVertices[33] = new VertexPositionNormal(topRightFront, rightNormal);
cubeVertices[35] = new VertexPositionNormal(bottomRightBack, rightNormal);
// Create the actual vertex buffer
shape.VertexBuffer = new VertexBuffer(device, VertexPositionNormal.SizeInBytes * cubeVertices.Length, BufferUsage.None);
shape.VertexBuffer.SetData(cubeVertices);
shape.VertexCount = 36;
shape.TriangleCount = 12;
shape.VertexDeclaration = new VertexDeclaration(device, VertexPositionNormal.VertexElements);
shape.VertexSizeInBytes = VertexPositionNormal.SizeInBytes;
return shape;
}
///
/// Creates a filled with vertices forming a sphere.
///
///
/// Draw with an INDEXED TriangleList. The values to use are as follows:
/// Vertex Count = slices * (stacks - 1) + 2
/// Primitive Count = slices * (stacks - 1) * 2
///
/// The that is associated with the created sphere.
/// Radius of the sphere. This value should be greater than or equal to 0.0f.
/// Number of slices about the Y axis.
/// Number of stacks along the Y axis. Should be 2 or greater. (stack of 1 is just a cylinder)
public static Shape3D CreateSphere(GraphicsDevice device, float radius, int slices, int stacks)
{
Shape3D shape = new Shape3D();
float sliceStep = MathHelper.TwoPi / slices;
float stackStep = MathHelper.Pi / stacks;
int vertexCount = slices * (stacks - 1) + 2;
int triangleCount = slices * (stacks - 1) * 2;
int indexCount = triangleCount * 3;
VertexPositionNormal[] sphereVertices = new VertexPositionNormal[vertexCount];
int currentVertex = 0;
sphereVertices[currentVertex++] = new VertexPositionNormal(new Vector3(0, -radius, 0), Vector3.Down);
float stackAngle = MathHelper.Pi - stackStep;
for (int i = 0; i < stacks - 1; i++)
{
float sliceAngle = 0;
for (int j = 0; j < slices; j++)
{
//NOTE: y and z were switched from normal spherical coordinates because the sphere is "oriented" along the Y axis as opposed to the Z axis
float x = (float)(radius * Math.Sin(stackAngle) * Math.Cos(sliceAngle));
float y = (float)(radius * Math.Cos(stackAngle));
float z = (float)(radius * Math.Sin(stackAngle) * Math.Sin(sliceAngle));
Vector3 position = new Vector3(x, y, z);
sphereVertices[currentVertex++] = new VertexPositionNormal(position, Vector3.Normalize(position));
sliceAngle += sliceStep;
}
stackAngle -= stackStep;
}
sphereVertices[currentVertex++] = new VertexPositionNormal(new Vector3(0, radius, 0), Vector3.Up);
// Create the actual vertex buffer
shape.VertexBuffer = new VertexBuffer(device, VertexPositionNormal.SizeInBytes * sphereVertices.Length, BufferUsage.None);
shape.VertexBuffer.SetData(sphereVertices);
shape.IndexBuffer = CreateIndexBuffer(device, vertexCount, indexCount, slices);
shape.VertexCount = vertexCount;
shape.TriangleCount = triangleCount;
shape.VertexDeclaration = new VertexDeclaration(device, VertexPositionNormal.VertexElements);
shape.VertexSizeInBytes = VertexPositionNormal.SizeInBytes;
return shape;
}
///
/// Creates a filled with vertices forming a cylinder.
///
///
/// Draw with an INDEXED TriangleList. The values to use are as follows:
/// Vertex Count = slices * (stacks + 1) + 2
/// Primitive Count = slices * (stacks + 1) * 2
///
/// The that is associated with the created cylinder.
/// Radius at the negative Y end. Value should be greater than or equal to 0.0f.
/// Radius at the positive Y end. Value should be greater than or equal to 0.0f.
/// Length of the cylinder along the Y-axis.
/// Number of slices about the Y axis.
/// Number of stacks along the Y axis.
public static Shape3D CreateCylinder(GraphicsDevice device, float bottomRadius, float topRadius, float length, int slices, int stacks)
{
// if both the top and bottom have a radius of zero, just return null, because invalid
if (bottomRadius <= 0 && topRadius <= 0)
{
return null;
}
Shape3D shape = new Shape3D();
float sliceStep = MathHelper.TwoPi / slices;
float heightStep = length / stacks;
float radiusStep = (topRadius - bottomRadius) / stacks;
float currentHeight = -length / 2;
int vertexCount = (stacks + 1) * slices + 2; //cone = stacks * slices + 1
int triangleCount = (stacks + 1) * slices * 2; //cone = stacks * slices * 2 + slices
int indexCount = triangleCount * 3;
float currentRadius = bottomRadius;
VertexPositionNormal[] vertices = new VertexPositionNormal[vertexCount];
// Start at the bottom of the cylinder
int currentVertex = 0;
vertices[currentVertex++] = new VertexPositionNormal(new Vector3(0, currentHeight, 0), Vector3.Down);
for (int i = 0; i <= stacks; i++)
{
float sliceAngle = 0;
for (int j = 0; j < slices; j++)
{
float x = currentRadius * (float)Math.Cos(sliceAngle);
float y = currentHeight;
float z = currentRadius * (float)Math.Sin(sliceAngle);
Vector3 position = new Vector3(x, y, z);
vertices[currentVertex++] = new VertexPositionNormal(position, Vector3.Normalize(position));
sliceAngle += sliceStep;
}
currentHeight += heightStep;
currentRadius += radiusStep;
}
vertices[currentVertex++] = new VertexPositionNormal(new Vector3(0, length / 2, 0), Vector3.Up);
// Create the actual vertex buffer object
shape.VertexBuffer = new VertexBuffer(device, VertexPositionNormal.SizeInBytes * vertices.Length, BufferUsage.None);
shape.VertexBuffer.SetData(vertices);
shape.IndexBuffer = CreateIndexBuffer(device, vertexCount, indexCount, slices);
shape.VertexCount = vertexCount;
shape.TriangleCount = triangleCount;
shape.VertexDeclaration = new VertexDeclaration(device, VertexPositionNormal.VertexElements);
shape.VertexSizeInBytes = VertexPositionNormal.SizeInBytes;
return shape;
}
///
/// Creates an for spherical shapes like Spheres, Cylinders, and Cones.
///
/// The that is associated with the created cylinder.
/// The total number of vertices making up the shape.
/// The total number of indices making up the shape.
/// The number of slices about the Y axis.
/// The index buffer containing the index data for the shape.
private static IndexBuffer CreateIndexBuffer(GraphicsDevice device, int vertexCount, int indexCount, int slices)
{
uint[] indices = new uint[indexCount];
int currentIndex = 0;
// Bottom circle/cone of shape
for (uint i = 1; i <= slices; i++)
{
indices[currentIndex++] = 0;
indices[currentIndex++] = i;
if (i - 1 == 0)
indices[currentIndex++] = i + (uint)slices - 1;
else
indices[currentIndex++] = i - 1;
}
// Middle sides of shape
for (uint i = 1; i < vertexCount - (uint)slices - 1; i++)
{
indices[currentIndex++] = i;
indices[currentIndex++] = i + (uint)slices;
if ((i - 1) % slices == 0)
indices[currentIndex++] = i + (uint)slices + (uint)slices - 1;
else
indices[currentIndex++] = i + (uint)slices - 1;
if ((i - 1) % slices == 0)
indices[currentIndex++] = i + (uint)slices - 1;
else
indices[currentIndex++] = i - 1;
indices[currentIndex++] = i;
if ((i - 1) % slices == 0)
indices[currentIndex++] = i + (uint)slices + (uint)slices - 1;
else
indices[currentIndex++] = i + (uint)slices - 1;
}
// Top circle/cone of shape
for (uint i = (uint)vertexCount - (uint)slices - 1; i < vertexCount - 1; i++)
{
indices[currentIndex++] = (uint)vertexCount - 1;
if ((i - 1) % slices == 0)
indices[currentIndex++] = i + (uint)slices - 1;
else
indices[currentIndex++] = i - 1;
indices[currentIndex++] = i;
}
// Create the actual index buffer
IndexBuffer indexBuffer = new IndexBuffer(device, typeof(uint), indexCount, BufferUsage.None);
indexBuffer.SetData(indices);
return indexBuffer;
}
#endregion
}
}