+104
-117
@@ -28,2 +28,6 @@ (function (global, factory) { | ||
| // set by filterPoints whenever it removes at least one node; read by earcutLinked's stall | ||
| // handler to decide whether another clip pass is worth attempting before the costlier stages | ||
| let filteredOut = false; | ||
| /** | ||
@@ -52,7 +56,3 @@ * Triangulate a polygon given as a flat array of vertex coordinates. | ||
| if (hasHoles) { | ||
| outerNode = eliminateHoles(data, holeIndices, outerNode, dim); | ||
| // collapse collinear/coincident points across the whole merged ring once before clipping | ||
| outerNode = filterPoints(outerNode); | ||
| } | ||
| if (hasHoles) outerNode = eliminateHoles(data, holeIndices, outerNode, dim); | ||
@@ -80,3 +80,3 @@ // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox | ||
| earcutLinked(outerNode, triangles, dim, minX, minY, invSize, 0); | ||
| earcutLinked(outerNode, triangles, minX, minY, invSize); | ||
@@ -121,2 +121,3 @@ return triangles; | ||
| if (full || p === end) end = p.prev; // pull the stop bound back past the removal | ||
| filteredOut = true; | ||
| removeNode(p); | ||
@@ -135,10 +136,8 @@ p = p.prev; // re-check the predecessor | ||
| // main ear slicing loop which triangulates a polygon (given as a linked list) | ||
| /** @param {Node | null} ear @param {number[]} triangles @param {number} dim @param {number} minX @param {number} minY @param {number} invSize @param {number} pass */ | ||
| function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) { | ||
| if (!ear) return; | ||
| /** @param {Node} ear @param {number[]} triangles @param {number} minX @param {number} minY @param {number} invSize */ | ||
| function earcutLinked(ear, triangles, minX, minY, invSize) { | ||
| // interlink polygon nodes in z-order | ||
| if (!pass && invSize) indexCurve(ear, minX, minY, invSize); | ||
| if (invSize) indexCurve(ear, minX, minY, invSize); | ||
| let stop = ear; | ||
| let stop = ear, cured = false; | ||
@@ -155,6 +154,4 @@ // iterate through ears, slicing them one by one | ||
| removeNode(ear); | ||
| ear = next; | ||
| stop = next; | ||
| continue; | ||
@@ -167,16 +164,18 @@ } | ||
| if (ear === stop) { | ||
| // try filtering points and slicing again | ||
| if (!pass) { | ||
| earcutLinked(filterPoints(ear), triangles, dim, minX, minY, invSize, 1); | ||
| // try filtering collinear/coincident points and slicing again — repeat as long as | ||
| // filtering actually removes nodes, since each removal can expose new ears | ||
| filteredOut = false; | ||
| ear = filterPoints(ear); | ||
| if (filteredOut) { stop = ear; continue; } | ||
| // if this didn't work, try curing all small self-intersections locally | ||
| } else if (pass === 1) { | ||
| ear = cureLocalIntersections(filterPoints(ear), triangles); | ||
| earcutLinked(ear, triangles, dim, minX, minY, invSize, 2); | ||
| // filtering is exhausted: cure small local self-intersections once, then retry | ||
| if (!cured) { | ||
| ear = cureLocalIntersections(ear, triangles); | ||
| stop = ear; | ||
| cured = true; | ||
| continue; | ||
| } | ||
| // as a last resort, try splitting the remaining polygon into two | ||
| } else if (pass === 2) { | ||
| splitEarcut(ear, triangles, dim, minX, minY, invSize); | ||
| } | ||
| splitEarcut(ear, triangles, minX, minY, invSize); | ||
| break; | ||
@@ -190,15 +189,6 @@ } | ||
| function isEar(ear) { | ||
| const a = ear.prev, | ||
| b = ear, | ||
| c = ear.next; | ||
| // reflex check (area(a, b, c) >= 0) is hoisted into the earcutLinked caller to avoid non-inlined call here | ||
| // make sure we don't have other points inside the potential ear; the point-in-triangle | ||
| // test (false when the point coincides with the first vertex a) is inlined here and in | ||
| // isEarHashed rather than called — V8 doesn't inline it and the call sits in the hot loop | ||
| const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y; | ||
| // triangle bbox | ||
| const x0 = Math.min(ax, bx, cx), | ||
| const a = ear.prev, b = ear, c = ear.next, | ||
| ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y, | ||
| x0 = Math.min(ax, bx, cx), // triangle bbox | ||
| y0 = Math.min(ay, by, cy), | ||
@@ -208,10 +198,9 @@ x1 = Math.max(ax, bx, cx), | ||
| // make sure we don't have other points inside the potential ear | ||
| let p = c.next; | ||
| while (p !== a) { | ||
| if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && | ||
| !(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; | ||
| if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && !(ax === p.x && ay === p.y) && | ||
| pointInTriangle(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false; | ||
| p = p.next; | ||
| } | ||
| return true; | ||
@@ -222,48 +211,24 @@ } | ||
| function isEarHashed(ear, minX, minY, invSize) { | ||
| const a = ear.prev, | ||
| b = ear, | ||
| c = ear.next; | ||
| // reflex check is hoisted into the earcutLinked caller (see isEar) | ||
| const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y; | ||
| // triangle bbox | ||
| const x0 = Math.min(ax, bx, cx), | ||
| const a = ear.prev, b = ear, c = ear.next, | ||
| ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y, | ||
| x0 = Math.min(ax, bx, cx), // triangle bbox | ||
| y0 = Math.min(ay, by, cy), | ||
| x1 = Math.max(ax, bx, cx), | ||
| y1 = Math.max(ay, by, cy); | ||
| // z-order range for the current triangle bbox; | ||
| const minZ = zOrder(x0, y0, minX, minY, invSize), | ||
| y1 = Math.max(ay, by, cy), | ||
| minZ = zOrder(x0, y0, minX, minY, invSize), // z-order range for the current triangle bbox; | ||
| maxZ = zOrder(x1, y1, minX, minY, invSize); | ||
| let p = ear.prevZ, | ||
| n = ear.nextZ; | ||
| // look for points inside the triangle in both directions | ||
| while (p && p.z >= minZ && n && n.z <= maxZ) { | ||
| if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== c && | ||
| !(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; | ||
| let p = ear.prevZ; | ||
| while (p && p.z >= minZ) { // look for points inside the triangle in decreasing z-order | ||
| if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== c && !(ax === p.x && ay === p.y) && | ||
| pointInTriangle(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false; | ||
| p = p.prevZ; | ||
| if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== c && | ||
| !(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; | ||
| n = n.nextZ; | ||
| } | ||
| // look for remaining points in decreasing z-order | ||
| while (p && p.z >= minZ) { | ||
| if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== c && | ||
| !(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; | ||
| p = p.prevZ; | ||
| } | ||
| // look for remaining points in increasing z-order | ||
| while (n && n.z <= maxZ) { | ||
| if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== c && | ||
| !(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; | ||
| let n = ear.nextZ; | ||
| while (n && n.z <= maxZ) { // look for points in increasing z-order | ||
| if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== c && !(ax === n.x && ay === n.y) && | ||
| pointInTriangle(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false; | ||
| n = n.nextZ; | ||
| } | ||
| return true; | ||
@@ -299,4 +264,4 @@ } | ||
| // try splitting polygon into two and triangulate them independently | ||
| /** @param {Node} start @param {number[]} triangles @param {number} dim @param {number} minX @param {number} minY @param {number} invSize */ | ||
| function splitEarcut(start, triangles, dim, minX, minY, invSize) { | ||
| /** @param {Node} start @param {number[]} triangles @param {number} minX @param {number} minY @param {number} invSize */ | ||
| function splitEarcut(start, triangles, minX, minY, invSize) { | ||
| // look for a valid diagonal that divides the polygon into two | ||
@@ -316,4 +281,4 @@ let a = start; | ||
| // run earcut on each half | ||
| earcutLinked(a, triangles, dim, minX, minY, invSize, 0); | ||
| earcutLinked(c, triangles, dim, minX, minY, invSize, 0); | ||
| earcutLinked(a, triangles, minX, minY, invSize); | ||
| earcutLinked(c, triangles, minX, minY, invSize); | ||
| return; | ||
@@ -358,3 +323,4 @@ } | ||
| return outerNode; | ||
| // collapse collinear/coincident points across the whole merged ring once before clipping | ||
| return filterPoints(outerNode); | ||
| } | ||
@@ -566,7 +532,15 @@ | ||
| // scratch array of node refs, reused across calls and grown on demand | ||
| // scratch buffers reused across calls and grown on demand: two node-ref arrays that | ||
| // ping-pong during the radix passes, plus parallel z-value arrays so the passes read | ||
| // z from contiguous memory instead of dereferencing each node. 256-entry histogram for | ||
| // 8-bit digits; the small histogram keeps per-call setup cheap (most rings are short) | ||
| /** @type {Node[]} */ | ||
| const sortArr = []; | ||
| /** @type {Node[]} */ | ||
| let sortBuf = []; | ||
| let zArr = new Uint32Array(0); | ||
| let zBuf = new Uint32Array(0); | ||
| const counts = new Uint32Array(256); | ||
| // interlink polygon nodes in z-order: collect into an array, quicksort by z, relink | ||
| // interlink polygon nodes in z-order: collect into an array, sort by z, relink | ||
| /** @param {Node} start @param {number} minX @param {number} minY @param {number} invSize */ | ||
@@ -583,3 +557,3 @@ function indexCurve(start, minX, minY, invSize) { | ||
| quicksortNodes(sortArr, 0, n - 1); | ||
| sortNodes(n); | ||
@@ -597,34 +571,47 @@ /** @type {Node | null} */ | ||
| // quicksort an array of nodes by z; middle-element pivot + insertion sort for small ranges | ||
| /** @param {Node[]} arr @param {number} left @param {number} right */ | ||
| function quicksortNodes(arr, left, right) { | ||
| while (right - left > 20) { | ||
| // middle pivot splits already-sorted/reversed runs evenly; real ring-order-by-z data | ||
| // is non-adversarial, so the median-of-three guard isn't needed | ||
| const pivot = arr[(left + right) >> 1].z; | ||
| let i = left, j = right, t; | ||
| while (i <= j) { | ||
| while (arr[i].z < pivot) i++; | ||
| while (arr[j].z > pivot) j--; | ||
| if (i <= j) { t = arr[i]; arr[i] = arr[j]; arr[j] = t; i++; j--; } | ||
| // sort the first n nodes of sortArr by z, in place: insertion sort for small n (cheaper | ||
| // than histogram setup), else LSD radix in four 8-bit passes (covering z's 30 bits) | ||
| /** @param {number} n */ | ||
| function sortNodes(n) { | ||
| if (n <= 32) { | ||
| for (let i = 1; i < n; i++) { | ||
| const node = sortArr[i], z = node.z; | ||
| let j = i - 1; | ||
| while (j >= 0 && sortArr[j].z > z) { sortArr[j + 1] = sortArr[j]; j--; } | ||
| sortArr[j + 1] = node; | ||
| } | ||
| // recurse into the smaller half, loop on the larger to bound stack depth | ||
| if (j - left < right - i) { | ||
| quicksortNodes(arr, left, j); | ||
| left = i; | ||
| } else { | ||
| quicksortNodes(arr, i, right); | ||
| right = j; | ||
| } | ||
| return; | ||
| } | ||
| // insertion sort the small remaining range | ||
| for (let i = left + 1; i <= right; i++) { | ||
| const node = arr[i], z = node.z; | ||
| let j = i - 1; | ||
| while (j >= left && arr[j].z > z) { arr[j + 1] = arr[j]; j--; } | ||
| arr[j + 1] = node; | ||
| if (zArr.length < n) { | ||
| zArr = new Uint32Array(n); | ||
| zBuf = new Uint32Array(n); | ||
| sortBuf = new Array(n); | ||
| } | ||
| for (let i = 0; i < n; i++) zArr[i] = sortArr[i].z; | ||
| // even pass count lands the sorted result back in sortArr | ||
| radixPass(n, sortArr, zArr, sortBuf, zBuf, 0); | ||
| radixPass(n, sortBuf, zBuf, sortArr, zArr, 8); | ||
| radixPass(n, sortArr, zArr, sortBuf, zBuf, 16); | ||
| radixPass(n, sortBuf, zBuf, sortArr, zArr, 24); | ||
| } | ||
| // one LSD radix pass: stably scatter the first n nodes (and their z) from src to dst, | ||
| // bucketed by the 8-bit digit of z at the given bit shift | ||
| /** @param {number} n @param {Node[]} src @param {Uint32Array} srcZ @param {Node[]} dst @param {Uint32Array} dstZ @param {number} shift */ | ||
| function radixPass(n, src, srcZ, dst, dstZ, shift) { | ||
| counts.fill(0); | ||
| for (let i = 0; i < n; i++) counts[(srcZ[i] >>> shift) & 0xff]++; | ||
| // turn per-bucket counts into start offsets (prefix sum) | ||
| let sum = 0; | ||
| for (let b = 0; b < 256; b++) { const c = counts[b]; counts[b] = sum; sum += c; } | ||
| for (let i = 0; i < n; i++) { | ||
| const z = srcZ[i]; | ||
| const pos = counts[(z >>> shift) & 0xff]++; | ||
| dst[pos] = src[i]; | ||
| dstZ[pos] = z; | ||
| } | ||
| } | ||
| // z-order of a point given coords and inverse of the longer side of data bbox | ||
@@ -674,6 +661,6 @@ /** @param {number} x @param {number} y @param {number} minX @param {number} minY @param {number} invSize @returns {number} */ | ||
| function isValidDiagonal(a, b) { | ||
| return a.next.i !== b.i && !intersectsPolygon(a, b) && // doesn't intersect other edges | ||
| (locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b) && // locally visible | ||
| (area(a.prev, a, b.prev) !== 0 || area(a, b.prev, b) !== 0) || // does not create opposite-facing sectors | ||
| equals(a, b) && area(a.prev, a, a.next) > 0 && area(b.prev, b, b.next) > 0); // special zero-length case | ||
| const zeroLength = equals(a, b) && area(a.prev, a, a.next) > 0 && area(b.prev, b, b.next) > 0; // degenerate case | ||
| return a.next.i !== b.i && (zeroLength || locallyInside(a, b) && locallyInside(b, a) && // // locally visible | ||
| (area(a.prev, a, b.prev) !== 0 || area(a, b.prev, b) !== 0)) && // no opposite-facing sectors | ||
| !intersectsPolygon(a, b) && (zeroLength || middleInside(a, b)); // doesn't intersect other edges, diagonal inside polygon | ||
| } | ||
@@ -680,0 +667,0 @@ |
@@ -1,1 +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;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})}); | ||
| !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})}); |
+1
-1
| { | ||
| "name": "earcut", | ||
| "version": "3.1.0", | ||
| "version": "3.1.1", | ||
| "description": "The fastest and smallest JavaScript polygon triangulation library for your WebGL apps", | ||
@@ -5,0 +5,0 @@ "main": "src/earcut.js", |
+104
-118
@@ -1,2 +0,1 @@ | ||
| /** | ||
@@ -23,2 +22,6 @@ * A vertex in a circular doubly linked list representing a polygon ring. | ||
| // set by filterPoints whenever it removes at least one node; read by earcutLinked's stall | ||
| // handler to decide whether another clip pass is worth attempting before the costlier stages | ||
| let filteredOut = false; | ||
| /** | ||
@@ -47,7 +50,3 @@ * Triangulate a polygon given as a flat array of vertex coordinates. | ||
| if (hasHoles) { | ||
| outerNode = eliminateHoles(data, holeIndices, outerNode, dim); | ||
| // collapse collinear/coincident points across the whole merged ring once before clipping | ||
| outerNode = filterPoints(outerNode); | ||
| } | ||
| if (hasHoles) outerNode = eliminateHoles(data, holeIndices, outerNode, dim); | ||
@@ -75,3 +74,3 @@ // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox | ||
| earcutLinked(outerNode, triangles, dim, minX, minY, invSize, 0); | ||
| earcutLinked(outerNode, triangles, minX, minY, invSize); | ||
@@ -116,2 +115,3 @@ return triangles; | ||
| if (full || p === end) end = p.prev; // pull the stop bound back past the removal | ||
| filteredOut = true; | ||
| removeNode(p); | ||
@@ -130,10 +130,8 @@ p = p.prev; // re-check the predecessor | ||
| // main ear slicing loop which triangulates a polygon (given as a linked list) | ||
| /** @param {Node | null} ear @param {number[]} triangles @param {number} dim @param {number} minX @param {number} minY @param {number} invSize @param {number} pass */ | ||
| function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) { | ||
| if (!ear) return; | ||
| /** @param {Node} ear @param {number[]} triangles @param {number} minX @param {number} minY @param {number} invSize */ | ||
| function earcutLinked(ear, triangles, minX, minY, invSize) { | ||
| // interlink polygon nodes in z-order | ||
| if (!pass && invSize) indexCurve(ear, minX, minY, invSize); | ||
| if (invSize) indexCurve(ear, minX, minY, invSize); | ||
| let stop = ear; | ||
| let stop = ear, cured = false; | ||
@@ -150,6 +148,4 @@ // iterate through ears, slicing them one by one | ||
| removeNode(ear); | ||
| ear = next; | ||
| stop = next; | ||
| continue; | ||
@@ -162,16 +158,18 @@ } | ||
| if (ear === stop) { | ||
| // try filtering points and slicing again | ||
| if (!pass) { | ||
| earcutLinked(filterPoints(ear), triangles, dim, minX, minY, invSize, 1); | ||
| // try filtering collinear/coincident points and slicing again — repeat as long as | ||
| // filtering actually removes nodes, since each removal can expose new ears | ||
| filteredOut = false; | ||
| ear = filterPoints(ear); | ||
| if (filteredOut) { stop = ear; continue; } | ||
| // if this didn't work, try curing all small self-intersections locally | ||
| } else if (pass === 1) { | ||
| ear = cureLocalIntersections(filterPoints(ear), triangles); | ||
| earcutLinked(ear, triangles, dim, minX, minY, invSize, 2); | ||
| // filtering is exhausted: cure small local self-intersections once, then retry | ||
| if (!cured) { | ||
| ear = cureLocalIntersections(ear, triangles); | ||
| stop = ear; | ||
| cured = true; | ||
| continue; | ||
| } | ||
| // as a last resort, try splitting the remaining polygon into two | ||
| } else if (pass === 2) { | ||
| splitEarcut(ear, triangles, dim, minX, minY, invSize); | ||
| } | ||
| splitEarcut(ear, triangles, minX, minY, invSize); | ||
| break; | ||
@@ -185,15 +183,6 @@ } | ||
| function isEar(ear) { | ||
| const a = ear.prev, | ||
| b = ear, | ||
| c = ear.next; | ||
| // reflex check (area(a, b, c) >= 0) is hoisted into the earcutLinked caller to avoid non-inlined call here | ||
| // make sure we don't have other points inside the potential ear; the point-in-triangle | ||
| // test (false when the point coincides with the first vertex a) is inlined here and in | ||
| // isEarHashed rather than called — V8 doesn't inline it and the call sits in the hot loop | ||
| const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y; | ||
| // triangle bbox | ||
| const x0 = Math.min(ax, bx, cx), | ||
| const a = ear.prev, b = ear, c = ear.next, | ||
| ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y, | ||
| x0 = Math.min(ax, bx, cx), // triangle bbox | ||
| y0 = Math.min(ay, by, cy), | ||
@@ -203,10 +192,9 @@ x1 = Math.max(ax, bx, cx), | ||
| // make sure we don't have other points inside the potential ear | ||
| let p = c.next; | ||
| while (p !== a) { | ||
| if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && | ||
| !(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; | ||
| if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && !(ax === p.x && ay === p.y) && | ||
| pointInTriangle(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false; | ||
| p = p.next; | ||
| } | ||
| return true; | ||
@@ -217,48 +205,24 @@ } | ||
| function isEarHashed(ear, minX, minY, invSize) { | ||
| const a = ear.prev, | ||
| b = ear, | ||
| c = ear.next; | ||
| // reflex check is hoisted into the earcutLinked caller (see isEar) | ||
| const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y; | ||
| // triangle bbox | ||
| const x0 = Math.min(ax, bx, cx), | ||
| const a = ear.prev, b = ear, c = ear.next, | ||
| ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y, | ||
| x0 = Math.min(ax, bx, cx), // triangle bbox | ||
| y0 = Math.min(ay, by, cy), | ||
| x1 = Math.max(ax, bx, cx), | ||
| y1 = Math.max(ay, by, cy); | ||
| // z-order range for the current triangle bbox; | ||
| const minZ = zOrder(x0, y0, minX, minY, invSize), | ||
| y1 = Math.max(ay, by, cy), | ||
| minZ = zOrder(x0, y0, minX, minY, invSize), // z-order range for the current triangle bbox; | ||
| maxZ = zOrder(x1, y1, minX, minY, invSize); | ||
| let p = ear.prevZ, | ||
| n = ear.nextZ; | ||
| // look for points inside the triangle in both directions | ||
| while (p && p.z >= minZ && n && n.z <= maxZ) { | ||
| if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== c && | ||
| !(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; | ||
| let p = ear.prevZ; | ||
| while (p && p.z >= minZ) { // look for points inside the triangle in decreasing z-order | ||
| if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== c && !(ax === p.x && ay === p.y) && | ||
| pointInTriangle(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false; | ||
| p = p.prevZ; | ||
| if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== c && | ||
| !(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; | ||
| n = n.nextZ; | ||
| } | ||
| // look for remaining points in decreasing z-order | ||
| while (p && p.z >= minZ) { | ||
| if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== c && | ||
| !(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; | ||
| p = p.prevZ; | ||
| } | ||
| // look for remaining points in increasing z-order | ||
| while (n && n.z <= maxZ) { | ||
| if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== c && | ||
| !(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; | ||
| let n = ear.nextZ; | ||
| while (n && n.z <= maxZ) { // look for points in increasing z-order | ||
| if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== c && !(ax === n.x && ay === n.y) && | ||
| pointInTriangle(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false; | ||
| n = n.nextZ; | ||
| } | ||
| return true; | ||
@@ -294,4 +258,4 @@ } | ||
| // try splitting polygon into two and triangulate them independently | ||
| /** @param {Node} start @param {number[]} triangles @param {number} dim @param {number} minX @param {number} minY @param {number} invSize */ | ||
| function splitEarcut(start, triangles, dim, minX, minY, invSize) { | ||
| /** @param {Node} start @param {number[]} triangles @param {number} minX @param {number} minY @param {number} invSize */ | ||
| function splitEarcut(start, triangles, minX, minY, invSize) { | ||
| // look for a valid diagonal that divides the polygon into two | ||
@@ -311,4 +275,4 @@ let a = start; | ||
| // run earcut on each half | ||
| earcutLinked(a, triangles, dim, minX, minY, invSize, 0); | ||
| earcutLinked(c, triangles, dim, minX, minY, invSize, 0); | ||
| earcutLinked(a, triangles, minX, minY, invSize); | ||
| earcutLinked(c, triangles, minX, minY, invSize); | ||
| return; | ||
@@ -353,3 +317,4 @@ } | ||
| return outerNode; | ||
| // collapse collinear/coincident points across the whole merged ring once before clipping | ||
| return filterPoints(outerNode); | ||
| } | ||
@@ -561,7 +526,15 @@ | ||
| // scratch array of node refs, reused across calls and grown on demand | ||
| // scratch buffers reused across calls and grown on demand: two node-ref arrays that | ||
| // ping-pong during the radix passes, plus parallel z-value arrays so the passes read | ||
| // z from contiguous memory instead of dereferencing each node. 256-entry histogram for | ||
| // 8-bit digits; the small histogram keeps per-call setup cheap (most rings are short) | ||
| /** @type {Node[]} */ | ||
| const sortArr = []; | ||
| /** @type {Node[]} */ | ||
| let sortBuf = []; | ||
| let zArr = new Uint32Array(0); | ||
| let zBuf = new Uint32Array(0); | ||
| const counts = new Uint32Array(256); | ||
| // interlink polygon nodes in z-order: collect into an array, quicksort by z, relink | ||
| // interlink polygon nodes in z-order: collect into an array, sort by z, relink | ||
| /** @param {Node} start @param {number} minX @param {number} minY @param {number} invSize */ | ||
@@ -578,3 +551,3 @@ function indexCurve(start, minX, minY, invSize) { | ||
| quicksortNodes(sortArr, 0, n - 1); | ||
| sortNodes(n); | ||
@@ -592,34 +565,47 @@ /** @type {Node | null} */ | ||
| // quicksort an array of nodes by z; middle-element pivot + insertion sort for small ranges | ||
| /** @param {Node[]} arr @param {number} left @param {number} right */ | ||
| function quicksortNodes(arr, left, right) { | ||
| while (right - left > 20) { | ||
| // middle pivot splits already-sorted/reversed runs evenly; real ring-order-by-z data | ||
| // is non-adversarial, so the median-of-three guard isn't needed | ||
| const pivot = arr[(left + right) >> 1].z; | ||
| let i = left, j = right, t; | ||
| while (i <= j) { | ||
| while (arr[i].z < pivot) i++; | ||
| while (arr[j].z > pivot) j--; | ||
| if (i <= j) { t = arr[i]; arr[i] = arr[j]; arr[j] = t; i++; j--; } | ||
| // sort the first n nodes of sortArr by z, in place: insertion sort for small n (cheaper | ||
| // than histogram setup), else LSD radix in four 8-bit passes (covering z's 30 bits) | ||
| /** @param {number} n */ | ||
| function sortNodes(n) { | ||
| if (n <= 32) { | ||
| for (let i = 1; i < n; i++) { | ||
| const node = sortArr[i], z = node.z; | ||
| let j = i - 1; | ||
| while (j >= 0 && sortArr[j].z > z) { sortArr[j + 1] = sortArr[j]; j--; } | ||
| sortArr[j + 1] = node; | ||
| } | ||
| // recurse into the smaller half, loop on the larger to bound stack depth | ||
| if (j - left < right - i) { | ||
| quicksortNodes(arr, left, j); | ||
| left = i; | ||
| } else { | ||
| quicksortNodes(arr, i, right); | ||
| right = j; | ||
| } | ||
| return; | ||
| } | ||
| // insertion sort the small remaining range | ||
| for (let i = left + 1; i <= right; i++) { | ||
| const node = arr[i], z = node.z; | ||
| let j = i - 1; | ||
| while (j >= left && arr[j].z > z) { arr[j + 1] = arr[j]; j--; } | ||
| arr[j + 1] = node; | ||
| if (zArr.length < n) { | ||
| zArr = new Uint32Array(n); | ||
| zBuf = new Uint32Array(n); | ||
| sortBuf = new Array(n); | ||
| } | ||
| for (let i = 0; i < n; i++) zArr[i] = sortArr[i].z; | ||
| // even pass count lands the sorted result back in sortArr | ||
| radixPass(n, sortArr, zArr, sortBuf, zBuf, 0); | ||
| radixPass(n, sortBuf, zBuf, sortArr, zArr, 8); | ||
| radixPass(n, sortArr, zArr, sortBuf, zBuf, 16); | ||
| radixPass(n, sortBuf, zBuf, sortArr, zArr, 24); | ||
| } | ||
| // one LSD radix pass: stably scatter the first n nodes (and their z) from src to dst, | ||
| // bucketed by the 8-bit digit of z at the given bit shift | ||
| /** @param {number} n @param {Node[]} src @param {Uint32Array} srcZ @param {Node[]} dst @param {Uint32Array} dstZ @param {number} shift */ | ||
| function radixPass(n, src, srcZ, dst, dstZ, shift) { | ||
| counts.fill(0); | ||
| for (let i = 0; i < n; i++) counts[(srcZ[i] >>> shift) & 0xff]++; | ||
| // turn per-bucket counts into start offsets (prefix sum) | ||
| let sum = 0; | ||
| for (let b = 0; b < 256; b++) { const c = counts[b]; counts[b] = sum; sum += c; } | ||
| for (let i = 0; i < n; i++) { | ||
| const z = srcZ[i]; | ||
| const pos = counts[(z >>> shift) & 0xff]++; | ||
| dst[pos] = src[i]; | ||
| dstZ[pos] = z; | ||
| } | ||
| } | ||
| // z-order of a point given coords and inverse of the longer side of data bbox | ||
@@ -669,6 +655,6 @@ /** @param {number} x @param {number} y @param {number} minX @param {number} minY @param {number} invSize @returns {number} */ | ||
| function isValidDiagonal(a, b) { | ||
| return a.next.i !== b.i && !intersectsPolygon(a, b) && // doesn't intersect other edges | ||
| (locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b) && // locally visible | ||
| (area(a.prev, a, b.prev) !== 0 || area(a, b.prev, b) !== 0) || // does not create opposite-facing sectors | ||
| equals(a, b) && area(a.prev, a, a.next) > 0 && area(b.prev, b, b.next) > 0); // special zero-length case | ||
| const zeroLength = equals(a, b) && area(a.prev, a, a.next) > 0 && area(b.prev, b, b.next) > 0; // degenerate case | ||
| return a.next.i !== b.i && (zeroLength || locallyInside(a, b) && locallyInside(b, a) && // // locally visible | ||
| (area(a.prev, a, b.prev) !== 0 || area(a, b.prev, b) !== 0)) && // no opposite-facing sectors | ||
| !intersectsPolygon(a, b) && (zeroLength || middleInside(a, b)); // doesn't intersect other edges, diagonal inside polygon | ||
| } | ||
@@ -675,0 +661,0 @@ |
89869
-1.23%