clipper2-ts 2.0.1-13 → 2.0.1-15
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/dist/Engine.d.ts.map +1 -1
- package/dist/Engine.js +13 -28
- package/dist/Engine.js.map +1 -1
- package/dist/Shewchuk.d.ts +3 -0
- package/dist/Shewchuk.d.ts.map +1 -0
- package/dist/Shewchuk.js +953 -0
- package/dist/Shewchuk.js.map +1 -0
- package/dist/Triangulation.d.ts +2 -5
- package/dist/Triangulation.d.ts.map +1 -1
- package/dist/Triangulation.js +85 -259
- package/dist/Triangulation.js.map +1 -1
- package/dist/cdt/SweepCDT.d.ts +7 -0
- package/dist/cdt/SweepCDT.d.ts.map +1 -0
- package/dist/cdt/SweepCDT.js +1272 -0
- package/dist/cdt/SweepCDT.js.map +1 -0
- package/dist/cdt/predicates.d.ts +3 -0
- package/dist/cdt/predicates.d.ts.map +1 -0
- package/dist/cdt/predicates.js +948 -0
- package/dist/cdt/predicates.js.map +1 -0
- package/dist/clipper2.min.mjs +6 -6
- package/dist/clipper2.min.mjs.map +1 -1
- package/package.json +1 -1
- package/src/Engine.ts +4 -17
- package/src/Shewchuk.ts +690 -0
- package/src/Triangulation.ts +87 -272
- package/src/cdt/SweepCDT.ts +1220 -0
- package/src/cdt/predicates.ts +682 -0
|
@@ -0,0 +1,1220 @@
|
|
|
1
|
+
// Sweep-line CDT — Domiter & Žalik 2008 (IJGIS 22:4, 449-462)
|
|
2
|
+
// DT engine from Žalik 2005 (Computer-Aided Design 37, 1027-1038).
|
|
3
|
+
// Edge insertion per Anglada 1997 (Computers & Graphics 21:2, 215-223).
|
|
4
|
+
//
|
|
5
|
+
// Triangle-based adjacency (3 vertices + 3 neighbor tris per triangle).
|
|
6
|
+
// Advancing front: doubly-linked list + hash table (Žalik §3.4, k=100).
|
|
7
|
+
// Zero allocations in the sweep loop.
|
|
8
|
+
|
|
9
|
+
import { orient2dSign, incircleSign } from './predicates.js';
|
|
10
|
+
|
|
11
|
+
export interface CDTResult {
|
|
12
|
+
triangles: Uint32Array;
|
|
13
|
+
triCount: number;
|
|
14
|
+
coords: Float64Array;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Triangle pool — SoA for cache-friendliness
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// tv[t*3+j] = vertex index at local position j (CCW winding)
|
|
21
|
+
// ta[t*3+j] = neighbor triangle opposite vertex j (NONE if boundary)
|
|
22
|
+
// tf[t] bits = fixed-edge flags, bit j ⇔ edge opposite vertex j is constrained
|
|
23
|
+
// td[t] = 1 if triangle is dead (removed during edge event)
|
|
24
|
+
|
|
25
|
+
const NONE = -1;
|
|
26
|
+
let tv: Int32Array;
|
|
27
|
+
let ta: Int32Array;
|
|
28
|
+
let tf: Uint8Array;
|
|
29
|
+
let td: Uint8Array;
|
|
30
|
+
let triCap = 0;
|
|
31
|
+
let triCount = 0;
|
|
32
|
+
|
|
33
|
+
function initTris(cap: number): void {
|
|
34
|
+
triCap = cap; triCount = 0;
|
|
35
|
+
tv = new Int32Array(cap * 3);
|
|
36
|
+
ta = new Int32Array(cap * 3).fill(NONE);
|
|
37
|
+
tf = new Uint8Array(cap);
|
|
38
|
+
td = new Uint8Array(cap);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function growTris(): void {
|
|
42
|
+
const c = triCap << 1;
|
|
43
|
+
const v2 = new Int32Array(c * 3); v2.set(tv); tv = v2;
|
|
44
|
+
const a2 = new Int32Array(c * 3).fill(NONE); a2.set(ta); ta = a2;
|
|
45
|
+
const f2 = new Uint8Array(c); f2.set(tf); tf = f2;
|
|
46
|
+
const d2 = new Uint8Array(c); d2.set(td); td = d2;
|
|
47
|
+
triCap = c;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function addTri(a: number, b: number, c: number): number {
|
|
51
|
+
if (triCount === triCap) growTris();
|
|
52
|
+
const t = triCount++, t3 = t * 3;
|
|
53
|
+
tv[t3] = a; tv[t3 + 1] = b; tv[t3 + 2] = c;
|
|
54
|
+
ta[t3] = ta[t3 + 1] = ta[t3 + 2] = NONE;
|
|
55
|
+
tf[t] = 0; td[t] = 0;
|
|
56
|
+
return t;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function link(t1: number, j1: number, t2: number, j2: number): void {
|
|
60
|
+
ta[t1 * 3 + j1] = t2;
|
|
61
|
+
ta[t2 * 3 + j2] = t1;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function localIdx(t: number, v: number): number {
|
|
65
|
+
const t3 = t * 3;
|
|
66
|
+
if (tv[t3] === v) return 0;
|
|
67
|
+
if (tv[t3 + 1] === v) return 1;
|
|
68
|
+
if (tv[t3 + 2] === v) return 2;
|
|
69
|
+
return -1;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function adjIdx(t: number, n: number): number {
|
|
73
|
+
const t3 = t * 3;
|
|
74
|
+
if (ta[t3] === n) return 0;
|
|
75
|
+
if (ta[t3 + 1] === n) return 1;
|
|
76
|
+
if (ta[t3 + 2] === n) return 2;
|
|
77
|
+
return -1;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function isFixed(t: number, j: number): boolean {
|
|
81
|
+
return (tf[t] & (1 << j)) !== 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function markFixed(t: number, j: number): void {
|
|
85
|
+
tf[t] |= (1 << j);
|
|
86
|
+
const n = ta[t * 3 + j];
|
|
87
|
+
if (n !== NONE) {
|
|
88
|
+
const nj = adjIdx(n, t);
|
|
89
|
+
if (nj >= 0) tf[n] |= (1 << nj);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
// Coordinates
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
let px: Float64Array;
|
|
97
|
+
let py: Float64Array;
|
|
98
|
+
|
|
99
|
+
function orient(a: number, b: number, c: number): number {
|
|
100
|
+
return orient2dSign(px[a], py[a], px[b], py[b], px[c], py[c]);
|
|
101
|
+
}
|
|
102
|
+
function inCircle(a: number, b: number, c: number, d: number): number {
|
|
103
|
+
return incircleSign(px[a], py[a], px[b], py[b], px[c], py[c], px[d], py[d]);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
// Advancing front — doubly-linked list + hash table (Žalik §3.4)
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
// Each AF node stores: vertex, triangle backing the AF edge to the right,
|
|
110
|
+
// prev/next pointers, hash-bucket chain pointer.
|
|
111
|
+
// Per Žalik Fig.14: key = x coordinate, vertex index v_i, triangle index t_i.
|
|
112
|
+
|
|
113
|
+
let afV: Int32Array; // vertex index
|
|
114
|
+
let afT: Int32Array; // triangle below AF edge (this node → next node)
|
|
115
|
+
let afP: Int32Array; // prev pointer
|
|
116
|
+
let afN: Int32Array; // next pointer
|
|
117
|
+
let afBk: Int32Array; // hash-bucket chain
|
|
118
|
+
let afCap = 0;
|
|
119
|
+
let afLen = 0;
|
|
120
|
+
|
|
121
|
+
let afHash: Int32Array;
|
|
122
|
+
let afHSize = 0;
|
|
123
|
+
let afXMin = 0;
|
|
124
|
+
let afXRng = 1;
|
|
125
|
+
|
|
126
|
+
function initAF(n: number, xmin: number, xmax: number): void {
|
|
127
|
+
afCap = Math.max(n + 4, 64); afLen = 0;
|
|
128
|
+
afV = new Int32Array(afCap);
|
|
129
|
+
afT = new Int32Array(afCap);
|
|
130
|
+
afP = new Int32Array(afCap);
|
|
131
|
+
afN = new Int32Array(afCap);
|
|
132
|
+
afBk = new Int32Array(afCap).fill(NONE);
|
|
133
|
+
afHSize = 1 + ((n / 100) | 0); // Žalik eq.(1), k=100
|
|
134
|
+
afHash = new Int32Array(afHSize).fill(NONE);
|
|
135
|
+
afXMin = xmin; afXRng = xmax - xmin || 1;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function afBucket(x: number): number {
|
|
139
|
+
let b = ((x - afXMin) / afXRng * (afHSize - 1)) | 0;
|
|
140
|
+
if (b < 0) b = 0; else if (b >= afHSize) b = afHSize - 1;
|
|
141
|
+
return b;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function growAF(): void {
|
|
145
|
+
const c = afCap << 1;
|
|
146
|
+
const g = (a: Int32Array) => { const b = new Int32Array(c); b.set(a); return b; };
|
|
147
|
+
afV = g(afV); afT = g(afT); afP = g(afP); afN = g(afN);
|
|
148
|
+
const bk = new Int32Array(c).fill(NONE); bk.set(afBk); afBk = bk;
|
|
149
|
+
afCap = c;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function afNew(v: number, tri: number): number {
|
|
153
|
+
if (afLen === afCap) growAF();
|
|
154
|
+
const id = afLen++;
|
|
155
|
+
afV[id] = v; afT[id] = tri;
|
|
156
|
+
afP[id] = afN[id] = NONE;
|
|
157
|
+
const b = afBucket(px[v]);
|
|
158
|
+
afBk[id] = afHash[b]; afHash[b] = id;
|
|
159
|
+
return id;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function afDel(id: number): void {
|
|
163
|
+
const p = afP[id], n = afN[id];
|
|
164
|
+
if (p !== NONE) afN[p] = n;
|
|
165
|
+
if (n !== NONE) afP[n] = p;
|
|
166
|
+
const b = afBucket(px[afV[id]]);
|
|
167
|
+
if (afHash[b] === id) { afHash[b] = afBk[id]; }
|
|
168
|
+
else {
|
|
169
|
+
let c = afHash[b];
|
|
170
|
+
while (c !== NONE && afBk[c] !== id) c = afBk[c];
|
|
171
|
+
if (c !== NONE) afBk[c] = afBk[id];
|
|
172
|
+
}
|
|
173
|
+
afBk[id] = NONE;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function afIns(v: number, tri: number, left: number, right: number): number {
|
|
177
|
+
const id = afNew(v, tri);
|
|
178
|
+
afP[id] = left; afN[id] = right;
|
|
179
|
+
if (left !== NONE) afN[left] = id;
|
|
180
|
+
if (right !== NONE) afP[right] = id;
|
|
181
|
+
return id;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Locate AF node whose segment [node, node.next] contains vertical
|
|
185
|
+
// projection of x. Returns the LEFT node. (Žalik §3.4)
|
|
186
|
+
function afLocate(x: number): number {
|
|
187
|
+
const b = afBucket(x);
|
|
188
|
+
let best = NONE, bestX = -Infinity;
|
|
189
|
+
|
|
190
|
+
for (let c = afHash[b]; c !== NONE; c = afBk[c]) {
|
|
191
|
+
const nx = px[afV[c]];
|
|
192
|
+
if (nx <= x && nx > bestX) { bestX = nx; best = c; }
|
|
193
|
+
}
|
|
194
|
+
if (best === NONE) {
|
|
195
|
+
for (let d = 1; d < afHSize; d++) {
|
|
196
|
+
for (const bb of [b - d, b + d]) {
|
|
197
|
+
if (bb < 0 || bb >= afHSize) continue;
|
|
198
|
+
for (let c = afHash[bb]; c !== NONE; c = afBk[c]) {
|
|
199
|
+
const nx = px[afV[c]];
|
|
200
|
+
if (nx <= x && nx > bestX) { bestX = nx; best = c; }
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (best !== NONE) break;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (best !== NONE) {
|
|
207
|
+
while (afN[best] !== NONE && px[afV[afN[best]]] <= x) best = afN[best];
|
|
208
|
+
}
|
|
209
|
+
return best;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ---------------------------------------------------------------------------
|
|
213
|
+
// Lawson legalization — Domiter §3.1 / Žalik §2.1
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
function legalize(t: number, pi: number): void {
|
|
216
|
+
const j = localIdx(t, pi);
|
|
217
|
+
if (j < 0) return;
|
|
218
|
+
const n = ta[t * 3 + j];
|
|
219
|
+
if (n === NONE) return;
|
|
220
|
+
if (isFixed(t, j)) return;
|
|
221
|
+
|
|
222
|
+
const t3 = t * 3;
|
|
223
|
+
const a = tv[t3 + (j + 1) % 3];
|
|
224
|
+
const b = tv[t3 + (j + 2) % 3];
|
|
225
|
+
|
|
226
|
+
const nj = adjIdx(n, t);
|
|
227
|
+
if (nj < 0) return;
|
|
228
|
+
const pk = tv[n * 3 + nj];
|
|
229
|
+
|
|
230
|
+
if (inCircle(a, b, pi, pk) <= 0) return;
|
|
231
|
+
|
|
232
|
+
// Flip edge a-b → pi-pk
|
|
233
|
+
const j1 = (j + 1) % 3, j2 = (j + 2) % 3;
|
|
234
|
+
const nj1 = (nj + 1) % 3, nj2 = (nj + 2) % 3;
|
|
235
|
+
const n3 = n * 3;
|
|
236
|
+
|
|
237
|
+
const tN_oppA = ta[t3 + j1];
|
|
238
|
+
const tN_oppB = ta[t3 + j2];
|
|
239
|
+
|
|
240
|
+
const vnj1 = tv[n3 + nj1], vnj2 = tv[n3 + nj2];
|
|
241
|
+
const nN1 = ta[n3 + nj1];
|
|
242
|
+
const nN2 = ta[n3 + nj2];
|
|
243
|
+
|
|
244
|
+
let nN_bside: number, nN_aside: number;
|
|
245
|
+
if (vnj1 === b) {
|
|
246
|
+
nN_bside = nN2; nN_aside = nN1;
|
|
247
|
+
} else {
|
|
248
|
+
nN_bside = nN1; nN_aside = nN2;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// t = (pi, pk, b)
|
|
252
|
+
tv[t3] = pi; tv[t3 + 1] = pk; tv[t3 + 2] = b;
|
|
253
|
+
ta[t3] = nN_bside;
|
|
254
|
+
ta[t3 + 1] = tN_oppA;
|
|
255
|
+
ta[t3 + 2] = n;
|
|
256
|
+
tf[t] = 0;
|
|
257
|
+
|
|
258
|
+
// n = (pi, a, pk)
|
|
259
|
+
tv[n3] = pi; tv[n3 + 1] = a; tv[n3 + 2] = pk;
|
|
260
|
+
ta[n3] = nN_aside;
|
|
261
|
+
ta[n3 + 1] = t;
|
|
262
|
+
ta[n3 + 2] = tN_oppB;
|
|
263
|
+
tf[n] = 0;
|
|
264
|
+
|
|
265
|
+
if (nN_bside !== NONE) { const k = adjIdx(nN_bside, n); if (k >= 0) ta[nN_bside * 3 + k] = t; }
|
|
266
|
+
if (tN_oppB !== NONE) { const k = adjIdx(tN_oppB, t); if (k >= 0) ta[tN_oppB * 3 + k] = n; }
|
|
267
|
+
|
|
268
|
+
legalize(t, pi);
|
|
269
|
+
legalize(n, pi);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// ---------------------------------------------------------------------------
|
|
273
|
+
// Helper: link new triangle to old triangle sharing edge (vA, vB)
|
|
274
|
+
// ---------------------------------------------------------------------------
|
|
275
|
+
function linkToOldTri(newTri: number, newOppJ: number, oldTri: number, vA: number, vB: number): void {
|
|
276
|
+
if (oldTri === NONE) return;
|
|
277
|
+
const ai = localIdx(oldTri, vA);
|
|
278
|
+
const bi = localIdx(oldTri, vB);
|
|
279
|
+
if (ai < 0 || bi < 0) return;
|
|
280
|
+
link(newTri, newOppJ, oldTri, 3 - ai - bi);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ---------------------------------------------------------------------------
|
|
284
|
+
// Point event — Domiter §3.4.1
|
|
285
|
+
// ---------------------------------------------------------------------------
|
|
286
|
+
let vertAF: Int32Array; // vertex → AF node id
|
|
287
|
+
|
|
288
|
+
function pointEvent(pi: number): void {
|
|
289
|
+
const x = px[pi];
|
|
290
|
+
const L = afLocate(x);
|
|
291
|
+
if (L === NONE) return;
|
|
292
|
+
const R = afN[L];
|
|
293
|
+
if (R === NONE) return;
|
|
294
|
+
|
|
295
|
+
const vL = afV[L], vR = afV[R];
|
|
296
|
+
|
|
297
|
+
// Duplicate check (Žalik §3.2.4)
|
|
298
|
+
if (px[pi] === px[vL] && py[pi] === py[vL]) return;
|
|
299
|
+
if (px[pi] === px[vR] && py[pi] === py[vR]) return;
|
|
300
|
+
|
|
301
|
+
// Check for left case: projection coincides with P_L (Domiter Fig.8)
|
|
302
|
+
// This happens when px[pi] === px[vL]
|
|
303
|
+
if (px[pi] === px[vL]) {
|
|
304
|
+
// Left case: create two triangles (Domiter Fig.8)
|
|
305
|
+
// Δ(vLL, vL, pi) and Δ(vL, vR, pi)
|
|
306
|
+
const LL = afP[L];
|
|
307
|
+
if (LL === NONE) return;
|
|
308
|
+
const vLL = afV[LL];
|
|
309
|
+
|
|
310
|
+
// Triangle 1: (vL, vR, pi) — same as middle case
|
|
311
|
+
const t1 = addTri(vL, vR, pi);
|
|
312
|
+
linkToOldTri(t1, 2, afT[L], vL, vR);
|
|
313
|
+
legalize(t1, pi);
|
|
314
|
+
|
|
315
|
+
// Triangle 2: (vLL, vL, pi)
|
|
316
|
+
const t2 = addTri(vLL, vL, pi);
|
|
317
|
+
linkToOldTri(t2, 2, afT[LL], vLL, vL);
|
|
318
|
+
// Link t2's edge vL-pi (opposite vLL, local 0) with t1's edge vL-pi (opposite vR, local 1... no)
|
|
319
|
+
// t1 = (vL, vR, pi): edge opposite vR at local 1 = vL-pi? No: edge opposite local 1 = (local2, local0) = (pi, vL)
|
|
320
|
+
// t2 = (vLL, vL, pi): edge opposite vLL at local 0 = (local1, local2) = (vL, pi)
|
|
321
|
+
// So t1 edge (pi, vL) at local 1 ↔ t2 edge (vL, pi) at local 0
|
|
322
|
+
link(t1, 1, t2, 0);
|
|
323
|
+
legalize(t2, pi);
|
|
324
|
+
|
|
325
|
+
// Update AF: remove L, insert pi between LL and R
|
|
326
|
+
afDel(L);
|
|
327
|
+
const piNode = afIns(pi, t1, LL, R);
|
|
328
|
+
vertAF[pi] = piNode;
|
|
329
|
+
afT[LL] = t2;
|
|
330
|
+
afT[piNode] = t1;
|
|
331
|
+
|
|
332
|
+
fanRight(pi, piNode);
|
|
333
|
+
fanLeft(pi, piNode);
|
|
334
|
+
basinDetect(pi, piNode);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// -------------------------------------------------------
|
|
339
|
+
// Middle case — Domiter Fig.7
|
|
340
|
+
// -------------------------------------------------------
|
|
341
|
+
// CCW triangle: (vL, vR, pi) — pi to left of vL→vR
|
|
342
|
+
const t = addTri(vL, vR, pi);
|
|
343
|
+
linkToOldTri(t, 2, afT[L], vL, vR);
|
|
344
|
+
legalize(t, pi);
|
|
345
|
+
|
|
346
|
+
const piNode = afIns(pi, t, L, R);
|
|
347
|
+
vertAF[pi] = piNode;
|
|
348
|
+
afT[L] = t;
|
|
349
|
+
afT[piNode] = t;
|
|
350
|
+
|
|
351
|
+
fanRight(pi, piNode);
|
|
352
|
+
fanLeft(pi, piNode);
|
|
353
|
+
basinDetect(pi, piNode);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// ---------------------------------------------------------------------------
|
|
357
|
+
// Fan right / fan left — Žalik §3.2.1, Domiter Fig.9
|
|
358
|
+
// ---------------------------------------------------------------------------
|
|
359
|
+
function fanRight(pi: number, piNode: number): void {
|
|
360
|
+
let cur = piNode;
|
|
361
|
+
while (true) {
|
|
362
|
+
const R = afN[cur];
|
|
363
|
+
if (R === NONE) break;
|
|
364
|
+
const RR = afN[R];
|
|
365
|
+
if (RR === NONE) break;
|
|
366
|
+
|
|
367
|
+
const vR = afV[R], vRR = afV[RR];
|
|
368
|
+
|
|
369
|
+
// Angle at pi between rays pi→vR and pi→vRR: if cos > 0 then < π/2
|
|
370
|
+
const dx1 = px[vR] - px[pi], dy1 = py[vR] - py[pi];
|
|
371
|
+
const dx2 = px[vRR] - px[pi], dy2 = py[vRR] - py[pi];
|
|
372
|
+
if (dx1 * dx2 + dy1 * dy2 <= 0) break;
|
|
373
|
+
|
|
374
|
+
const t = addTri(vR, vRR, pi);
|
|
375
|
+
linkToOldTri(t, 2, afT[R], vR, vRR);
|
|
376
|
+
linkToOldTri(t, 1, afT[cur], vR, pi);
|
|
377
|
+
legalize(t, pi);
|
|
378
|
+
|
|
379
|
+
afDel(R);
|
|
380
|
+
afN[cur] = RR; afP[RR] = cur;
|
|
381
|
+
afT[cur] = t;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function fanLeft(pi: number, piNode: number): void {
|
|
386
|
+
let cur = piNode;
|
|
387
|
+
while (true) {
|
|
388
|
+
const L = afP[cur];
|
|
389
|
+
if (L === NONE) break;
|
|
390
|
+
const LL = afP[L];
|
|
391
|
+
if (LL === NONE) break;
|
|
392
|
+
|
|
393
|
+
const vL = afV[L], vLL = afV[LL];
|
|
394
|
+
|
|
395
|
+
const dx1 = px[vL] - px[pi], dy1 = py[vL] - py[pi];
|
|
396
|
+
const dx2 = px[vLL] - px[pi], dy2 = py[vLL] - py[pi];
|
|
397
|
+
if (dx1 * dx2 + dy1 * dy2 <= 0) break;
|
|
398
|
+
|
|
399
|
+
const t = addTri(vLL, vL, pi);
|
|
400
|
+
linkToOldTri(t, 0, afT[L], vL, pi);
|
|
401
|
+
linkToOldTri(t, 2, afT[LL], vLL, vL);
|
|
402
|
+
legalize(t, pi);
|
|
403
|
+
|
|
404
|
+
afDel(L);
|
|
405
|
+
afP[cur] = LL; afN[LL] = cur;
|
|
406
|
+
afT[LL] = t;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// ---------------------------------------------------------------------------
|
|
411
|
+
// Basin detection — Žalik Fig.10, Domiter Fig.10
|
|
412
|
+
// ---------------------------------------------------------------------------
|
|
413
|
+
// After inserting pi: check if the angle at vR between rays vR→pi and
|
|
414
|
+
// vR→vR+ is > 3π/4. If so, fill the basin.
|
|
415
|
+
function basinDetect(pi: number, piNode: number): void {
|
|
416
|
+
// Right side basin
|
|
417
|
+
const R = afN[piNode];
|
|
418
|
+
if (R !== NONE) {
|
|
419
|
+
const RR = afN[R];
|
|
420
|
+
if (RR !== NONE) {
|
|
421
|
+
const vR = afV[R], vRR = afV[RR];
|
|
422
|
+
// Slope of line pi→vR+: if angle > 3π/4, cos < -√2/2 ≈ -0.707
|
|
423
|
+
// Actually Žalik says: "slope of line connecting v_i and v_{R+} is
|
|
424
|
+
// determined as to whether it is smaller than 3π/4"
|
|
425
|
+
// This means the angle at vR between AF segments is checked.
|
|
426
|
+
// Basin condition: angle at vR (between vR→pi and vR→vRR) > 3π/4
|
|
427
|
+
const dx1 = px[pi] - px[vR], dy1 = py[pi] - py[vR];
|
|
428
|
+
const dx2 = px[vRR] - px[vR], dy2 = py[vRR] - py[vR];
|
|
429
|
+
const dot = dx1 * dx2 + dy1 * dy2;
|
|
430
|
+
const cross = dx1 * dy2 - dy1 * dx2;
|
|
431
|
+
// angle > 3π/4 iff cos < cos(3π/4) = -√2/2 and the angle is reflex
|
|
432
|
+
// cos(θ) = dot / (|a||b|); if dot < 0 and cross < 0 (reflex), it's a basin
|
|
433
|
+
if (dot < 0 && cross < 0) {
|
|
434
|
+
fillBasinRight(pi, piNode);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Left side basin
|
|
440
|
+
const L = afP[piNode];
|
|
441
|
+
if (L !== NONE) {
|
|
442
|
+
const LL = afP[L];
|
|
443
|
+
if (LL !== NONE) {
|
|
444
|
+
const vL = afV[L], vLL = afV[LL];
|
|
445
|
+
const dx1 = px[pi] - px[vL], dy1 = py[pi] - py[vL];
|
|
446
|
+
const dx2 = px[vLL] - px[vL], dy2 = py[vLL] - py[vL];
|
|
447
|
+
const dot = dx1 * dx2 + dy1 * dy2;
|
|
448
|
+
const cross = dx1 * dy2 - dy1 * dx2;
|
|
449
|
+
if (dot < 0 && cross > 0) {
|
|
450
|
+
fillBasinLeft(pi, piNode);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function fillBasinRight(pi: number, piNode: number): void {
|
|
457
|
+
// Find basin bottom: walk right from piNode until y starts increasing
|
|
458
|
+
let R = afN[piNode];
|
|
459
|
+
if (R === NONE) return;
|
|
460
|
+
let RR = afN[R];
|
|
461
|
+
while (RR !== NONE) {
|
|
462
|
+
const vR = afV[R], vRR = afV[RR];
|
|
463
|
+
if (py[vRR] > py[vR]) break; // bottom found at R
|
|
464
|
+
R = RR;
|
|
465
|
+
RR = afN[RR];
|
|
466
|
+
}
|
|
467
|
+
// Now fill: fan from pi to everything between piNode and the basin right border
|
|
468
|
+
// Use the same fan-right logic but without the π/2 angle limit
|
|
469
|
+
let cur = piNode;
|
|
470
|
+
while (true) {
|
|
471
|
+
const rn = afN[cur];
|
|
472
|
+
if (rn === NONE) break;
|
|
473
|
+
const rnn = afN[rn];
|
|
474
|
+
if (rnn === NONE) break;
|
|
475
|
+
|
|
476
|
+
const vR = afV[rn], vRR = afV[rnn];
|
|
477
|
+
|
|
478
|
+
// Only fill while the triple (pi, vR, vRR) is CCW
|
|
479
|
+
if (orient(pi, vR, vRR) <= 0) break;
|
|
480
|
+
|
|
481
|
+
const t = addTri(vR, vRR, pi);
|
|
482
|
+
linkToOldTri(t, 2, afT[rn], vR, vRR);
|
|
483
|
+
linkToOldTri(t, 1, afT[cur], vR, pi);
|
|
484
|
+
legalize(t, pi);
|
|
485
|
+
|
|
486
|
+
afDel(rn);
|
|
487
|
+
afN[cur] = rnn; afP[rnn] = cur;
|
|
488
|
+
afT[cur] = t;
|
|
489
|
+
|
|
490
|
+
// Stop once we've passed the bottom
|
|
491
|
+
if (rn === R) break;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function fillBasinLeft(pi: number, piNode: number): void {
|
|
496
|
+
let L = afP[piNode];
|
|
497
|
+
if (L === NONE) return;
|
|
498
|
+
let LL = afP[L];
|
|
499
|
+
while (LL !== NONE) {
|
|
500
|
+
const vL = afV[L], vLL = afV[LL];
|
|
501
|
+
if (py[vLL] > py[vL]) break;
|
|
502
|
+
L = LL;
|
|
503
|
+
LL = afP[LL];
|
|
504
|
+
}
|
|
505
|
+
let cur = piNode;
|
|
506
|
+
while (true) {
|
|
507
|
+
const ln = afP[cur];
|
|
508
|
+
if (ln === NONE) break;
|
|
509
|
+
const lnn = afP[ln];
|
|
510
|
+
if (lnn === NONE) break;
|
|
511
|
+
|
|
512
|
+
const vL = afV[ln], vLL = afV[lnn];
|
|
513
|
+
|
|
514
|
+
if (orient(pi, vLL, vL) <= 0) break;
|
|
515
|
+
|
|
516
|
+
const t = addTri(vLL, vL, pi);
|
|
517
|
+
linkToOldTri(t, 0, afT[ln], vL, pi);
|
|
518
|
+
linkToOldTri(t, 2, afT[lnn], vLL, vL);
|
|
519
|
+
legalize(t, pi);
|
|
520
|
+
|
|
521
|
+
afDel(ln);
|
|
522
|
+
afP[cur] = lnn; afN[lnn] = cur;
|
|
523
|
+
afT[lnn] = t;
|
|
524
|
+
|
|
525
|
+
if (ln === L) break;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// ---------------------------------------------------------------------------
|
|
530
|
+
// Edge event — Domiter §3.4.2, Anglada §4
|
|
531
|
+
// ---------------------------------------------------------------------------
|
|
532
|
+
// Pre-allocated scratch arrays for pseudo-polygon vertices.
|
|
533
|
+
// Max size bounded by number of vertices.
|
|
534
|
+
let scrU: Int32Array; // upper pseudo-polygon
|
|
535
|
+
let scrL: Int32Array; // lower pseudo-polygon
|
|
536
|
+
let scrBndT: Int32Array; // boundary tri for each removed tri
|
|
537
|
+
let scrBndJ: Int32Array; // boundary local idx for each removed tri
|
|
538
|
+
let scrRm: Int32Array; // removed triangle indices
|
|
539
|
+
|
|
540
|
+
function edgeEvent(a: number, b: number): void {
|
|
541
|
+
// Check if edge already exists in the triangulation
|
|
542
|
+
if (markExisting(a, b)) return;
|
|
543
|
+
|
|
544
|
+
// Domiter §3.4.2: three cases based on position of edge vs AF.
|
|
545
|
+
//
|
|
546
|
+
// Locate first crossed triangle (Domiter Fig.11): walk triangles around
|
|
547
|
+
// vertex a using cross-product to find which triangle's opposite edge
|
|
548
|
+
// is pierced by ray a→b.
|
|
549
|
+
|
|
550
|
+
// First, find any triangle around vertex a.
|
|
551
|
+
const startT = findTriAround(a);
|
|
552
|
+
if (startT === NONE) return;
|
|
553
|
+
|
|
554
|
+
// Check if edge is entirely along the AF (Domiter Fig.13: no triangles crossed).
|
|
555
|
+
// If a and b are both on the AF and the AF path from a to b doesn't dip below
|
|
556
|
+
// the edge, we only need AF traversal.
|
|
557
|
+
const aNode = vertAF[a], bNode = vertAF[b];
|
|
558
|
+
|
|
559
|
+
// Try to find the first crossed triangle by walking the one-ring around a.
|
|
560
|
+
const first = findFirstCrossed(a, b, startT);
|
|
561
|
+
|
|
562
|
+
if (first === NONE) {
|
|
563
|
+
// No triangle is crossed — Domiter Fig.13: edge lies along/above AF.
|
|
564
|
+
// AF traversal only: lower pseudo-polygon from AF vertices a→b.
|
|
565
|
+
if (aNode !== NONE && bNode !== NONE) {
|
|
566
|
+
edgeEventAFOnly(a, b, aNode, bNode);
|
|
567
|
+
}
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Case 1 (Domiter Fig.12): edge entirely below AF — triangle traversal.
|
|
572
|
+
// Case 3 (Domiter Fig.14): edge crosses AF — mixed traversal.
|
|
573
|
+
// We handle both with the same traversal that switches between triangle
|
|
574
|
+
// and AF traversal as described in Domiter §3.4.2.
|
|
575
|
+
edgeEventTraversal(a, b, first);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Edge insertion with no triangle crossing — Domiter Fig.13.
|
|
579
|
+
// Only a lower pseudo-polygon exists, formed by AF vertices from a to b.
|
|
580
|
+
function edgeEventAFOnly(a: number, b: number, aNode: number, bNode: number): void {
|
|
581
|
+
// Collect AF vertices between a and b (exclusive) into lower pseudo-polygon.
|
|
582
|
+
let nL = 0;
|
|
583
|
+
let nd = afN[aNode];
|
|
584
|
+
while (nd !== NONE && afV[nd] !== b) {
|
|
585
|
+
scrL[nL++] = afV[nd];
|
|
586
|
+
nd = afN[nd];
|
|
587
|
+
}
|
|
588
|
+
if (nL === 0) {
|
|
589
|
+
// Edge a→b is already an AF edge. Just mark it.
|
|
590
|
+
// Find the triangle backing this AF edge and mark it fixed.
|
|
591
|
+
const t = afT[aNode];
|
|
592
|
+
if (t !== NONE) {
|
|
593
|
+
const ai = localIdx(t, a), bi = localIdx(t, b);
|
|
594
|
+
if (ai >= 0 && bi >= 0) markFixed(t, 3 - ai - bi);
|
|
595
|
+
}
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// Remove AF nodes between a and b, and triangulate the lower pseudo-polygon.
|
|
600
|
+
// The AF triangles along this stretch become the boundary.
|
|
601
|
+
// Collect removed AF nodes and their backing triangles.
|
|
602
|
+
// Also need to remove the AF nodes and relink a→b.
|
|
603
|
+
|
|
604
|
+
// Remove AF nodes between a and b
|
|
605
|
+
nd = afN[aNode];
|
|
606
|
+
while (nd !== NONE && afV[nd] !== b) {
|
|
607
|
+
const next = afN[nd];
|
|
608
|
+
afDel(nd);
|
|
609
|
+
nd = next;
|
|
610
|
+
}
|
|
611
|
+
// Relink: aNode→bNode
|
|
612
|
+
afN[aNode] = bNode; afP[bNode] = aNode;
|
|
613
|
+
|
|
614
|
+
// Triangulate lower pseudo-polygon (vertices below edge a→b)
|
|
615
|
+
// These are the vertices we collected in scrL[0..nL).
|
|
616
|
+
// Use Anglada's recursive pseudo-polygon triangulation.
|
|
617
|
+
const t = triPseudoPoly(a, b, scrL, 0, nL);
|
|
618
|
+
|
|
619
|
+
// Mark edge a-b as fixed on the resulting triangle
|
|
620
|
+
if (t !== NONE) {
|
|
621
|
+
const ai = localIdx(t, a), bi = localIdx(t, b);
|
|
622
|
+
if (ai >= 0 && bi >= 0) markFixed(t, 3 - ai - bi);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// Update AF triangle pointer for a
|
|
626
|
+
if (t !== NONE) afT[aNode] = t;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// Edge insertion with triangle traversal — Domiter Fig.12/14, Anglada §4.
|
|
630
|
+
function edgeEventTraversal(a: number, b: number, first: number): void {
|
|
631
|
+
// Walk through triangles pierced by edge a→b (Anglada's AddEdgeCDT).
|
|
632
|
+
// Collect upper and lower pseudo-polygon vertices.
|
|
633
|
+
let nU = 0, nL = 0, nRm = 0;
|
|
634
|
+
|
|
635
|
+
let t = first;
|
|
636
|
+
let v = a; // vertex we entered from
|
|
637
|
+
|
|
638
|
+
while (true) {
|
|
639
|
+
scrRm[nRm++] = t;
|
|
640
|
+
td[t] = 1; // mark dead
|
|
641
|
+
|
|
642
|
+
const t3 = t * 3;
|
|
643
|
+
const v0 = tv[t3], v1 = tv[t3 + 1], v2 = tv[t3 + 2];
|
|
644
|
+
|
|
645
|
+
// Check if b is a vertex of this triangle
|
|
646
|
+
if (v0 === b || v1 === b || v2 === b) {
|
|
647
|
+
// Classify remaining non-{a,b} vertices
|
|
648
|
+
for (let j = 0; j < 3; j++) {
|
|
649
|
+
const vj = tv[t3 + j];
|
|
650
|
+
if (vj === a || vj === b) continue;
|
|
651
|
+
const o = orient(a, b, vj);
|
|
652
|
+
if (o > 0) scrU[nU++] = vj;
|
|
653
|
+
else if (o < 0) scrL[nL++] = vj;
|
|
654
|
+
}
|
|
655
|
+
break;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// Find the opposed triangle and vertex (Anglada):
|
|
659
|
+
// t_seq = OpposedTriangle(t, v) — the adjacent triangle of t opposite v
|
|
660
|
+
// v_seq = OpposedVertex(t_seq, t) — the vertex of t_seq not in t
|
|
661
|
+
const vi = localIdx(t, v);
|
|
662
|
+
if (vi < 0) break;
|
|
663
|
+
const tSeq = ta[t3 + vi];
|
|
664
|
+
if (tSeq === NONE) break;
|
|
665
|
+
|
|
666
|
+
const vSeqJ = adjIdx(tSeq, t);
|
|
667
|
+
if (vSeqJ < 0) break;
|
|
668
|
+
const vSeq = tv[tSeq * 3 + vSeqJ];
|
|
669
|
+
|
|
670
|
+
// Classify v_seq
|
|
671
|
+
const oSeq = orient(a, b, vSeq);
|
|
672
|
+
if (oSeq > 0) {
|
|
673
|
+
// v_seq above edge ab
|
|
674
|
+
scrU[nU++] = vSeq;
|
|
675
|
+
// v = vertex shared by t and t_seq above ab
|
|
676
|
+
// The two vertices shared by t and t_seq are the edge opposite v in t
|
|
677
|
+
// and opposite vSeq in tSeq. These are the same two vertices.
|
|
678
|
+
// We need the one that's above ab.
|
|
679
|
+
const e1 = tv[t3 + (vi + 1) % 3], e2 = tv[t3 + (vi + 2) % 3];
|
|
680
|
+
const o1 = (e1 === a || e1 === b) ? 0 : orient(a, b, e1);
|
|
681
|
+
const o2 = (e2 === a || e2 === b) ? 0 : orient(a, b, e2);
|
|
682
|
+
v = (o1 >= 0 && o1 >= o2) ? e1 : e2;
|
|
683
|
+
} else if (oSeq < 0) {
|
|
684
|
+
// v_seq below edge ab
|
|
685
|
+
scrL[nL++] = vSeq;
|
|
686
|
+
const e1 = tv[t3 + (vi + 1) % 3], e2 = tv[t3 + (vi + 2) % 3];
|
|
687
|
+
const o1 = (e1 === a || e1 === b) ? 0 : orient(a, b, e1);
|
|
688
|
+
const o2 = (e2 === a || e2 === b) ? 0 : orient(a, b, e2);
|
|
689
|
+
v = (o1 <= 0 && o1 <= o2) ? e1 : e2;
|
|
690
|
+
} else {
|
|
691
|
+
// v_seq is ON the edge — split into two sub-edges (Domiter)
|
|
692
|
+
// Retriangulate what we have so far, then recurse.
|
|
693
|
+
retriangulateRemoved(a, b, nU, nL, nRm);
|
|
694
|
+
edgeEvent(a, vSeq);
|
|
695
|
+
edgeEvent(vSeq, b);
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
t = tSeq;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
retriangulateRemoved(a, b, nU, nL, nRm);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// Retriangulate the region left by removed triangles.
|
|
706
|
+
// Upper pseudo-polygon P_U = scrU[0..nU) with edge (a, b)
|
|
707
|
+
// Lower pseudo-polygon P_L = scrL[0..nL) with edge (b, a)
|
|
708
|
+
function retriangulateRemoved(
|
|
709
|
+
a: number, b: number,
|
|
710
|
+
nU: number, nL: number, nRm: number,
|
|
711
|
+
): void {
|
|
712
|
+
// Save boundary adjacencies from removed triangles.
|
|
713
|
+
// For each removed triangle, check its 3 neighbors. If a neighbor is alive
|
|
714
|
+
// (not dead), record that boundary edge for later linking.
|
|
715
|
+
// We use a simple flat array: for each boundary edge, store [extTri, extJ, e0, e1].
|
|
716
|
+
let nBnd = 0;
|
|
717
|
+
for (let i = 0; i < nRm; i++) {
|
|
718
|
+
const rt = scrRm[i], rt3 = rt * 3;
|
|
719
|
+
for (let j = 0; j < 3; j++) {
|
|
720
|
+
const nb = ta[rt3 + j];
|
|
721
|
+
if (nb === NONE || td[nb]) continue;
|
|
722
|
+
// This edge is a boundary edge. Get its vertices.
|
|
723
|
+
const e0 = tv[rt3 + (j + 1) % 3], e1 = tv[rt3 + (j + 2) % 3];
|
|
724
|
+
const ej = adjIdx(nb, rt);
|
|
725
|
+
if (ej < 0) continue;
|
|
726
|
+
// Store in scratch: [extTri, extJ, e0, e1]
|
|
727
|
+
scrBndT[nBnd] = nb;
|
|
728
|
+
scrBndJ[nBnd] = ej;
|
|
729
|
+
// Encode edge as min/max pair for matching
|
|
730
|
+
// We'll store e0, e1 directly and match by checking both orientations
|
|
731
|
+
scrBndT[nBnd + nRm * 3] = e0; // reuse space after nRm*3
|
|
732
|
+
scrBndJ[nBnd + nRm * 3] = e1;
|
|
733
|
+
nBnd++;
|
|
734
|
+
// Unlink the neighbor's pointer to the dead triangle
|
|
735
|
+
ta[nb * 3 + ej] = NONE;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// Triangulate upper and lower pseudo-polygons
|
|
740
|
+
const tU = triPseudoPoly(a, b, scrU, 0, nU);
|
|
741
|
+
const tL = triPseudoPoly(b, a, scrL, 0, nL);
|
|
742
|
+
|
|
743
|
+
// Link tU and tL across edge a-b if both exist
|
|
744
|
+
if (tU !== NONE && tL !== NONE) {
|
|
745
|
+
const ui = localIdx(tU, a), uj = localIdx(tU, b);
|
|
746
|
+
const li = localIdx(tL, b), lj = localIdx(tL, a);
|
|
747
|
+
if (ui >= 0 && uj >= 0 && li >= 0 && lj >= 0) {
|
|
748
|
+
link(tU, 3 - ui - uj, tL, 3 - li - lj);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// Reconstitute boundary adjacencies (Anglada §4 last step).
|
|
753
|
+
// For each new triangle, check its unlinked edges against boundary edges.
|
|
754
|
+
// Since the number of boundary edges is small (bounded by the removed region),
|
|
755
|
+
// a nested scan is fast enough.
|
|
756
|
+
reconstituteBoundary(nBnd, nRm);
|
|
757
|
+
|
|
758
|
+
// Mark edge a-b as fixed
|
|
759
|
+
if (tU !== NONE) {
|
|
760
|
+
const ai = localIdx(tU, a), bi = localIdx(tU, b);
|
|
761
|
+
if (ai >= 0 && bi >= 0) markFixed(tU, 3 - ai - bi);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// Update AF triangle pointers that may have referenced removed triangles.
|
|
765
|
+
updateAFAfterRemoval(nRm);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
function reconstituteBoundary(nBnd: number, nRm: number): void {
|
|
769
|
+
// Walk all triangles created since the removal started and link their
|
|
770
|
+
// unconnected edges to boundary neighbors.
|
|
771
|
+
// New triangles were appended starting from triCount - (number created).
|
|
772
|
+
// But we don't track that precisely. Instead, walk new tris by checking
|
|
773
|
+
// triangles from the end that are alive and have NONE adjacencies.
|
|
774
|
+
//
|
|
775
|
+
// Actually, simpler: iterate boundary edges and find which new triangle
|
|
776
|
+
// has that edge.
|
|
777
|
+
for (let i = 0; i < nBnd; i++) {
|
|
778
|
+
const extT = scrBndT[i], extJ = scrBndJ[i];
|
|
779
|
+
const e0 = scrBndT[i + nRm * 3], e1 = scrBndJ[i + nRm * 3];
|
|
780
|
+
// Find the new (alive) triangle that has edge (e0, e1)
|
|
781
|
+
// Walk from extT's perspective: the new triangle should be linkable.
|
|
782
|
+
// Since triPseudoPoly creates new triangles, search backward from triCount.
|
|
783
|
+
for (let t = triCount - 1; t >= 0; t--) {
|
|
784
|
+
if (td[t]) continue;
|
|
785
|
+
const i0 = localIdx(t, e0), i1 = localIdx(t, e1);
|
|
786
|
+
if (i0 >= 0 && i1 >= 0) {
|
|
787
|
+
const oj = 3 - i0 - i1;
|
|
788
|
+
if (ta[t * 3 + oj] === NONE) {
|
|
789
|
+
link(t, oj, extT, extJ);
|
|
790
|
+
break;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
function updateAFAfterRemoval(nRm: number): void {
|
|
798
|
+
// For each removed triangle, any AF node referencing it needs updating.
|
|
799
|
+
// Walk AF and fix stale triangle pointers.
|
|
800
|
+
for (let nd = 0; nd < afLen; nd++) {
|
|
801
|
+
if (afP[nd] === NONE && afN[nd] === NONE && nd > 0) continue; // orphan
|
|
802
|
+
const t = afT[nd];
|
|
803
|
+
if (t === NONE || !td[t]) continue;
|
|
804
|
+
// This AF node's triangle was removed. Find a live replacement.
|
|
805
|
+
const v = afV[nd];
|
|
806
|
+
const nn = afN[nd];
|
|
807
|
+
if (nn === NONE) continue;
|
|
808
|
+
const vn = afV[nn];
|
|
809
|
+
// Find a live triangle containing both v and vn.
|
|
810
|
+
for (let i = triCount - 1; i >= 0; i--) {
|
|
811
|
+
if (td[i]) continue;
|
|
812
|
+
if (localIdx(i, v) >= 0 && localIdx(i, vn) >= 0) {
|
|
813
|
+
afT[nd] = i;
|
|
814
|
+
break;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// ---------------------------------------------------------------------------
|
|
821
|
+
// Pseudo-polygon triangulation — Anglada §4, Fig.8
|
|
822
|
+
// ---------------------------------------------------------------------------
|
|
823
|
+
// Recursive Delaunay triangulation of pseudo-polygon.
|
|
824
|
+
// edge (a, b), vertices P[off..off+len)
|
|
825
|
+
// Returns the triangle on edge a-b, or NONE if empty.
|
|
826
|
+
//
|
|
827
|
+
// The recursion returns the last triangle created (the one on edge ab),
|
|
828
|
+
// and we link children to parents as we go — no Map needed.
|
|
829
|
+
|
|
830
|
+
function triPseudoPoly(
|
|
831
|
+
a: number, b: number,
|
|
832
|
+
P: Int32Array, off: number, len: number,
|
|
833
|
+
): number {
|
|
834
|
+
if (len === 0) return NONE;
|
|
835
|
+
|
|
836
|
+
// Find c = vertex in P that maximizes Delaunay criterion
|
|
837
|
+
let ci = 0;
|
|
838
|
+
for (let i = 1; i < len; i++) {
|
|
839
|
+
const oa = orient(a, P[off + ci], b);
|
|
840
|
+
if (oa > 0) {
|
|
841
|
+
if (inCircle(a, P[off + ci], b, P[off + i]) > 0) ci = i;
|
|
842
|
+
} else if (oa < 0) {
|
|
843
|
+
if (inCircle(a, b, P[off + ci], P[off + i]) > 0) ci = i;
|
|
844
|
+
} else {
|
|
845
|
+
ci = i;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
const c = P[off + ci];
|
|
850
|
+
|
|
851
|
+
// Recurse on P_E = P[off..off+ci) with edge (a, c)
|
|
852
|
+
const tE = triPseudoPoly(a, c, P, off, ci);
|
|
853
|
+
// Recurse on P_D = P[off+ci+1..off+len) with edge (c, b)
|
|
854
|
+
const tD = triPseudoPoly(c, b, P, off + ci + 1, len - ci - 1);
|
|
855
|
+
|
|
856
|
+
// Create triangle (a, c, b) — ensure CCW
|
|
857
|
+
const o = orient(a, c, b);
|
|
858
|
+
if (o === 0) return NONE; // degenerate
|
|
859
|
+
let t: number;
|
|
860
|
+
if (o > 0) {
|
|
861
|
+
t = addTri(a, c, b);
|
|
862
|
+
} else {
|
|
863
|
+
t = addTri(a, b, c);
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// Link to child triangles:
|
|
867
|
+
// tE is on edge a-c → find its local edge a-c and link
|
|
868
|
+
if (tE !== NONE) {
|
|
869
|
+
const ei0 = localIdx(tE, a), ei1 = localIdx(tE, c);
|
|
870
|
+
if (ei0 >= 0 && ei1 >= 0) {
|
|
871
|
+
const ti0 = localIdx(t, a), ti1 = localIdx(t, c);
|
|
872
|
+
if (ti0 >= 0 && ti1 >= 0) {
|
|
873
|
+
link(t, 3 - ti0 - ti1, tE, 3 - ei0 - ei1);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
if (tD !== NONE) {
|
|
878
|
+
const di0 = localIdx(tD, c), di1 = localIdx(tD, b);
|
|
879
|
+
if (di0 >= 0 && di1 >= 0) {
|
|
880
|
+
const ti0 = localIdx(t, c), ti1 = localIdx(t, b);
|
|
881
|
+
if (ti0 >= 0 && ti1 >= 0) {
|
|
882
|
+
link(t, 3 - ti0 - ti1, tD, 3 - di0 - di1);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
return t;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// ---------------------------------------------------------------------------
|
|
891
|
+
// markExisting / findTriAround / findFirstCrossed
|
|
892
|
+
// ---------------------------------------------------------------------------
|
|
893
|
+
|
|
894
|
+
function markExisting(a: number, b: number): boolean {
|
|
895
|
+
const start = findTriAround(a);
|
|
896
|
+
if (start === NONE) return false;
|
|
897
|
+
|
|
898
|
+
// Walk one-ring around a in both directions
|
|
899
|
+
let t = start;
|
|
900
|
+
for (let i = 0; i < triCount; i++) {
|
|
901
|
+
if (td[t]) break;
|
|
902
|
+
const ai = localIdx(t, a);
|
|
903
|
+
if (ai < 0) break;
|
|
904
|
+
const t3 = t * 3;
|
|
905
|
+
if (tv[t3 + (ai + 1) % 3] === b) { markFixed(t, (ai + 2) % 3); return true; }
|
|
906
|
+
if (tv[t3 + (ai + 2) % 3] === b) { markFixed(t, (ai + 1) % 3); return true; }
|
|
907
|
+
t = ta[t3 + (ai + 1) % 3]; // CW
|
|
908
|
+
if (t === NONE || t === start) break;
|
|
909
|
+
}
|
|
910
|
+
t = start;
|
|
911
|
+
for (let i = 0; i < triCount; i++) {
|
|
912
|
+
if (td[t]) break;
|
|
913
|
+
const ai = localIdx(t, a);
|
|
914
|
+
if (ai < 0) break;
|
|
915
|
+
const t3 = t * 3;
|
|
916
|
+
if (tv[t3 + (ai + 1) % 3] === b) { markFixed(t, (ai + 2) % 3); return true; }
|
|
917
|
+
if (tv[t3 + (ai + 2) % 3] === b) { markFixed(t, (ai + 1) % 3); return true; }
|
|
918
|
+
t = ta[t3 + (ai + 2) % 3]; // CCW
|
|
919
|
+
if (t === NONE || t === start) break;
|
|
920
|
+
}
|
|
921
|
+
return false;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
// Find any live triangle containing vertex v.
|
|
925
|
+
// AF node for v stores the triangle backing the AF edge.
|
|
926
|
+
// After flips, that triangle may no longer contain v, but a neighbor will.
|
|
927
|
+
function findTriAround(v: number): number {
|
|
928
|
+
const nd = vertAF[v];
|
|
929
|
+
if (nd === NONE || nd >= afLen) return NONE;
|
|
930
|
+
|
|
931
|
+
// Check afT[nd] and afT[prev]
|
|
932
|
+
let t = afT[nd];
|
|
933
|
+
if (t !== NONE && !td[t] && localIdx(t, v) >= 0) return t;
|
|
934
|
+
|
|
935
|
+
const p = afP[nd];
|
|
936
|
+
if (p !== NONE) {
|
|
937
|
+
t = afT[p];
|
|
938
|
+
if (t !== NONE && !td[t] && localIdx(t, v) >= 0) return t;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// Walk neighbors of those triangles (max 2 hops covers all flip cases)
|
|
942
|
+
for (const seed of [afT[nd], p !== NONE ? afT[p] : NONE]) {
|
|
943
|
+
if (seed === NONE || seed >= triCount) continue;
|
|
944
|
+
if (!td[seed]) {
|
|
945
|
+
for (let j = 0; j < 3; j++) {
|
|
946
|
+
const nb = ta[seed * 3 + j];
|
|
947
|
+
if (nb !== NONE && !td[nb] && localIdx(nb, v) >= 0) return nb;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
return NONE;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// Find first triangle around vertex a whose opposite edge is crossed by a→b.
|
|
955
|
+
// Domiter Fig.11: walk triangles around a using cross-product.
|
|
956
|
+
function findFirstCrossed(a: number, b: number, start: number): number {
|
|
957
|
+
// Walk one-ring around a in CW direction
|
|
958
|
+
let t = start;
|
|
959
|
+
for (let i = 0; i < triCount; i++) {
|
|
960
|
+
if (td[t]) { t = NONE; break; }
|
|
961
|
+
const ai = localIdx(t, a);
|
|
962
|
+
if (ai < 0) break;
|
|
963
|
+
const t3 = t * 3;
|
|
964
|
+
const v1 = tv[t3 + (ai + 1) % 3], v2 = tv[t3 + (ai + 2) % 3];
|
|
965
|
+
if (v1 === b || v2 === b) return NONE; // edge already exists
|
|
966
|
+
const o1 = orient(a, b, v1), o2 = orient(a, b, v2);
|
|
967
|
+
if ((o1 > 0 && o2 < 0) || (o1 < 0 && o2 > 0)) return t;
|
|
968
|
+
t = ta[t3 + (ai + 1) % 3]; // CW step
|
|
969
|
+
if (t === NONE || t === start) break;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// Try CCW direction
|
|
973
|
+
t = start;
|
|
974
|
+
for (let i = 0; i < triCount; i++) {
|
|
975
|
+
if (td[t]) { t = NONE; break; }
|
|
976
|
+
const ai = localIdx(t, a);
|
|
977
|
+
if (ai < 0) break;
|
|
978
|
+
const t3 = t * 3;
|
|
979
|
+
const v1 = tv[t3 + (ai + 1) % 3], v2 = tv[t3 + (ai + 2) % 3];
|
|
980
|
+
if (v1 === b || v2 === b) return NONE;
|
|
981
|
+
const o1 = orient(a, b, v1), o2 = orient(a, b, v2);
|
|
982
|
+
if ((o1 > 0 && o2 < 0) || (o1 < 0 && o2 > 0)) return t;
|
|
983
|
+
t = ta[t3 + (ai + 2) % 3]; // CCW step
|
|
984
|
+
if (t === NONE || t === start) break;
|
|
985
|
+
}
|
|
986
|
+
return NONE;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// ---------------------------------------------------------------------------
|
|
990
|
+
// Finalization — Domiter §3.5
|
|
991
|
+
// ---------------------------------------------------------------------------
|
|
992
|
+
// (i) Remove triangles with artificial points
|
|
993
|
+
// (ii) Add bordering triangles to complete the convex hull
|
|
994
|
+
//
|
|
995
|
+
// Walk the AF from right of P_{-1} to P_{-2}, taking triples of consecutive
|
|
996
|
+
// AF points. If signed area > 0, add triangle and legalize. (Domiter Fig.15a)
|
|
997
|
+
// Then follow artificial triangles from P_{-2} to P_{-1}, deleting them and
|
|
998
|
+
// adding triangles where signed area < 0. (Domiter Fig.15b/c)
|
|
999
|
+
|
|
1000
|
+
function finalize(A1: number, A2: number): void {
|
|
1001
|
+
// Upper convex hull: walk AF from first real point to A2
|
|
1002
|
+
let nd = afN[vertAF[A1]]; // first real point (right of A1)
|
|
1003
|
+
if (nd === NONE) return;
|
|
1004
|
+
|
|
1005
|
+
while (true) {
|
|
1006
|
+
const nn = afN[nd];
|
|
1007
|
+
if (nn === NONE) break;
|
|
1008
|
+
if (afV[nn] === A2) break;
|
|
1009
|
+
const nnn = afN[nn];
|
|
1010
|
+
if (nnn === NONE) break;
|
|
1011
|
+
|
|
1012
|
+
const v1 = afV[nd], v2 = afV[nn], v3 = afV[nnn];
|
|
1013
|
+
if (v2 === A2 || v3 === A2) break;
|
|
1014
|
+
|
|
1015
|
+
const o = orient(v1, v2, v3);
|
|
1016
|
+
if (o > 0) {
|
|
1017
|
+
// Create triangle and remove middle vertex from AF
|
|
1018
|
+
const t = addTri(v1, v2, v3);
|
|
1019
|
+
linkToOldTri(t, 2, afT[nd], v1, v2);
|
|
1020
|
+
linkToOldTri(t, 0, afT[nn], v2, v3);
|
|
1021
|
+
legalize(t, v1);
|
|
1022
|
+
legalize(t, v3);
|
|
1023
|
+
|
|
1024
|
+
afDel(nn);
|
|
1025
|
+
afN[nd] = nnn; afP[nnn] = nd;
|
|
1026
|
+
afT[nd] = t;
|
|
1027
|
+
|
|
1028
|
+
// Step back to check the new triple
|
|
1029
|
+
const pp = afP[nd];
|
|
1030
|
+
if (pp !== NONE && afV[pp] !== A1) nd = pp;
|
|
1031
|
+
} else {
|
|
1032
|
+
nd = nn;
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// Lower convex hull: follow artificial triangles from A2 to A1.
|
|
1037
|
+
// (Domiter Fig.15b/c) — These are triangles that include A1 or A2.
|
|
1038
|
+
// The simplest approach: after the upper hull is done, just mark all
|
|
1039
|
+
// triangles containing A1 or A2 as dead.
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// ---------------------------------------------------------------------------
|
|
1043
|
+
// Main entry point
|
|
1044
|
+
// ---------------------------------------------------------------------------
|
|
1045
|
+
|
|
1046
|
+
export function sweepTriangulate(
|
|
1047
|
+
coords: Float64Array,
|
|
1048
|
+
edges: Uint32Array | null,
|
|
1049
|
+
holes: Float64Array | null,
|
|
1050
|
+
): CDTResult {
|
|
1051
|
+
const n = coords.length >> 1;
|
|
1052
|
+
if (n < 3) return { triangles: new Uint32Array(0), triCount: 0, coords };
|
|
1053
|
+
|
|
1054
|
+
// Build coordinate arrays with 2 extra artificial points
|
|
1055
|
+
const nv = n + 2;
|
|
1056
|
+
px = new Float64Array(nv);
|
|
1057
|
+
py = new Float64Array(nv);
|
|
1058
|
+
for (let i = 0; i < n; i++) { px[i] = coords[i * 2]; py[i] = coords[i * 2 + 1]; }
|
|
1059
|
+
|
|
1060
|
+
let xmin = px[0], xmax = px[0], ymin = py[0], ymax = py[0];
|
|
1061
|
+
for (let i = 1; i < n; i++) {
|
|
1062
|
+
if (px[i] < xmin) xmin = px[i]; if (px[i] > xmax) xmax = px[i];
|
|
1063
|
+
if (py[i] < ymin) ymin = py[i]; if (py[i] > ymax) ymax = py[i];
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
// Artificial points P_{-1}, P_{-2} — Domiter §3.3, α=0.3
|
|
1067
|
+
const dx = 0.3 * (xmax - xmin + 1);
|
|
1068
|
+
const dy = 0.3 * (ymax - ymin + 1);
|
|
1069
|
+
const A1 = n, A2 = n + 1;
|
|
1070
|
+
px[A1] = xmin - dx; py[A1] = ymin - dy;
|
|
1071
|
+
px[A2] = xmax + dx; py[A2] = ymin - dy;
|
|
1072
|
+
|
|
1073
|
+
// Sort points by y then x — Domiter §3.3
|
|
1074
|
+
const order = new Uint32Array(n);
|
|
1075
|
+
for (let i = 0; i < n; i++) order[i] = i;
|
|
1076
|
+
order.sort((a, b) => { const d = py[a] - py[b]; return d !== 0 ? d : px[a] - px[b]; });
|
|
1077
|
+
|
|
1078
|
+
// Build edge info: upper endpoint → list of lower endpoints
|
|
1079
|
+
// Pre-count edges per vertex to avoid dynamic arrays
|
|
1080
|
+
let edgeLo: Int32Array | null = null;
|
|
1081
|
+
let edgeOff: Int32Array | null = null;
|
|
1082
|
+
if (edges !== null && edges.length > 0) {
|
|
1083
|
+
const ne = edges.length >> 1;
|
|
1084
|
+
const cnt = new Int32Array(n);
|
|
1085
|
+
for (let i = 0; i < ne; i++) {
|
|
1086
|
+
let ea = edges[i * 2], eb = edges[i * 2 + 1];
|
|
1087
|
+
if (py[ea] < py[eb] || (py[ea] === py[eb] && px[ea] < px[eb])) {
|
|
1088
|
+
cnt[eb]++;
|
|
1089
|
+
} else {
|
|
1090
|
+
cnt[ea]++;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
edgeOff = new Int32Array(n + 1);
|
|
1094
|
+
for (let i = 0; i < n; i++) edgeOff[i + 1] = edgeOff[i] + cnt[i];
|
|
1095
|
+
edgeLo = new Int32Array(edgeOff[n]);
|
|
1096
|
+
cnt.fill(0);
|
|
1097
|
+
for (let i = 0; i < ne; i++) {
|
|
1098
|
+
let ea = edges[i * 2], eb = edges[i * 2 + 1];
|
|
1099
|
+
let upper: number;
|
|
1100
|
+
let lower: number;
|
|
1101
|
+
if (py[ea] < py[eb] || (py[ea] === py[eb] && px[ea] < px[eb])) {
|
|
1102
|
+
upper = eb; lower = ea;
|
|
1103
|
+
} else {
|
|
1104
|
+
upper = ea; lower = eb;
|
|
1105
|
+
}
|
|
1106
|
+
edgeLo[edgeOff[upper] + cnt[upper]++] = lower;
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
// Initialize triangle pool
|
|
1111
|
+
const estTris = Math.max(n * 3, 64);
|
|
1112
|
+
initTris(estTris);
|
|
1113
|
+
|
|
1114
|
+
// Scratch arrays for edge events
|
|
1115
|
+
scrU = new Int32Array(n);
|
|
1116
|
+
scrL = new Int32Array(n);
|
|
1117
|
+
scrRm = new Int32Array(estTris);
|
|
1118
|
+
scrBndT = new Int32Array(estTris * 4);
|
|
1119
|
+
scrBndJ = new Int32Array(estTris * 4);
|
|
1120
|
+
|
|
1121
|
+
// Initialize AF
|
|
1122
|
+
initAF(n, xmin - dx, xmax + dx);
|
|
1123
|
+
|
|
1124
|
+
// vertAF: vertex → AF node id
|
|
1125
|
+
vertAF = new Int32Array(nv).fill(NONE);
|
|
1126
|
+
|
|
1127
|
+
// Initial triangle: (A1, P0, A2) — Domiter Fig.6
|
|
1128
|
+
const p0 = order[0];
|
|
1129
|
+
const t0 = addTri(A1, p0, A2);
|
|
1130
|
+
|
|
1131
|
+
// Initial advancing front: A1 → P0 → A2
|
|
1132
|
+
const nd1 = afNew(A1, t0);
|
|
1133
|
+
const nd0 = afNew(p0, t0);
|
|
1134
|
+
const nd2 = afNew(A2, NONE);
|
|
1135
|
+
afN[nd1] = nd0; afP[nd0] = nd1;
|
|
1136
|
+
afN[nd0] = nd2; afP[nd2] = nd0;
|
|
1137
|
+
vertAF[A1] = nd1; vertAF[p0] = nd0; vertAF[A2] = nd2;
|
|
1138
|
+
|
|
1139
|
+
// Sweep remaining points
|
|
1140
|
+
for (let i = 1; i < n; i++) {
|
|
1141
|
+
const pi = order[i];
|
|
1142
|
+
pointEvent(pi);
|
|
1143
|
+
|
|
1144
|
+
// Edge events
|
|
1145
|
+
if (edgeLo !== null && edgeOff !== null) {
|
|
1146
|
+
const start = edgeOff[pi], end = edgeOff[pi + 1];
|
|
1147
|
+
for (let e = start; e < end; e++) {
|
|
1148
|
+
edgeEvent(pi, edgeLo[e]);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
// Finalization — Domiter §3.5
|
|
1154
|
+
finalize(A1, A2);
|
|
1155
|
+
|
|
1156
|
+
// ----- Output -----
|
|
1157
|
+
// Filter out dead triangles and those with artificial points.
|
|
1158
|
+
let dead: Uint8Array | null = null;
|
|
1159
|
+
let outCount = 0;
|
|
1160
|
+
|
|
1161
|
+
if (holes !== null && holes.length >= 2) {
|
|
1162
|
+
dead = new Uint8Array(triCount);
|
|
1163
|
+
for (let t = 0; t < triCount; t++) {
|
|
1164
|
+
const t3 = t * 3;
|
|
1165
|
+
if (td[t] || tv[t3] >= n || tv[t3 + 1] >= n || tv[t3 + 2] >= n) dead[t] = 1;
|
|
1166
|
+
}
|
|
1167
|
+
const nh = holes.length >> 1;
|
|
1168
|
+
for (let h = 0; h < nh; h++) {
|
|
1169
|
+
const hx = holes[h * 2], hy = holes[h * 2 + 1];
|
|
1170
|
+
const seed = findTriContaining(hx, hy, n, dead);
|
|
1171
|
+
if (seed === NONE) continue;
|
|
1172
|
+
const q = [seed]; dead[seed] = 1;
|
|
1173
|
+
while (q.length) {
|
|
1174
|
+
const ct = q.pop()!;
|
|
1175
|
+
for (let j = 0; j < 3; j++) {
|
|
1176
|
+
if (isFixed(ct, j)) continue;
|
|
1177
|
+
const nb = ta[ct * 3 + j];
|
|
1178
|
+
if (nb === NONE || dead[nb]) continue;
|
|
1179
|
+
dead[nb] = 1; q.push(nb);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
for (let t = 0; t < triCount; t++) { if (!dead[t]) outCount++; }
|
|
1184
|
+
} else {
|
|
1185
|
+
for (let t = 0; t < triCount; t++) {
|
|
1186
|
+
if (td[t]) continue;
|
|
1187
|
+
const t3 = t * 3;
|
|
1188
|
+
if (tv[t3] < n && tv[t3 + 1] < n && tv[t3 + 2] < n) outCount++;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
const out = new Uint32Array(outCount * 3);
|
|
1193
|
+
let idx = 0;
|
|
1194
|
+
for (let t = 0; t < triCount; t++) {
|
|
1195
|
+
if (td[t]) continue;
|
|
1196
|
+
const t3 = t * 3;
|
|
1197
|
+
if (tv[t3] >= n || tv[t3 + 1] >= n || tv[t3 + 2] >= n) {
|
|
1198
|
+
if (dead === null) continue;
|
|
1199
|
+
}
|
|
1200
|
+
if (dead !== null && dead[t]) continue;
|
|
1201
|
+
out[idx++] = tv[t3]; out[idx++] = tv[t3 + 1]; out[idx++] = tv[t3 + 2];
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
return { triangles: out, triCount: outCount, coords };
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
function findTriContaining(hx: number, hy: number, nReal: number, dead: Uint8Array): number {
|
|
1208
|
+
for (let t = 0; t < triCount; t++) {
|
|
1209
|
+
if (dead[t]) continue;
|
|
1210
|
+
const t3 = t * 3;
|
|
1211
|
+
const a = tv[t3], b = tv[t3 + 1], c = tv[t3 + 2];
|
|
1212
|
+
if (a >= nReal || b >= nReal || c >= nReal) continue;
|
|
1213
|
+
const o1 = orient2dSign(px[a], py[a], px[b], py[b], hx, hy);
|
|
1214
|
+
const o2 = orient2dSign(px[b], py[b], px[c], py[c], hx, hy);
|
|
1215
|
+
const o3 = orient2dSign(px[c], py[c], px[a], py[a], hx, hy);
|
|
1216
|
+
if (o1 >= 0 && o2 >= 0 && o3 >= 0) return t;
|
|
1217
|
+
if (o1 <= 0 && o2 <= 0 && o3 <= 0) return t;
|
|
1218
|
+
}
|
|
1219
|
+
return NONE;
|
|
1220
|
+
}
|