Shape Generation
From XNAWiki
The algorithms presented here generate various simple geometric shapes in TriangleList format.
Fullscreen Quad
- width = number of cells wide
- height = number of cells high
public static void FullscreenQuad(int width, int height,
out VertexPositionTexture[] vertices, out int[] indices)
{
width = Math.Max(1, width);
height = Math.Max(1, height);
float cellWidth = 1f / width++,
cellHeight = 1f / height++;
vertices = new VertexPositionTexture[width * height];
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
vertices[x + y * width] = new VertexPositionTexture()
{
Position = new Vector3()
{
X = (2f * (x * cellWidth)) - 1f,
Y = ((2f * (y * cellHeight)) - 1f) * -1f,
Z = 0f
},
TextureCoordinate = new Vector2()
{
X = x * cellWidth,
Y = y * cellHeight
}
};
}
}
indices = new int[(width - 1) * (height - 1) * 6];
for (int y = 0, counter = 0; y < height - 1; y++)
{
for (int x = 0; x < width - 1; x++)
{
int topLeft = x + (y + 1) * width;
int topRight = (x + 1) + (y + 1) * width;
int bottomLeft = x + y * width;
int bottomRight = (x + 1) + y * width;
// left triangle
indices[counter++] = bottomLeft;
indices[counter++] = topRight;
indices[counter++] = topLeft;
// right triangle
indices[counter++] = bottomRight;
indices[counter++] = topRight;
indices[counter++] = bottomLeft;
}
}
}Sphere
- radius = radius of the sphere
- n = tessellation level (higher for more detail)
- invertFace = should the triangles face inwards
- Note: the texture coordinates are not all that good.
public static void Sphere(float radius, int n, bool invertFaces,
out VertexPositionTexture[] vertices, out int[] indices)
{
// Sphere Basis:
// r = radius
// theta = lines of latitude (horizontal division)
// phi = lines of longitude (vertical division)
// x = r * cos(theta) * cos(phi)
// y = r * cos(theta) * sin(phi)
// z = r * sin(theta)
if (radius < 0)
radius *= -1;
n = Math.Max(4, n);
n += (n % 2);
int vertexCount = 0,
indexCount = 0;
vertices = new VertexPositionTexture[((n / 2) * (n - 1)) + 1];
indices = new int[(((n / 2) - 2) * (n + 1) * 6) + (6 * (n + 1))];
for (int j = 0; j <= n / 2; j++)
{
float theta = j * MathHelper.TwoPi / n - MathHelper.PiOver2;
for (int i = 0; i <= n; i++)
{
float phi = i * MathHelper.TwoPi / n;
vertices[vertexCount++] = new VertexPositionTexture()
{
Position = new Vector3()
{
X = radius * (float)(Math.Cos(theta) * Math.Cos(phi)),
Y = radius * (float)(Math.Sin(theta)),
Z = radius * (float)(Math.Cos(theta) * Math.Sin(phi))
},
TextureCoordinate = new Vector2()
{
X = i / n,
Y = 2 * j / n
}
};
if (j == 0)
{ // bottom cap
for (i = 0; i <= n; i++)
{
int i0 = 0,
i1 = (i % n) + 1,
i2 = i;
indices[indexCount++] = i0;
indices[indexCount++] = invertFaces ? i2 : i1;
indices[indexCount++] = invertFaces ? i1 : i2;
}
}
else if (j < n / 2 - 1)
{ // middle area
int i0 = vertexCount - 1,
i1 = vertexCount,
i2 = vertexCount + n,
i3 = vertexCount + n + 1;
indices[indexCount++] = i0;
indices[indexCount++] = invertFaces ? i2 : i1;
indices[indexCount++] = invertFaces ? i1 : i2;
indices[indexCount++] = i1;
indices[indexCount++] = invertFaces ? i2 : i3;
indices[indexCount++] = invertFaces ? i3 : i2;
}
else if (j == n / 2)
{ // top cap
for (i = 0; i <= n; i++)
{
int i0 = (vertexCount - 1),
i1 = (vertexCount - 1) - ((i % n) + 1),
i2 = (vertexCount - 1) - i;
indices[indexCount++] = i0;
indices[indexCount++] = invertFaces ? i2 : i1;
indices[indexCount++] = invertFaces ? i1 : i2;
}
}
}
}
}Cylinder
- p0 = starting point that forms the cylinder's center axis
- p1 = ending point that forms the cylinder's center axis
- r0 = radius at the starting point
- r1 = radius at the ending point
- segments = number of times the cylinder is sliced running from p0 to p1
- slices = number of times the cylinder is sliced around it's radius
- invertFace = should the triangles of the cylinder face inward
static Random ms_random = new Random();
public static void Cylinder(Vector3 p0, Vector3 p1, float r0, float r1,
int segments, int slices, bool invertFaces,
out VertexPositionTexture[] vertices, out int[] indices)
{
segments = Math.Max(1, segments);
slices = Math.Max(3, slices);
// this vector should not be between start and end
Vector3 p;
p.X = (float)ms_random.NextDouble();
p.Y = (float)ms_random.NextDouble();
p.Z = (float)ms_random.NextDouble();
// derive two points on the plane formed by [end - start]
Vector3 r = Vector3.Cross(p - p0, p1 - p0);
Vector3 s = Vector3.Cross(r, p1 - p0);
r.Normalize();
s.Normalize();
int vertexCount = 0, indexCount = 0;
float invSegments = 1f / segments, invSlices = 1f / slices;
vertices = new VertexPositionTexture[((segments + 1) * (slices + 1)) + 2];
indices = new int[(slices + (slices * segments)) * 6];
for (int j = 0; j <= segments; j++)
{
Vector3 center = Vector3.Lerp(p0, p1, j * invSegments);
float radius = MathHelper.Lerp(r0, r1, j * invSegments);
if (j == 0)
{
vertices[vertexCount++] = new VertexPositionTexture()
{
Position = center,
TextureCoordinate = new Vector2(0.5f, j * invSegments)
};
}
for (int i = 0; i <= slices; i++)
{
float theta = i * MathHelper.TwoPi * invSlices;
float rCosTheta = radius * (float)Math.Cos(theta),
rSinTheta = radius * (float)Math.Sin(theta);
vertices[vertexCount++] = new VertexPositionTexture()
{
Position = new Vector3()
{
X = center.X + rCosTheta * r.X + rSinTheta * s.X,
Y = center.Y + rCosTheta * r.Y + rSinTheta * s.Y,
Z = center.Z + rCosTheta * r.Z + rSinTheta * s.Z
},
TextureCoordinate = new Vector2()
{
X = i * invSlices,
Y = j * invSegments
}
};
if (i < slices)
{
// just an alias to assist with think of each vertex that's
// iterated in here as the bottom right corner of a triangle
int vRef = vertexCount - 1;
if (j == 0)
{ // start cap - i0 is always center point on start cap
int i0 = 0,
i1 = vRef + 1,
i2 = vRef;
indices[indexCount++] = i0;
indices[indexCount++] = invertFaces ? i2 : i1;
indices[indexCount++] = invertFaces ? i1 : i2;
}
if (j == segments)
{ // end cap - i0 is always the center point on end cap
int i0 = (vRef + slices + 2) - (vRef % (slices + 1)),
i1 = vRef,
i2 = vRef + 1;
indices[indexCount++] = i0;
indices[indexCount++] = invertFaces ? i2 : i1;
indices[indexCount++] = invertFaces ? i1 : i2;
}
if (j < segments)
{ // middle area
int i0 = vRef,
i1 = vRef + 1,
i2 = vRef + slices + 2,
i3 = vRef + slices + 1;
indices[indexCount++] = i0;
indices[indexCount++] = invertFaces ? i2 : i1;
indices[indexCount++] = invertFaces ? i1 : i2;
indices[indexCount++] = i0;
indices[indexCount++] = invertFaces ? i3 : i2;
indices[indexCount++] = invertFaces ? i2 : i3;
}
}
}
if (j == segments)
{
vertices[vertexCount++] = new VertexPositionTexture()
{
Position = center,
TextureCoordinate = new Vector2(0.5f, j * invSegments)
};
}
}
}Capsole
- Note: similar to a cylinder, but with hemisphere end caps.
- p0 = starting point that forms the cylinder's center axis
- p1 = ending point that forms the cylinder's center axis
- r0 = radius at the starting point, also radius of starting cap
- r1 = radius at the ending point, also radius of ending cap
- segments = number of times the cylinder is sliced running from p0 to p1
- slices = number of times the cylinder is sliced around it's radius
- invertFace = should the triangles of the cylinder face inward
public static void Capsole(Vector3 p0, Vector3 p1, float r0, float r1,
int segments, int slices, bool invertFaces,
out VertexPositionTexture[] vertices, out int[] indices)
{
segments = Math.Max(1, segments);
slices = Math.Max(3, slices);
slices += slices % 2;
Vector3 normal = p1 - p0;
float length = normal.Length();
// this vector should not be between p0 and p1
Vector3 p;
p.X = (float)ms_random.NextDouble();
p.Y = (float)ms_random.NextDouble();
p.Z = (float)ms_random.NextDouble();
// derive two points on the plane formed by [p1 - p0]
Vector3 r = Vector3.Cross(p - p0, normal);
Vector3 s = Vector3.Cross(r, normal);
r.Normalize();
s.Normalize();
normal.Normalize();
float invSegments = 1f / segments,
invSlices = 1f / slices,
capRadialSlice = MathHelper.Pi / slices,
cylRadialSlice = MathHelper.TwoPi / slices;
Vector2 inc = new Vector2(1f / slices, 1f / segments);
int vertexCount = 0, indexCount = 0;
vertices = new VertexPositionTexture[((segments + 1) * (slices * 2 + 1)) + 2];
indices = new int[((segments * slices * 2) + slices) * 6];
#region Start Cap
for (int j = 0; j < (slices / 2); j++)
{
float phi = j * capRadialSlice - MathHelper.PiOver2;
float cosPhi = (float)Math.Cos(phi), sinPhi = (float)Math.Sin(phi);
for (int i = 0; i <= slices; i++)
{
int i0, i1, i2, i3;
if (j == 0)
{
if (i == 0)
{
vertices[vertexCount++] = new VertexPositionTexture()
{
Position = p0 + (normal * -r0),
TextureCoordinate = new Vector2(0.5f, 0)
};
i++;
}
i0 = 0;
i1 = i + 1;
i2 = i;
indices[indexCount++] = i0;
indices[indexCount++] = invertFaces ? i2 : i1;
indices[indexCount++] = invertFaces ? i1 : i2;
continue;
}
float theta = i * cylRadialSlice;
float cosTheta = (float)Math.Cos(theta), sinTheta = (float)Math.Sin(theta);
Vector3 sphere = new Vector3()
{
X = r0 * cosPhi * cosTheta,
Y = r0 * sinPhi,
Z = r0 * cosPhi * sinTheta
};
Vector3 center = p0 + (normal * sphere.Y);
float radius = Vector3.Distance(Vector3.Up * sphere.Y, sphere);
vertices[vertexCount++] = new VertexPositionTexture()
{
Position = center + radius * cosTheta * r + radius * sinTheta * s,
TextureCoordinate = inc * new Vector2(i, 0)
};
if (i < slices)
{
i0 = vertexCount - 1;
i1 = vertexCount;
i2 = vertexCount + slices + 1;
i3 = vertexCount + slices;
indices[indexCount++] = i0;
indices[indexCount++] = invertFaces ? i2 : i1;
indices[indexCount++] = invertFaces ? i1 : i2;
indices[indexCount++] = i0;
indices[indexCount++] = invertFaces ? i3 : i2;
indices[indexCount++] = invertFaces ? i2 : i3;
}
}
}
#endregion
#region Cylinder
for (int j = 0; j <= segments; j++)
{
Vector3 center = Vector3.Lerp(p0, p1, j * invSegments);
float radius = MathHelper.Lerp(r0, r1, j * invSegments);
for (int i = 0; i <= slices; i++)
{
float theta = i * cylRadialSlice;
float cosTheta = (float)Math.Cos(theta),
sinTheta = (float)Math.Sin(theta);
vertices[vertexCount++] = new VertexPositionTexture()
{
Position = center + radius * cosTheta * r + radius * sinTheta * s,
TextureCoordinate = inc * new Vector2(i, j)
};
if (i < slices && j < segments)
{ // middle area
int i0 = vertexCount - 1,
i1 = vertexCount,
i2 = vertexCount + slices + 1,
i3 = vertexCount + slices;
indices[indexCount++] = i0;
indices[indexCount++] = invertFaces ? i2 : i1;
indices[indexCount++] = invertFaces ? i1 : i2;
indices[indexCount++] = i0;
indices[indexCount++] = invertFaces ? i3 : i2;
indices[indexCount++] = invertFaces ? i2 : i3;
}
}
}
#endregion
#region End Cap
for (int j = 0; j <= (slices / 2); j++)
{
float phi = j * capRadialSlice;
float cosPhi = (float)Math.Cos(phi), sinPhi = (float)Math.Sin(phi);
for (int i = 0; i <= slices; i++)
{
int vRef;
if (j > 0)
{
float theta = i * cylRadialSlice;
float cosTheta = (float)Math.Cos(theta), sinTheta = (float)Math.Sin(theta);
Vector3 sphere = new Vector3()
{
X = r1 * cosPhi * cosTheta,
Y = r1 * sinPhi,
Z = r1 * cosPhi * sinTheta
};
Vector3 center = p1 + (normal * sphere.Y);
float radius = Vector3.Distance(Vector3.Up * sphere.Y, sphere);
vertices[vertexCount++] = new VertexPositionTexture()
{
Position = center + radius * cosTheta * r + radius * sinTheta * s,
TextureCoordinate = inc * new Vector2(i, segments)
};
vRef = vertexCount - 1;
}
else // during the first iteration, offset the vertices 1 row down
vRef = vertexCount - slices - 1 + i;
if (i < slices)
{
int i0, i1, i2, i3;
if (j < (slices / 2))
{
i0 = vRef;
i1 = vRef + 1;
i2 = vRef + slices + 2;
i3 = vRef + slices + 1;
indices[indexCount++] = i0;
indices[indexCount++] = invertFaces ? i2 : i1;
indices[indexCount++] = invertFaces ? i1 : i2;
indices[indexCount++] = i0;
indices[indexCount++] = invertFaces ? i3 : i2;
indices[indexCount++] = invertFaces ? i2 : i3;
}
else
{
i0 = (vRef + slices + 2) - (vRef % (slices + 1));
i1 = vRef;
i2 = vRef + 1;
indices[indexCount++] = i0;
indices[indexCount++] = invertFaces ? i2 : i1;
indices[indexCount++] = invertFaces ? i1 : i2;
}
}
}
if (j == (slices / 2))
{
vertices[vertexCount++] = new VertexPositionTexture()
{
Position = p1 + (normal * r0),
TextureCoordinate = new Vector2(0.5f, 1)
};
}
}
#endregion
}Torus
- r0 = radius from center to center of tubular section
- r1 = radius of the tubular section
- segments = number of times the tubular section is divided
- slices = number of radial slices around the tubular section
- invertFaces = should the triangles face inward
public static void Torus(float r0, float r1, int segments, int slices, bool invertFaces,
out VertexPositionNormalTexture[] vertices, out int[] indices)
{
segments = Math.Max(3, segments);
slices = Math.Max(3, slices);
float invSegments = 1f / segments, invSlices = 1f / slices;
float radSegment = MathHelper.TwoPi * invSegments,
radSlice = MathHelper.TwoPi * invSlices;
int indexCount = 0;
vertices = new VertexPositionNormalTexture[(segments + 1) * (slices + 1)];
indices = new int[segments * slices * (lines ? 8 : 6)];
for (int j = 0; j <= segments; j++)
{
float theta = j * radSegment - MathHelper.PiOver2;
float cosTheta = (float)Math.Cos(theta), sinTheta = (float)Math.Sin(theta);
for (int i = 0; i <= slices; i++)
{
float phi = i * radSlice;
float cosPhi = (float)Math.Cos(phi), sinPhi = (float)Math.Sin(phi);
Vector3 position = new Vector3()
{
X = cosTheta * (r0 + r1 * cosPhi),
Y = r1 * sinPhi,
Z = sinTheta * (r0 + r1 * cosPhi)
};
Vector3 center = new Vector3()
{
X = r0 * cosTheta,
Y = 0,
Z = r0 * sinTheta
};
vertices[(j * (slices + 1)) + i] = new VertexPositionNormalTexture()
{
Position = position,
Normal = Vector3.Normalize(position - center),
TextureCoordinate = new Vector2(j * invSegments, i * invSegments)
};
// 0---2
// | \ |
// 1---3
if (j < segments && i < slices)
{
int i0 = (j * (slices + 1)) + i,
i1 = (j * (slices + 1)) + i + 1,
i2 = ((j + 1) * (slices + 1)) + i,
i3 = ((j + 1) * (slices + 1)) + i + 1;
{
indices[indexCount++] = i0;
indices[indexCount++] = invertFaces ? i1 : i3;
indices[indexCount++] = invertFaces ? i3 : i1;
indices[indexCount++] = i0;
indices[indexCount++] = invertFaces ? i3 : i2;
indices[indexCount++] = invertFaces ? i2 : i3;
}
}
}
}
}Super Cylinder
- Note: this is a really cool variation of the cylinder that depending on what is set for m, n1, n2, and n3 can generate some interesting shapes.
- p0 = starting point that forms the cylinder's center axis
- p1 = ending point that forms the cylinder's center axis
- r0 = radius at the starting point
- r1 = radius at the ending point
- m = number of corners around the radius to divide the cylinder into
- n1 = uniform power applied to radius, below 1 pinches inward, above 1 flares outward
- n2 = same as n1 but only on the x-axis
- n3 = same as n1 but only on the y-axis
- segments = number of times the cylinder is sliced running from p0 to p1
- slices = number of times the cylinder is sliced around it's radius
- invertFace = should the triangles of the cylinder face inward
static float Eval_SuperShape2D(float m, float n1, float n2, float n3, float phi)
{
float t1 = (float)Math.Pow(Math.Abs(Math.Cos(m * phi / 4)), n2);
float t2 = (float)Math.Pow(Math.Abs(Math.Sin(m * phi / 4)), n3);
return (float)Math.Sqrt(Math.Pow(t1 + t2, 1f / n1));
}
public static void SuperCylinder(Vector3 p0, Vector3 p1, float r0, float r1,
float m, float n1, float n2, float n3,
int segments, int slices, bool invertFaces,
out VertexPositionTexture[] vertices, out int[] indices)
{
segments = Math.Max(1, segments);
slices = Math.Max(3, slices);
// this vector should not be between start and end
Vector3 p;
p.X = (float)ms_random.NextDouble();
p.Y = (float)ms_random.NextDouble();
p.Z = (float)ms_random.NextDouble();
// derive two points on the plane formed by [end - start]
Vector3 r = Vector3.Cross(p - p0, p1 - p0);
Vector3 s = Vector3.Cross(r, p1 - p0);
r.Normalize();
s.Normalize();
int vertexCount = 0, indexCount = 0;
float invSegments = 1f / segments, invSlices = 1f / slices;
vertices = new VertexPositionTexture[((segments + 1) * (slices + 1)) + 2];
indices = new int[(slices + (slices * segments)) * 6];
for (int j = 0; j <= segments; j++)
{
Vector3 center = Vector3.Lerp(p0, p1, j * invSegments);
float radius = MathHelper.Lerp(r0, r1, j * invSegments);
if (j == 0)
{
vertices[vertexCount++] = new VertexPositionTexture()
{
Position = center,
TextureCoordinate = new Vector2(0.5f, j * invSegments)
};
}
for (int i = 0; i <= slices; i++)
{
float theta = i * MathHelper.TwoPi * invSlices;
float sRadius = Eval_SuperShape2D(m, n1, n2, n3, theta);
sRadius = (float)Math.Sqrt(sRadius);
float rCosTheta = sRadius * radius * (float)Math.Cos(theta),
rSinTheta = sRadius * radius * (float)Math.Sin(theta);
vertices[vertexCount++] = new VertexPositionTexture()
{
Position = new Vector3()
{
X = center.X + rCosTheta * r.X + rSinTheta * s.X,
Y = center.Y + rCosTheta * r.Y + rSinTheta * s.Y,
Z = center.Z + rCosTheta * r.Z + rSinTheta * s.Z
},
TextureCoordinate = new Vector2()
{
X = i * invSlices,
Y = j * invSegments
}
};
if (i < slices)
{
// just an alias to assist with think of each vertex that's
// iterated in here as the bottom right corner of a triangle
int vRef = vertexCount - 1;
if (j == 0)
{ // start cap - i0 is always center point on start cap
int i0 = 0,
i1 = vRef + 1,
i2 = vRef;
indices[indexCount++] = i0;
indices[indexCount++] = invertFaces ? i2 : i1;
indices[indexCount++] = invertFaces ? i1 : i2;
}
if (j == segments)
{ // end cap - i0 is always the center point on end cap
int i0 = (vRef + slices + 2) - (vRef % (slices + 1)),
i1 = vRef,
i2 = vRef + 1;
indices[indexCount++] = i0;
indices[indexCount++] = invertFaces ? i2 : i1;
indices[indexCount++] = invertFaces ? i1 : i2;
}
if (j < segments)
{ // middle area
int i0 = vRef,
i1 = vRef + 1,
i2 = vRef + slices + 2,
i3 = vRef + slices + 1;
indices[indexCount++] = i0;
indices[indexCount++] = invertFaces ? i2 : i1;
indices[indexCount++] = invertFaces ? i1 : i2;
indices[indexCount++] = i0;
indices[indexCount++] = invertFaces ? i3 : i2;
indices[indexCount++] = invertFaces ? i2 : i3;
}
}
}
if (j == segments)
{
vertices[vertexCount++] = new VertexPositionTexture()
{
Position = center,
TextureCoordinate = new Vector2(0.5f, j * invSegments)
};
}
}
}