pts 0.12.8 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,884 @@
1
+ /*! Pts.js is licensed under Apache License 2.0. Copyright © 2017-current William Ngan and contributors. (https://github.com/williamngan/pts) */
2
+
3
+ /**
4
+ * Internal 2D Delaunay triangulation used by [`Delaunay`](#link).
5
+ *
6
+ * Points are inserted one at a time in Hilbert-curve order, so each point is
7
+ * found by a short walk from the previous insertion. An insertion removes every
8
+ * triangle whose circumcircle contains the point and fans the resulting cavity
9
+ * from it (Bowyer–Watson). The convex hull is represented by ghost triangles
10
+ * that share one vertex "at infinity", which lets hull growth use the same
11
+ * cavity step as interior insertion and keeps every half-edge paired.
12
+ *
13
+ * Orientation and in-circle decisions are exact. A floating-point error
14
+ * bound settles the easy cases; integer coordinates below 2^25 are resolved
15
+ * with limb arithmetic in doubles; anything else is evaluated in BigInt from
16
+ * the exact binary value of each coordinate. Exact decisions are what make
17
+ * cocircular grids, collinear runs, points on edges, and duplicate points
18
+ * safe: they can never produce a degenerate triangle or an endless walk.
19
+ */
20
+
21
+ /** A triangulation of the finite, distinct input points. */
22
+ export type Triangulation = {
23
+ /** Point indices, three per triangle, counterclockwise. */
24
+ triangles: Uint32Array;
25
+ /**
26
+ * For half-edge `h = 3t + e` (from `triangles[h]` to the triangle's next vertex),
27
+ * the index of the opposite half-edge in the adjacent triangle, or -1 on the hull.
28
+ */
29
+ neighbors: Int32Array;
30
+ /** Point indices of the convex hull, counterclockwise. Empty when there are no triangles. */
31
+ hull: Uint32Array;
32
+ };
33
+
34
+ const EMPTY: Triangulation = {
35
+ triangles: new Uint32Array(0),
36
+ neighbors: new Int32Array(0),
37
+ hull: new Uint32Array(0),
38
+ };
39
+
40
+ // ---------------------------------------------------------------- predicates
41
+
42
+ // 2^-53: half an ulp at 1, the rounding error of one double operation
43
+ const EPS = 1.1102230246251565e-16;
44
+ // Bounds on the rounding error of the determinants below, relative to the sum
45
+ // of the absolute values of their terms (the classic forward error analysis
46
+ // of the orientation and in-circle determinants).
47
+ const ORIENT_BOUND = (3 + 16 * EPS) * EPS;
48
+ const INCIRCLE_BOUND = (10 + 96 * EPS) * EPS;
49
+ // Coordinates that become integers below 2^31 when scaled by one power of two
50
+ // (pixel grids, halves, quarters, every Float32 value in a sane range) keep
51
+ // their differences exact in a double; the products those differences form are
52
+ // then evaluated exactly in limb arithmetic without BigInt.
53
+ const INT_LIMIT = 2147483648;
54
+ const _input = new Float64Array(8);
55
+ // the scale shared by every coordinate of the set being triangulated, when
56
+ // one exists (set by `triangulate`); 0 means decide per call
57
+ let _presetScale = 0;
58
+
59
+ /**
60
+ * The smallest power of two that turns every value in `_input[0..count)` into
61
+ * an integer below 2^31 in magnitude, or 0 when no scale up to 2^30 does.
62
+ */
63
+ function _integerScale(count: number): number {
64
+ if (_presetScale > 0) return _presetScale;
65
+ let scale = 1;
66
+ for (let k = 0; k <= 30; k++) {
67
+ let exact = true;
68
+ for (let i = 0; i < count; i++) {
69
+ const v = _input[i] * scale;
70
+ if (v >= INT_LIMIT || v <= -INT_LIMIT) return 0; // scaling only grows
71
+ if ((v | 0) !== v) exact = false;
72
+ }
73
+ if (exact) return scale;
74
+ scale *= 2;
75
+ }
76
+ return 0;
77
+ }
78
+
79
+ /**
80
+ * Orientation of `c` relative to the directed line `a → b`:
81
+ * 1 when `c` is to the left (counterclockwise turn), -1 to the right, 0 when collinear.
82
+ * The result is exact for any finite doubles.
83
+ */
84
+ export function orient2d(
85
+ ax: number,
86
+ ay: number,
87
+ bx: number,
88
+ by: number,
89
+ cx: number,
90
+ cy: number,
91
+ ): number {
92
+ const left = (ax - cx) * (by - cy);
93
+ const right = (ay - cy) * (bx - cx);
94
+ const det = left - right;
95
+ const bound = ORIENT_BOUND * (Math.abs(left) + Math.abs(right));
96
+ if (det > bound) return 1;
97
+ if (-det > bound) return -1;
98
+ _input[0] = ax;
99
+ _input[1] = ay;
100
+ _input[2] = bx;
101
+ _input[3] = by;
102
+ _input[4] = cx;
103
+ _input[5] = cy;
104
+ const scale = _integerScale(6);
105
+ if (scale !== 0) {
106
+ return _orientInt(
107
+ (ax - cx) * scale,
108
+ (by - cy) * scale,
109
+ (ay - cy) * scale,
110
+ (bx - cx) * scale,
111
+ );
112
+ }
113
+ return orient2dExact(ax, ay, bx, by, cx, cy);
114
+ }
115
+
116
+ /**
117
+ * In-circle test: 1 when `d` is strictly inside the circle through the
118
+ * counterclockwise triangle `a, b, c`, -1 when outside, 0 when on the circle.
119
+ * The result is exact for any finite doubles.
120
+ */
121
+ export function incircle(
122
+ ax: number,
123
+ ay: number,
124
+ bx: number,
125
+ by: number,
126
+ cx: number,
127
+ cy: number,
128
+ dx: number,
129
+ dy: number,
130
+ ): number {
131
+ const adx = ax - dx;
132
+ const ady = ay - dy;
133
+ const bdx = bx - dx;
134
+ const bdy = by - dy;
135
+ const cdx = cx - dx;
136
+ const cdy = cy - dy;
137
+
138
+ const bdxcdy = bdx * cdy;
139
+ const cdxbdy = cdx * bdy;
140
+ const alift = adx * adx + ady * ady;
141
+ const cdxady = cdx * ady;
142
+ const adxcdy = adx * cdy;
143
+ const blift = bdx * bdx + bdy * bdy;
144
+ const adxbdy = adx * bdy;
145
+ const bdxady = bdx * ady;
146
+ const clift = cdx * cdx + cdy * cdy;
147
+
148
+ const det =
149
+ alift * (bdxcdy - cdxbdy) +
150
+ blift * (cdxady - adxcdy) +
151
+ clift * (adxbdy - bdxady);
152
+ const permanent =
153
+ (Math.abs(bdxcdy) + Math.abs(cdxbdy)) * alift +
154
+ (Math.abs(cdxady) + Math.abs(adxcdy)) * blift +
155
+ (Math.abs(adxbdy) + Math.abs(bdxady)) * clift;
156
+ const bound = INCIRCLE_BOUND * permanent;
157
+ if (det > bound) return 1;
158
+ if (-det > bound) return -1;
159
+ _input[0] = ax;
160
+ _input[1] = ay;
161
+ _input[2] = bx;
162
+ _input[3] = by;
163
+ _input[4] = cx;
164
+ _input[5] = cy;
165
+ _input[6] = dx;
166
+ _input[7] = dy;
167
+ const scale = _integerScale(8);
168
+ if (scale !== 0) {
169
+ return _incircleInt(
170
+ adx * scale,
171
+ ady * scale,
172
+ bdx * scale,
173
+ bdy * scale,
174
+ cdx * scale,
175
+ cdy * scale,
176
+ );
177
+ }
178
+ return incircleExact(ax, ay, bx, by, cx, cy, dx, dy);
179
+ }
180
+
181
+ // ---- exact integer arithmetic on differences below 2^32, in base-2^18 limbs
182
+ // held in doubles: a limb product is below 2^36 and no accumulated coefficient
183
+ // reaches 2^41, so every operation is exact.
184
+ const LIMB = 262144;
185
+ const INV_LIMB = 1 / LIMB;
186
+ const _acc = new Float64Array(8);
187
+ const _liftA = new Float64Array(4);
188
+ const _liftB = new Float64Array(4);
189
+ const _liftC = new Float64Array(4);
190
+ const _crossA = new Float64Array(4);
191
+ const _crossB = new Float64Array(4);
192
+ const _crossC = new Float64Array(4);
193
+
194
+ /** Add `sign * x * y` for non-negative integers x, y below 2^32 into `_acc[at..at+2]`. */
195
+ function _mulAdd22(x: number, y: number, sign: number, at: number): void {
196
+ const x0 = x & 0x3ffff;
197
+ const x1 = x >>> 18;
198
+ const y0 = y & 0x3ffff;
199
+ const y1 = y >>> 18;
200
+ _acc[at] += sign * x0 * y0;
201
+ _acc[at + 1] += sign * (x0 * y1 + x1 * y0);
202
+ _acc[at + 2] += sign * x1 * y1;
203
+ }
204
+
205
+ /** Add `sign * a * m` for four-limb non-negative numbers into `_acc[0..6]`. */
206
+ function _mulAdd44(a: Float64Array, m: Float64Array, sign: number): void {
207
+ for (let i = 0; i < 4; i++) {
208
+ const ai = sign * a[i];
209
+ if (ai === 0) continue;
210
+ for (let j = 0; j < 4; j++) _acc[i + j] += ai * m[j];
211
+ }
212
+ }
213
+
214
+ /** Carry-normalize `_acc[0..n)` to limbs in [0, LIMB); returns the signed final carry. */
215
+ function _carry(n: number): number {
216
+ let carry = 0;
217
+ for (let k = 0; k < n; k++) {
218
+ const v = _acc[k] + carry;
219
+ carry = Math.floor(v * INV_LIMB); // exact: a power-of-two scale
220
+ _acc[k] = v - carry * LIMB;
221
+ }
222
+ return carry;
223
+ }
224
+
225
+ /** Sign of the normalized accumulator: the carry decides, else any nonzero limb. */
226
+ function _accSign(n: number, carry: number): number {
227
+ if (carry !== 0) return carry > 0 ? 1 : -1;
228
+ for (let k = 0; k < n; k++) if (_acc[k] !== 0) return 1;
229
+ return 0;
230
+ }
231
+
232
+ /**
233
+ * Write the magnitude of the normalized accumulator (n limbs plus carry) into
234
+ * `out[0..n]` and return its sign. A negative value is complemented limb by limb.
235
+ */
236
+ function _magnitude(n: number, carry: number, out: Float64Array): number {
237
+ if (carry >= 0) {
238
+ let zero = carry === 0;
239
+ for (let k = 0; k < n; k++) {
240
+ out[k] = _acc[k];
241
+ if (_acc[k] !== 0) zero = false;
242
+ }
243
+ out[n] = carry;
244
+ return zero ? 0 : 1;
245
+ }
246
+ let up = 1;
247
+ for (let k = 0; k < n; k++) {
248
+ let m = LIMB - 1 - _acc[k] + up;
249
+ up = 0;
250
+ if (m === LIMB) {
251
+ m = 0;
252
+ up = 1;
253
+ }
254
+ out[k] = m;
255
+ }
256
+ out[n] = -carry - 1 + up;
257
+ return -1;
258
+ }
259
+
260
+ /** Sign of `a * b - c * d` for integers below 2^32 in magnitude. */
261
+ function _orientInt(a: number, b: number, c: number, d: number): number {
262
+ _acc[0] = 0;
263
+ _acc[1] = 0;
264
+ _acc[2] = 0;
265
+ _mulAdd22(Math.abs(a), Math.abs(b), a < 0 !== b < 0 ? -1 : 1, 0);
266
+ _mulAdd22(Math.abs(c), Math.abs(d), c < 0 !== d < 0 ? 1 : -1, 0);
267
+ return _accSign(3, _carry(3));
268
+ }
269
+
270
+ /** `dx² + dy²` as four limbs. */
271
+ function _lift(dx: number, dy: number, out: Float64Array): void {
272
+ _acc[0] = 0;
273
+ _acc[1] = 0;
274
+ _acc[2] = 0;
275
+ _mulAdd22(Math.abs(dx), Math.abs(dx), 1, 0);
276
+ _mulAdd22(Math.abs(dy), Math.abs(dy), 1, 0);
277
+ const carry = _carry(3);
278
+ out[0] = _acc[0];
279
+ out[1] = _acc[1];
280
+ out[2] = _acc[2];
281
+ out[3] = carry;
282
+ }
283
+
284
+ /** Sign of `p * q - r * t`, with its magnitude written as four limbs. */
285
+ function _cross(
286
+ p: number,
287
+ q: number,
288
+ r: number,
289
+ t: number,
290
+ out: Float64Array,
291
+ ): number {
292
+ _acc[0] = 0;
293
+ _acc[1] = 0;
294
+ _acc[2] = 0;
295
+ _mulAdd22(Math.abs(p), Math.abs(q), p < 0 !== q < 0 ? -1 : 1, 0);
296
+ _mulAdd22(Math.abs(r), Math.abs(t), r < 0 !== t < 0 ? 1 : -1, 0);
297
+ return _magnitude(3, _carry(3), out);
298
+ }
299
+
300
+ /** Exact in-circle sign from integer differences below 2^32 in magnitude. */
301
+ function _incircleInt(
302
+ adx: number,
303
+ ady: number,
304
+ bdx: number,
305
+ bdy: number,
306
+ cdx: number,
307
+ cdy: number,
308
+ ): number {
309
+ _lift(adx, ady, _liftA);
310
+ _lift(bdx, bdy, _liftB);
311
+ _lift(cdx, cdy, _liftC);
312
+ const sa = _cross(bdx, cdy, cdx, bdy, _crossA);
313
+ const sb = _cross(cdx, ady, adx, cdy, _crossB);
314
+ const sc = _cross(adx, bdy, bdx, ady, _crossC);
315
+ for (let k = 0; k < 7; k++) _acc[k] = 0;
316
+ if (sa !== 0) _mulAdd44(_liftA, _crossA, sa);
317
+ if (sb !== 0) _mulAdd44(_liftB, _crossB, sb);
318
+ if (sc !== 0) _mulAdd44(_liftC, _crossC, sc);
319
+ return _accSign(7, _carry(7));
320
+ }
321
+
322
+ // Exact evaluation: each double is sign * mantissa * 2^exponent with an
323
+ // integer mantissa, so scaling every input to the smallest exponent gives
324
+ // exact integers whose determinant has the same sign as the real one.
325
+ const _bits = new Float64Array(1);
326
+ const _words = new Uint32Array(_bits.buffer);
327
+ _bits[0] = 1;
328
+ const HI = _words[1] === 0x3ff00000 ? 1 : 0;
329
+ const LO = HI ^ 1;
330
+ const _mant = new Float64Array(8);
331
+ const _expo = new Int32Array(8);
332
+ // BigInt is only touched inside the exact fallback, so loading this module
333
+ // never requires it
334
+
335
+ function _scaled(values: Float64Array, count: number): bigint[] {
336
+ let minExpo = 0x7fffffff;
337
+ for (let i = 0; i < count; i++) {
338
+ _bits[0] = values[i];
339
+ const hi = _words[HI];
340
+ const biased = (hi >>> 20) & 0x7ff;
341
+ let m = (hi & 0xfffff) * 4294967296 + _words[LO];
342
+ let e = -1074; // subnormal: the fraction is the mantissa
343
+ if (biased !== 0) {
344
+ m += 4503599627370496; // implicit leading bit
345
+ e = biased - 1075;
346
+ }
347
+ _mant[i] = hi >>> 31 ? -m : m;
348
+ _expo[i] = e;
349
+ if (m !== 0 && e < minExpo) minExpo = e;
350
+ }
351
+ const out: bigint[] = [];
352
+ for (let i = 0; i < count; i++) {
353
+ out.push(
354
+ _mant[i] === 0
355
+ ? BigInt(0)
356
+ : BigInt(_mant[i]) << BigInt(_expo[i] - minExpo),
357
+ );
358
+ }
359
+ return out;
360
+ }
361
+
362
+ /** Exact orientation, evaluated in BigInt. Used when the fast filter cannot decide. */
363
+ export function orient2dExact(
364
+ ax: number,
365
+ ay: number,
366
+ bx: number,
367
+ by: number,
368
+ cx: number,
369
+ cy: number,
370
+ ): number {
371
+ _input[0] = ax;
372
+ _input[1] = ay;
373
+ _input[2] = bx;
374
+ _input[3] = by;
375
+ _input[4] = cx;
376
+ _input[5] = cy;
377
+ const s = _scaled(_input, 6);
378
+ const det = (s[0] - s[4]) * (s[3] - s[5]) - (s[1] - s[5]) * (s[2] - s[4]);
379
+ const zero = BigInt(0);
380
+ return det > zero ? 1 : det < zero ? -1 : 0;
381
+ }
382
+
383
+ /** Exact in-circle test, evaluated in BigInt. Used when the fast filter cannot decide. */
384
+ export function incircleExact(
385
+ ax: number,
386
+ ay: number,
387
+ bx: number,
388
+ by: number,
389
+ cx: number,
390
+ cy: number,
391
+ dx: number,
392
+ dy: number,
393
+ ): number {
394
+ _input[0] = ax;
395
+ _input[1] = ay;
396
+ _input[2] = bx;
397
+ _input[3] = by;
398
+ _input[4] = cx;
399
+ _input[5] = cy;
400
+ _input[6] = dx;
401
+ _input[7] = dy;
402
+ const s = _scaled(_input, 8);
403
+ const adx = s[0] - s[6];
404
+ const ady = s[1] - s[7];
405
+ const bdx = s[2] - s[6];
406
+ const bdy = s[3] - s[7];
407
+ const cdx = s[4] - s[6];
408
+ const cdy = s[5] - s[7];
409
+ const det =
410
+ (adx * adx + ady * ady) * (bdx * cdy - cdx * bdy) +
411
+ (bdx * bdx + bdy * bdy) * (cdx * ady - adx * cdy) +
412
+ (cdx * cdx + cdy * cdy) * (adx * bdy - bdx * ady);
413
+ const zero = BigInt(0);
414
+ return det > zero ? 1 : det < zero ? -1 : 0;
415
+ }
416
+
417
+ // ----------------------------------------------------------- insertion order
418
+
419
+ /** Distance along a Hilbert curve of order `order` for grid cell (x, y). */
420
+ export function hilbertIndex(order: number, x: number, y: number): number {
421
+ const n = 1 << order;
422
+ let d = 0;
423
+ for (let s = n >> 1; s > 0; s >>= 1) {
424
+ const rx = (x & s) !== 0 ? 1 : 0;
425
+ const ry = (y & s) !== 0 ? 1 : 0;
426
+ d += s * s * ((3 * rx) ^ ry);
427
+ if (ry === 0) {
428
+ if (rx === 1) {
429
+ x = n - 1 - x;
430
+ y = n - 1 - y;
431
+ }
432
+ const t = x;
433
+ x = y;
434
+ y = t;
435
+ }
436
+ }
437
+ return d;
438
+ }
439
+
440
+ /**
441
+ * Point indices sorted along a Hilbert curve over the bounding box, ties (and
442
+ * duplicate points) in index order. Non-finite points sort with the origin cell.
443
+ */
444
+ export function hilbertOrder(
445
+ coords: ArrayLike<number>,
446
+ n: number,
447
+ ): Uint32Array {
448
+ let minX = Infinity;
449
+ let minY = Infinity;
450
+ let maxX = -Infinity;
451
+ let maxY = -Infinity;
452
+ for (let i = 0; i < n; i++) {
453
+ const x = coords[2 * i];
454
+ const y = coords[2 * i + 1];
455
+ if (!Number.isFinite(x) || !Number.isFinite(y)) continue;
456
+ if (x < minX) minX = x;
457
+ if (x > maxX) maxX = x;
458
+ if (y < minY) minY = y;
459
+ if (y > maxY) maxY = y;
460
+ }
461
+ // about sixteen cells per point is fine enough for locality; the order is
462
+ // capped so keys stay within 32 bits
463
+ let order = 2;
464
+ while (order < 16 && 1 << (2 * order) < 16 * n) order++;
465
+ const cells = (1 << order) - 1;
466
+ const span = Math.max(maxX - minX, maxY - minY);
467
+ const scale = span > 0 && Number.isFinite(span) ? cells / span : 0;
468
+
469
+ const keys = new Uint32Array(n);
470
+ for (let i = 0; i < n; i++) {
471
+ const x = coords[2 * i];
472
+ const y = coords[2 * i + 1];
473
+ let gx = 0;
474
+ let gy = 0;
475
+ if (Number.isFinite(x) && Number.isFinite(y)) {
476
+ gx = Math.min(cells, Math.max(0, Math.floor((x - minX) * scale)));
477
+ gy = Math.min(cells, Math.max(0, Math.floor((y - minY) * scale)));
478
+ }
479
+ keys[i] = hilbertIndex(order, gx, gy);
480
+ }
481
+
482
+ // stable least-significant-digit radix sort, one byte per pass, over the
483
+ // bytes the keys actually use
484
+ let from = new Uint32Array(n);
485
+ let to = new Uint32Array(n);
486
+ for (let i = 0; i < n; i++) from[i] = i;
487
+ const counts = new Int32Array(257);
488
+ for (let shift = 0; shift < 2 * order; shift += 8) {
489
+ counts.fill(0);
490
+ for (let i = 0; i < n; i++) counts[((keys[i] >>> shift) & 255) + 1]++;
491
+ for (let b = 0; b < 256; b++) counts[b + 1] += counts[b];
492
+ for (let i = 0; i < n; i++) {
493
+ const p = from[i];
494
+ to[counts[(keys[p] >>> shift) & 255]++] = p;
495
+ }
496
+ const swap = from;
497
+ from = to;
498
+ to = swap;
499
+ }
500
+ return from;
501
+ }
502
+
503
+ // ------------------------------------------------------------- triangulation
504
+
505
+ /**
506
+ * Delaunay-triangulate points given as a flat `[x0, y0, x1, y1, ...]` array.
507
+ * Duplicate points and points with non-finite coordinates are left out; the
508
+ * lowest index of a duplicate is the one used. Returns no triangles when
509
+ * fewer than three distinct, non-collinear points exist.
510
+ * @param coords flat coordinates
511
+ * @param n number of points; defaults to half the array length
512
+ */
513
+ export function triangulate(
514
+ coords: Float64Array,
515
+ n: number = coords.length >> 1,
516
+ ): Triangulation {
517
+ if (n < 3) return EMPTY;
518
+ _presetScale = sharedScale(coords, n);
519
+ try {
520
+ return _triangulate(coords, n);
521
+ } finally {
522
+ _presetScale = 0;
523
+ }
524
+ }
525
+
526
+ /**
527
+ * The smallest power of two that makes every finite coordinate an integer
528
+ * below 2^31 in magnitude, or 0 when there is none. Float32 values (every Pt)
529
+ * always have one in a sane coordinate range.
530
+ */
531
+ export function sharedScale(coords: ArrayLike<number>, n: number): number {
532
+ let bits = 0;
533
+ let max = 0;
534
+ for (let i = 0; i < 2 * n; i++) {
535
+ const v = coords[i];
536
+ if (!Number.isFinite(v)) continue;
537
+ if (v === 0) continue;
538
+ if (Math.abs(v) > max) max = Math.abs(v);
539
+ _bits[0] = v;
540
+ const hi = _words[HI];
541
+ const lo = _words[LO];
542
+ const biased = (hi >>> 20) & 0x7ff;
543
+ // v = m * 2^e with an integer m; trailing zero bits of m move into e
544
+ const e = biased === 0 ? -1074 : biased - 1075;
545
+ let trailing: number;
546
+ if (lo !== 0) {
547
+ trailing = 31 - Math.clz32(lo & -lo);
548
+ } else {
549
+ const m = (hi & 0xfffff) | (biased === 0 ? 0 : 0x100000);
550
+ trailing = 32 + (31 - Math.clz32(m & -m));
551
+ }
552
+ const fractional = -(e + trailing);
553
+ if (fractional > bits) bits = fractional;
554
+ if (bits > 30) return 0;
555
+ }
556
+ const scale = Math.pow(2, bits);
557
+ return max * scale < INT_LIMIT ? scale : 0;
558
+ }
559
+
560
+ function _triangulate(coords: Float64Array, n: number): Triangulation {
561
+ const order = hilbertOrder(coords, n);
562
+
563
+ // the first three non-collinear distinct points seed the triangulation
564
+ let i0 = -1;
565
+ let i1 = -1;
566
+ let i2 = -1;
567
+ let k = 0;
568
+ for (; k < n; k++) {
569
+ const i = order[k];
570
+ if (Number.isFinite(coords[2 * i]) && Number.isFinite(coords[2 * i + 1])) {
571
+ i0 = i;
572
+ k++;
573
+ break;
574
+ }
575
+ }
576
+ if (i0 < 0) return EMPTY;
577
+ for (; k < n; k++) {
578
+ const i = order[k];
579
+ const x = coords[2 * i];
580
+ const y = coords[2 * i + 1];
581
+ if (
582
+ Number.isFinite(x) &&
583
+ Number.isFinite(y) &&
584
+ (x !== coords[2 * i0] || y !== coords[2 * i0 + 1])
585
+ ) {
586
+ i1 = i;
587
+ k++;
588
+ break;
589
+ }
590
+ }
591
+ if (i1 < 0) return EMPTY;
592
+ for (; k < n; k++) {
593
+ const i = order[k];
594
+ const x = coords[2 * i];
595
+ const y = coords[2 * i + 1];
596
+ if (!Number.isFinite(x) || !Number.isFinite(y)) continue;
597
+ const o = orient2d(
598
+ coords[2 * i0],
599
+ coords[2 * i0 + 1],
600
+ coords[2 * i1],
601
+ coords[2 * i1 + 1],
602
+ x,
603
+ y,
604
+ );
605
+ if (o !== 0) {
606
+ i2 = i;
607
+ if (o < 0) {
608
+ const swap = i1;
609
+ i1 = i2;
610
+ i2 = swap;
611
+ }
612
+ break;
613
+ }
614
+ }
615
+ if (i2 < 0) return EMPTY; // every point is on one line
616
+
617
+ // Triangles are stored as vertex triples with their twin half-edges. Ghost
618
+ // triangles carry the infinite vertex INF in slot 2; their slot-0 → slot-1
619
+ // edge is a hull edge traversed with the outside on the left.
620
+ const INF = n;
621
+ const capacity = 2 * n + 4;
622
+ const tv = new Int32Array(capacity * 3);
623
+ const adj = new Int32Array(capacity * 3);
624
+ const live = new Uint8Array(capacity);
625
+ const mark = new Int32Array(capacity);
626
+ const freed = new Int32Array(capacity);
627
+ let freedCount = 0;
628
+ let triCount = 0;
629
+ const stack = new Int32Array(capacity);
630
+ const cavity = new Int32Array(capacity);
631
+ const edgeFrom = new Int32Array(capacity * 3);
632
+ const edgeTo = new Int32Array(capacity * 3);
633
+ const edgeTwin = new Int32Array(capacity * 3);
634
+ const toPoint = new Int32Array(n + 1);
635
+ const fromPoint = new Int32Array(n + 1);
636
+ let stamp = 0;
637
+
638
+ const alloc = (a: number, b: number, c: number): number => {
639
+ const t = freedCount > 0 ? freed[--freedCount] : triCount++;
640
+ const h = 3 * t;
641
+ tv[h] = a;
642
+ tv[h + 1] = b;
643
+ tv[h + 2] = c;
644
+ live[t] = 1;
645
+ return t;
646
+ };
647
+ const link = (h: number, g: number): void => {
648
+ adj[h] = g;
649
+ adj[g] = h;
650
+ };
651
+
652
+ // first triangle and its three ghosts
653
+ const t0 = alloc(i0, i1, i2);
654
+ const g0 = alloc(i1, i0, INF);
655
+ const g1 = alloc(i2, i1, INF);
656
+ const g2 = alloc(i0, i2, INF);
657
+ link(3 * t0, 3 * g0);
658
+ link(3 * t0 + 1, 3 * g1);
659
+ link(3 * t0 + 2, 3 * g2);
660
+ link(3 * g0 + 1, 3 * g2 + 2);
661
+ link(3 * g1 + 1, 3 * g0 + 2);
662
+ link(3 * g2 + 1, 3 * g1 + 2);
663
+
664
+ // Whether p lies in the "circumdisk" of ghost g: strictly outside its hull
665
+ // edge u → v, or on the open segment between u and v.
666
+ const inGhostDisk = (g: number, px: number, py: number): boolean => {
667
+ const u = tv[3 * g];
668
+ const v = tv[3 * g + 1];
669
+ const ux = coords[2 * u];
670
+ const uy = coords[2 * u + 1];
671
+ const vx = coords[2 * v];
672
+ const vy = coords[2 * v + 1];
673
+ const o = orient2d(ux, uy, vx, vy, px, py);
674
+ if (o !== 0) return o > 0;
675
+ // collinear: strictly between the endpoints along the wider axis
676
+ return Math.abs(vx - ux) >= Math.abs(vy - uy)
677
+ ? vx > ux
678
+ ? px > ux && px < vx
679
+ : px < ux && px > vx
680
+ : vy > uy
681
+ ? py > uy && py < vy
682
+ : py < uy && py > vy;
683
+ };
684
+
685
+ // Whether p is inside the circumdisk of triangle t (for a ghost: its
686
+ // outer half-plane plus the open hull edge). Vertices are never inside.
687
+ const inDisk = (t: number, px: number, py: number): boolean => {
688
+ const h = 3 * t;
689
+ if (tv[h + 2] === INF) return inGhostDisk(t, px, py);
690
+ const a = tv[h];
691
+ const b = tv[h + 1];
692
+ const c = tv[h + 2];
693
+ return (
694
+ incircle(
695
+ coords[2 * a],
696
+ coords[2 * a + 1],
697
+ coords[2 * b],
698
+ coords[2 * b + 1],
699
+ coords[2 * c],
700
+ coords[2 * c + 1],
701
+ px,
702
+ py,
703
+ ) > 0
704
+ );
705
+ };
706
+
707
+ // Walk from triangle t to a triangle whose circumdisk contains p. Returns
708
+ // -1 when p coincides with an existing vertex. The walk always starts from a
709
+ // finite triangle and enters a ghost only across a hull edge that p lies
710
+ // strictly outside of; a ghost reached any other way (p collinear with its
711
+ // edge, or inside the hull) hands the walk back to the finite side, which
712
+ // then locates p or crosses the right hull edge.
713
+ const locate = (t: number, px: number, py: number): number => {
714
+ let steps = 0;
715
+ const limit = 4 * triCount + 64;
716
+ while (true) {
717
+ const h = 3 * t;
718
+ const a = tv[h];
719
+ const b = tv[h + 1];
720
+ const c = tv[h + 2];
721
+ const ax = coords[2 * a];
722
+ const ay = coords[2 * a + 1];
723
+ const bx = coords[2 * b];
724
+ const by = coords[2 * b + 1];
725
+ if (c === INF) {
726
+ if (orient2d(ax, ay, bx, by, px, py) > 0) return t;
727
+ t = (adj[h] / 3) | 0;
728
+ } else {
729
+ const cx = coords[2 * c];
730
+ const cy = coords[2 * c + 1];
731
+ if (orient2d(ax, ay, bx, by, px, py) < 0) {
732
+ t = (adj[h] / 3) | 0;
733
+ } else if (orient2d(bx, by, cx, cy, px, py) < 0) {
734
+ t = (adj[h + 1] / 3) | 0;
735
+ } else if (orient2d(cx, cy, ax, ay, px, py) < 0) {
736
+ t = (adj[h + 2] / 3) | 0;
737
+ } else if (
738
+ (px === ax && py === ay) ||
739
+ (px === bx && py === by) ||
740
+ (px === cx && py === cy)
741
+ ) {
742
+ return -1;
743
+ } else {
744
+ return t;
745
+ }
746
+ }
747
+ // the visibility walk terminates on a Delaunay triangulation; never spin
748
+ if (++steps > limit) throw new Error("Delaunay walk did not terminate");
749
+ }
750
+ };
751
+
752
+ const insert = (p: number, start: number): number => {
753
+ const px = coords[2 * p];
754
+ const py = coords[2 * p + 1];
755
+ const t = locate(start, px, py);
756
+ if (t < 0) return start; // p is an existing vertex
757
+
758
+ // the cavity: every triangle whose circumdisk contains p, by adjacency
759
+ stamp++;
760
+ let top = 0;
761
+ let count = 0;
762
+ stack[top++] = t;
763
+ mark[t] = stamp;
764
+ while (top > 0) {
765
+ const s = stack[--top];
766
+ cavity[count++] = s;
767
+ const h = 3 * s;
768
+ for (let e = 0; e < 3; e++) {
769
+ const o = (adj[h + e] / 3) | 0;
770
+ if (mark[o] !== stamp && inDisk(o, px, py)) {
771
+ mark[o] = stamp;
772
+ stack[top++] = o;
773
+ }
774
+ }
775
+ }
776
+
777
+ // its boundary: cavity half-edges whose twin lies outside the cavity,
778
+ // recorded by vertex before the cavity's slots are recycled
779
+ let edges = 0;
780
+ for (let i = 0; i < count; i++) {
781
+ const h = 3 * cavity[i];
782
+ for (let e = 0; e < 3; e++) {
783
+ const twin = adj[h + e];
784
+ if (mark[(twin / 3) | 0] !== stamp) {
785
+ edgeFrom[edges] = tv[h + e];
786
+ edgeTo[edges] = tv[h + ((e + 1) % 3)];
787
+ edgeTwin[edges] = twin;
788
+ edges++;
789
+ }
790
+ }
791
+ }
792
+ for (let i = 0; i < count; i++) {
793
+ live[cavity[i]] = 0;
794
+ freed[freedCount++] = cavity[i];
795
+ }
796
+
797
+ // fan the cavity: one triangle (u, v, p) per boundary edge u → v, with
798
+ // the infinite vertex kept in slot 2
799
+ let first = -1;
800
+ for (let i = 0; i < edges; i++) {
801
+ const u = edgeFrom[i];
802
+ const v = edgeTo[i];
803
+ let t2: number;
804
+ let hEdge: number;
805
+ let hToP: number;
806
+ let hFromP: number;
807
+ if (u === INF) {
808
+ t2 = alloc(v, p, INF);
809
+ hEdge = 3 * t2 + 2;
810
+ hToP = 3 * t2;
811
+ hFromP = 3 * t2 + 1;
812
+ } else if (v === INF) {
813
+ t2 = alloc(p, u, INF);
814
+ hEdge = 3 * t2 + 1;
815
+ hToP = 3 * t2 + 2;
816
+ hFromP = 3 * t2;
817
+ } else {
818
+ t2 = alloc(u, v, p);
819
+ hEdge = 3 * t2;
820
+ hToP = 3 * t2 + 1;
821
+ hFromP = 3 * t2 + 2;
822
+ if (first < 0) first = t2;
823
+ }
824
+ link(hEdge, edgeTwin[i]);
825
+ toPoint[v] = hToP;
826
+ fromPoint[u] = hFromP;
827
+ }
828
+ for (let i = 0; i < edges; i++) {
829
+ const v = edgeTo[i];
830
+ link(toPoint[v], fromPoint[v]);
831
+ }
832
+ return first < 0 ? start : first;
833
+ };
834
+
835
+ let last = t0;
836
+ for (let j = 0; j < n; j++) {
837
+ const i = order[j];
838
+ if (i === i0 || i === i1 || i === i2) continue;
839
+ if (!Number.isFinite(coords[2 * i]) || !Number.isFinite(coords[2 * i + 1]))
840
+ continue;
841
+ last = insert(i, last);
842
+ }
843
+
844
+ // compact the finite triangles and renumber their adjacency
845
+ const index = new Int32Array(triCount);
846
+ let m = 0;
847
+ let ghost = -1;
848
+ for (let t = 0; t < triCount; t++) {
849
+ if (!live[t]) {
850
+ index[t] = -1;
851
+ } else if (tv[3 * t + 2] === INF) {
852
+ index[t] = -1;
853
+ ghost = t;
854
+ } else {
855
+ index[t] = m++;
856
+ }
857
+ }
858
+ const triangles = new Uint32Array(m * 3);
859
+ const neighbors = new Int32Array(m * 3);
860
+ for (let t = 0; t < triCount; t++) {
861
+ const k2 = index[t];
862
+ if (k2 < 0) continue;
863
+ for (let e = 0; e < 3; e++) {
864
+ triangles[3 * k2 + e] = tv[3 * t + e];
865
+ const o = adj[3 * t + e];
866
+ const ot = index[(o / 3) | 0];
867
+ neighbors[3 * k2 + e] = ot < 0 ? -1 : 3 * ot + (o % 3);
868
+ }
869
+ }
870
+ // the hull, counterclockwise, by walking the ring of ghosts
871
+ let hullCount = 0;
872
+ let g = ghost;
873
+ do {
874
+ hullCount++;
875
+ g = (adj[3 * g + 2] / 3) | 0;
876
+ } while (g !== ghost);
877
+ const hull = new Uint32Array(hullCount);
878
+ g = ghost;
879
+ for (let i = 0; i < hullCount; i++) {
880
+ hull[i] = tv[3 * g + 1];
881
+ g = (adj[3 * g + 2] / 3) | 0;
882
+ }
883
+ return { triangles, neighbors, hull };
884
+ }