Saturday, May 09, 2015

Icosahedron disjoint visual effect

I first became interested in icosahedrons when a close friend of mine r00t built a game world consisting of millions of nodes. I helped to build a hierarchical visualization of the nodes, which you can see here: gr1d v1z. Due to their regular structure, icosahedrons make an excellent geometry for laying out discreet points in a spherical shape.

r00t found this stackoverflow question which asks how to create this icosahedron visual effect. Sounds like fun, let's give it a try!

We want to build an icosahedron where the triangles have gaps between them, like so:



And we want to control the size of the triangles such that the gap can be large or small:


Clearly we can achieve this effect with scaling, but we have to make a distinction between scaling the entire icosahedron (which affects size and position), and scaling the individual triangles (which will affect size only). We will need to construct individual triangles instead of one big geometry.

To construct and icosahedron, you build 20 triangles from 12 vertices:
(0, ±1, ±τ)
(±1, ±τ, 0)
τ, 0, ±1)
where τ \tfrac{1+\sqrt{5}}{2}

To scale up and down in position, each triangle will need to be constructed with points centered around zero. To center a triangle on zero we find the midpoint of the 3 vertices, and subtract that from each of the points. Now the triangle can be scaled without moving away from the origin.

The triangles are then added to a parent object, and positioned at the midpoint we calculated earlier. Thus when the triangles are scaled, they are scaled relative to their position in the icosahedron shape.

Here is the code to construct the disjoint triangles:

(defn icos []
  (let [icosahedron (js/THREE.Object3D.)]
    (doseq [triangle indices
            :let [vs (map vertices triangle)
                  [x y z :as face-center] (map average (transpose vs))
                  normalized-vertices (for [v vs]
                                        (map - v face-center))
                  face-mesh (geo normalized-vertices)]]
      (.set (.-position face-mesh) x y z)
      (.add icosahedron face-mesh))
    icosahedron))

For each triangle, look up its vertices. Find the face center. Subtract the face center from each vertex. Create the geometry. Position the geometry at the face center relative to the icosahedron. You can see the full rendering code on github.

The final result: Icopop allows you to scale the triangles or the icosahedron while it spins.

I love how simple geometry can create such a visually pleasing effect.