Title: Build a geodesic sphere with WPF and C#
Building a geodesic sphere is actually pretty easy after you know how to build an icosahedron.
Start by building an icosahedron as described in the post Platonic Solids Part 6: The icosahedron. Each of the icosahedron's faces is a triangle. Divide the triangle into 9 new triangles as shown in Figure 1.
In Figure 1 the points labeled [0], [1], and [2] are the triangle's original corners (ordered for proper outward orientation according to the right-hand rule).
Push out the newly created points A, B, C, D, E, F, and G so they are the same distance from the center of the sphere as the original icosahedron vertices. If the icosahedron is centered at the origin and its outer radius is R, then you would move the new point (X, Y, Z) to:
new_X = X * R / Sqr(X * X + Y * Y + Z * Z)
new_Y = Y * R / Sqr(X * X + Y * Y + Z * Z)
new_Z = Z * R / Sqr(X * X + Y * Y + Z * Z)
Now use the new points to build the triangles shown in Figure 1. The result is a new solid with 9 times as many triangles as before. Because the initial icosahedron had 20 faces, the new solid has 9×20 = 180 faces.
If you like you can repeat those steps to convert each of the 180 faces into 9 new triangles giving 120×9 = 1,620 faces. You can continue subdividing triangles to get as smooth a result as you desire. (Although 1,620 faces is probably enough unless you're building a really huge sphere, perhaps for a lunar colony.)
You may recall from my first post on the Platonic solids that the only Platonic solids in 3 dimensions are the tetrahedron, cube, octahedron, dodecahedron, and icosahedron. The reason the new solid isn't Platonic is because its faces don't all have the same side lengths.
The icosahedron starts with faces that are equilateral triangles so it is Platonic. When you divide an equilateral triangle as described above, the resulting triangles are not equilateral. The example includes code that displays the number of different kinds of triangles in the solid and their angles.
The first level sphere contains two kinds of triangles with angles:
54.6347°, 54.6347°, 70.7305°
58.5832°, 60.7084°, 60.7084°
The second level sphere contains 12 kinds of triangles and the third level sphere contains 85 kinds of triangles.
Example Code
The example program uses a Triangle class to manage the triangles it builds. This class is mostly a wrapper for an array that holds three points. It also includes the following code to subdivide a triangle into 9 new triangles.
// Subdivide this triangle and put the
// new triangles in the list triangles.
public void Subdivide(List<Triangle> triangles,
Point3D center, double radius)
{
// Find the dividing points.
Vector3D v01 = Points[1] - Points[0];
Vector3D v02 = Points[2] - Points[0];
Vector3D v12 = Points[2] - Points[1];
Point3D A = Points[0] + v01 * 1.0 / 3.0;
Point3D B = Points[0] + v02 * 1.0 / 3.0;
Point3D C = Points[0] + v01 * 2.0 / 3.0;
Point3D D = Points[0] + v01 * 2.0 / 3.0 + v12 * 1.0 / 3.0;
Point3D E = Points[0] + v02 * 2.0 / 3.0;
Point3D F = Points[1] + v12 * 1.0 / 3.0;
Point3D G = Points[1] + v12 * 2.0 / 3.0;
// Normalize the points.
NormalizePoint(ref A, center, radius);
NormalizePoint(ref B, center, radius);
NormalizePoint(ref C, center, radius);
NormalizePoint(ref D, center, radius);
NormalizePoint(ref E, center, radius);
NormalizePoint(ref F, center, radius);
NormalizePoint(ref G, center, radius);
// Make the triangles.
triangles.Add(new Triangle(Points[0], A, B));
triangles.Add(new Triangle(A, C, D));
triangles.Add(new Triangle(A, D, B));
triangles.Add(new Triangle(B, D, E));
triangles.Add(new Triangle(C, Points[1], F));
triangles.Add(new Triangle(C, F, D));
triangles.Add(new Triangle(D, F, G));
triangles.Add(new Triangle(D, G, E));
triangles.Add(new Triangle(E, G, Points[2]));
}
// Make the point the indicated distance away from the center.
private void NormalizePoint(ref Point3D point,
Point3D center, double distance)
{
Vector3D vector = point - center;
point = center + vector / vector.Length * distance;
}
The Subdivide method creates points A, B, C, D, E, F, and G. It calls the NormalizePoint method to make those points the correct distance from the sphere's center and then uses the normalized points to build the 9 new triangles shown in Figure 1.
The NormalizePoint method finds a vector from the sphere's center to a point. It divides the vector by its length so it has length 1, multiplies it by the desired radius, and adds the result to the sphere's center. The result is a point in the same direction from the center of the sphere as the original point but with distance radius from the center.
The main program uses the following code snippet to build the geodesic sphere.
// Build the initial icosahedron.
...
// The radius is the distance from
// the top point to the origin.
double radius = points[0].Y;
// Divide the triangles if desired
// to make the geodesic sphere.
Point3D origin = new Point3D(0, 0, 0);
for (int i = 0; i < Level; i++)
{
List<Triangle> new_triangles = new List<Triangle>();
foreach (Triangle triangle in triangles)
{
triangle.Subdivide(new_triangles, origin, radius);
}
triangles = new_triangles;
}
The program first builds the initial icosahedron (not shown here). Then this code sets the radius equal to the Y coordinate of the top vertex.
Next the code enters a loop that ranges from 0 to Level - 1 to refine the sphere the desired number of times. Inside each loop, it creates a new_triangles list to hold the sphere's subdivided triangles. It then loops through the triangles and calls their Subdivide methods to make new triangles in the new_triangles list. When it has finished subdividing the triangles, the code sets the triangles list equal to the new_triangles list and repeats if necessary.
After that the program works much as previous examples do. The only other interesting change is in the way the program draws the triangles' edges when you check the Edges box.
You could simply draw all of every triangle's edges but each edge is shared by two triangles so you would end up drawing each edge twice.
Instead the program uses an Edge class to represent the edges. This class uses the following code to implement the IEquatable<Edge> interface.
public bool Equals(Edge other)
{
Vector3D v1 = this.Point1 - other.Point1;
Vector3D v2 = this.Point2 - other.Point2;
if ((v1.Length < 0.001) && (v2.Length < 0.001)) return true;
v1 = this.Point2 - other.Point1;
v2 = this.Point1 - other.Point2;
if ((v1.Length < 0.001) && (v2.Length < 0.001)) return true;
return false;
}
This method returns true if this Edge is close enough to another Edge that we should consider them the same. The code checks the distance between the objects' points in case the points are slightly different due to rounding errors.
The main program then builds a HashSet to contain Edge objects and loops through the triangles.
For each triangle, the program adds any edges that are not already in the HashSet to the HashSet.
When it is finished, the HashSet contains a list of the edges with each appearing only once. The program loops through the HashSet and adds the necessary segments to the 3D edge model.
This sounds more complicated than it really is. There are a few details to handle, but each is relatively easy if you use the Triangle and Edge classes.
Download the example to experiment with it and to see additional details.
|