source: node_modules/delaunator/index.js@ ba17441

Last change on this file since ba17441 was e4c61dd, checked in by istevanoska <ilinastevanoska@…>, 6 months ago

Prototype 1.1

  • Property mode set to 100644
File size: 15.3 KB
RevLine 
[e4c61dd]1
2const EPSILON = Math.pow(2, -52);
3const EDGE_STACK = new Uint32Array(512);
4
5import {orient2d} from 'robust-predicates';
6
7export default class Delaunator {
8
9 static from(points, getX = defaultGetX, getY = defaultGetY) {
10 const n = points.length;
11 const coords = new Float64Array(n * 2);
12
13 for (let i = 0; i < n; i++) {
14 const p = points[i];
15 coords[2 * i] = getX(p);
16 coords[2 * i + 1] = getY(p);
17 }
18
19 return new Delaunator(coords);
20 }
21
22 constructor(coords) {
23 const n = coords.length >> 1;
24 if (n > 0 && typeof coords[0] !== 'number') throw new Error('Expected coords to contain numbers.');
25
26 this.coords = coords;
27
28 // arrays that will store the triangulation graph
29 const maxTriangles = Math.max(2 * n - 5, 0);
30 this._triangles = new Uint32Array(maxTriangles * 3);
31 this._halfedges = new Int32Array(maxTriangles * 3);
32
33 // temporary arrays for tracking the edges of the advancing convex hull
34 this._hashSize = Math.ceil(Math.sqrt(n));
35 this._hullPrev = new Uint32Array(n); // edge to prev edge
36 this._hullNext = new Uint32Array(n); // edge to next edge
37 this._hullTri = new Uint32Array(n); // edge to adjacent triangle
38 this._hullHash = new Int32Array(this._hashSize); // angular edge hash
39
40 // temporary arrays for sorting points
41 this._ids = new Uint32Array(n);
42 this._dists = new Float64Array(n);
43
44 this.update();
45 }
46
47 update() {
48 const {coords, _hullPrev: hullPrev, _hullNext: hullNext, _hullTri: hullTri, _hullHash: hullHash} = this;
49 const n = coords.length >> 1;
50
51 // populate an array of point indices; calculate input data bbox
52 let minX = Infinity;
53 let minY = Infinity;
54 let maxX = -Infinity;
55 let maxY = -Infinity;
56
57 for (let i = 0; i < n; i++) {
58 const x = coords[2 * i];
59 const y = coords[2 * i + 1];
60 if (x < minX) minX = x;
61 if (y < minY) minY = y;
62 if (x > maxX) maxX = x;
63 if (y > maxY) maxY = y;
64 this._ids[i] = i;
65 }
66 const cx = (minX + maxX) / 2;
67 const cy = (minY + maxY) / 2;
68
69 let i0, i1, i2;
70
71 // pick a seed point close to the center
72 for (let i = 0, minDist = Infinity; i < n; i++) {
73 const d = dist(cx, cy, coords[2 * i], coords[2 * i + 1]);
74 if (d < minDist) {
75 i0 = i;
76 minDist = d;
77 }
78 }
79 const i0x = coords[2 * i0];
80 const i0y = coords[2 * i0 + 1];
81
82 // find the point closest to the seed
83 for (let i = 0, minDist = Infinity; i < n; i++) {
84 if (i === i0) continue;
85 const d = dist(i0x, i0y, coords[2 * i], coords[2 * i + 1]);
86 if (d < minDist && d > 0) {
87 i1 = i;
88 minDist = d;
89 }
90 }
91 let i1x = coords[2 * i1];
92 let i1y = coords[2 * i1 + 1];
93
94 let minRadius = Infinity;
95
96 // find the third point which forms the smallest circumcircle with the first two
97 for (let i = 0; i < n; i++) {
98 if (i === i0 || i === i1) continue;
99 const r = circumradius(i0x, i0y, i1x, i1y, coords[2 * i], coords[2 * i + 1]);
100 if (r < minRadius) {
101 i2 = i;
102 minRadius = r;
103 }
104 }
105 let i2x = coords[2 * i2];
106 let i2y = coords[2 * i2 + 1];
107
108 if (minRadius === Infinity) {
109 // order collinear points by dx (or dy if all x are identical)
110 // and return the list as a hull
111 for (let i = 0; i < n; i++) {
112 this._dists[i] = (coords[2 * i] - coords[0]) || (coords[2 * i + 1] - coords[1]);
113 }
114 quicksort(this._ids, this._dists, 0, n - 1);
115 const hull = new Uint32Array(n);
116 let j = 0;
117 for (let i = 0, d0 = -Infinity; i < n; i++) {
118 const id = this._ids[i];
119 const d = this._dists[id];
120 if (d > d0) {
121 hull[j++] = id;
122 d0 = d;
123 }
124 }
125 this.hull = hull.subarray(0, j);
126 this.triangles = new Uint32Array(0);
127 this.halfedges = new Uint32Array(0);
128 return;
129 }
130
131 // swap the order of the seed points for counter-clockwise orientation
132 if (orient2d(i0x, i0y, i1x, i1y, i2x, i2y) < 0) {
133 const i = i1;
134 const x = i1x;
135 const y = i1y;
136 i1 = i2;
137 i1x = i2x;
138 i1y = i2y;
139 i2 = i;
140 i2x = x;
141 i2y = y;
142 }
143
144 const center = circumcenter(i0x, i0y, i1x, i1y, i2x, i2y);
145 this._cx = center.x;
146 this._cy = center.y;
147
148 for (let i = 0; i < n; i++) {
149 this._dists[i] = dist(coords[2 * i], coords[2 * i + 1], center.x, center.y);
150 }
151
152 // sort the points by distance from the seed triangle circumcenter
153 quicksort(this._ids, this._dists, 0, n - 1);
154
155 // set up the seed triangle as the starting hull
156 this._hullStart = i0;
157 let hullSize = 3;
158
159 hullNext[i0] = hullPrev[i2] = i1;
160 hullNext[i1] = hullPrev[i0] = i2;
161 hullNext[i2] = hullPrev[i1] = i0;
162
163 hullTri[i0] = 0;
164 hullTri[i1] = 1;
165 hullTri[i2] = 2;
166
167 hullHash.fill(-1);
168 hullHash[this._hashKey(i0x, i0y)] = i0;
169 hullHash[this._hashKey(i1x, i1y)] = i1;
170 hullHash[this._hashKey(i2x, i2y)] = i2;
171
172 this.trianglesLen = 0;
173 this._addTriangle(i0, i1, i2, -1, -1, -1);
174
175 for (let k = 0, xp, yp; k < this._ids.length; k++) {
176 const i = this._ids[k];
177 const x = coords[2 * i];
178 const y = coords[2 * i + 1];
179
180 // skip near-duplicate points
181 if (k > 0 && Math.abs(x - xp) <= EPSILON && Math.abs(y - yp) <= EPSILON) continue;
182 xp = x;
183 yp = y;
184
185 // skip seed triangle points
186 if (i === i0 || i === i1 || i === i2) continue;
187
188 // find a visible edge on the convex hull using edge hash
189 let start = 0;
190 for (let j = 0, key = this._hashKey(x, y); j < this._hashSize; j++) {
191 start = hullHash[(key + j) % this._hashSize];
192 if (start !== -1 && start !== hullNext[start]) break;
193 }
194
195 start = hullPrev[start];
196 let e = start, q;
197 while (q = hullNext[e], orient2d(x, y, coords[2 * e], coords[2 * e + 1], coords[2 * q], coords[2 * q + 1]) >= 0) {
198 e = q;
199 if (e === start) {
200 e = -1;
201 break;
202 }
203 }
204 if (e === -1) continue; // likely a near-duplicate point; skip it
205
206 // add the first triangle from the point
207 let t = this._addTriangle(e, i, hullNext[e], -1, -1, hullTri[e]);
208
209 // recursively flip triangles from the point until they satisfy the Delaunay condition
210 hullTri[i] = this._legalize(t + 2);
211 hullTri[e] = t; // keep track of boundary triangles on the hull
212 hullSize++;
213
214 // walk forward through the hull, adding more triangles and flipping recursively
215 let n = hullNext[e];
216 while (q = hullNext[n], orient2d(x, y, coords[2 * n], coords[2 * n + 1], coords[2 * q], coords[2 * q + 1]) < 0) {
217 t = this._addTriangle(n, i, q, hullTri[i], -1, hullTri[n]);
218 hullTri[i] = this._legalize(t + 2);
219 hullNext[n] = n; // mark as removed
220 hullSize--;
221 n = q;
222 }
223
224 // walk backward from the other side, adding more triangles and flipping
225 if (e === start) {
226 while (q = hullPrev[e], orient2d(x, y, coords[2 * q], coords[2 * q + 1], coords[2 * e], coords[2 * e + 1]) < 0) {
227 t = this._addTriangle(q, i, e, -1, hullTri[e], hullTri[q]);
228 this._legalize(t + 2);
229 hullTri[q] = t;
230 hullNext[e] = e; // mark as removed
231 hullSize--;
232 e = q;
233 }
234 }
235
236 // update the hull indices
237 this._hullStart = hullPrev[i] = e;
238 hullNext[e] = hullPrev[n] = i;
239 hullNext[i] = n;
240
241 // save the two new edges in the hash table
242 hullHash[this._hashKey(x, y)] = i;
243 hullHash[this._hashKey(coords[2 * e], coords[2 * e + 1])] = e;
244 }
245
246 this.hull = new Uint32Array(hullSize);
247 for (let i = 0, e = this._hullStart; i < hullSize; i++) {
248 this.hull[i] = e;
249 e = hullNext[e];
250 }
251
252 // trim typed triangle mesh arrays
253 this.triangles = this._triangles.subarray(0, this.trianglesLen);
254 this.halfedges = this._halfedges.subarray(0, this.trianglesLen);
255 }
256
257 _hashKey(x, y) {
258 return Math.floor(pseudoAngle(x - this._cx, y - this._cy) * this._hashSize) % this._hashSize;
259 }
260
261 _legalize(a) {
262 const {_triangles: triangles, _halfedges: halfedges, coords} = this;
263
264 let i = 0;
265 let ar = 0;
266
267 // recursion eliminated with a fixed-size stack
268 while (true) {
269 const b = halfedges[a];
270
271 /* if the pair of triangles doesn't satisfy the Delaunay condition
272 * (p1 is inside the circumcircle of [p0, pl, pr]), flip them,
273 * then do the same check/flip recursively for the new pair of triangles
274 *
275 * pl pl
276 * /||\ / \
277 * al/ || \bl al/ \a
278 * / || \ / \
279 * / a||b \ flip /___ar___\
280 * p0\ || /p1 => p0\---bl---/p1
281 * \ || / \ /
282 * ar\ || /br b\ /br
283 * \||/ \ /
284 * pr pr
285 */
286 const a0 = a - a % 3;
287 ar = a0 + (a + 2) % 3;
288
289 if (b === -1) { // convex hull edge
290 if (i === 0) break;
291 a = EDGE_STACK[--i];
292 continue;
293 }
294
295 const b0 = b - b % 3;
296 const al = a0 + (a + 1) % 3;
297 const bl = b0 + (b + 2) % 3;
298
299 const p0 = triangles[ar];
300 const pr = triangles[a];
301 const pl = triangles[al];
302 const p1 = triangles[bl];
303
304 const illegal = inCircle(
305 coords[2 * p0], coords[2 * p0 + 1],
306 coords[2 * pr], coords[2 * pr + 1],
307 coords[2 * pl], coords[2 * pl + 1],
308 coords[2 * p1], coords[2 * p1 + 1]);
309
310 if (illegal) {
311 triangles[a] = p1;
312 triangles[b] = p0;
313
314 const hbl = halfedges[bl];
315
316 // edge swapped on the other side of the hull (rare); fix the halfedge reference
317 if (hbl === -1) {
318 let e = this._hullStart;
319 do {
320 if (this._hullTri[e] === bl) {
321 this._hullTri[e] = a;
322 break;
323 }
324 e = this._hullPrev[e];
325 } while (e !== this._hullStart);
326 }
327 this._link(a, hbl);
328 this._link(b, halfedges[ar]);
329 this._link(ar, bl);
330
331 const br = b0 + (b + 1) % 3;
332
333 // don't worry about hitting the cap: it can only happen on extremely degenerate input
334 if (i < EDGE_STACK.length) {
335 EDGE_STACK[i++] = br;
336 }
337 } else {
338 if (i === 0) break;
339 a = EDGE_STACK[--i];
340 }
341 }
342
343 return ar;
344 }
345
346 _link(a, b) {
347 this._halfedges[a] = b;
348 if (b !== -1) this._halfedges[b] = a;
349 }
350
351 // add a new triangle given vertex indices and adjacent half-edge ids
352 _addTriangle(i0, i1, i2, a, b, c) {
353 const t = this.trianglesLen;
354
355 this._triangles[t] = i0;
356 this._triangles[t + 1] = i1;
357 this._triangles[t + 2] = i2;
358
359 this._link(t, a);
360 this._link(t + 1, b);
361 this._link(t + 2, c);
362
363 this.trianglesLen += 3;
364
365 return t;
366 }
367}
368
369// monotonically increases with real angle, but doesn't need expensive trigonometry
370function pseudoAngle(dx, dy) {
371 const p = dx / (Math.abs(dx) + Math.abs(dy));
372 return (dy > 0 ? 3 - p : 1 + p) / 4; // [0..1]
373}
374
375function dist(ax, ay, bx, by) {
376 const dx = ax - bx;
377 const dy = ay - by;
378 return dx * dx + dy * dy;
379}
380
381function inCircle(ax, ay, bx, by, cx, cy, px, py) {
382 const dx = ax - px;
383 const dy = ay - py;
384 const ex = bx - px;
385 const ey = by - py;
386 const fx = cx - px;
387 const fy = cy - py;
388
389 const ap = dx * dx + dy * dy;
390 const bp = ex * ex + ey * ey;
391 const cp = fx * fx + fy * fy;
392
393 return dx * (ey * cp - bp * fy) -
394 dy * (ex * cp - bp * fx) +
395 ap * (ex * fy - ey * fx) < 0;
396}
397
398function circumradius(ax, ay, bx, by, cx, cy) {
399 const dx = bx - ax;
400 const dy = by - ay;
401 const ex = cx - ax;
402 const ey = cy - ay;
403
404 const bl = dx * dx + dy * dy;
405 const cl = ex * ex + ey * ey;
406 const d = 0.5 / (dx * ey - dy * ex);
407
408 const x = (ey * bl - dy * cl) * d;
409 const y = (dx * cl - ex * bl) * d;
410
411 return x * x + y * y;
412}
413
414function circumcenter(ax, ay, bx, by, cx, cy) {
415 const dx = bx - ax;
416 const dy = by - ay;
417 const ex = cx - ax;
418 const ey = cy - ay;
419
420 const bl = dx * dx + dy * dy;
421 const cl = ex * ex + ey * ey;
422 const d = 0.5 / (dx * ey - dy * ex);
423
424 const x = ax + (ey * bl - dy * cl) * d;
425 const y = ay + (dx * cl - ex * bl) * d;
426
427 return {x, y};
428}
429
430function quicksort(ids, dists, left, right) {
431 if (right - left <= 20) {
432 for (let i = left + 1; i <= right; i++) {
433 const temp = ids[i];
434 const tempDist = dists[temp];
435 let j = i - 1;
436 while (j >= left && dists[ids[j]] > tempDist) ids[j + 1] = ids[j--];
437 ids[j + 1] = temp;
438 }
439 } else {
440 const median = (left + right) >> 1;
441 let i = left + 1;
442 let j = right;
443 swap(ids, median, i);
444 if (dists[ids[left]] > dists[ids[right]]) swap(ids, left, right);
445 if (dists[ids[i]] > dists[ids[right]]) swap(ids, i, right);
446 if (dists[ids[left]] > dists[ids[i]]) swap(ids, left, i);
447
448 const temp = ids[i];
449 const tempDist = dists[temp];
450 while (true) {
451 do i++; while (dists[ids[i]] < tempDist);
452 do j--; while (dists[ids[j]] > tempDist);
453 if (j < i) break;
454 swap(ids, i, j);
455 }
456 ids[left + 1] = ids[j];
457 ids[j] = temp;
458
459 if (right - i + 1 >= j - left) {
460 quicksort(ids, dists, i, right);
461 quicksort(ids, dists, left, j - 1);
462 } else {
463 quicksort(ids, dists, left, j - 1);
464 quicksort(ids, dists, i, right);
465 }
466 }
467}
468
469function swap(arr, i, j) {
470 const tmp = arr[i];
471 arr[i] = arr[j];
472 arr[j] = tmp;
473}
474
475function defaultGetX(p) {
476 return p[0];
477}
478function defaultGetY(p) {
479 return p[1];
480}
Note: See TracBrowser for help on using the repository browser.