Does anyone have an algorithm for creating a sphere proceduraly with la
amount of latitude lines, lo
amount of longitude lines, and a radius of r
? I need it to work with Unity, so the vertex positions need to be defined and then, the triangles defined via indexes (more info).
EDIT
I managed to get the code working in unity. But I think I might have done something wrong. When I turn up the detailLevel
, All it does is add more vertices and polygons without moving them around. Did I forget something?
EDIT 2
I tried scaling the mesh along its normals. This is what I got. I think I'm missing something. Am I supposed to only scale certain normals?
Answer
To get something like this:
Create an icosahedron (20-sided regular solid) and subdivide the faces to get a sphere (see code below).
The idea is basically:
- Create a regular n-hedron (a solid where every face is the same size and every edge is the same length). I use an icosahedron because it's the regular solid with the greatest number of faces. (There's a proof for that somewhere out there. Feel free to Google if you're really curious.) This will give you a sphere where nearly every face is the same size, making texturing a little easier.
Subdivide each face into four equally-sized faces. Each time you do this, it'll quadruple the number of faces in the model.
/// i0
/// / \
/// m02-m01
/// / \ / \
/// i2---m12---i1
i0
, i1
, and i2
are the vertices of the original triangle. (Actually, indices into the vertex buffer, but that's another topic). m01
is the midpoint of the edge (i0,i1)
, m12 is the midpoint of the edge (i1,12)
, and m02
is, obviously, the midpoint of the edge (i0,i2)
.
Whenever you subdivide a face, make sure that you don't create duplicate vertices. Each midpoint will be shared by one other source face (since the edges are shared between faces). The code below accounts for that by maintaining a dictionary of named midpoints that have been created, and returning the index of a previously created midpoint when it's available rather than creating a new one.
Repeat until you've reached the desired number of faces for your cube.
When you're done, normalize all of the vertices to smooth out the surface. If you don't do this, you'll just get a higher-res icosahedron instead of a sphere.
Voila! You're done. Convert the resulting vector and index buffers into a
VertexBuffer
andIndexBuffer
, and draw withDevice.DrawIndexedPrimitives()
.
Here's what you'd use in your "Sphere" class to create the model (XNA datatypes and C#, but it should be pretty clear):
var vectors = new List();
var indices = new List();
GeometryProvider.Icosahedron(vectors, indices);
for (var i = 0; i < _detailLevel; i++)
GeometryProvider.Subdivide(vectors, indices, true);
/// normalize vectors to "inflate" the icosahedron into a sphere.
for (var i = 0; i < vectors.Count; i++)
vectors[i]=Vector3.Normalize(vectors[i]);
And the GeometryProvider
class
public static class GeometryProvider
{
private static int GetMidpointIndex(Dictionary midpointIndices, List vertices, int i0, int i1)
{
var edgeKey = string.Format("{0}_{1}", Math.Min(i0, i1), Math.Max(i0, i1));
var midpointIndex = -1;
if (!midpointIndices.TryGetValue(edgeKey, out midpointIndex))
{
var v0 = vertices[i0];
var v1 = vertices[i1];
var midpoint = (v0 + v1) / 2f;
if (vertices.Contains(midpoint))
midpointIndex = vertices.IndexOf(midpoint);
else
{
midpointIndex = vertices.Count;
vertices.Add(midpoint);
midpointIndices.Add(edgeKey, midpointIndex);
}
}
return midpointIndex;
}
///
/// i0
/// / \
/// m02-m01
/// / \ / \
/// i2---m12---i1
///
///
///
public static void Subdivide(List vectors, List indices, bool removeSourceTriangles)
{
var midpointIndices = new Dictionary();
var newIndices = new List(indices.Count * 4);
if (!removeSourceTriangles)
newIndices.AddRange(indices);
for (var i = 0; i < indices.Count - 2; i += 3)
{
var i0 = indices[i];
var i1 = indices[i + 1];
var i2 = indices[i + 2];
var m01 = GetMidpointIndex(midpointIndices, vectors, i0, i1);
var m12 = GetMidpointIndex(midpointIndices, vectors, i1, i2);
var m02 = GetMidpointIndex(midpointIndices, vectors, i2, i0);
newIndices.AddRange(
new[] {
i0,m01,m02
,
i1,m12,m01
,
i2,m02,m12
,
m02,m01,m12
}
);
}
indices.Clear();
indices.AddRange(newIndices);
}
///
/// create a regular icosahedron (20-sided polyhedron)
///
///
///
///
///
///
/// You can create this programmatically instead of using the given vertex
/// and index list, but it's kind of a pain and rather pointless beyond a
/// learning exercise.
///
/// note: icosahedron definition may have come from the OpenGL red book. I don't recall where I found it.
public static void Icosahedron(List vertices, List indices)
{
indices.AddRange(
new int[]
{
0,4,1,
0,9,4,
9,5,4,
4,5,8,
4,8,1,
8,10,1,
8,3,10,
5,3,8,
5,2,3,
2,7,3,
7,10,3,
7,6,10,
7,11,6,
11,0,6,
0,1,6,
6,1,10,
9,0,11,
9,11,2,
9,2,5,
7,2,11
}
.Select(i => i + vertices.Count)
);
var X = 0.525731112119133606f;
var Z = 0.850650808352039932f;
vertices.AddRange(
new[]
{
new Vector3(-X, 0f, Z),
new Vector3(X, 0f, Z),
new Vector3(-X, 0f, -Z),
new Vector3(X, 0f, -Z),
new Vector3(0f, Z, X),
new Vector3(0f, Z, -X),
new Vector3(0f, -Z, X),
new Vector3(0f, -Z, -X),
new Vector3(Z, X, 0f),
new Vector3(-Z, X, 0f),
new Vector3(Z, -X, 0f),
new Vector3(-Z, -X, 0f)
}
);
}
}
No comments:
Post a Comment