Drawing an icosahedron
I'd like to draw an icosphere, the first step of which is drawing an icosahedron. There are lots of ways we could calculate the locations of the vertices, such as using the Golden Ratio, but here we'll use spherical coordinates, which I'll refer to in terms of 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 remaining 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 indices 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, we use the 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 conversion is adjusted from what's shown on Wikipedia, necessary because I've measured latitude from the equator instead of using the more mathematical approach of measuring latitudes from the north pole.
We can now 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:
To make an icosphere, we'll subdivide these triangles until we have an adequate approximation of a sphere.