exupero's blog
RSSApps

Drawing an icosphere

In the previous post we drew a 20-sided icosahedron:

To make an icosphere, we'll divide each triangular face into four triangles. Here's an icosahedron with one face subdivided:

And here's the code to do it:

(defn average [& xs]
  (/ (reduce + xs) (count xs)))
(defn subdivide-face [[a b c]]
  (let [ab (map average a b)
        ac (map average a c)
        bc (map average b c)]
    [[a ab ac]
     [b bc ab]
     [c ac bc]
     [ab bc ac]]))

Using mesh from the previous post, we can subdivide faces with

(mapcat subdivide-face mesh)

You'll notice this isn't any closer to a sphere than the original icosahedron. While we now have 80 triangles, we still only have 20 flat faces. We need to slide each of the new points out to the surface of the bounding sphere.

(defn extend-point [point]
  (let [length (Math/sqrt (transduce (map #(* % %)) + point))]
    (map #(/ % length) point)))

This function considers each point to be a vector from the center of the icosahedron and scales that vector to length 1, which puts it on the boundary of the unit sphere.

We'll use a transducer to subdivide the mesh:

(def subdivide
  (comp
    (mapcat subdivide-face)
    (map (partial map extend-point))))
(sequence subdivide mesh)

A couple more levels of subdivision:

(sequence (comp subdivide subdivide) mesh)
(sequence (comp subdivide subdivide subdivide) mesh)

We now have 1,280 faces and a decent approximation of a sphere.

One use for icospheres is when you want a relatively even distribution of points around the sphere, as opposed to UV spheres, which mimic longitude and latitude and therefore tend to cluster points more densely around the poles.