Amateur Geometry

Editorial Note: This one is mostly for me. I want to record my solution to a puzzle so that I can throw out my notes.

A few days ago I began to play with the Platonic solids. (I was fooling around with OpenGL, and needed some aesthetically pleasing shapes to render.) I could have just looked up their geometry, but elected instead to derive it from a combination of inspection, guesswork, and simple trigonometry. This isn’t the “right” way to solve the problem, and it doesn’t yield an elegant solution, but, hey, an answer is an answer. Below, I talk a bit about how I found it.

Preliminaries

It’s a lot easier to solve this problem if you’ve got some solids to stare at, and, due to some embarrassing personal history, I do. The dice seen to the right proved quite helpful when reasoning about how the points of the polyhedra relate to one another.

There are an infinite number of representations of the Platonic solids; they can be scaled to any size, moved to any position, and rotated to any orientation. For our purposes we’re only interested in solids with points that all lie on the unit sphere. There are still an infinite number of those, but we’ll settle for any that are found in convenient orientations.

Trivial Cases

The points of the octahedron and cube are easy to figure out. The 4 cardinal directions, plus top and bottom, give us the 6 points of the octahedron. A cube’s 8 points are mirrored by all 3 coordinate planes, and are therefore easily derived.

In the interests of completeness, here’s a little Python to generate points and faces for these two polyhedra:

def make_hexahedron_points():
    d = 1.0/math.sqrt(3)

    return ((-d,-d, d),( d,-d, d),( d, d, d),(-d, d, d),
            (-d,-d,-d),( d,-d,-d),( d, d,-d),(-d, d,-d))

octohedron_vertices = (( 0.0,  1.0,  0.0),
                       (-1.0,  0.0,  0.0),
                       ( 0.0,  0.0,  1.0),
                       ( 1.0,  0.0,  0.0),
                       ( 0.0,  0.0, -1.0),
                       ( 0.0, -1.0,  0.0))

octohedron_faces = ((5,2,1),(5,3,2),(5,4,3),(5,1,4),(0,1,2),(0,2,3),(0,3,4),(0,4,1))

hexahedron_vertices = make_hexahedron_points()

hexahedron_faces = ((0,1,2,3),(4,7,6,5),
                    (0,4,5,1),(1,5,6,2),(2,6,7,3),(3,7,4,0))

Tangent

The remaining three solids require a bit more analysis, but, fortunately, 90% of that analysis is shared between them. Let’s begin by considering 4 abstract points near the top of the unit sphere:

A = (0,1,0)
B = (0,1-Y,Z)
C = (X,1-Y,Zc)
D = (0,1-Y,0)
|A|=|B|=|C|=1

Furthermore, let’s specify that |AB|==|AC|, label the angle between AB and AC as theta, and the angle between DB and DC as phi. (Yes, this would be easier if I knocked together some illustrations, but I’m lazy and the blog is free. Anyway.)

Consider the triangles ADC and ADB. Since these are both right triangles, we know that |DC|=|DB|:

|AD|*|AD|+|DC|*|DC|=|AC|*|AC|		(pythagorean t.)
|AD|*|AD|+|DB|*|DB|=|AB|*|AB|		(pythagorean t.)
|AC|*|AC|=|AB|*|AB|			(|AB|==|AC| given)
|AD|*|AD|+|DC|*|DC|=|AD|*|AD|+|DB|*|DB|
|DC|*|DC|=|DB|*|DB|
|DC|=|DB|

By taking the DB*DC dot product, we can eliminate Zc as an independent variable:

DB=(0,0,Z)				(vector subtraction)
DC=(X,0,Zc)				(vector subtraction)
|DB| = Z				(length definition)
|DC| = |DB| = Z				(earlier result)
DB*DC=Z*Zc				(dot product definition)
DB*DC=cos(phi)*|DB|*|DC|		(dot product geometric interpretation)
Z*Zc=cos(phi)*Z*Z
Zc=Z*cos(phi)

Finally, from the AB*AC dot product, we can calculate T, where Y=Z*T:

AB=(0,-Y,Z)					(vector subtraction)
AC=(X,-Y,Zc)					(vector subtraction)
|AB|=sqrt(Y*Y+Z*Z)				(length definition)
|AC|=|AB|					(given)
AB*AC=Y*Y+Z*Zc					(dot product definition)
AB*AC=cos(theta)*|AB|*|AC|			(dot product geometric interpretation)
Y*Y+Z*Zc=cos(theta)*|AB|*|AB|
Y*Y+Z*Zc=cos(theta)*(Y*Y+Z*Z)
Y*Y+Z*Zc=cos(theta)*Y*Y+cos(theta)*Z*Z
Y*Y*(1-cos(theta))=cos(theta)*Z*Z-Z*Zc
Y*Y*(1-cos(theta))=cos(theta)*Z*Z-Z*Z*cos(phi)	(earlier result)
Y*Y*(1-cos(theta))=Z*Z*(cos(theta)-cos(phi))
Y*Y/(Z*Z)=(cos(theta)-cos(phi))/(1-cos(theta))
Y/Z=sqrt((cos(theta)-cos(phi))/(1-cos(theta)))
T=sqrt((cos(theta)-cos(phi))/(1-cos(theta)))

