exupero's blog
RSSApps

Drawing an icosahedron

I'd like to draw an icosphere, the first step of which is drawing an icosahedron. There are lots of ways to calculate the locations of the vertices, such as using the Golden Ratio, but to me the most intuitive way seems to be to use spherical coordinates, which I'll talk about in terms of geophysical longitude and latitude.

An icosahedron has 12 vertices and 20 triangular faces. One vertex is the "north pole" of the icosahedron, 90°N (longitude irrelevant), another is at 90°S. The other ten are distributed evenly around arctan(1/2) ≈ 26.57° north and south of the equator, five at 26.57°N, five at 26.57°S, though the two rings are offset from each other by 36° longitude. Stated in code:

(def vertices
  (let [lat (Math/toDegrees (Math/atan 0.5))]
    (concat
      ; north pole
      [[0 90]]
      ; northern ring
      (for [i (range 0 360 (/ 360 5))]
        [i lat])
      ; southern ring, offset from northern ring
      (for [i (range (/ 360 10) 360 (/ 360 5))]
        [i (- lat)])
      ; south pole
      [[0 -90]])))

The 20 faces of an icosahedron occur in groups of five: five at the top around the north pole, five around the middle pointing down, five around the middle pointing up, and five around the bottom. Referring to vertices by their indexes in the sequence above, we have

(def triangles
  [; top triangles
   [0 2 1] [0 3 2] [0 4 3] [0 5 4] [0 1 5]
   ; middle triangles pointing down
   [1 2 6] [2 3 7] [3 4 8] [4 5 9] [5 1 10]
   ; middle triangles pointing up
   [6 2 7] [7 3 8] [8 4 9] [9 5 10] [10 1 6]
   ; bottom triangles
   [11 9 10] [11 8 9] [11 7 8] [11 6 7] [11 10 6]])

To convert these longitudes and latitudes to Cartesian coordinates, I'll use this function:

(defn ->cartesian [[lon lat]]
  [(* (Math/cos (Math/toRadians lat)) (Math/sin (Math/toRadians lon)))
   (* (Math/cos (Math/toRadians lat)) (Math/cos (Math/toRadians lon)))
   (Math/sin (Math/toRadians lat))])

This is a different conversion than what's on Wikipedia because I'm measuring latitude north and south of a 0° equator, as opposed to the traditional scheme of measuring angles from the north pole.

Now we can map triangle definitions to trios of spherical-coordinate vertices, and from spherical coordinates to Cartesian coordinates:

(def mesh
  (map (partial map (comp ->cartesian (vec vertices)))
       triangles))

Rendering these triangles produces the following shape, which you can drag to rotate:

To make an icosphere, we'll subdivide these triangles until we have an adequate approximation of a sphere.