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

A dodecahedron has 12 faces each of which is a pentagon. Figure 1 shows an dodecahedron with its hidden surfaces removed.

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.

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:

Variable | Value |
---|---|

t1 | 2π/5 |

t2 | π/10 |

t3 | 3π/10 |

t4 | π/5 |

d1 | S/2/Sin(t4) |

d2 | d1·Cos(t4) |

d3 | d1·Cos(t2) |

d4 | d1·Sin(t2) |

Table 1: Intermediate Values

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}= S^{2}= (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:

S^{2}= (d1 - Fx)^{2}+ d5^{2}(2·d3)^{2}= (d4 - Fx)^{2}+ d5^{2}+ d3^{2}

Subtracting the second equation from the first gives:

S^{2}- (2·d3)^{2}= (d1 - Fx)^{2}- (d4 - Fx)^{2}- d3^{2}= (d1^{2}- 2·d1·Fx + Fx^{2}) - (d4^{2}- 2·d4·Fx + Fx^{2}) - d3^{2}= 2·Fx·(d4 - d1) + (d1^{2}- d3^{2}- d4^{2})

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 = [S^{2}- (2·d3)^{2}- (d1^{2}- d3^{2}- d4^{2})] / [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:

S^{2}+ (2·d3)^{2}= (d1 - Fx)^{2}+ 2·d5^{2}+ (d4 - Fx)^{2}+ d3^{2}

If you solve this for d5 you get:

d5 = Sqr(1/2·(S^{2}+ (2·d3)^{2}- (d1 - Fx)^{2}- (d4 - Fx)^{2}- d3^{2}))

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:

Ax^{2}+ Ay^{2}+ Az^{2}= Fx^{2}+ Fy^{2}+ Fz^{2}

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

d1^{2}+ (d5 + Fy)^{2}= Fx^{2}+ Fy^{2}

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

d1^{2}+ d5^{2}+ 2·d5·Fy + Fy^{2}= Fx^{2}+ Fy^{2}2·d5·Fy = Fx^{2}- d1^{2}- d5^{2}Fy = (Fx^{2}- d1^{2}- d5^{2}) / (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.

Variable | Value |
---|---|

Fx | [S^{2} – (2·d3)^{2} – (d1^{2} – d3^{2} – d4^{2})] / [2·(d4 – d1)] |

d5 | Sqr(1/2·(S^{2} + (2·d3)^{2} – (d1 – Fx)^{2} – (d4 – Fx)^{2} – d3^{2})) |

Fy | (Fx^{2} – d1^{2} – d5^{2}) / (2·d5) |

Ay | d5 + Fy |

Table 2: More Intermediate Values

Table 3 shows the final vertex coordinates.

Vertex | X | Y | Z |
---|---|---|---|

A | d1 | Ay | 0 |

B | d4 | Ay | d3 |

C | -d2 | Ay | S/2 |

D | -d2 | Ay | -S/2 |

E | d4 | Ay | -d3 |

F | Fx | Fy | 0 |

G | Fx·Sin(t2) | Fy | Fx·Cos(t2) |

H | -Fx·Sin(t3) | Fy | Fx·Cos(t3) |

I | -Fx·Sin(t3) | Fy | -Fx·Cos(t3) |

J | Fx·Sin(t2) | Fy | -Fx·Cos(t2) |

K | Fx·Sin(t3) | -Fy | Fx·Cos(t3) |

L | -Fx·Sin(t2) | -Fy | Fx·Cos(t2) |

M | -Fx | -Fy | 0 |

N | -Fx·Sin(t2) | -Fy | -Fx·Cos(t2) |

O | Fx·Sin(t3) | -Fy | -Fx·Cos(t3) |

P | d2 | -Ay | S/2 |

Q | -d4 | -Ay | d3 |

R | -d1 | -Ay | 0 |

S | -d4 | -Ay | -d3 |

T | d2 | -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); Listpoints = 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 and look at the code for details.