Let’s now define Z in terms of T (and, indirectly, define the absolute location of all 4 points in terms of the angles theta and phi):

|B|=1					(given)
|B|=sqrt((1-Y)*(1-Y)+Z*Z)		(length definition)
(1-Y)*(1-Y)+Z*Z=1
1-2*Y+Y*Y+Z*Z=1
1-2*Z*T+Z*T*Z*T+Z*Z=1			(definition of T)
Z*Z*(T*T+1)=2*Z*T
Z=2*T/(T*T+1)

Tetrahedron

A tetrahedron inscribed in the unit sphere can be oriented s.t. three of its vertices map onto the just-discussed points A, B, and C with an angle theta of 60 degrees, and an angle phi of 120 degrees. (In such an orientation the triangle ABC is an equilateral face of the tetrahedron and phi covers 1/3rd of a full rotation.) Grinding the math gives a T of sqrt(2). Here’s some Python:

def make_tetrahedron_points():
    z = 2.0*math.sqrt(2)/3.0
    y = -1.0/3.0

    zsin60 = z*math.sin(60*math.pi/180)
    zcos60 = z*math.cos(60*math.pi/180)

    return ((0.0,1.0,0.0),(0.0,y,z),(zsin60,y,-zcos60),(-zsin60,y,-zcos60))

tetrahedron_vertices = make_tetrahedron_points()

tetrahedron_faces = ((1,3,2),(1,0,3),(3,0,2),(2,0,1))

Icosahedron

In a very similar way, an icosahedron inscribed in the unit sphere can be oriented s.t. three of its vertices map onto the just-discussed points A, B, and C with an angle theta of 60 degrees, and an angle phi of 72 degrees. (In such an orientation the triangle ABC is an equilateral face of the icosahedron and phi covers 1/5th of a full rotation, since the bases of the 5 faces which meet at A form a pentagon.) Grinding the math gives a T of sqrt(1.0-2.0*cos72). Inspection of our model reveals that 2 points of the polyhedron are at the top and bottom of the unit sphere, while the other 10 are arranged in two pentagons offset by Y from the poles. Here’s some Python:

def make_icosahedron_points():
    cos72 = math.cos(72*math.pi/180)

    t = math.sqrt(1.0-2.0*cos72)
    z = t/(1.0-cos72)
    y = 1.0 - z*t

    zsin72 = z*math.sin(72*math.pi/180)
    zcos72 = z*math.cos(72*math.pi/180)
    zsin36 = z*math.sin(36*math.pi/180)
    zcos36 = z*math.cos(36*math.pi/180)

    return ((0.0,1.0,0.0),(0.0,y,z),
            (zsin72,y,zcos72),(zsin36,y,-zcos36),(-zsin36,y,-zcos36),(-zsin72,y,zcos72),
            (0.0,-1.0,0.0),(0.0,-y,-z),
            (zsin72,-y,-zcos72),(zsin36,-y,zcos36),(-zsin36,-y,zcos36),(-zsin72,-y,-zcos72))

icosahedron_vertices = make_icosahedron_points()

icosahedron_faces = ((0,1,2),(0,2,3),(0,3,4),(0,4,5),(0,5,1),
                     (1,9,2),(2,8,3),(3,7,4),(4,11,5),(5,10,1),
                     (11,10,5),(10,9,1),(9,8,2),(8,7,3),(7,11,4),
                     (7,8,6),(8,9,6),(9,10,6),(10,11,6),(11,7,6))

Dodecahedron

Dodecahedrons are the hardest Platonic solids to reason about. They have the most points (20) and the most obvious arrangement of those points (4 parallel pentagons) doesn’t directly map onto the A,B,C model used above. The shortest argument seems to go through the edge length, |AB|, which I’ll call E. Something like this:

AB=(0,-Y,Z)					(vector subtraction)
|AB|=sqrt(Y*Y+Z*Z)				(length definition)
|AB|=sqrt(Z*T*Z*T+Z*Z)				(definition of T)
|AB|=sqrt(Z*Z*(T*T+1))
|AB|=sqrt(Z*2*T)				(earlier result)
|AB|=sqrt(4*T*T/(T*T+1))			(earlier result)

T=sqrt((cos(theta)-cos(phi))/(1-cos(theta)))	(earlier result)
T*T=(cos(theta)-cos(phi))/(1-cos(theta))
T*T+1=(1-cos(phi))/(1-cos(theta))
T*T/T*T+1=(cos(theta)-cos(phi))/(1-cos(phi))
|AB|=2*sqrt((cos(theta)-cos(phi))/(1-cos(phi)))
E=2*sqrt((cos(theta)-cos(phi))/(1-cos(phi)))	(definition of E)

