{{ message }}

Instantly share code, notes, and snippets.

Last active Oct 8, 2017
Platonic Geodesic

Use the range slider to change the degree of subdivision in this geodesic sphere.

The base shape, visible when subdivision is disabled, is one of the 5 regular convex polyhedra (with triangularized faces for cube and dodecahedron), a.k.a. Platonic solids:

• tethrahedron
• hexahedron (cube)
• octahedron
• dodecahedron
• icosahedron

Built with a modified version of the d3.geodesic plugin which adds all Platonic solids.

Evolved from my previous block which was as well inspired by Mike Bostock's Geodesic Rainbow.

 (function() { var phi = (1 + Math.sqrt(5)) / 2, // golden ratio pi = Math.PI, a, b, c, r; var degrees = 180 / pi; // faces for platonic solids from http://paulbourke.net/geometry/platonic/ // (some faces vertices order reverted to make the relavant sequence clockwise) // ****** tetrahedron ****** r = Math.sqrt(3); var vertices_tetrahedron = [ [ 1, 1, 1], [-1, 1, -1], [ 1, -1, -1], [-1, -1, 1] ].map(function(vertex) { return [vertex[0] / r, vertex[1] /r, vertex[2] /r]; }); var vertices_faces_tetrahedron = [ 0, 1, 2, 1, 3, 2, 0, 2, 3, 0, 3, 1, ].map(function(idx) { return vertices_tetrahedron[idx]; }); var faces_tetrahedron = chunk(vertices_faces_tetrahedron, 3); d3.tetrahedron = { vertices: function() { return vertices_tetrahedron; }, faces: function() { return faces_tetrahedron; }, multipolygon: function(n) { return { type: "MultiPolygon", coordinates: subdivideFaces(~~n, this).map(function(face) { face = face.map(project); face.push(face[0]); face = [face]; return face; }) }; }, polygons: function(n) { return d3.tetrahedron.multipolygon(~~n).coordinates.map(function(face) { return {type: "Polygon", coordinates: face}; }); }, multilinestring: function(n) { return { type: "MultiLineString", coordinates: subdivideEdges(~~n, this).map(function(edge) { return edge.map(project); }) }; } }; // ****** octahedron ****** a = 1 / (2 * Math.sqrt(2)); b = 1 / 2; r = b; var vertices_octahedron = [ [-a, 0, a], [-a, 0, -a], [ 0, b, 0], [ a, 0, -a], [ 0, -b, 0], [ a, 0, a] ].map(function(vertex) { return [vertex[0] / r, vertex[1] /r, vertex[2] /r]; }); var vertices_faces_octahedron = [ 0, 1, 2, 1, 3, 2, 3, 5, 2, 5, 0, 2, 3, 1, 4, 1, 0, 4, 5, 3, 4, 0, 5, 4, ].map(function(idx) { return vertices_octahedron[idx]; }); var faces_octahedron = chunk(vertices_faces_octahedron, 3); d3.octahedron = { vertices: function() { return vertices_octahedron; }, faces: function() { return faces_octahedron; }, multipolygon: function(n) { return { type: "MultiPolygon", coordinates: subdivideFaces(~~n, this).map(function(face) { face = face.map(project); face.push(face[0]); face = [face]; return face; }) }; }, polygons: function(n) { return d3.octahedron.multipolygon(~~n).coordinates.map(function(face) { return {type: "Polygon", coordinates: face}; }); }, multilinestring: function(n) { return { type: "MultiLineString", coordinates: subdivideEdges(~~n, this).map(function(edge) { return edge.map(project); }) }; } }; // ****** hexahedron (cube) ****** r = Math.sqrt(3); var vertices_hexahedron = [ [-1, -1, -1], [ 1, -1, -1], [ 1, -1, 1], [-1, -1, 1], [-1, 1, -1], [ 1, 1, -1], [ 1, 1, 1], [-1, 1, 1] ].map(function(vertex) { return [vertex[0] / r, vertex[1] /r, vertex[2] /r]; }); var vertices_faces_hexahedron = [ 0, 3, 2, 1, 3, 0, 4, 7, 3, 7, 6, 2, 4, 5, 6, 7, 5, 1, 2, 6, 0, 1, 5, 4 ].map(function(idx) { return vertices_hexahedron[idx]; }); var faces_hexahedron = chunk(vertices_faces_hexahedron, 4) // split each square into two triangles .reduce( function(accumulator, face, faceIndex, faces) { accumulator.push( [face[0], face[1], face[3]], [face[1], face[2], face[3]]); return accumulator; }, [] ); d3.hexahedron = { vertices: function() { return vertices_hexahedron; }, faces: function() { return faces_hexahedron; }, multipolygon: function(n) { return { type: "MultiPolygon", coordinates: subdivideFaces(~~n, this).map(function(face) { face = face.map(project); face.push(face[0]); face = [face]; return face; }) }; }, polygons: function(n) { return d3.hexahedron.multipolygon(~~n).coordinates.map(function(face) { return {type: "Polygon", coordinates: face}; }); }, multilinestring: function(n) { return { type: "MultiLineString", coordinates: subdivideEdges(~~n, this).map(function(edge) { return edge.map(project); }) }; } }; // ****** dodecahedron ****** b = 1 / phi, c = 2 - phi, r = b * Math.sqrt(3); var vertices_dodecahedron = [ [ c, 0, 1], [-c, 0, 1], [-b, b, b], [ 0, 1, c], [ b, b, b], [ b, -b, b], [ 0, -1, c], [-b, -b, b], [ c, 0, -1], [-c, 0, -1], [-b, -b, -b], [ 0, -1, -c], [ b, -b, -b], [ b, b, -b], [ 0, 1, -c], [-b, b, -b], [ 1, c, 0], [-1, c, 0], [-1, -c, 0], [ 1, -c, 0] ].map(function(vertex) { return [vertex[0] / r, vertex[1] /r, vertex[2] /r]; }); var vertices_faces_dodecahedron = [ 0, 1, 2, 3, 4, 1, 0, 5, 6, 7, 8, 9, 10, 11, 12, 9, 8, 13, 14, 15, 13, 16, 4, 3, 14, 2, 17, 15, 14, 3, 10, 18, 7, 6, 11, 5, 19, 12, 11, 6, 16, 19, 5, 0, 4, 19, 16, 13, 8, 12, 17, 18, 10, 9, 15, 18, 17, 2, 1, 7 ].map(function(idx) { return vertices_dodecahedron[idx]; }); var faces_dodecahedron = chunk(vertices_faces_dodecahedron, 5) .reduce( function(accumulator, face, faceIndex, faces) { accumulator.push( [face[0], face[1], face[4]], [face[1], face[2], face[4]], [face[2], face[3], face[4]]); return accumulator; }, [] ); d3.dodecahedron = { vertices: function() { return vertices_dodecahedron; }, faces: function() { return faces_dodecahedron; }, multipolygon: function(n) { return { type: "MultiPolygon", coordinates: subdivideFaces(~~n, this).map(function(face) { face = face.map(project); face.push(face[0]); face = [face]; return face; }) }; }, polygons: function(n) { return d3.dodecahedron.multipolygon(~~n).coordinates.map(function(face) { return {type: "Polygon", coordinates: face}; }); }, multilinestring: function(n) { return { type: "MultiLineString", coordinates: subdivideEdges(~~n, this).map(function(edge) { return edge.map(project); }) }; } }; // ****** icosahedron ****** a = 0.5, b = 1 / (2 * phi), r = Math.sqrt(a * a + b * b); var vertices_icosahedron = [ [ 0, b, -a], [ b, a, 0], [-b, a, 0], [ 0, b, a], [ 0, -b, a], [-a, 0, b], [ a, 0, b], [ 0, -b, -a], [ a, 0, -b], [-a, 0, -b], [ b, -a, 0], [-b, -a, 0] ].map(function(vertex) { return [vertex[0] / r, vertex[1] /r, vertex[2] /r]; }); var vertices_faces_icosahedron = [ 0, 1, 2, 3, 2, 1, 3, 4, 5, 3, 6, 4, 0, 7, 8, 0, 9, 7, 4, 10, 11, 7, 11, 10, 2, 5, 9, 11, 9, 5, 1, 8, 6, 10, 6, 8, 3, 5, 2, 3, 1, 6, 0, 2, 9, 0, 8, 1, 7, 9, 11, 7, 10, 8, 4, 11, 5, 4, 6, 10, ].map(function(idx) { return vertices_icosahedron[idx]; }); var faces_icosahedron = chunk(vertices_faces_icosahedron, 3); d3.icosahedron = { vertices: function() { return vertices_icosahedron; }, faces: function() { return faces_icosahedron; }, multipolygon: function(n) { return { type: "MultiPolygon", coordinates: subdivideFaces(~~n, this).map(function(face) { face = face.map(project); face.push(face[0]); face = [face]; return face; }) }; }, polygons: function(n) { return d3.icosahedron.multipolygon(~~n).coordinates.map(function(face) { return {type: "Polygon", coordinates: face}; }); }, multilinestring: function(n) { return { type: "MultiLineString", coordinates: subdivideEdges(~~n, this).map(function(edge) { return edge.map(project); }) }; } }; function subdivideFaces(n, polyhedron) { return d3.merge(polyhedron.faces().map(function(face) { var i01 = interpolate(face[0], face[1]), i02 = interpolate(face[0], face[2]), faces = []; faces.push([ face[0], i01(1 / n), i02(1 / n) ]); for (var i = 1; i < n; ++i) { var i1 = interpolate(i01(i / n), i02(i / n)), i2 = interpolate(i01((i + 1) / n), i02((i + 1) / n)); for (var j = 0; j <= i; ++j) { faces.push([ i1(j / i), i2(j / (i + 1)), i2((j + 1) / (i + 1)) ]); } for (var j = 0; j < i; ++j) { faces.push([ i1(j / i), i2((j + 1) / (i + 1)), i1((j + 1) / i) ]); } } return faces; })); } function subdivideEdges(n, polyhedron) { var edges = {}; subdivideFaces(n, polyhedron).forEach(function(face) { add(face[0], face[1]); add(face[1], face[2]); add(face[2], face[0]); }); function add(p0, p1) { var t; if (p0[0] < p1[0] || (p0[0] == p1[0] && (p0[1] < p1[1] || (p0[1] == p1[1] && p0[2] < p1[2])))) t = p0, p0 = p1, p1 = t; polyhedron.edges()[p0.map(round) + " " + p1.map(round)] = [p0, p1]; } function round(d) { return d3.round(d, 4); } return d3.values(edges); } function chunk(arr, n) { return arr.reduce(function(p, cur, i) { (p[i/n|0] || (p[i/n|0] = [])).push(cur); return p; },[]); }; function centroid(polygon) { var k = polygon.length; var c = polygon.reduce(function(accumulator, item) { return [ accumulator[0] + item[0], accumulator[1] + item[1], accumulator[2] + item[2] ]; }, [0, 0, 0]); return [c[0]/k, c[1]/k, c[2]/k]; } function interpolate(p0, p1) { var x0 = p0[0], y0 = p0[1], z0 = p0[2], x1 = p1[0] - x0, y1 = p1[1] - y0, z1 = p1[2] - z0; return function(t) { return [ x0 + t * x1, y0 + t * y1, z0 + t * z1 ]; }; } function cartesian(spherical) { var lambda = spherical[0], phi = spherical[1], cosPhi = Math.cos(phi); return [cosPhi * Math.cos(lambda), cosPhi * Math.sin(lambda), Math.sin(phi)]; } function project(p) { var x = p[0], y = p[1], z = p[2]; return [ Math.atan2(y, x) * degrees, Math.acos(z / Math.sqrt(x * x + y * y + z * z)) * degrees - 90 ]; } function polygonArea(polygon) { var i = -1, n = polygon.length, a, b = polygon[n - 1], area = 0; while (++i < n) { a = b; b = polygon[i]; area += a[1] * b[0] - a[0] * b[1]; } return area / 2; } // v1 - v2 function vectorDifference(v1, v2) { return [ v1[0] - v2[0], v1[1] - v2[1], v1[2] - v2[2] ]; } // v1 x v2 function vectorCrossproduct(v1, v2) { return [ v1[1] * v2[2] - v1[2] * v2[1], v1[2] * v2[0] - v1[0] * v2[2], v1[0] * v2[1] - v1[1] * v2[0] ]; } function vectorLength(v) { return Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); } function triangleArea(triangle) { var p0 = triangle[0], p1 = triangle[1], p2 = triangle[2]; var d1 = vectorDifference(p1, p0), d2 = vectorDifference(p2, p0); var c = vectorCrossproduct(d1, d2), l = vectorLength(c); return l / 2; } function onlyUnique(value, index, self) { return self.indexOf(value) === index; } })();

tetrahedron
octaedron
hexahedron (cube)
dodecahedron
icosahedron

Drag to rotate!

 // Version 0.0.0. Copyright 2017 Mike Bostock. (function(global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global.versor = factory()); }(this, (function() {'use strict'; var acos = Math.acos, asin = Math.asin, atan2 = Math.atan2, cos = Math.cos, max = Math.max, min = Math.min, PI = Math.PI, sin = Math.sin, sqrt = Math.sqrt, radians = PI / 180, degrees = 180 / PI; // Returns the unit quaternion for the given Euler rotation angles [λ, φ, γ]. function versor(e) { var l = e[0] / 2 * radians, sl = sin(l), cl = cos(l), // λ / 2 p = e[1] / 2 * radians, sp = sin(p), cp = cos(p), // φ / 2 g = e[2] / 2 * radians, sg = sin(g), cg = cos(g); // γ / 2 return [ cl * cp * cg + sl * sp * sg, sl * cp * cg - cl * sp * sg, cl * sp * cg + sl * cp * sg, cl * cp * sg - sl * sp * cg ]; } // Returns Cartesian coordinates [x, y, z] given spherical coordinates [λ, φ]. versor.cartesian = function(e) { var l = e[0] * radians, p = e[1] * radians, cp = cos(p); return [cp * cos(l), cp * sin(l), sin(p)]; }; // Returns the Euler rotation angles [λ, φ, γ] for the given quaternion. versor.rotation = function(q) { return [ atan2(2 * (q[0] * q[1] + q[2] * q[3]), 1 - 2 * (q[1] * q[1] + q[2] * q[2])) * degrees, asin(max(-1, min(1, 2 * (q[0] * q[2] - q[3] * q[1])))) * degrees, atan2(2 * (q[0] * q[3] + q[1] * q[2]), 1 - 2 * (q[2] * q[2] + q[3] * q[3])) * degrees ]; }; // Returns the quaternion to rotate between two cartesian points on the sphere. versor.delta = function(v0, v1) { var w = cross(v0, v1), l = sqrt(dot(w, w)); if (!l) return [1, 0, 0, 0]; var t = acos(max(-1, min(1, dot(v0, v1)))) / 2, s = sin(t); // t = θ / 2 return [cos(t), w[2] / l * s, -w[1] / l * s, w[0] / l * s]; }; // Returns the quaternion that represents q0 * q1. versor.multiply = function(q0, q1) { return [ q0[0] * q1[0] - q0[1] * q1[1] - q0[2] * q1[2] - q0[3] * q1[3], q0[0] * q1[1] + q0[1] * q1[0] + q0[2] * q1[3] - q0[3] * q1[2], q0[0] * q1[2] - q0[1] * q1[3] + q0[2] * q1[0] + q0[3] * q1[1], q0[0] * q1[3] + q0[1] * q1[2] - q0[2] * q1[1] + q0[3] * q1[0] ]; }; function cross(v0, v1) { return [ v0[1] * v1[2] - v0[2] * v1[1], v0[2] * v1[0] - v0[0] * v1[2], v0[0] * v1[1] - v0[1] * v1[0] ]; } function dot(v0, v1) { return v0[0] * v1[0] + v0[1] * v1[1] + v0[2] * v1[2]; } return versor; })));