earcut 3.1.0 → 3.1.1
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/earcut.dev.js +105 -118
- package/dist/earcut.min.js +1 -1
- package/package.json +1 -1
- package/src/earcut.js +105 -119
package/dist/earcut.dev.js
CHANGED
|
@@ -25,6 +25,10 @@ typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
|
|
25
25
|
/** @type {Set<Node>} */
|
|
26
26
|
const steiners = new Set();
|
|
27
27
|
|
|
28
|
+
// set by filterPoints whenever it removes at least one node; read by earcutLinked's stall
|
|
29
|
+
// handler to decide whether another clip pass is worth attempting before the costlier stages
|
|
30
|
+
let filteredOut = false;
|
|
31
|
+
|
|
28
32
|
/**
|
|
29
33
|
* Triangulate a polygon given as a flat array of vertex coordinates.
|
|
30
34
|
*
|
|
@@ -48,11 +52,7 @@ function earcut(data, holeIndices, dim = 2) {
|
|
|
48
52
|
|
|
49
53
|
let minX = 0, minY = 0, invSize = 0;
|
|
50
54
|
|
|
51
|
-
if (hasHoles)
|
|
52
|
-
outerNode = eliminateHoles(data, holeIndices, outerNode, dim);
|
|
53
|
-
// collapse collinear/coincident points across the whole merged ring once before clipping
|
|
54
|
-
outerNode = filterPoints(outerNode);
|
|
55
|
-
}
|
|
55
|
+
if (hasHoles) outerNode = eliminateHoles(data, holeIndices, outerNode, dim);
|
|
56
56
|
|
|
57
57
|
// if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
|
|
58
58
|
if (data.length > 80 * dim) {
|
|
@@ -75,7 +75,7 @@ function earcut(data, holeIndices, dim = 2) {
|
|
|
75
75
|
invSize = invSize !== 0 ? 32767 / invSize : 0;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
earcutLinked(outerNode, triangles,
|
|
78
|
+
earcutLinked(outerNode, triangles, minX, minY, invSize);
|
|
79
79
|
|
|
80
80
|
return triangles;
|
|
81
81
|
}
|
|
@@ -115,6 +115,7 @@ function filterPoints(start, end = start) {
|
|
|
115
115
|
if (p !== p.next && (steiners.size === 0 || !steiners.has(p)) &&
|
|
116
116
|
(equals(p, p.next) || area(p.prev, p, p.next) === 0)) {
|
|
117
117
|
if (full || p === end) end = p.prev; // pull the stop bound back past the removal
|
|
118
|
+
filteredOut = true;
|
|
118
119
|
removeNode(p);
|
|
119
120
|
p = p.prev; // re-check the predecessor
|
|
120
121
|
again = true;
|
|
@@ -128,14 +129,12 @@ function filterPoints(start, end = start) {
|
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
// main ear slicing loop which triangulates a polygon (given as a linked list)
|
|
131
|
-
/** @param {Node
|
|
132
|
-
function earcutLinked(ear, triangles,
|
|
133
|
-
if (!ear) return;
|
|
134
|
-
|
|
132
|
+
/** @param {Node} ear @param {number[]} triangles @param {number} minX @param {number} minY @param {number} invSize */
|
|
133
|
+
function earcutLinked(ear, triangles, minX, minY, invSize) {
|
|
135
134
|
// interlink polygon nodes in z-order
|
|
136
|
-
if (
|
|
135
|
+
if (invSize) indexCurve(ear, minX, minY, invSize);
|
|
137
136
|
|
|
138
|
-
let stop = ear;
|
|
137
|
+
let stop = ear, cured = false;
|
|
139
138
|
|
|
140
139
|
// iterate through ears, slicing them one by one
|
|
141
140
|
while (ear.prev !== ear.next) {
|
|
@@ -147,10 +146,8 @@ function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) {
|
|
|
147
146
|
triangles.push(prev.i, ear.i, next.i); // cut off the triangle
|
|
148
147
|
|
|
149
148
|
removeNode(ear);
|
|
150
|
-
|
|
151
149
|
ear = next;
|
|
152
150
|
stop = next;
|
|
153
|
-
|
|
154
151
|
continue;
|
|
155
152
|
}
|
|
156
153
|
|
|
@@ -158,20 +155,22 @@ function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) {
|
|
|
158
155
|
|
|
159
156
|
// if we looped through the whole remaining polygon and can't find any more ears
|
|
160
157
|
if (ear === stop) {
|
|
161
|
-
// try filtering points and slicing again
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
158
|
+
// try filtering collinear/coincident points and slicing again — repeat as long as
|
|
159
|
+
// filtering actually removes nodes, since each removal can expose new ears
|
|
160
|
+
filteredOut = false;
|
|
161
|
+
ear = filterPoints(ear);
|
|
162
|
+
if (filteredOut) { stop = ear; continue; }
|
|
163
|
+
|
|
164
|
+
// filtering is exhausted: cure small local self-intersections once, then retry
|
|
165
|
+
if (!cured) {
|
|
166
|
+
ear = cureLocalIntersections(ear, triangles);
|
|
167
|
+
stop = ear;
|
|
168
|
+
cured = true;
|
|
169
|
+
continue;
|
|
173
170
|
}
|
|
174
171
|
|
|
172
|
+
// as a last resort, try splitting the remaining polygon into two
|
|
173
|
+
splitEarcut(ear, triangles, minX, minY, invSize);
|
|
175
174
|
break;
|
|
176
175
|
}
|
|
177
176
|
}
|
|
@@ -180,82 +179,48 @@ function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) {
|
|
|
180
179
|
// check whether a polygon node forms a valid ear with adjacent nodes
|
|
181
180
|
/** @param {Node} ear @returns {boolean} */
|
|
182
181
|
function isEar(ear) {
|
|
183
|
-
const a = ear.prev,
|
|
184
|
-
b = ear,
|
|
185
|
-
c = ear.next;
|
|
186
|
-
|
|
187
182
|
// reflex check (area(a, b, c) >= 0) is hoisted into the earcutLinked caller to avoid non-inlined call here
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
// isEarHashed rather than called — V8 doesn't inline it and the call sits in the hot loop
|
|
192
|
-
const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y;
|
|
193
|
-
|
|
194
|
-
// triangle bbox
|
|
195
|
-
const x0 = Math.min(ax, bx, cx),
|
|
183
|
+
const a = ear.prev, b = ear, c = ear.next,
|
|
184
|
+
ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y,
|
|
185
|
+
x0 = Math.min(ax, bx, cx), // triangle bbox
|
|
196
186
|
y0 = Math.min(ay, by, cy),
|
|
197
187
|
x1 = Math.max(ax, bx, cx),
|
|
198
188
|
y1 = Math.max(ay, by, cy);
|
|
199
189
|
|
|
190
|
+
// make sure we don't have other points inside the potential ear
|
|
200
191
|
let p = c.next;
|
|
201
192
|
while (p !== a) {
|
|
202
|
-
if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 &&
|
|
203
|
-
|
|
204
|
-
area(p.prev, p, p.next) >= 0) return false;
|
|
193
|
+
if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && !(ax === p.x && ay === p.y) &&
|
|
194
|
+
pointInTriangle(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false;
|
|
205
195
|
p = p.next;
|
|
206
196
|
}
|
|
207
|
-
|
|
208
197
|
return true;
|
|
209
198
|
}
|
|
210
199
|
|
|
211
200
|
/** @param {Node} ear @param {number} minX @param {number} minY @param {number} invSize @returns {boolean} */
|
|
212
201
|
function isEarHashed(ear, minX, minY, invSize) {
|
|
213
|
-
const a = ear.prev,
|
|
214
|
-
b = ear,
|
|
215
|
-
c = ear.next;
|
|
216
|
-
|
|
217
202
|
// reflex check is hoisted into the earcutLinked caller (see isEar)
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
// triangle bbox
|
|
222
|
-
const x0 = Math.min(ax, bx, cx),
|
|
203
|
+
const a = ear.prev, b = ear, c = ear.next,
|
|
204
|
+
ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y,
|
|
205
|
+
x0 = Math.min(ax, bx, cx), // triangle bbox
|
|
223
206
|
y0 = Math.min(ay, by, cy),
|
|
224
207
|
x1 = Math.max(ax, bx, cx),
|
|
225
|
-
y1 = Math.max(ay, by, cy)
|
|
226
|
-
|
|
227
|
-
// z-order range for the current triangle bbox;
|
|
228
|
-
const minZ = zOrder(x0, y0, minX, minY, invSize),
|
|
208
|
+
y1 = Math.max(ay, by, cy),
|
|
209
|
+
minZ = zOrder(x0, y0, minX, minY, invSize), // z-order range for the current triangle bbox;
|
|
229
210
|
maxZ = zOrder(x1, y1, minX, minY, invSize);
|
|
230
211
|
|
|
231
|
-
let p = ear.prevZ
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
while (p && p.z >= minZ && n && n.z <= maxZ) {
|
|
236
|
-
if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== c &&
|
|
237
|
-
!(ax === p.x && ay === p.y) && (cx - p.x) * (ay - p.y) >= (ax - p.x) * (cy - p.y) && (ax - p.x) * (by - p.y) >= (bx - p.x) * (ay - p.y) && (bx - p.x) * (cy - p.y) >= (cx - p.x) * (by - p.y) && area(p.prev, p, p.next) >= 0) return false;
|
|
212
|
+
let p = ear.prevZ;
|
|
213
|
+
while (p && p.z >= minZ) { // look for points inside the triangle in decreasing z-order
|
|
214
|
+
if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== c && !(ax === p.x && ay === p.y) &&
|
|
215
|
+
pointInTriangle(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false;
|
|
238
216
|
p = p.prevZ;
|
|
239
|
-
|
|
240
|
-
if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== c &&
|
|
241
|
-
!(ax === n.x && ay === n.y) && (cx - n.x) * (ay - n.y) >= (ax - n.x) * (cy - n.y) && (ax - n.x) * (by - n.y) >= (bx - n.x) * (ay - n.y) && (bx - n.x) * (cy - n.y) >= (cx - n.x) * (by - n.y) && area(n.prev, n, n.next) >= 0) return false;
|
|
242
|
-
n = n.nextZ;
|
|
243
217
|
}
|
|
244
|
-
|
|
245
|
-
// look for
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
!(ax === p.x && ay === p.y) && (cx - p.x) * (ay - p.y) >= (ax - p.x) * (cy - p.y) && (ax - p.x) * (by - p.y) >= (bx - p.x) * (ay - p.y) && (bx - p.x) * (cy - p.y) >= (cx - p.x) * (by - p.y) && area(p.prev, p, p.next) >= 0) return false;
|
|
249
|
-
p = p.prevZ;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// look for remaining points in increasing z-order
|
|
253
|
-
while (n && n.z <= maxZ) {
|
|
254
|
-
if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== c &&
|
|
255
|
-
!(ax === n.x && ay === n.y) && (cx - n.x) * (ay - n.y) >= (ax - n.x) * (cy - n.y) && (ax - n.x) * (by - n.y) >= (bx - n.x) * (ay - n.y) && (bx - n.x) * (cy - n.y) >= (cx - n.x) * (by - n.y) && area(n.prev, n, n.next) >= 0) return false;
|
|
218
|
+
let n = ear.nextZ;
|
|
219
|
+
while (n && n.z <= maxZ) { // look for points in increasing z-order
|
|
220
|
+
if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== c && !(ax === n.x && ay === n.y) &&
|
|
221
|
+
pointInTriangle(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false;
|
|
256
222
|
n = n.nextZ;
|
|
257
223
|
}
|
|
258
|
-
|
|
259
224
|
return true;
|
|
260
225
|
}
|
|
261
226
|
|
|
@@ -286,8 +251,8 @@ function cureLocalIntersections(start, triangles) {
|
|
|
286
251
|
}
|
|
287
252
|
|
|
288
253
|
// try splitting polygon into two and triangulate them independently
|
|
289
|
-
/** @param {Node} start @param {number[]} triangles @param {number}
|
|
290
|
-
function splitEarcut(start, triangles,
|
|
254
|
+
/** @param {Node} start @param {number[]} triangles @param {number} minX @param {number} minY @param {number} invSize */
|
|
255
|
+
function splitEarcut(start, triangles, minX, minY, invSize) {
|
|
291
256
|
// look for a valid diagonal that divides the polygon into two
|
|
292
257
|
let a = start;
|
|
293
258
|
do {
|
|
@@ -302,8 +267,8 @@ function splitEarcut(start, triangles, dim, minX, minY, invSize) {
|
|
|
302
267
|
c = filterPoints(c, c.next);
|
|
303
268
|
|
|
304
269
|
// run earcut on each half
|
|
305
|
-
earcutLinked(a, triangles,
|
|
306
|
-
earcutLinked(c, triangles,
|
|
270
|
+
earcutLinked(a, triangles, minX, minY, invSize);
|
|
271
|
+
earcutLinked(c, triangles, minX, minY, invSize);
|
|
307
272
|
return;
|
|
308
273
|
}
|
|
309
274
|
b = b.next;
|
|
@@ -343,7 +308,8 @@ function eliminateHoles(data, holeIndices, outerNode, dim) {
|
|
|
343
308
|
}
|
|
344
309
|
indexActive = false;
|
|
345
310
|
|
|
346
|
-
|
|
311
|
+
// collapse collinear/coincident points across the whole merged ring once before clipping
|
|
312
|
+
return filterPoints(outerNode);
|
|
347
313
|
}
|
|
348
314
|
|
|
349
315
|
/** @param {Node} a @param {Node} b @returns {number} */
|
|
@@ -550,11 +516,19 @@ function sectorContainsSector(m, p) {
|
|
|
550
516
|
return area(m.prev, m, p.prev) < 0 && area(p.next, m, m.next) < 0;
|
|
551
517
|
}
|
|
552
518
|
|
|
553
|
-
// scratch
|
|
519
|
+
// scratch buffers reused across calls and grown on demand: two node-ref arrays that
|
|
520
|
+
// ping-pong during the radix passes, plus parallel z-value arrays so the passes read
|
|
521
|
+
// z from contiguous memory instead of dereferencing each node. 256-entry histogram for
|
|
522
|
+
// 8-bit digits; the small histogram keeps per-call setup cheap (most rings are short)
|
|
554
523
|
/** @type {Node[]} */
|
|
555
524
|
const sortArr = [];
|
|
525
|
+
/** @type {Node[]} */
|
|
526
|
+
let sortBuf = [];
|
|
527
|
+
let zArr = new Uint32Array(0);
|
|
528
|
+
let zBuf = new Uint32Array(0);
|
|
529
|
+
const counts = new Uint32Array(256);
|
|
556
530
|
|
|
557
|
-
// interlink polygon nodes in z-order: collect into an array,
|
|
531
|
+
// interlink polygon nodes in z-order: collect into an array, sort by z, relink
|
|
558
532
|
/** @param {Node} start @param {number} minX @param {number} minY @param {number} invSize */
|
|
559
533
|
function indexCurve(start, minX, minY, invSize) {
|
|
560
534
|
let p = start;
|
|
@@ -566,7 +540,7 @@ function indexCurve(start, minX, minY, invSize) {
|
|
|
566
540
|
p = p.next;
|
|
567
541
|
} while (p !== start);
|
|
568
542
|
|
|
569
|
-
|
|
543
|
+
sortNodes(n);
|
|
570
544
|
|
|
571
545
|
/** @type {Node | null} */
|
|
572
546
|
let prev = null;
|
|
@@ -579,35 +553,48 @@ function indexCurve(start, minX, minY, invSize) {
|
|
|
579
553
|
/** @type {Node} */ (prev).nextZ = null;
|
|
580
554
|
}
|
|
581
555
|
|
|
582
|
-
//
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
while (arr[i].z < pivot) i++;
|
|
593
|
-
while (arr[j].z > pivot) j--;
|
|
594
|
-
if (i <= j) { t = arr[i]; arr[i] = arr[j]; arr[j] = t; i++; j--; }
|
|
595
|
-
}
|
|
596
|
-
// recurse into the smaller half, loop on the larger to bound stack depth
|
|
597
|
-
if (j - left < right - i) {
|
|
598
|
-
quicksortNodes(arr, left, j);
|
|
599
|
-
left = i;
|
|
600
|
-
} else {
|
|
601
|
-
quicksortNodes(arr, i, right);
|
|
602
|
-
right = j;
|
|
556
|
+
// sort the first n nodes of sortArr by z, in place: insertion sort for small n (cheaper
|
|
557
|
+
// than histogram setup), else LSD radix in four 8-bit passes (covering z's 30 bits)
|
|
558
|
+
/** @param {number} n */
|
|
559
|
+
function sortNodes(n) {
|
|
560
|
+
if (n <= 32) {
|
|
561
|
+
for (let i = 1; i < n; i++) {
|
|
562
|
+
const node = sortArr[i], z = node.z;
|
|
563
|
+
let j = i - 1;
|
|
564
|
+
while (j >= 0 && sortArr[j].z > z) { sortArr[j + 1] = sortArr[j]; j--; }
|
|
565
|
+
sortArr[j + 1] = node;
|
|
603
566
|
}
|
|
567
|
+
return;
|
|
604
568
|
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
569
|
+
|
|
570
|
+
if (zArr.length < n) {
|
|
571
|
+
zArr = new Uint32Array(n);
|
|
572
|
+
zBuf = new Uint32Array(n);
|
|
573
|
+
sortBuf = new Array(n);
|
|
574
|
+
}
|
|
575
|
+
for (let i = 0; i < n; i++) zArr[i] = sortArr[i].z;
|
|
576
|
+
|
|
577
|
+
// even pass count lands the sorted result back in sortArr
|
|
578
|
+
radixPass(n, sortArr, zArr, sortBuf, zBuf, 0);
|
|
579
|
+
radixPass(n, sortBuf, zBuf, sortArr, zArr, 8);
|
|
580
|
+
radixPass(n, sortArr, zArr, sortBuf, zBuf, 16);
|
|
581
|
+
radixPass(n, sortBuf, zBuf, sortArr, zArr, 24);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// one LSD radix pass: stably scatter the first n nodes (and their z) from src to dst,
|
|
585
|
+
// bucketed by the 8-bit digit of z at the given bit shift
|
|
586
|
+
/** @param {number} n @param {Node[]} src @param {Uint32Array} srcZ @param {Node[]} dst @param {Uint32Array} dstZ @param {number} shift */
|
|
587
|
+
function radixPass(n, src, srcZ, dst, dstZ, shift) {
|
|
588
|
+
counts.fill(0);
|
|
589
|
+
for (let i = 0; i < n; i++) counts[(srcZ[i] >>> shift) & 0xff]++;
|
|
590
|
+
// turn per-bucket counts into start offsets (prefix sum)
|
|
591
|
+
let sum = 0;
|
|
592
|
+
for (let b = 0; b < 256; b++) { const c = counts[b]; counts[b] = sum; sum += c; }
|
|
593
|
+
for (let i = 0; i < n; i++) {
|
|
594
|
+
const z = srcZ[i];
|
|
595
|
+
const pos = counts[(z >>> shift) & 0xff]++;
|
|
596
|
+
dst[pos] = src[i];
|
|
597
|
+
dstZ[pos] = z;
|
|
611
598
|
}
|
|
612
599
|
}
|
|
613
600
|
|
|
@@ -655,10 +642,10 @@ function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) {
|
|
|
655
642
|
// check if a diagonal between two polygon nodes is valid (lies in polygon interior)
|
|
656
643
|
/** @param {Node} a @param {Node} b @returns {boolean} true when the diagonal is valid */
|
|
657
644
|
function isValidDiagonal(a, b) {
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
645
|
+
const zeroLength = equals(a, b) && area(a.prev, a, a.next) > 0 && area(b.prev, b, b.next) > 0; // degenerate case
|
|
646
|
+
return a.next.i !== b.i && (zeroLength || locallyInside(a, b) && locallyInside(b, a) && // // locally visible
|
|
647
|
+
(area(a.prev, a, b.prev) !== 0 || area(a, b.prev, b) !== 0)) && // no opposite-facing sectors
|
|
648
|
+
!intersectsPolygon(a, b) && (zeroLength || middleInside(a, b)); // doesn't intersect other edges, diagonal inside polygon
|
|
662
649
|
}
|
|
663
650
|
|
|
664
651
|
// signed area of a triangle
|
package/dist/earcut.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((t="undefined"!=typeof globalThis?globalThis:t||self).earcut={})}(this,function(t){"use strict";const n=new Set;function
|
|
1
|
+
!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((t="undefined"!=typeof globalThis?globalThis:t||self).earcut={})}(this,function(t){"use strict";const n=new Set;let e=!1;function x(t,n,e,x,r){let o=null;if(r===H(t,n,e,x)>0)for(let r=n;r<e;r+=x)o=D(r/x|0,t[r],t[r+1],o);else for(let r=e-x;r>=n;r-=x)o=D(r/x|0,t[r],t[r+1],o);return o&&P(o,o.next)&&(E(o),o=o.next),o}function r(t,x=t){const r=x===t;let o,i=t;do{o=!1,i===i.next||0!==n.size&&n.has(i)||!P(i,i.next)&&0!==O(i.prev,i,i.next)?(r||i!==x)&&(i=i.next,o=!r):((r||i===x)&&(x=i.prev),e=!0,E(i),i=i.prev,o=!0)}while(o||i!==x);return x}function o(t,n,x,o,c){c&&function(t,n,e,x){let r=t,o=0;do{r.z=F(r.x,r.y,n,e,x),Z[o++]=r,r=r.next}while(r!==t);!function(t){if(t<=32){for(let n=1;n<t;n++){const t=Z[n],e=t.z;let x=n-1;for(;x>=0&&Z[x].z>e;)Z[x+1]=Z[x],x--;Z[x+1]=t}return}b.length<t&&(b=new Uint32Array(t),A=new Uint32Array(t),z=new Array(t));for(let n=0;n<t;n++)b[n]=Z[n].z;j(t,Z,b,z,A,0),j(t,z,A,Z,b,8),j(t,Z,b,z,A,16),j(t,z,A,Z,b,24)}(o);let i=null;for(let t=0;t<o;t++){const n=Z[t];n.prevZ=i,i&&(i.nextZ=n),i=n}i.nextZ=null}(t,x,o,c);let y=t,s=!1;for(;t.prev!==t.next;){const h=t.prev,a=t.next;if(O(h,t,a)<0&&(c?l(t,x,o,c):i(t)))n.push(h.i,t.i,a.i),E(t),t=a,y=a;else if((t=a)===y){if(e=!1,t=r(t),e){y=t;continue}if(!s){y=t=f(t,n),s=!0;continue}u(t,n,x,o,c);break}}}function i(t){const n=t.prev,e=t,x=t.next,r=n.x,o=e.x,i=x.x,l=n.y,f=e.y,u=x.y,c=Math.min(r,o,i),y=Math.min(l,f,u),s=Math.max(r,o,i),h=Math.max(l,f,u);let a=x.next;for(;a!==n;){if(a.x>=c&&a.x<=s&&a.y>=y&&a.y<=h&&(r!==a.x||l!==a.y)&&_(r,l,o,f,i,u,a.x,a.y)&&O(a.prev,a,a.next)>=0)return!1;a=a.next}return!0}function l(t,n,e,x){const r=t.prev,o=t,i=t.next,l=r.x,f=o.x,u=i.x,c=r.y,y=o.y,s=i.y,h=Math.min(l,f,u),a=Math.min(c,y,s),p=Math.max(l,f,u),v=Math.max(c,y,s),d=F(h,a,n,e,x),M=F(p,v,n,e,x);let m=t.prevZ;for(;m&&m.z>=d;){if(m.x>=h&&m.x<=p&&m.y>=a&&m.y<=v&&m!==i&&(l!==m.x||c!==m.y)&&_(l,c,f,y,u,s,m.x,m.y)&&O(m.prev,m,m.next)>=0)return!1;m=m.prevZ}let w=t.nextZ;for(;w&&w.z<=M;){if(w.x>=h&&w.x<=p&&w.y>=a&&w.y<=v&&w!==i&&(l!==w.x||c!==w.y)&&_(l,c,f,y,u,s,w.x,w.y)&&O(w.prev,w,w.next)>=0)return!1;w=w.nextZ}return!0}function f(t,n){let e=t,x=!1;do{const r=e.prev,o=e.next.next;S(r,e,e.next,o,!1)&&B(r,o)&&B(o,r)&&(n.push(r.i,e.i,o.i),E(e),E(e.next),e=t=o,x=!0),e=e.next}while(e!==t);return x?r(e):e}function u(t,n,e,x,i){let l=t;do{let t=l.next.next;for(;t!==l.prev;){if(l.i!==t.i&&k(l,t)){let f=C(l,t);return l=r(l,l.next),f=r(f,f.next),o(l,n,e,x,i),void o(f,n,e,x,i)}t=t.next}l=l.next}while(l!==t)}let c=!1;function y(t,n){return t.x-n.x||t.y-n.y||(t.next.y-t.y)/(t.next.x-t.x)-(n.next.y-n.y)/(n.next.x-n.x)}function s(t,n){const e=function(t,n){let e=n;const x=t.x,r=t.y;let o,i=-1/0;if(P(t,e))return e;for(let n=0,l=0;n<p;n++,l+=4){if(r<a[l+1]||r>a[l+3]||a[l]>x||a[l+2]<=i)continue;const f=m(n);e=w(n);do{if(e.prev.next===e){if(P(t,e.next))return e.next;if(r<=e.y&&r>=e.next.y&&e.next.y!==e.y){const t=e.x+(r-e.y)*(e.next.x-e.x)/(e.next.y-e.y);if(t<=x&&t>i&&(i=t,o=e.x<e.next.x?e:e.next,t===x))return o}}e=e.next}while(e!==f)}if(!o)return null;const l=o.x,f=o.y,u=Math.min(r,f),c=Math.max(r,f);let y=1/0;for(let n=0,s=0;n<p;n++,s+=4){if(a[s+2]<l||a[s]>x||a[s+3]<u||a[s+1]>c)continue;const h=m(n);e=w(n);do{if(e.prev.next===e&&x>=e.x&&e.x>=l&&x!==e.x&&_(r<f?x:i,r,l,f,r<f?i:x,r,e.x,e.y)){const n=Math.abs(r-e.y)/(x-e.x);(B(e,t)||e.y===r&&e.next.y===r&&e.next.x>x)&&(n<y||n===y&&(e.x>o.x||e.x===o.x&&g(o,e)))&&(o=e,y=n)}e=e.next}while(e!==h)}return o}(t,n);if(!e)return n;const x=C(e,t);return M(e,x.next.next),r(x,x.next),r(e,e.next)}const h=16;let a=new Float64Array(0),p=0;const v=[],d=[];function M(t,n){let e=t;do{const t=p++;v[t]=e;let x=1/0,r=1/0,o=-1/0,i=-1/0,l=0;do{const n=e.next;e.z=t,e.x<x&&(x=e.x),e.x>o&&(o=e.x),e.y<r&&(r=e.y),e.y>i&&(i=e.y),n.x<x&&(x=n.x),n.x>o&&(o=n.x),n.y<r&&(r=n.y),n.y>i&&(i=n.y),e=n}while(++l<h&&e!==n);d[t]=e;const f=4*t;a[f]=x,a[f+1]=r,a[f+2]=o,a[f+3]=i}while(e!==n)}function m(t){let n=d[t];for(;n.prev.next!==n;)n=n.next;return d[t]=n,n}function w(t){let n=v[t];for(;n.prev.next!==n;)n=n.next;return v[t]=n,n}function g(t,n){return O(t.prev,t,n.prev)<0&&O(n.next,t,t.next)<0}const Z=[];let z=[],b=new Uint32Array(0),A=new Uint32Array(0);const U=new Uint32Array(256);function j(t,n,e,x,r,o){U.fill(0);for(let n=0;n<t;n++)U[e[n]>>>o&255]++;let i=0;for(let t=0;t<256;t++){const n=U[t];U[t]=i,i+=n}for(let i=0;i<t;i++){const t=e[i],l=U[t>>>o&255]++;x[l]=n[i],r[l]=t}}function F(t,n,e,x,r){return(t=1431655765&((t=858993459&((t=252645135&((t=16711935&((t=(t-e)*r|0)|t<<8))|t<<4))|t<<2))|t<<1))|(n=1431655765&((n=858993459&((n=252645135&((n=16711935&((n=(n-x)*r|0)|n<<8))|n<<4))|n<<2))|n<<1))<<1}function T(t){let n=t,e=t;do{(n.x<e.x||n.x===e.x&&n.y<e.y)&&(e=n),n=n.next}while(n!==t);return e}function _(t,n,e,x,r,o,i,l){return(r-i)*(n-l)>=(t-i)*(o-l)&&(t-i)*(x-l)>=(e-i)*(n-l)&&(e-i)*(o-l)>=(r-i)*(x-l)}function k(t,n){const e=P(t,n)&&O(t.prev,t,t.next)>0&&O(n.prev,n,n.next)>0;return t.next.i!==n.i&&(e||B(t,n)&&B(n,t)&&(0!==O(t.prev,t,n.prev)||0!==O(t,n.prev,n)))&&!function(t,n){const e=Math.min(t.x,n.x),x=Math.max(t.x,n.x),r=Math.min(t.y,n.y),o=Math.max(t.y,n.y);let i=t;do{const l=i.next;if(i.x>x&&l.x>x||i.x<e&&l.x<e||i.y>o&&l.y>o||i.y<r&&l.y<r)i=l;else{if(i.i!==t.i&&l.i!==t.i&&i.i!==n.i&&l.i!==n.i&&S(i,l,t,n))return!0;i=l}}while(i!==t);return!1}(t,n)&&(e||function(t,n){let e=t,x=!1;const r=(t.x+n.x)/2,o=(t.y+n.y)/2;do{const t=e.next;e.y>o!=t.y>o&&r<(t.x-e.x)*(o-e.y)/(t.y-e.y)+e.x&&(x=!x),e=t}while(e!==t);return x}(t,n))}function O(t,n,e){return(n.y-t.y)*(e.x-n.x)-(n.x-t.x)*(e.y-n.y)}function P(t,n){return t.x===n.x&&t.y===n.y}function S(t,n,e,x,r=!0){const o=O(t,n,e),i=O(t,n,x),l=O(e,x,t),f=O(e,x,n);return(o>0&&i<0||o<0&&i>0)&&(l>0&&f<0||l<0&&f>0)||!!r&&(!(0!==o||!q(t,e,n))||(!(0!==i||!q(t,x,n))||(!(0!==l||!q(e,t,x))||!(0!==f||!q(e,n,x)))))}function q(t,n,e){return n.x<=Math.max(t.x,e.x)&&n.x>=Math.min(t.x,e.x)&&n.y<=Math.max(t.y,e.y)&&n.y>=Math.min(t.y,e.y)}function B(t,n){return O(t.prev,t,t.next)<0?O(t,n,t.next)>=0&&O(t,t.prev,n)>=0:O(t,n,t.prev)<0||O(t,t.next,n)<0}function C(t,n){const e=G(t.i,t.x,t.y),x=G(n.i,n.x,n.y),r=t.next,o=n.prev;return t.next=n,n.prev=t,e.next=r,r.prev=e,x.next=e,e.prev=x,o.next=x,x.prev=o,x}function D(t,n,e,x){const r=G(t,n,e);return x?(r.next=x.next,r.prev=x,x.next.prev=r,x.next=r):(r.prev=r,r.next=r),r}function E(t){t.next.prev=t.prev,t.prev.next=t.next,t.prevZ&&(t.prevZ.nextZ=t.nextZ),t.nextZ&&(t.nextZ.prevZ=t.prevZ),c&&function(t,n){const e=4*t.z;n.x<a[e]&&(a[e]=n.x),n.y<a[e+1]&&(a[e+1]=n.y),n.x>a[e+2]&&(a[e+2]=n.x),n.y>a[e+3]&&(a[e+3]=n.y)}(t.prev,t.next)}function G(t,n,e){return{i:t,x:n,y:e,prev:null,next:null,z:0,prevZ:null,nextZ:null}}function H(t,n,e,x){let r=0;for(let o=n,i=e-x;o<e;o+=x)r+=(t[i]-t[o])*(t[o+1]+t[i+1]),i=o;return r}t.default=function(t,e,i=2){const l=e&&e.length,f=l?e[0]*i:t.length;n.size&&n.clear();let u=x(t,0,f,i,!0);const v=[];if(!u||u.next===u.prev)return v;let d=0,m=0,w=0;if(l&&(u=function(t,e,o,i){const l=[];for(let r=0,o=e.length;r<o;r++){const f=x(t,e[r]*i,r<o-1?e[r+1]*i:t.length,i,!1);f===f.next&&n.add(f),l.push(T(f))}l.sort(y),function(t,n){const e=Math.ceil((t+2*n)/h)+n+2;a.length<4*e&&(a=new Float64Array(4*e));p=0}(t.length/i,e.length),M(o,o),c=!0;for(let t=0;t<l.length;t++)o=s(l[t],o);return c=!1,r(o)}(t,e,u,i)),t.length>80*i){d=t[0],m=t[1];let n=d,e=m;for(let x=i;x<f;x+=i){const r=t[x],o=t[x+1];r<d&&(d=r),o<m&&(m=o),r>n&&(n=r),o>e&&(e=o)}w=Math.max(n-d,e-m),w=0!==w?32767/w:0}return o(u,v,d,m,w),v},t.deviation=function(t,n,e,x){const r=n&&n.length,o=r?n[0]*e:t.length;let i=Math.abs(H(t,0,o,e));if(r)for(let x=0,r=n.length;x<r;x++){const o=n[x]*e,l=x<r-1?n[x+1]*e:t.length;i-=Math.abs(H(t,o,l,e))}let l=0;for(let n=0;n<x.length;n+=3){const r=x[n]*e,o=x[n+1]*e,i=x[n+2]*e;l+=Math.abs((t[r]-t[i])*(t[o+1]-t[r+1])-(t[r]-t[o])*(t[i+1]-t[r+1]))}return 0===i&&0===l?0:Math.abs((l-i)/i)},t.flatten=function(t){const n=[],e=[],x=t[0][0].length;let r=0,o=0;for(const i of t){for(const t of i)for(let e=0;e<x;e++)n.push(t[e]);o&&(r+=o,e.push(r)),o=i.length}return{vertices:n,holes:e,dimensions:x}},Object.defineProperty(t,"__esModule",{value:!0})});
|
package/package.json
CHANGED
package/src/earcut.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
/**
|
|
3
2
|
* A vertex in a circular doubly linked list representing a polygon ring.
|
|
4
3
|
* `prev`/`next` are always linked (set immediately after {@link createNode}), so they're typed
|
|
@@ -20,6 +19,10 @@
|
|
|
20
19
|
/** @type {Set<Node>} */
|
|
21
20
|
const steiners = new Set();
|
|
22
21
|
|
|
22
|
+
// set by filterPoints whenever it removes at least one node; read by earcutLinked's stall
|
|
23
|
+
// handler to decide whether another clip pass is worth attempting before the costlier stages
|
|
24
|
+
let filteredOut = false;
|
|
25
|
+
|
|
23
26
|
/**
|
|
24
27
|
* Triangulate a polygon given as a flat array of vertex coordinates.
|
|
25
28
|
*
|
|
@@ -43,11 +46,7 @@ export default function earcut(data, holeIndices, dim = 2) {
|
|
|
43
46
|
|
|
44
47
|
let minX = 0, minY = 0, invSize = 0;
|
|
45
48
|
|
|
46
|
-
if (hasHoles)
|
|
47
|
-
outerNode = eliminateHoles(data, holeIndices, outerNode, dim);
|
|
48
|
-
// collapse collinear/coincident points across the whole merged ring once before clipping
|
|
49
|
-
outerNode = filterPoints(outerNode);
|
|
50
|
-
}
|
|
49
|
+
if (hasHoles) outerNode = eliminateHoles(data, holeIndices, outerNode, dim);
|
|
51
50
|
|
|
52
51
|
// if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
|
|
53
52
|
if (data.length > 80 * dim) {
|
|
@@ -70,7 +69,7 @@ export default function earcut(data, holeIndices, dim = 2) {
|
|
|
70
69
|
invSize = invSize !== 0 ? 32767 / invSize : 0;
|
|
71
70
|
}
|
|
72
71
|
|
|
73
|
-
earcutLinked(outerNode, triangles,
|
|
72
|
+
earcutLinked(outerNode, triangles, minX, minY, invSize);
|
|
74
73
|
|
|
75
74
|
return triangles;
|
|
76
75
|
}
|
|
@@ -110,6 +109,7 @@ function filterPoints(start, end = start) {
|
|
|
110
109
|
if (p !== p.next && (steiners.size === 0 || !steiners.has(p)) &&
|
|
111
110
|
(equals(p, p.next) || area(p.prev, p, p.next) === 0)) {
|
|
112
111
|
if (full || p === end) end = p.prev; // pull the stop bound back past the removal
|
|
112
|
+
filteredOut = true;
|
|
113
113
|
removeNode(p);
|
|
114
114
|
p = p.prev; // re-check the predecessor
|
|
115
115
|
again = true;
|
|
@@ -123,14 +123,12 @@ function filterPoints(start, end = start) {
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
// main ear slicing loop which triangulates a polygon (given as a linked list)
|
|
126
|
-
/** @param {Node
|
|
127
|
-
function earcutLinked(ear, triangles,
|
|
128
|
-
if (!ear) return;
|
|
129
|
-
|
|
126
|
+
/** @param {Node} ear @param {number[]} triangles @param {number} minX @param {number} minY @param {number} invSize */
|
|
127
|
+
function earcutLinked(ear, triangles, minX, minY, invSize) {
|
|
130
128
|
// interlink polygon nodes in z-order
|
|
131
|
-
if (
|
|
129
|
+
if (invSize) indexCurve(ear, minX, minY, invSize);
|
|
132
130
|
|
|
133
|
-
let stop = ear;
|
|
131
|
+
let stop = ear, cured = false;
|
|
134
132
|
|
|
135
133
|
// iterate through ears, slicing them one by one
|
|
136
134
|
while (ear.prev !== ear.next) {
|
|
@@ -142,10 +140,8 @@ function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) {
|
|
|
142
140
|
triangles.push(prev.i, ear.i, next.i); // cut off the triangle
|
|
143
141
|
|
|
144
142
|
removeNode(ear);
|
|
145
|
-
|
|
146
143
|
ear = next;
|
|
147
144
|
stop = next;
|
|
148
|
-
|
|
149
145
|
continue;
|
|
150
146
|
}
|
|
151
147
|
|
|
@@ -153,20 +149,22 @@ function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) {
|
|
|
153
149
|
|
|
154
150
|
// if we looped through the whole remaining polygon and can't find any more ears
|
|
155
151
|
if (ear === stop) {
|
|
156
|
-
// try filtering points and slicing again
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
152
|
+
// try filtering collinear/coincident points and slicing again — repeat as long as
|
|
153
|
+
// filtering actually removes nodes, since each removal can expose new ears
|
|
154
|
+
filteredOut = false;
|
|
155
|
+
ear = filterPoints(ear);
|
|
156
|
+
if (filteredOut) { stop = ear; continue; }
|
|
157
|
+
|
|
158
|
+
// filtering is exhausted: cure small local self-intersections once, then retry
|
|
159
|
+
if (!cured) {
|
|
160
|
+
ear = cureLocalIntersections(ear, triangles);
|
|
161
|
+
stop = ear;
|
|
162
|
+
cured = true;
|
|
163
|
+
continue;
|
|
168
164
|
}
|
|
169
165
|
|
|
166
|
+
// as a last resort, try splitting the remaining polygon into two
|
|
167
|
+
splitEarcut(ear, triangles, minX, minY, invSize);
|
|
170
168
|
break;
|
|
171
169
|
}
|
|
172
170
|
}
|
|
@@ -175,82 +173,48 @@ function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) {
|
|
|
175
173
|
// check whether a polygon node forms a valid ear with adjacent nodes
|
|
176
174
|
/** @param {Node} ear @returns {boolean} */
|
|
177
175
|
function isEar(ear) {
|
|
178
|
-
const a = ear.prev,
|
|
179
|
-
b = ear,
|
|
180
|
-
c = ear.next;
|
|
181
|
-
|
|
182
176
|
// reflex check (area(a, b, c) >= 0) is hoisted into the earcutLinked caller to avoid non-inlined call here
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
// isEarHashed rather than called — V8 doesn't inline it and the call sits in the hot loop
|
|
187
|
-
const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y;
|
|
188
|
-
|
|
189
|
-
// triangle bbox
|
|
190
|
-
const x0 = Math.min(ax, bx, cx),
|
|
177
|
+
const a = ear.prev, b = ear, c = ear.next,
|
|
178
|
+
ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y,
|
|
179
|
+
x0 = Math.min(ax, bx, cx), // triangle bbox
|
|
191
180
|
y0 = Math.min(ay, by, cy),
|
|
192
181
|
x1 = Math.max(ax, bx, cx),
|
|
193
182
|
y1 = Math.max(ay, by, cy);
|
|
194
183
|
|
|
184
|
+
// make sure we don't have other points inside the potential ear
|
|
195
185
|
let p = c.next;
|
|
196
186
|
while (p !== a) {
|
|
197
|
-
if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 &&
|
|
198
|
-
|
|
199
|
-
area(p.prev, p, p.next) >= 0) return false;
|
|
187
|
+
if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && !(ax === p.x && ay === p.y) &&
|
|
188
|
+
pointInTriangle(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false;
|
|
200
189
|
p = p.next;
|
|
201
190
|
}
|
|
202
|
-
|
|
203
191
|
return true;
|
|
204
192
|
}
|
|
205
193
|
|
|
206
194
|
/** @param {Node} ear @param {number} minX @param {number} minY @param {number} invSize @returns {boolean} */
|
|
207
195
|
function isEarHashed(ear, minX, minY, invSize) {
|
|
208
|
-
const a = ear.prev,
|
|
209
|
-
b = ear,
|
|
210
|
-
c = ear.next;
|
|
211
|
-
|
|
212
196
|
// reflex check is hoisted into the earcutLinked caller (see isEar)
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
// triangle bbox
|
|
217
|
-
const x0 = Math.min(ax, bx, cx),
|
|
197
|
+
const a = ear.prev, b = ear, c = ear.next,
|
|
198
|
+
ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y,
|
|
199
|
+
x0 = Math.min(ax, bx, cx), // triangle bbox
|
|
218
200
|
y0 = Math.min(ay, by, cy),
|
|
219
201
|
x1 = Math.max(ax, bx, cx),
|
|
220
|
-
y1 = Math.max(ay, by, cy)
|
|
221
|
-
|
|
222
|
-
// z-order range for the current triangle bbox;
|
|
223
|
-
const minZ = zOrder(x0, y0, minX, minY, invSize),
|
|
202
|
+
y1 = Math.max(ay, by, cy),
|
|
203
|
+
minZ = zOrder(x0, y0, minX, minY, invSize), // z-order range for the current triangle bbox;
|
|
224
204
|
maxZ = zOrder(x1, y1, minX, minY, invSize);
|
|
225
205
|
|
|
226
|
-
let p = ear.prevZ
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
while (p && p.z >= minZ && n && n.z <= maxZ) {
|
|
231
|
-
if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== c &&
|
|
232
|
-
!(ax === p.x && ay === p.y) && (cx - p.x) * (ay - p.y) >= (ax - p.x) * (cy - p.y) && (ax - p.x) * (by - p.y) >= (bx - p.x) * (ay - p.y) && (bx - p.x) * (cy - p.y) >= (cx - p.x) * (by - p.y) && area(p.prev, p, p.next) >= 0) return false;
|
|
206
|
+
let p = ear.prevZ;
|
|
207
|
+
while (p && p.z >= minZ) { // look for points inside the triangle in decreasing z-order
|
|
208
|
+
if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== c && !(ax === p.x && ay === p.y) &&
|
|
209
|
+
pointInTriangle(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false;
|
|
233
210
|
p = p.prevZ;
|
|
234
|
-
|
|
235
|
-
if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== c &&
|
|
236
|
-
!(ax === n.x && ay === n.y) && (cx - n.x) * (ay - n.y) >= (ax - n.x) * (cy - n.y) && (ax - n.x) * (by - n.y) >= (bx - n.x) * (ay - n.y) && (bx - n.x) * (cy - n.y) >= (cx - n.x) * (by - n.y) && area(n.prev, n, n.next) >= 0) return false;
|
|
237
|
-
n = n.nextZ;
|
|
238
211
|
}
|
|
239
|
-
|
|
240
|
-
// look for
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
!(ax === p.x && ay === p.y) && (cx - p.x) * (ay - p.y) >= (ax - p.x) * (cy - p.y) && (ax - p.x) * (by - p.y) >= (bx - p.x) * (ay - p.y) && (bx - p.x) * (cy - p.y) >= (cx - p.x) * (by - p.y) && area(p.prev, p, p.next) >= 0) return false;
|
|
244
|
-
p = p.prevZ;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// look for remaining points in increasing z-order
|
|
248
|
-
while (n && n.z <= maxZ) {
|
|
249
|
-
if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== c &&
|
|
250
|
-
!(ax === n.x && ay === n.y) && (cx - n.x) * (ay - n.y) >= (ax - n.x) * (cy - n.y) && (ax - n.x) * (by - n.y) >= (bx - n.x) * (ay - n.y) && (bx - n.x) * (cy - n.y) >= (cx - n.x) * (by - n.y) && area(n.prev, n, n.next) >= 0) return false;
|
|
212
|
+
let n = ear.nextZ;
|
|
213
|
+
while (n && n.z <= maxZ) { // look for points in increasing z-order
|
|
214
|
+
if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== c && !(ax === n.x && ay === n.y) &&
|
|
215
|
+
pointInTriangle(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false;
|
|
251
216
|
n = n.nextZ;
|
|
252
217
|
}
|
|
253
|
-
|
|
254
218
|
return true;
|
|
255
219
|
}
|
|
256
220
|
|
|
@@ -281,8 +245,8 @@ function cureLocalIntersections(start, triangles) {
|
|
|
281
245
|
}
|
|
282
246
|
|
|
283
247
|
// try splitting polygon into two and triangulate them independently
|
|
284
|
-
/** @param {Node} start @param {number[]} triangles @param {number}
|
|
285
|
-
function splitEarcut(start, triangles,
|
|
248
|
+
/** @param {Node} start @param {number[]} triangles @param {number} minX @param {number} minY @param {number} invSize */
|
|
249
|
+
function splitEarcut(start, triangles, minX, minY, invSize) {
|
|
286
250
|
// look for a valid diagonal that divides the polygon into two
|
|
287
251
|
let a = start;
|
|
288
252
|
do {
|
|
@@ -297,8 +261,8 @@ function splitEarcut(start, triangles, dim, minX, minY, invSize) {
|
|
|
297
261
|
c = filterPoints(c, c.next);
|
|
298
262
|
|
|
299
263
|
// run earcut on each half
|
|
300
|
-
earcutLinked(a, triangles,
|
|
301
|
-
earcutLinked(c, triangles,
|
|
264
|
+
earcutLinked(a, triangles, minX, minY, invSize);
|
|
265
|
+
earcutLinked(c, triangles, minX, minY, invSize);
|
|
302
266
|
return;
|
|
303
267
|
}
|
|
304
268
|
b = b.next;
|
|
@@ -338,7 +302,8 @@ function eliminateHoles(data, holeIndices, outerNode, dim) {
|
|
|
338
302
|
}
|
|
339
303
|
indexActive = false;
|
|
340
304
|
|
|
341
|
-
|
|
305
|
+
// collapse collinear/coincident points across the whole merged ring once before clipping
|
|
306
|
+
return filterPoints(outerNode);
|
|
342
307
|
}
|
|
343
308
|
|
|
344
309
|
/** @param {Node} a @param {Node} b @returns {number} */
|
|
@@ -545,11 +510,19 @@ function sectorContainsSector(m, p) {
|
|
|
545
510
|
return area(m.prev, m, p.prev) < 0 && area(p.next, m, m.next) < 0;
|
|
546
511
|
}
|
|
547
512
|
|
|
548
|
-
// scratch
|
|
513
|
+
// scratch buffers reused across calls and grown on demand: two node-ref arrays that
|
|
514
|
+
// ping-pong during the radix passes, plus parallel z-value arrays so the passes read
|
|
515
|
+
// z from contiguous memory instead of dereferencing each node. 256-entry histogram for
|
|
516
|
+
// 8-bit digits; the small histogram keeps per-call setup cheap (most rings are short)
|
|
549
517
|
/** @type {Node[]} */
|
|
550
518
|
const sortArr = [];
|
|
519
|
+
/** @type {Node[]} */
|
|
520
|
+
let sortBuf = [];
|
|
521
|
+
let zArr = new Uint32Array(0);
|
|
522
|
+
let zBuf = new Uint32Array(0);
|
|
523
|
+
const counts = new Uint32Array(256);
|
|
551
524
|
|
|
552
|
-
// interlink polygon nodes in z-order: collect into an array,
|
|
525
|
+
// interlink polygon nodes in z-order: collect into an array, sort by z, relink
|
|
553
526
|
/** @param {Node} start @param {number} minX @param {number} minY @param {number} invSize */
|
|
554
527
|
function indexCurve(start, minX, minY, invSize) {
|
|
555
528
|
let p = start;
|
|
@@ -561,7 +534,7 @@ function indexCurve(start, minX, minY, invSize) {
|
|
|
561
534
|
p = p.next;
|
|
562
535
|
} while (p !== start);
|
|
563
536
|
|
|
564
|
-
|
|
537
|
+
sortNodes(n);
|
|
565
538
|
|
|
566
539
|
/** @type {Node | null} */
|
|
567
540
|
let prev = null;
|
|
@@ -574,35 +547,48 @@ function indexCurve(start, minX, minY, invSize) {
|
|
|
574
547
|
/** @type {Node} */ (prev).nextZ = null;
|
|
575
548
|
}
|
|
576
549
|
|
|
577
|
-
//
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
while (arr[i].z < pivot) i++;
|
|
588
|
-
while (arr[j].z > pivot) j--;
|
|
589
|
-
if (i <= j) { t = arr[i]; arr[i] = arr[j]; arr[j] = t; i++; j--; }
|
|
590
|
-
}
|
|
591
|
-
// recurse into the smaller half, loop on the larger to bound stack depth
|
|
592
|
-
if (j - left < right - i) {
|
|
593
|
-
quicksortNodes(arr, left, j);
|
|
594
|
-
left = i;
|
|
595
|
-
} else {
|
|
596
|
-
quicksortNodes(arr, i, right);
|
|
597
|
-
right = j;
|
|
550
|
+
// sort the first n nodes of sortArr by z, in place: insertion sort for small n (cheaper
|
|
551
|
+
// than histogram setup), else LSD radix in four 8-bit passes (covering z's 30 bits)
|
|
552
|
+
/** @param {number} n */
|
|
553
|
+
function sortNodes(n) {
|
|
554
|
+
if (n <= 32) {
|
|
555
|
+
for (let i = 1; i < n; i++) {
|
|
556
|
+
const node = sortArr[i], z = node.z;
|
|
557
|
+
let j = i - 1;
|
|
558
|
+
while (j >= 0 && sortArr[j].z > z) { sortArr[j + 1] = sortArr[j]; j--; }
|
|
559
|
+
sortArr[j + 1] = node;
|
|
598
560
|
}
|
|
561
|
+
return;
|
|
599
562
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
563
|
+
|
|
564
|
+
if (zArr.length < n) {
|
|
565
|
+
zArr = new Uint32Array(n);
|
|
566
|
+
zBuf = new Uint32Array(n);
|
|
567
|
+
sortBuf = new Array(n);
|
|
568
|
+
}
|
|
569
|
+
for (let i = 0; i < n; i++) zArr[i] = sortArr[i].z;
|
|
570
|
+
|
|
571
|
+
// even pass count lands the sorted result back in sortArr
|
|
572
|
+
radixPass(n, sortArr, zArr, sortBuf, zBuf, 0);
|
|
573
|
+
radixPass(n, sortBuf, zBuf, sortArr, zArr, 8);
|
|
574
|
+
radixPass(n, sortArr, zArr, sortBuf, zBuf, 16);
|
|
575
|
+
radixPass(n, sortBuf, zBuf, sortArr, zArr, 24);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// one LSD radix pass: stably scatter the first n nodes (and their z) from src to dst,
|
|
579
|
+
// bucketed by the 8-bit digit of z at the given bit shift
|
|
580
|
+
/** @param {number} n @param {Node[]} src @param {Uint32Array} srcZ @param {Node[]} dst @param {Uint32Array} dstZ @param {number} shift */
|
|
581
|
+
function radixPass(n, src, srcZ, dst, dstZ, shift) {
|
|
582
|
+
counts.fill(0);
|
|
583
|
+
for (let i = 0; i < n; i++) counts[(srcZ[i] >>> shift) & 0xff]++;
|
|
584
|
+
// turn per-bucket counts into start offsets (prefix sum)
|
|
585
|
+
let sum = 0;
|
|
586
|
+
for (let b = 0; b < 256; b++) { const c = counts[b]; counts[b] = sum; sum += c; }
|
|
587
|
+
for (let i = 0; i < n; i++) {
|
|
588
|
+
const z = srcZ[i];
|
|
589
|
+
const pos = counts[(z >>> shift) & 0xff]++;
|
|
590
|
+
dst[pos] = src[i];
|
|
591
|
+
dstZ[pos] = z;
|
|
606
592
|
}
|
|
607
593
|
}
|
|
608
594
|
|
|
@@ -650,10 +636,10 @@ function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) {
|
|
|
650
636
|
// check if a diagonal between two polygon nodes is valid (lies in polygon interior)
|
|
651
637
|
/** @param {Node} a @param {Node} b @returns {boolean} true when the diagonal is valid */
|
|
652
638
|
function isValidDiagonal(a, b) {
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
639
|
+
const zeroLength = equals(a, b) && area(a.prev, a, a.next) > 0 && area(b.prev, b, b.next) > 0; // degenerate case
|
|
640
|
+
return a.next.i !== b.i && (zeroLength || locallyInside(a, b) && locallyInside(b, a) && // // locally visible
|
|
641
|
+
(area(a.prev, a, b.prev) !== 0 || area(a, b.prev, b) !== 0)) && // no opposite-facing sectors
|
|
642
|
+
!intersectsPolygon(a, b) && (zeroLength || middleInside(a, b)); // doesn't intersect other edges, diagonal inside polygon
|
|
657
643
|
}
|
|
658
644
|
|
|
659
645
|
// signed area of a triangle
|