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