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.
- package/README.md +92 -80
- package/dist/index.d.mts +6208 -1254
- package/dist/index.d.mts.map +1 -0
- package/dist/index.d.ts +6208 -1254
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9314 -10680
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +9266 -10611
- package/dist/index.mjs.map +1 -0
- package/dist/pts.js +9446 -10777
- package/dist/pts.js.map +1 -0
- package/dist/pts.min.js +3 -5
- package/dist/pts.min.js.map +1 -0
- package/package.json +91 -31
- package/src/Canvas.ts +1642 -0
- package/src/Color.ts +1109 -0
- package/src/Create.ts +1547 -0
- package/src/Dom.ts +940 -0
- package/src/Form.ts +312 -0
- package/src/Image.ts +722 -0
- package/src/LinearAlgebra.ts +530 -0
- package/src/Num.ts +1091 -0
- package/src/Op.ts +2127 -0
- package/src/Physics.ts +1233 -0
- package/src/Play.ts +861 -0
- package/src/Pt.ts +1303 -0
- package/src/Space.ts +898 -0
- package/src/Svg.ts +1573 -0
- package/src/Types.ts +301 -0
- package/src/Typography.ts +228 -0
- package/src/UI.ts +757 -0
- package/src/Util.ts +454 -0
- package/src/_module.ts +18 -0
- package/src/_script.ts +94 -0
- package/src/_triangulate.ts +884 -0
- package/src/uheprng.ts +153 -0
|
@@ -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
|
+
}
|