(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; } })();