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.
@@ -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, dim, minX, minY, invSize, 0);
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 | null} ear @param {number[]} triangles @param {number} dim @param {number} minX @param {number} minY @param {number} invSize @param {number} pass */
132
- function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) {
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 (!pass && invSize) indexCurve(ear, minX, minY, invSize);
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
- if (!pass) {
163
- earcutLinked(filterPoints(ear), triangles, dim, minX, minY, invSize, 1);
164
-
165
- // if this didn't work, try curing all small self-intersections locally
166
- } else if (pass === 1) {
167
- ear = cureLocalIntersections(filterPoints(ear), triangles);
168
- earcutLinked(ear, triangles, dim, minX, minY, invSize, 2);
169
-
170
- // as a last resort, try splitting the remaining polygon into two
171
- } else if (pass === 2) {
172
- splitEarcut(ear, triangles, dim, minX, minY, invSize);
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
- // make sure we don't have other points inside the potential ear; the point-in-triangle
190
- // test (false when the point coincides with the first vertex a) is inlined here and in
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
- !(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) &&
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
- const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y;
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
- n = ear.nextZ;
233
-
234
- // look for points inside the triangle in both directions
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 remaining points in decreasing z-order
246
- while (p && p.z >= minZ) {
247
- if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== c &&
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} dim @param {number} minX @param {number} minY @param {number} invSize */
290
- function splitEarcut(start, triangles, dim, minX, minY, invSize) {
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, dim, minX, minY, invSize, 0);
306
- earcutLinked(c, triangles, dim, minX, minY, invSize, 0);
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
- return outerNode;
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 array of node refs, reused across calls and grown on demand
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, quicksort by z, relink
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
- quicksortNodes(sortArr, 0, n - 1);
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
- // quicksort an array of nodes by z; middle-element pivot + insertion sort for small ranges
583
- /** @param {Node[]} arr @param {number} left @param {number} right */
584
- function quicksortNodes(arr, left, right) {
585
- while (right - left > 20) {
586
- // middle pivot splits already-sorted/reversed runs evenly; real ring-order-by-z data
587
- // is non-adversarial, so the median-of-three guard isn't needed
588
- const pivot = arr[(left + right) >> 1].z;
589
-
590
- let i = left, j = right, t;
591
- while (i <= j) {
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
- // insertion sort the small remaining range
606
- for (let i = left + 1; i <= right; i++) {
607
- const node = arr[i], z = node.z;
608
- let j = i - 1;
609
- while (j >= left && arr[j].z > z) { arr[j + 1] = arr[j]; j--; }
610
- arr[j + 1] = node;
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
- return a.next.i !== b.i && !intersectsPolygon(a, b) && // doesn't intersect other edges
659
- (locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b) && // locally visible
660
- (area(a.prev, a, b.prev) !== 0 || area(a, b.prev, b) !== 0) || // does not create opposite-facing sectors
661
- equals(a, b) && area(a.prev, a, a.next) > 0 && area(b.prev, b, b.next) > 0); // special zero-length case
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
@@ -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 e(t,n,e,x,r){let o=null;if(r===C(t,n,e,x)>0)for(let r=n;r<e;r+=x)o=S(r/x|0,t[r],t[r+1],o);else for(let r=e-x;r>=n;r-=x)o=S(r/x|0,t[r],t[r+1],o);return o&&T(o,o.next)&&(q(o),o=o.next),o}function x(t,e=t){const x=e===t;let r,o=t;do{r=!1,o===o.next||0!==n.size&&n.has(o)||!T(o,o.next)&&0!==F(o.prev,o,o.next)?(x||o!==e)&&(o=o.next,r=!x):((x||o===e)&&(e=o.prev),q(o),o=o.prev,r=!0)}while(r||o!==e);return e}function r(t,n,e,u,l,c,s){if(!t)return;!s&&c&&function(t,n,e,x){let r=t,o=0;do{r.z=w(r.x,r.y,n,e,x),g[o++]=r,r=r.next}while(r!==t);z(g,0,o-1);let i=null;for(let t=0;t<o;t++){const n=g[t];n.prevZ=i,i&&(i.nextZ=n),i=n}i.nextZ=null}(t,u,l,c);let p=t;for(;t.prev!==t.next;){const h=t.prev,a=t.next;if(F(h,t,a)<0&&(c?i(t,u,l,c):o(t)))n.push(h.i,t.i,a.i),q(t),t=a,p=a;else if((t=a)===p){s?1===s?r(t=y(x(t),n),n,e,u,l,c,2):2===s&&f(t,n,e,u,l,c):r(x(t),n,e,u,l,c,1);break}}}function o(t){const n=t.prev,e=t,x=t.next,r=n.x,o=e.x,i=x.x,y=n.y,f=e.y,u=x.y,l=Math.min(r,o,i),c=Math.min(y,f,u),s=Math.max(r,o,i),p=Math.max(y,f,u);let h=x.next;for(;h!==n;){if(h.x>=l&&h.x<=s&&h.y>=c&&h.y<=p&&(r!==h.x||y!==h.y)&&(i-h.x)*(y-h.y)>=(r-h.x)*(u-h.y)&&(r-h.x)*(f-h.y)>=(o-h.x)*(y-h.y)&&(o-h.x)*(u-h.y)>=(i-h.x)*(f-h.y)&&F(h.prev,h,h.next)>=0)return!1;h=h.next}return!0}function i(t,n,e,x){const r=t.prev,o=t,i=t.next,y=r.x,f=o.x,u=i.x,l=r.y,c=o.y,s=i.y,p=Math.min(y,f,u),h=Math.min(l,c,s),a=Math.max(y,f,u),v=Math.max(l,c,s),d=w(p,h,n,e,x),M=w(a,v,n,e,x);let m=t.prevZ,Z=t.nextZ;for(;m&&m.z>=d&&Z&&Z.z<=M;){if(m.x>=p&&m.x<=a&&m.y>=h&&m.y<=v&&m!==i&&(y!==m.x||l!==m.y)&&(u-m.x)*(l-m.y)>=(y-m.x)*(s-m.y)&&(y-m.x)*(c-m.y)>=(f-m.x)*(l-m.y)&&(f-m.x)*(s-m.y)>=(u-m.x)*(c-m.y)&&F(m.prev,m,m.next)>=0)return!1;if(m=m.prevZ,Z.x>=p&&Z.x<=a&&Z.y>=h&&Z.y<=v&&Z!==i&&(y!==Z.x||l!==Z.y)&&(u-Z.x)*(l-Z.y)>=(y-Z.x)*(s-Z.y)&&(y-Z.x)*(c-Z.y)>=(f-Z.x)*(l-Z.y)&&(f-Z.x)*(s-Z.y)>=(u-Z.x)*(c-Z.y)&&F(Z.prev,Z,Z.next)>=0)return!1;Z=Z.nextZ}for(;m&&m.z>=d;){if(m.x>=p&&m.x<=a&&m.y>=h&&m.y<=v&&m!==i&&(y!==m.x||l!==m.y)&&(u-m.x)*(l-m.y)>=(y-m.x)*(s-m.y)&&(y-m.x)*(c-m.y)>=(f-m.x)*(l-m.y)&&(f-m.x)*(s-m.y)>=(u-m.x)*(c-m.y)&&F(m.prev,m,m.next)>=0)return!1;m=m.prevZ}for(;Z&&Z.z<=M;){if(Z.x>=p&&Z.x<=a&&Z.y>=h&&Z.y<=v&&Z!==i&&(y!==Z.x||l!==Z.y)&&(u-Z.x)*(l-Z.y)>=(y-Z.x)*(s-Z.y)&&(y-Z.x)*(c-Z.y)>=(f-Z.x)*(l-Z.y)&&(f-Z.x)*(s-Z.y)>=(u-Z.x)*(c-Z.y)&&F(Z.prev,Z,Z.next)>=0)return!1;Z=Z.nextZ}return!0}function y(t,n){let e=t,r=!1;do{const x=e.prev,o=e.next.next;_(x,e,e.next,o,!1)&&O(x,o)&&O(o,x)&&(n.push(x.i,e.i,o.i),q(e),q(e.next),e=t=o,r=!0),e=e.next}while(e!==t);return r?x(e):e}function f(t,n,e,o,i,y){let f=t;do{let t=f.next.next;for(;t!==f.prev;){if(f.i!==t.i&&A(f,t)){let u=P(f,t);return f=x(f,f.next),u=x(u,u.next),r(f,n,e,o,i,y,0),void r(u,n,e,o,i,y,0)}t=t.next}f=f.next}while(f!==t)}let u=!1;function l(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 c(t,n){const e=function(t,n){let e=n;const x=t.x,r=t.y;let o,i=-1/0;if(T(t,e))return e;for(let n=0,y=0;n<h;n++,y+=4){if(r<p[y+1]||r>p[y+3]||p[y]>x||p[y+2]<=i)continue;const f=M(n);e=m(n);do{if(e.prev.next===e){if(T(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 y=o.x,f=o.y,u=Math.min(r,f),l=Math.max(r,f);let c=1/0;for(let n=0,s=0;n<h;n++,s+=4){if(p[s+2]<y||p[s]>x||p[s+3]<u||p[s+1]>l)continue;const h=M(n);e=m(n);do{if(e.prev.next===e&&x>=e.x&&e.x>=y&&x!==e.x&&j(r<f?x:i,r,y,f,r<f?i:x,r,e.x,e.y)){const n=Math.abs(r-e.y)/(x-e.x);(O(e,t)||e.y===r&&e.next.y===r&&e.next.x>x)&&(n<c||n===c&&(e.x>o.x||e.x===o.x&&Z(o,e)))&&(o=e,c=n)}e=e.next}while(e!==h)}return o}(t,n);if(!e)return n;const r=P(e,t);return d(e,r.next.next),x(r,r.next),x(e,e.next)}const s=16;let p=new Float64Array(0),h=0;const a=[],v=[];function d(t,n){let e=t;do{const t=h++;a[t]=e;let x=1/0,r=1/0,o=-1/0,i=-1/0,y=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(++y<s&&e!==n);v[t]=e;const f=4*t;p[f]=x,p[f+1]=r,p[f+2]=o,p[f+3]=i}while(e!==n)}function M(t){let n=v[t];for(;n.prev.next!==n;)n=n.next;return v[t]=n,n}function m(t){let n=a[t];for(;n.prev.next!==n;)n=n.next;return a[t]=n,n}function Z(t,n){return F(t.prev,t,n.prev)<0&&F(n.next,t,t.next)<0}const g=[];function z(t,n,e){for(;e-n>20;){const x=t[n+e>>1].z;let r,o=n,i=e;for(;o<=i;){for(;t[o].z<x;)o++;for(;t[i].z>x;)i--;o<=i&&(r=t[o],t[o]=t[i],t[i]=r,o++,i--)}i-n<e-o?(z(t,n,i),n=o):(z(t,o,e),e=i)}for(let x=n+1;x<=e;x++){const e=t[x],r=e.z;let o=x-1;for(;o>=n&&t[o].z>r;)t[o+1]=t[o],o--;t[o+1]=e}}function w(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 b(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 j(t,n,e,x,r,o,i,y){return(r-i)*(n-y)>=(t-i)*(o-y)&&(t-i)*(x-y)>=(e-i)*(n-y)&&(e-i)*(o-y)>=(r-i)*(x-y)}function A(t,n){return t.next.i!==n.i&&!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 y=i.next;if(i.x>x&&y.x>x||i.x<e&&y.x<e||i.y>o&&y.y>o||i.y<r&&y.y<r)i=y;else{if(i.i!==t.i&&y.i!==t.i&&i.i!==n.i&&y.i!==n.i&&_(i,y,t,n))return!0;i=y}}while(i!==t);return!1}(t,n)&&(O(t,n)&&O(n,t)&&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)&&(0!==F(t.prev,t,n.prev)||0!==F(t,n.prev,n))||T(t,n)&&F(t.prev,t,t.next)>0&&F(n.prev,n,n.next)>0)}function F(t,n,e){return(n.y-t.y)*(e.x-n.x)-(n.x-t.x)*(e.y-n.y)}function T(t,n){return t.x===n.x&&t.y===n.y}function _(t,n,e,x,r=!0){const o=F(t,n,e),i=F(t,n,x),y=F(e,x,t),f=F(e,x,n);return(o>0&&i<0||o<0&&i>0)&&(y>0&&f<0||y<0&&f>0)||!!r&&(!(0!==o||!k(t,e,n))||(!(0!==i||!k(t,x,n))||(!(0!==y||!k(e,t,x))||!(0!==f||!k(e,n,x)))))}function k(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 O(t,n){return F(t.prev,t,t.next)<0?F(t,n,t.next)>=0&&F(t,t.prev,n)>=0:F(t,n,t.prev)<0||F(t,t.next,n)<0}function P(t,n){const e=B(t.i,t.x,t.y),x=B(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 S(t,n,e,x){const r=B(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 q(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),u&&function(t,n){const e=4*t.z;n.x<p[e]&&(p[e]=n.x),n.y<p[e+1]&&(p[e+1]=n.y),n.x>p[e+2]&&(p[e+2]=n.x),n.y>p[e+3]&&(p[e+3]=n.y)}(t.prev,t.next)}function B(t,n,e){return{i:t,x:n,y:e,prev:null,next:null,z:0,prevZ:null,nextZ:null}}function C(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,o,i=2){const y=o&&o.length,f=y?o[0]*i:t.length;n.size&&n.clear();let a=e(t,0,f,i,!0);const v=[];if(!a||a.next===a.prev)return v;let M=0,m=0,Z=0;if(y&&(a=function(t,x,r,o){const i=[];for(let r=0,y=x.length;r<y;r++){const f=e(t,x[r]*o,r<y-1?x[r+1]*o:t.length,o,!1);f===f.next&&n.add(f),i.push(b(f))}i.sort(l),function(t,n){const e=Math.ceil((t+2*n)/s)+n+2;p.length<4*e&&(p=new Float64Array(4*e));h=0}(t.length/o,x.length),d(r,r),u=!0;for(let t=0;t<i.length;t++)r=c(i[t],r);return u=!1,r}(t,o,a,i),a=x(a)),t.length>80*i){M=t[0],m=t[1];let n=M,e=m;for(let x=i;x<f;x+=i){const r=t[x],o=t[x+1];r<M&&(M=r),o<m&&(m=o),r>n&&(n=r),o>e&&(e=o)}Z=Math.max(n-M,e-m),Z=0!==Z?32767/Z:0}return r(a,v,i,M,m,Z,0),v},t.deviation=function(t,n,e,x){const r=n&&n.length,o=r?n[0]*e:t.length;let i=Math.abs(C(t,0,o,e));if(r)for(let x=0,r=n.length;x<r;x++){const o=n[x]*e,y=x<r-1?n[x+1]*e:t.length;i-=Math.abs(C(t,o,y,e))}let y=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;y+=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===y?0:Math.abs((y-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})});
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "earcut",
3
- "version": "3.1.0",
3
+ "version": "3.1.1",
4
4
  "description": "The fastest and smallest JavaScript polygon triangulation library for your WebGL apps",
5
5
  "main": "src/earcut.js",
6
6
  "type": "module",
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, dim, minX, minY, invSize, 0);
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 | null} ear @param {number[]} triangles @param {number} dim @param {number} minX @param {number} minY @param {number} invSize @param {number} pass */
127
- function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) {
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 (!pass && invSize) indexCurve(ear, minX, minY, invSize);
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
- if (!pass) {
158
- earcutLinked(filterPoints(ear), triangles, dim, minX, minY, invSize, 1);
159
-
160
- // if this didn't work, try curing all small self-intersections locally
161
- } else if (pass === 1) {
162
- ear = cureLocalIntersections(filterPoints(ear), triangles);
163
- earcutLinked(ear, triangles, dim, minX, minY, invSize, 2);
164
-
165
- // as a last resort, try splitting the remaining polygon into two
166
- } else if (pass === 2) {
167
- splitEarcut(ear, triangles, dim, minX, minY, invSize);
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
- // make sure we don't have other points inside the potential ear; the point-in-triangle
185
- // test (false when the point coincides with the first vertex a) is inlined here and in
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
- !(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) &&
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
- const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y;
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
- n = ear.nextZ;
228
-
229
- // look for points inside the triangle in both directions
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 remaining points in decreasing z-order
241
- while (p && p.z >= minZ) {
242
- if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== c &&
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} dim @param {number} minX @param {number} minY @param {number} invSize */
285
- function splitEarcut(start, triangles, dim, minX, minY, invSize) {
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, dim, minX, minY, invSize, 0);
301
- earcutLinked(c, triangles, dim, minX, minY, invSize, 0);
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
- return outerNode;
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 array of node refs, reused across calls and grown on demand
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, quicksort by z, relink
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
- quicksortNodes(sortArr, 0, n - 1);
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
- // quicksort an array of nodes by z; middle-element pivot + insertion sort for small ranges
578
- /** @param {Node[]} arr @param {number} left @param {number} right */
579
- function quicksortNodes(arr, left, right) {
580
- while (right - left > 20) {
581
- // middle pivot splits already-sorted/reversed runs evenly; real ring-order-by-z data
582
- // is non-adversarial, so the median-of-three guard isn't needed
583
- const pivot = arr[(left + right) >> 1].z;
584
-
585
- let i = left, j = right, t;
586
- while (i <= j) {
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
- // insertion sort the small remaining range
601
- for (let i = left + 1; i <= right; i++) {
602
- const node = arr[i], z = node.z;
603
- let j = i - 1;
604
- while (j >= left && arr[j].z > z) { arr[j + 1] = arr[j]; j--; }
605
- arr[j + 1] = node;
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
- return a.next.i !== b.i && !intersectsPolygon(a, b) && // doesn't intersect other edges
654
- (locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b) && // locally visible
655
- (area(a.prev, a, b.prev) !== 0 || area(a, b.prev, b) !== 0) || // does not create opposite-facing sectors
656
- equals(a, b) && area(a.prev, a, a.next) > 0 && area(b.prev, b, b.next) > 0); // special zero-length case
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