Now, even though it’s not a convenient arrangement for most things, a dodecahedron inscribed in the unit sphere can be oriented s.t. three of its vertices map onto the above-discussed points A, B, and C with an angle theta of 108 degrees, and an angle phi of 120 degrees. (In such an orientation the triangle ABC is a partial face of the dodecahedron, and theta is an an interior angle of that pentagon. Phi covers 1/3rd of a full rotation, since the 3 edges which meet at A are symmetric. I admit that this construction isn’t watertight, but if you stare at a dodecahedron, I think you’ll find it convincing.) Grinding the math gives an E of 2*sqrt((cos108+0.5)/1.5).

Now we need to define the twenty vertices of a dodecahedron in terms of the single number E (and, admittedly, some arbitrary orientation terms). Inspection of our model reveals that its points are arranged in 4 pentagons, each parallel to the XZ plane. The edge length of the top and bottom pentagons is just the edge length of the dodecahedron, E. The edge length of the middle two pentagons — let’s call it E2 — can be derived from E, since E2 is the distance between two non-adjacent points of a face of the dodecahedron, which is just a pentagon with edge length E. From the pentagon edge lengths, we can compute the pentagon radiuses (distances from the pentagon centers to the pentagon vertices), and from the radiuses, the fact that the pentagons are centered on the Y axis, and the fact that the vertices lie on the unit sphere, we can compute the Y-offset of the pentagons themselves.

Yes, some pictures would really help. Also, the arguments above could be tightened up a lot. Instead, you get this:

E=2*sqrt((cos(theta)-cos(phi))/(1-cos(phi)))	(previous result)
sin(36)=0.5*E/R					(from face trigonometry)
R=0.5*E/sin(36)
Y1*Y1+R*R=1					(unit sphere)
Y1=sqrt(1-R*R)

sin(36)=0.5*E2/R2				(from pentagon trigonometry)
E2=2*E*sin(54)					(from face trigonometry)
R2=0.5*E2/sin(36)
R2=0.5*2*E*sin(54)/sin(36)
R2=2*R*sin(54)
Y2*Y2+R2*R2=1					(unit sphere)
Y2=sqrt(1-R2*R2)

And, at last, some Python:

def make_dodecahedron_points():
    cos108 = math.cos(108*math.pi/180)
    sin36 = math.sin(36*math.pi/180)
    sin54 = math.sin(54*math.pi/180)

    el = math.sqrt((4 + 8*cos108)/3)
    z1 = 0.5*el/sin36
    y1 = math.sqrt(1-z1**2)
    z2 = 2*z1*sin54
    y2 = math.sqrt(1-z2**2)

    sin72 = math.sin(72*math.pi/180); z1sin72 = z1*sin72; z2sin72 = z2*sin72
    cos72 = math.cos(72*math.pi/180); z1cos72 = z1*cos72; z2cos72 = z2*cos72
    sin36 = math.sin(36*math.pi/180); z1sin36 = z1*sin36; z2sin36 = z2*sin36
    cos36 = math.cos(36*math.pi/180); z1cos36 = z1*cos36; z2cos36 = z2*cos36

    return ((0.0,y1,z1),
            (z1sin72,y1,z1cos72),(z1sin36,y1,-z1cos36),(-z1sin36,y1,-z1cos36),(-z1sin72,y1,z1cos72),
            (0.0,y2,z2),
            (z2sin72,y2,z2cos72),(z2sin36,y2,-z2cos36),(-z2sin36,y2,-z2cos36),(-z2sin72,y2,z2cos72),
            (0.0,-y1,-z1),
            (z1sin72,-y1,-z1cos72),(z1sin36,-y1,z1cos36),(-z1sin36,-y1,z1cos36),(-z1sin72,-y1,-z1cos72),
            (0.0,-y2,-z2),
            (z2sin72,-y2,-z2cos72),(z2sin36,-y2,z2cos36),(-z2sin36,-y2,z2cos36),(-z2sin72,-y2,-z2cos72))

dodecahedron_vertices = make_dodecahedron_points()

dodecahedron_faces = ((0,1,2,3,4),
                      (0,5,17,6,1),(1,6,16,7,2),(2,7,15,8,3),(3,8,19,9,4),(4,9,18,5,0),
                      (10,11,12,13,14),
                      (10,15,7,16,11),(11,16,6,17,12),(12,17,5,18,13),(13,18,9,19,14),(14,19,8,15,10))

In Conclusion

I’m left with a new appreciation for the ancient Greeks. It’s easy enough to construct these things with hand-wavy arguments and common trigonometry, but to work them out from first principles 2500 years ago is another matter entirely.

One can also get an inkling of why Plato associated the dodecahedron not with any of the 4 usual elements, but with the arrangement of “the constellations on the whole heaven”.

Share and Enjoy:
  • Twitter
  • Facebook
  • Digg
  • Reddit
  • HackerNews
  • del.icio.us
  • Google Bookmarks
  • Slashdot
This entry was posted in Projects, Python. Bookmark the permalink.

Comments are closed.