[C# Helper]
Index Books FAQ Contact About Rod
[Beginning Database Design Solutions, Second Edition]

[Beginning Software Engineering, Second Edition]

[Essential Algorithms, Second Edition]

[The Modern C# Challenge]

[WPF 3d, Three-Dimensional Graphics with WPF and C#]

[The C# Helper Top 100]

[Interview Puzzles Dissected]

[C# 24-Hour Trainer]

[C# 5.0 Programmer's Reference]

[MCSD Certification Toolkit (Exam 70-483): Programming in C#]

Title: Platonic Solids Part 7: The dodecahedron

[Platonic Solids Part 7: The dodecahedron]

If you enjoyed calculating the coordinates of the vertices in an icosahedron, you're in for a treat! Finding the vertices for a dodecahedron is even harder, largely because a dodecahedron has more vertices. An icosahedron has 20 triangular faces and each vertex is shared by 5 faces so it has a total of 20×3÷5 = 12 vertices. A dodecahedron has 12 pentagonal faces and each vertex is shared by 3 faces so it has 12×5÷3 = 20 vertices.

Finding the Dodecahedron's Vertices

[Platonic Solids Part 7: The dodecahedron] A dodecahedron has 12 faces each of which is a pentagon. Figure 1 shows an dodecahedron with its hidden surfaces removed.


[Platonic Solids Part 7: The dodecahedron] Figure 2 shows the same dodecahedron with hidden surfaces drawn in dashed lines and its vertices labeled A through T.

The dodecahedron is centered at the origin in these figures. Vertices a and l lie on the Y-axis so they have X and Z coordinates 0. Vertices A and Q lie in the Y-Z plane so they have X coordinate 0.


[Platonic Solids Part 7: The dodecahedron] Figure 3 shows the dodecahedron's top face. You can use this figure to calculate the X and Z coordinates of vertices A through E.

The first step is to calculate the labeled angles t1 through t4. Because a pentagon has 5 sides, t1 = 2π/5. Angle t4 is half as big so t4 = π/5;.

The angle between the X and Z axes is π/2. Because that angle equals t1 + t2:

    t1 + t2 = π/2

So:

    t2 = π/2 - t1 = π/2 - 2π/5 = π/10

Finally t1 = t2 + t3 so:

    t3 = t1 - t2 = 2π/5 - π/10 = 3π/10

Now you can use the angles to calculate X and Z coordinates. First note that all of the distances from the center of the pentagon to its vertices are the same. The distance from the center to vertex A is labeled d1 in Figure 3, so they all have that distance.

Then using trigonometry you can calculate:

VariableValue
t12π/5
t2π/10
t33π/10
t4π/5
d1S/2/Sin(t4)
d2d1·Cos(t4)
d3d1·Cos(t2)
d4d1·Sin(t2)
Table 1: Intermediate Values

[Platonic Solids Part 7: The dodecahedron] Figure 4 shows the complete dodecahedron from the top. You can see from the Figure that the bottom face P-Q-R-S-T is the same as the top face but rotated 180 degrees. Using that fact and the values in the table above, you can find the X and Z values for those points as well.

If you look again at Figures 2 and 3, you can see that the distance |AF| is S and the distance |BF| is 2·d3. Using the distance formula and squaring the results gives:

|AF|2 = S2 = (Ax - Fx)2 + (Ay - Fy)2 + (Az - Fz)2 |BF|2 = (2·d3)2 = (Bx - Fx)2 + (By - Fy)2 + (Bz - Fz)2

Figures 2, 3, and 4 show that Az = Fz = 0, Ax = d1, Bx = d4, Bz = d3, and Ay = By. Define d5 = Ay = Fy. Then these equations become:

S2 = (d1 - Fx)2 + d52 (2·d3)2 = (d4 - Fx)2 + d52 + d32

Subtracting the second equation from the first gives:

S2 - (2·d3)2 = (d1 - Fx)2 - (d4 - Fx)2 - d32 = (d12 - 2·d1·Fx + Fx2) - (d42 - 2·d4·Fx + Fx2) - d32 = 2·Fx·(d4 - d1) + (d12 - d32 - d42)

Table 1 gives the values of d1, d4, and Bz, so this equation only has one remaining unknown value: Fx. Solving for Fx gives:

Fx = [S2 - (2·d3)2 - (d12 - d32 - d42)] / [2·(d4 - d1)]

This is a bit messy but you know all of the values on the right hand side so you can calculate Fx.

Now adding the two equations for |AF|2 and |BF|2 gives:

S2 + (2·d3)2 = (d1 - Fx)2 + 2·d52 + (d4 - Fx)2 + d32

If you solve this for d5 you get:

d5 = Sqr(1/2·(S2 + (2·d3)2 - (d1 - Fx)2 - (d4 - Fx)2 - d32))

This is another messy expression but at this point you know all of the values necessary to calculate d5.

If you look closely at Figure 4, you'll see that vertices F-G-H-I-J form a hexagon similar to the one shown in Figure 3 except it's bigger. The only difference is the distance from the pentagon's center to its vertices. For vertices F though J this distance is Fx. Using that fact and trigonometry similar to that shown in Figure 3 you can find the X and Z coordinates of vertices F through J.

Using symmetry you can find the X and Z coordinates for vertices K through O.

All you need to do now is find the vertices' Y coordinates.

By symmetry every vertex must be the same distance from the origin. In particular, vertices A an F must be the same distance from the origin. That means:

Ax2 + Ay2 + Az2 = Fx2 + Fy2 + Fz2

Earlier calculations showed that d5 = Ay - Fy. Substituting Fz = 0, Ax = d1, Az = 0, and Ay = d5 + Fy into this equation gives:

d12 + (d5 + Fy)2 = Fx2 + Fy2

You know Fx at this point so you can solve this equation for its only unknown value Fy:

d12 + d52 + 2·d5·Fy + Fy2 = Fx2 + Fy2 2·d5·Fy = Fx2 - d12 - d52 Fy = (Fx2 - d12 - d52) / (2·d5)

From these values and symmetry you can calculate the remaining vertex coordinates. Table 2 shows the remaining intermediate values needed to perform the calculations.

VariableValue
Fx[S2 - (2·d3)2 - (d12 - d32 - d42)] / [2·(d4 - d1)]
d5Sqr(1/2·(S2 + (2·d3)2 - (d1 - Fx)2 - (d4 - Fx)2 - d32))
Fy(Fx2 - d12 - d52) / (2·d5)
Ayd5 + Fy
Table 2: More Intermediate Values

Table 3 shows the final vertex coordinates.

VertexXYZ
Ad1Ay0
Bd4Ayd3
C-d2AyS/2
D-d2Ay-S/2
Ed4Ay-d3
FFxFy0
GFx·Sin(t2)FyFx·Cos(t2)
H-Fx·Sin(t3)FyFx·Cos(t3)
I-Fx·Sin(t3)Fy-Fx·Cos(t3)
JFx·Sin(t2)Fy-Fx·Cos(t2)
KFx·Sin(t3)-FyFx·Cos(t3)
L-Fx·Sin(t2)-FyFx·Cos(t2)
M-Fx-Fy0
N-Fx·Sin(t2)-Fy-Fx·Cos(t2)
OFx·Sin(t3)-Fy-Fx·Cos(t3)
Pd2-AyS/2
Q-d4-Ayd3
R-d1-Ay0
S-d4-Ay-d3
Td2-Ay-S/2
Table 3: Vertex Coordinates

Piece of cake!

The Example Program

The following code shows how the example program generates the dodecahedron's vertices.

// Return the vertices for an dodecahedron. private Point3D[] MakeVertices(double side_length) { // Value t1 is actually never used. double s = side_length; //double t1 = 2.0 * Math.PI / 5.0; double t2 = Math.PI / 10.0; double t3 = 3.0 * Math.PI / 10.0; double t4 = Math.PI / 5.0; double d1 = s / 2.0 / Math.Sin(t4); double d2 = d1 * Math.Cos(t4); double d3 = d1 * Math.Cos(t2); double d4 = d1 * Math.Sin(t2); double Fx = (s * s - (2.0 * d3) * (2.0 * d3) - (d1 * d1 - d3 * d3 - d4 * d4)) / (2.0 * (d4 - d1)); double d5 = Math.Sqrt(0.5 * (s * s + (2.0 * d3) * (2.0 * d3) - (d1 - Fx) * (d1 - Fx) - (d4 - Fx) * (d4 - Fx) - d3 * d3)); double Fy = (Fx * Fx - d1 * d1 - d5 * d5) / (2.0 * d5); double Ay = d5 + Fy; Point3D A = new Point3D(d1, Ay, 0); Point3D B = new Point3D(d4, Ay, d3); Point3D C = new Point3D(-d2, Ay, s / 2); Point3D D = new Point3D(-d2, Ay, -s / 2); Point3D E = new Point3D(d4, Ay, -d3); Point3D F = new Point3D(Fx, Fy, 0); Point3D G = new Point3D(Fx * Math.Sin(t2), Fy, Fx * Math.Cos(t2)); Point3D H = new Point3D(-Fx * Math.Sin(t3), Fy, Fx * Math.Cos(t3)); Point3D I = new Point3D(-Fx * Math.Sin(t3), Fy, -Fx * Math.Cos(t3)); Point3D J = new Point3D(Fx * Math.Sin(t2), Fy, -Fx * Math.Cos(t2)); Point3D K = new Point3D(Fx * Math.Sin(t3), -Fy, Fx * Math.Cos(t3)); Point3D L = new Point3D(-Fx * Math.Sin(t2), -Fy, Fx * Math.Cos(t2)); Point3D M = new Point3D(-Fx, -Fy, 0); Point3D N = new Point3D(-Fx * Math.Sin(t2), -Fy, -Fx * Math.Cos(t2)); Point3D O = new Point3D(Fx * Math.Sin(t3), -Fy, -Fx * Math.Cos(t3)); Point3D P = new Point3D(d2, -Ay, s / 2); Point3D Q = new Point3D(-d4, -Ay, d3); Point3D R = new Point3D(-d1, -Ay, 0); Point3D S = new Point3D(-d4, -Ay, -d3); Point3D T = new Point3D(d2, -Ay, -s / 2); List points = new List(); points.Add(A); points.Add(B); points.Add(C); points.Add(D); points.Add(E); points.Add(F); points.Add(G); points.Add(H); points.Add(I); points.Add(J); points.Add(K); points.Add(L); points.Add(M); points.Add(N); points.Add(O); points.Add(P); points.Add(Q); points.Add(R); points.Add(S); points.Add(T); return points.ToArray(); }

This code uses the equations above to calculate the values needed to generate the vertices' coordinates. It then creates the vertices, adds them to a list, and returns the list of vertices converted into an array.

I found identifying the points more confusing in this example than in the previous ones, so I added the following ToIndex method.

// Find a point's index from its letter. private int ToIndex(char ch) { return ch - 'A'; }

This method simply subtracts 'A' from the point's name to get an index. That makes A = 0, B = 1, C = 2, and so forth.

Other parts of the program use ToIndex to identify points by their letters in the Figures rather than by their indices. For example, the following AddSegment method adds a new line segment between two named points to a mesh.

private void AddSegment(MeshGeometry3D mesh, Point3D[] points, char name1, char name2, Vector3D up) { AddSegment(mesh, points[ToIndex(name1)], points[ToIndex(name2)], up); }

This method uses ToIndex to get the indices of the points with the given letter names. It then passes them into the AddSegment method used by previous example. That method creates a skinny rectangular prism between the two points to represent a line segment. Download the example and look at the code for details.

I made three other major changes in this example to make drawing easier. First the following AddPolygon methods creates all of the triangles needed to represent a polygon.

// Add a polygon to the indicated mesh. // Do not reuse old points but reuse these points. private void AddPolygon(MeshGeometry3D mesh, params Point3D[] points) { // Create the points. int index1 = mesh.Positions.Count; foreach (Point3D point in points) mesh.Positions.Add(point); // Create the triangles. for (int i = 1; i < points.Length - 1; i++) { mesh.TriangleIndices.Add(index1); mesh.TriangleIndices.Add(index1 + i); mesh.TriangleIndices.Add(index1 + i + 1); } }

The points passed into the method must be outwardly oriented and must be co-planar. If they aren't co-planar, the method creates a sort of bent potato chip shape.

If the points have names A, B, C, ..., M, then the method creates triangles A-B-C, A-C-D, ..., A-L-M. Those triangles tile the polygon.

The second change is similar to the first one but it adds the segments that represent a polygon.

private void AddPolygonSegments(MeshGeometry3D mesh, string point_names, Point3D[] points, Vector3D up) { for (int i = 0; i < point_names.Length; i++) { char ch1 = point_names[i]; char ch2 = point_names[(i + 1) % point_names.Length]; AddSegment(mesh, points, ch1, ch2, up); } }

This method loops through the points that make up a polygon and calls the following new version of the AddSegment method to represent each of the polygon's edges. The new version of this method is the third major change.

private void AddSegment(MeshGeometry3D mesh, Point3D[] points, char name1, char name2, Vector3D up) { Point3D point1 = points[ToIndex(name1)]; Point3D point2 = points[ToIndex(name2)]; VerifyEdgeLength(1, point1, point2); AddSegment(mesh, point1, point2, up); }

This version of AddSegment uses ToIndex to get the points representing the two vertices it is connecting. It verifies that the points are 1 unit apart and finally calls the previous version of AddSegment to create the prism representing the segment.

The new techniques of using AddPolygon and AddPolygonSegments, and of making AddPolygonSegments verify the lengths of the edges makes those tasks a bit easier than they are in the previous examples. (I should have used them in the previous examples but I didn't think of them until I was building this one.)

The rest of the program's code deals with using WPF to display the dodecahedron's faces, its edges, and the axes.

Download the example to experiment with it and to see additional details.

© 2009-2023 Rocky Mountain Computer Consulting, Inc. All rights reserved.