UNPKG

5.25 kBJavaScriptView Raw
1/*
2 * Buckminster Fuller’s AirOcean arrangement of the icosahedron
3 *
4 * Implemented for D3.js by Jason Davies (2013),
5 * Enrico Spinielli (2017) and Philippe Rivière (2017, 2018)
6 *
7 */
8import { atan, degrees } from "./math.js";
9import polyhedral from "./polyhedral/index.js";
10import { default as grayFullerRaw } from "./grayfuller.js";
11import {
12 geoCentroid as centroid,
13 geoContains as contains,
14 geoGnomonic as gnomonic,
15 geoProjection as projection
16} from "d3-geo";
17import { range } from "d3-array";
18
19function airoceanRaw(faceProjection) {
20 var theta = atan(0.5) * degrees;
21
22 // construction inspired by
23 // https://en.wikipedia.org/wiki/Regular_icosahedron#Spherical_coordinates
24 var vertices = [[0, 90], [0, -90]].concat(
25 range(10).map(function(i) {
26 var phi = (i * 36 + 180) % 360 - 180;
27 return [phi, i & 1 ? theta : -theta];
28 })
29 );
30
31 // icosahedron
32 var polyhedron = [
33 [0, 3, 11],
34 [0, 5, 3],
35 [0, 7, 5],
36 [0, 9, 7],
37 [0, 11, 9], // North
38 [2, 11, 3],
39 [3, 4, 2],
40 [4, 3, 5],
41 [5, 6, 4],
42 [6, 5, 7],
43 [7, 8, 6],
44 [8, 7, 9],
45 [9, 10, 8],
46 [10, 9, 11],
47 [11, 2, 10], // Equator
48 [1, 2, 4],
49 [1, 4, 6],
50 [1, 6, 8],
51 [1, 8, 10],
52 [1, 10, 2] // South
53 ].map(function(face) {
54 return face.map(function(i) {
55 return vertices[i];
56 });
57 });
58
59 // add centroid
60 polyhedron.forEach(function(face) {
61 face.centroid = centroid({ type: "MultiPoint", coordinates: face });
62 });
63
64 // split the relevant faces:
65 // * face[15] in the centroid: this will become face[15], face[20] and face[21]
66 // * face[14] in the middle of the side: this will become face[14] and face[22]
67 (function() {
68 var face, tmp, mid, centroid;
69
70 // Split face[15] in 3 faces at centroid.
71 face = polyhedron[15];
72 centroid = face.centroid;
73 tmp = face.slice();
74 face[0] = centroid; // (new) face[15]
75
76 face = [tmp[0], centroid, tmp[2]];
77 face.centroid = centroid;
78 polyhedron.push(face); // face[20]
79
80 face = [tmp[0], tmp[1], centroid];
81 face.centroid = centroid;
82 polyhedron.push(face); // face[21]
83
84 // Split face 14 at the edge.
85 face = polyhedron[14];
86 centroid = face.centroid;
87 tmp = face.slice();
88
89 // compute planar midpoint
90 var proj = gnomonic()
91 .scale(1)
92 .translate([0, 0])
93 .rotate([-centroid[0], -centroid[1]]);
94 var a = proj(face[1]),
95 b = proj(face[2]);
96 mid = proj.invert([(a[0] + b[0]) / 2, (a[1] + b[1]) / 2]);
97 face[1] = mid; // (new) face[14]
98
99 // build the new half face
100 face = [tmp[0], tmp[1], mid];
101 face.centroid = centroid; // use original face[14] centroid
102 polyhedron.push(face); // face[22]
103
104 // cut face 19 to connect to 22
105 face = polyhedron[19];
106 centroid = face.centroid;
107 tmp = face.slice();
108 face[1] = mid;
109
110 // build the new half face
111 face = [mid, tmp[0], tmp[1]];
112 face.centroid = centroid;
113 polyhedron.push(face); // face[23]
114 })();
115
116 var airocean = function(faceProjection) {
117 faceProjection =
118 faceProjection ||
119 function(face) {
120 // for half-triangles this is definitely not centroid({type: "MultiPoint", coordinates: face});
121 var c = face.centroid;
122 return gnomonic()
123 .scale(1)
124 .translate([0, 0])
125 .rotate([-c[0], -c[1]]);
126 };
127
128 var faces = polyhedron.map(function(face, i) {
129 var polygon = face.slice();
130 polygon.push(polygon[0]);
131
132 return {
133 face: face,
134 site: face.centroid,
135 id: i,
136 contains: function(lambda, phi) {
137 return contains({ type: "Polygon", coordinates: [polygon] }, [
138 lambda * degrees,
139 phi * degrees
140 ]);
141 },
142 project: faceProjection(face)
143 };
144 });
145
146 // Connect each face to a parent face.
147 var parents = [
148 // N
149 -1, // 0
150 0, // 1
151 1, // 2
152 11, // 3
153 13, // 4
154
155 // Eq
156 6, // 5
157 7, // 6
158 1, // 7
159 7, // 8
160 8, // 9
161
162 9, // 10
163 10, // 11
164 11, // 12
165 12, // 13
166 13, // 14
167
168 // S
169 6, // 15
170 8, // 16
171 10, // 17
172 17, // 18
173 21, // 19
174 16, // 20
175 15, // 21
176 19, // 22
177 19 // 23
178 ];
179
180 parents.forEach(function(d, i) {
181 var node = faces[d];
182 node && (node.children || (node.children = [])).push(faces[i]);
183 });
184
185 function face(lambda, phi) {
186 for (var i = 0; i < faces.length; i++) {
187 if (faces[i].contains(lambda, phi)) return faces[i];
188 }
189 }
190
191 // Polyhedral projection
192 var proj = polyhedral(
193 faces[0], // the root face
194 face // a function that returns a face given coords
195 );
196
197 proj.faces = faces;
198 return proj;
199 };
200
201 return airocean(faceProjection);
202}
203
204export default function () {
205 var p = airoceanRaw(function(face) {
206 var c = face.centroid;
207
208 face.direction =
209 Math.abs(c[1] - 52.62) < 1 || Math.abs(c[1] + 10.81) < 1 ? 0 : 60;
210 return projection(grayFullerRaw())
211 .scale(1)
212 .translate([0, 0])
213 .rotate([-c[0], -c[1], face.direction || 0]);
214 });
215
216 return p
217 .rotate([-83.65929, 25.44458, -87.45184])
218 .angle(-60)
219 .scale(45.4631)
220 .center([126, 0]);
221}