earcut 3.0.0 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  ISC License
2
2
 
3
- Copyright (c) 2016, Mapbox
3
+ Copyright (c) 2024, Mapbox
4
4
 
5
5
  Permission to use, copy, modify, and/or distribute this software for any purpose
6
6
  with or without fee is hereby granted, provided that the above copyright notice
package/README.md CHANGED
@@ -118,3 +118,4 @@ Alternatively, there's a UMD browser bundle with an `earcut` global variable (ex
118
118
  - [the3deers/earcut-java](https://github.com/the3deers/earcut-java) (Java)
119
119
  - [Larpon/earcut](https://github.com/Larpon/earcut) (V)
120
120
  - [Cawfree/earcut-j](https://github.com/Cawfree/earcut-j) (Java, outdated)
121
+ - [measuredweighed/SwiftEarcut](https://github.com/measuredweighed/SwiftEarcut) (Swift)
@@ -19,10 +19,10 @@ function earcut(data, holeIndices, dim = 2) {
19
19
 
20
20
  // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
21
21
  if (data.length > 80 * dim) {
22
- minX = Infinity;
23
- minY = Infinity;
24
- let maxX = -Infinity;
25
- let maxY = -Infinity;
22
+ minX = data[0];
23
+ minY = data[1];
24
+ let maxX = minX;
25
+ let maxY = minY;
26
26
 
27
27
  for (let i = dim; i < outerLen; i += dim) {
28
28
  const x = data[i];
@@ -145,16 +145,16 @@ function isEar(ear) {
145
145
  // now make sure we don't have other points inside the potential ear
146
146
  const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y;
147
147
 
148
- // triangle bbox; min & max are calculated like this for speed
149
- const x0 = ax < bx ? (ax < cx ? ax : cx) : (bx < cx ? bx : cx),
150
- y0 = ay < by ? (ay < cy ? ay : cy) : (by < cy ? by : cy),
151
- x1 = ax > bx ? (ax > cx ? ax : cx) : (bx > cx ? bx : cx),
152
- y1 = ay > by ? (ay > cy ? ay : cy) : (by > cy ? by : cy);
148
+ // triangle bbox
149
+ const x0 = Math.min(ax, bx, cx),
150
+ y0 = Math.min(ay, by, cy),
151
+ x1 = Math.max(ax, bx, cx),
152
+ y1 = Math.max(ay, by, cy);
153
153
 
154
154
  let p = c.next;
155
155
  while (p !== a) {
156
156
  if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 &&
157
- pointInTriangle(ax, ay, bx, by, cx, cy, p.x, p.y) &&
157
+ pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, p.x, p.y) &&
158
158
  area(p.prev, p, p.next) >= 0) return false;
159
159
  p = p.next;
160
160
  }
@@ -171,11 +171,11 @@ function isEarHashed(ear, minX, minY, invSize) {
171
171
 
172
172
  const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y;
173
173
 
174
- // triangle bbox; min & max are calculated like this for speed
175
- const x0 = ax < bx ? (ax < cx ? ax : cx) : (bx < cx ? bx : cx),
176
- y0 = ay < by ? (ay < cy ? ay : cy) : (by < cy ? by : cy),
177
- x1 = ax > bx ? (ax > cx ? ax : cx) : (bx > cx ? bx : cx),
178
- y1 = ay > by ? (ay > cy ? ay : cy) : (by > cy ? by : cy);
174
+ // triangle bbox
175
+ const x0 = Math.min(ax, bx, cx),
176
+ y0 = Math.min(ay, by, cy),
177
+ x1 = Math.max(ax, bx, cx),
178
+ y1 = Math.max(ay, by, cy);
179
179
 
180
180
  // z-order range for the current triangle bbox;
181
181
  const minZ = zOrder(x0, y0, minX, minY, invSize),
@@ -187,25 +187,25 @@ function isEarHashed(ear, minX, minY, invSize) {
187
187
  // look for points inside the triangle in both directions
188
188
  while (p && p.z >= minZ && n && n.z <= maxZ) {
189
189
  if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c &&
190
- pointInTriangle(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false;
190
+ pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false;
191
191
  p = p.prevZ;
192
192
 
193
193
  if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c &&
194
- pointInTriangle(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false;
194
+ pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false;
195
195
  n = n.nextZ;
196
196
  }
197
197
 
198
198
  // look for remaining points in decreasing z-order
199
199
  while (p && p.z >= minZ) {
200
200
  if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c &&
201
- pointInTriangle(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false;
201
+ pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false;
202
202
  p = p.prevZ;
203
203
  }
204
204
 
205
205
  // look for remaining points in increasing z-order
206
206
  while (n && n.z <= maxZ) {
207
207
  if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c &&
208
- pointInTriangle(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false;
208
+ pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false;
209
209
  n = n.nextZ;
210
210
  }
211
211
 
@@ -273,7 +273,7 @@ function eliminateHoles(data, holeIndices, outerNode, dim) {
273
273
  queue.push(getLeftmost(list));
274
274
  }
275
275
 
276
- queue.sort(compareX);
276
+ queue.sort(compareXYSlope);
277
277
 
278
278
  // process holes from left to right
279
279
  for (let i = 0; i < queue.length; i++) {
@@ -283,11 +283,22 @@ function eliminateHoles(data, holeIndices, outerNode, dim) {
283
283
  return outerNode;
284
284
  }
285
285
 
286
- function compareX(a, b) {
287
- return a.x - b.x;
286
+ function compareXYSlope(a, b) {
287
+ let result = a.x - b.x;
288
+ // when the left-most point of 2 holes meet at a vertex, sort the holes counterclockwise so that when we find
289
+ // the bridge to the outer shell is always the point that they meet at.
290
+ if (result === 0) {
291
+ result = a.y - b.y;
292
+ if (result === 0) {
293
+ const aSlope = (a.next.y - a.y) / (a.next.x - a.x);
294
+ const bSlope = (b.next.y - b.y) / (b.next.x - b.x);
295
+ result = aSlope - bSlope;
296
+ }
297
+ }
298
+ return result;
288
299
  }
289
300
 
290
- // find a bridge between vertices that connects hole with an outer ring and and link it
301
+ // find a bridge between vertices that connects hole with an outer ring and link it
291
302
  function eliminateHole(hole, outerNode) {
292
303
  const bridge = findHoleBridge(hole, outerNode);
293
304
  if (!bridge) {
@@ -311,8 +322,11 @@ function findHoleBridge(hole, outerNode) {
311
322
 
312
323
  // find a segment intersected by a ray from the hole's leftmost point to the left;
313
324
  // segment's endpoint with lesser x will be potential connection point
325
+ // unless they intersect at a vertex, then choose the vertex
326
+ if (equals(hole, p)) return p;
314
327
  do {
315
- if (hy <= p.y && hy >= p.next.y && p.next.y !== p.y) {
328
+ if (equals(hole, p.next)) return p.next;
329
+ else if (hy <= p.y && hy >= p.next.y && p.next.y !== p.y) {
316
330
  const x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y);
317
331
  if (x <= hx && x > qx) {
318
332
  qx = x;
@@ -468,9 +482,14 @@ function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) {
468
482
  (bx - px) * (cy - py) >= (cx - px) * (by - py);
469
483
  }
470
484
 
485
+ // check if a point lies within a convex triangle but false if its equal to the first point of the triangle
486
+ function pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, px, py) {
487
+ return !(ax === px && ay === py) && pointInTriangle(ax, ay, bx, by, cx, cy, px, py);
488
+ }
489
+
471
490
  // check if a diagonal between two polygon nodes is valid (lies in polygon interior)
472
491
  function isValidDiagonal(a, b) {
473
- return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) && // dones't intersect other edges
492
+ return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) && // doesn't intersect other edges
474
493
  (locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b) && // locally visible
475
494
  (area(a.prev, a, b.prev) || area(a, b.prev, b)) || // does not create opposite-facing sectors
476
495
  equals(a, b) && area(a.prev, a, a.next) > 0 && area(b.prev, b, b.next) > 0); // special zero-length case
@@ -1 +1 @@
1
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).earcut={})}(this,(function(e){"use strict";function t(e,t,n,r,x){let i;if(x===j(e,t,n,r)>0)for(let x=t;x<n;x+=r)i=w(x/r|0,e[x],e[x+1],i);else for(let x=n-r;x>=t;x-=r)i=w(x/r|0,e[x],e[x+1],i);return i&&a(i,i.next)&&(z(i),i=i.next),i}function n(e,t){if(!e)return e;t||(t=e);let n,r=e;do{if(n=!1,r.steiner||!a(r,r.next)&&0!==h(r.prev,r,r.next))r=r.next;else{if(z(r),r=t=r.prev,r===r.next)break;n=!0}}while(n||r!==t);return t}function r(e,t,f,l,c,p,s){if(!e)return;!s&&p&&function(e,t,n,r){let x=e;do{0===x.z&&(x.z=y(x.x,x.y,t,n,r)),x.prevZ=x.prev,x.nextZ=x.next,x=x.next}while(x!==e);x.prevZ.nextZ=null,x.prevZ=null,function(e){let t,n=1;do{let r,x=e;e=null;let i=null;for(t=0;x;){t++;let o=x,u=0;for(let e=0;e<n&&(u++,o=o.nextZ,o);e++);let f=n;for(;u>0||f>0&&o;)0!==u&&(0===f||!o||x.z<=o.z)?(r=x,x=x.nextZ,u--):(r=o,o=o.nextZ,f--),i?i.nextZ=r:e=r,r.prevZ=i,i=r;x=o}i.nextZ=null,n*=2}while(t>1)}(x)}(e,l,c,p);let v=e;for(;e.prev!==e.next;){const y=e.prev,h=e.next;if(p?i(e,l,c,p):x(e))t.push(y.i,e.i,h.i),z(e),e=h.next,v=h.next;else if((e=h)===v){s?1===s?r(e=o(n(e),t),t,f,l,c,p,2):2===s&&u(e,t,f,l,c,p):r(n(e),t,f,l,c,p,1);break}}}function x(e){const t=e.prev,n=e,r=e.next;if(h(t,n,r)>=0)return!1;const x=t.x,i=n.x,o=r.x,u=t.y,f=n.y,l=r.y,c=x<i?x<o?x:o:i<o?i:o,y=u<f?u<l?u:l:f<l?f:l,p=x>i?x>o?x:o:i>o?i:o,v=u>f?u>l?u:l:f>l?f:l;let a=r.next;for(;a!==t;){if(a.x>=c&&a.x<=p&&a.y>=y&&a.y<=v&&s(x,u,i,f,o,l,a.x,a.y)&&h(a.prev,a,a.next)>=0)return!1;a=a.next}return!0}function i(e,t,n,r){const x=e.prev,i=e,o=e.next;if(h(x,i,o)>=0)return!1;const u=x.x,f=i.x,l=o.x,c=x.y,p=i.y,v=o.y,a=u<f?u<l?u:l:f<l?f:l,Z=c<p?c<v?c:v:p<v?p:v,d=u>f?u>l?u:l:f>l?f:l,g=c>p?c>v?c:v:p>v?p:v,b=y(a,Z,t,n,r),M=y(d,g,t,n,r);let w=e.prevZ,z=e.nextZ;for(;w&&w.z>=b&&z&&z.z<=M;){if(w.x>=a&&w.x<=d&&w.y>=Z&&w.y<=g&&w!==x&&w!==o&&s(u,c,f,p,l,v,w.x,w.y)&&h(w.prev,w,w.next)>=0)return!1;if(w=w.prevZ,z.x>=a&&z.x<=d&&z.y>=Z&&z.y<=g&&z!==x&&z!==o&&s(u,c,f,p,l,v,z.x,z.y)&&h(z.prev,z,z.next)>=0)return!1;z=z.nextZ}for(;w&&w.z>=b;){if(w.x>=a&&w.x<=d&&w.y>=Z&&w.y<=g&&w!==x&&w!==o&&s(u,c,f,p,l,v,w.x,w.y)&&h(w.prev,w,w.next)>=0)return!1;w=w.prevZ}for(;z&&z.z<=M;){if(z.x>=a&&z.x<=d&&z.y>=Z&&z.y<=g&&z!==x&&z!==o&&s(u,c,f,p,l,v,z.x,z.y)&&h(z.prev,z,z.next)>=0)return!1;z=z.nextZ}return!0}function o(e,t){let r=e;do{const n=r.prev,x=r.next.next;!a(n,x)&&Z(n,r,r.next,x)&&b(n,x)&&b(x,n)&&(t.push(n.i,r.i,x.i),z(r),z(r.next),r=e=x),r=r.next}while(r!==e);return n(r)}function u(e,t,x,i,o,u){let f=e;do{let e=f.next.next;for(;e!==f.prev;){if(f.i!==e.i&&v(f,e)){let l=M(f,e);return f=n(f,f.next),l=n(l,l.next),r(f,t,x,i,o,u,0),void r(l,t,x,i,o,u,0)}e=e.next}f=f.next}while(f!==e)}function f(e,t){return e.x-t.x}function l(e,t){const r=function(e,t){let n=t;const r=e.x,x=e.y;let i,o=-1/0;do{if(x<=n.y&&x>=n.next.y&&n.next.y!==n.y){const e=n.x+(x-n.y)*(n.next.x-n.x)/(n.next.y-n.y);if(e<=r&&e>o&&(o=e,i=n.x<n.next.x?n:n.next,e===r))return i}n=n.next}while(n!==t);if(!i)return null;const u=i,f=i.x,l=i.y;let y=1/0;n=i;do{if(r>=n.x&&n.x>=f&&r!==n.x&&s(x<l?r:o,x,f,l,x<l?o:r,x,n.x,n.y)){const t=Math.abs(x-n.y)/(r-n.x);b(n,e)&&(t<y||t===y&&(n.x>i.x||n.x===i.x&&c(i,n)))&&(i=n,y=t)}n=n.next}while(n!==u);return i}(e,t);if(!r)return t;const x=M(r,e);return n(x,x.next),n(r,r.next)}function c(e,t){return h(e.prev,e,t.prev)<0&&h(t.next,e,e.next)<0}function y(e,t,n,r,x){return(e=1431655765&((e=858993459&((e=252645135&((e=16711935&((e=(e-n)*x|0)|e<<8))|e<<4))|e<<2))|e<<1))|(t=1431655765&((t=858993459&((t=252645135&((t=16711935&((t=(t-r)*x|0)|t<<8))|t<<4))|t<<2))|t<<1))<<1}function p(e){let t=e,n=e;do{(t.x<n.x||t.x===n.x&&t.y<n.y)&&(n=t),t=t.next}while(t!==e);return n}function s(e,t,n,r,x,i,o,u){return(x-o)*(t-u)>=(e-o)*(i-u)&&(e-o)*(r-u)>=(n-o)*(t-u)&&(n-o)*(i-u)>=(x-o)*(r-u)}function v(e,t){return e.next.i!==t.i&&e.prev.i!==t.i&&!function(e,t){let n=e;do{if(n.i!==e.i&&n.next.i!==e.i&&n.i!==t.i&&n.next.i!==t.i&&Z(n,n.next,e,t))return!0;n=n.next}while(n!==e);return!1}(e,t)&&(b(e,t)&&b(t,e)&&function(e,t){let n=e,r=!1;const x=(e.x+t.x)/2,i=(e.y+t.y)/2;do{n.y>i!=n.next.y>i&&n.next.y!==n.y&&x<(n.next.x-n.x)*(i-n.y)/(n.next.y-n.y)+n.x&&(r=!r),n=n.next}while(n!==e);return r}(e,t)&&(h(e.prev,e,t.prev)||h(e,t.prev,t))||a(e,t)&&h(e.prev,e,e.next)>0&&h(t.prev,t,t.next)>0)}function h(e,t,n){return(t.y-e.y)*(n.x-t.x)-(t.x-e.x)*(n.y-t.y)}function a(e,t){return e.x===t.x&&e.y===t.y}function Z(e,t,n,r){const x=g(h(e,t,n)),i=g(h(e,t,r)),o=g(h(n,r,e)),u=g(h(n,r,t));return x!==i&&o!==u||(!(0!==x||!d(e,n,t))||(!(0!==i||!d(e,r,t))||(!(0!==o||!d(n,e,r))||!(0!==u||!d(n,t,r)))))}function d(e,t,n){return t.x<=Math.max(e.x,n.x)&&t.x>=Math.min(e.x,n.x)&&t.y<=Math.max(e.y,n.y)&&t.y>=Math.min(e.y,n.y)}function g(e){return e>0?1:e<0?-1:0}function b(e,t){return h(e.prev,e,e.next)<0?h(e,t,e.next)>=0&&h(e,e.prev,t)>=0:h(e,t,e.prev)<0||h(e,e.next,t)<0}function M(e,t){const n=m(e.i,e.x,e.y),r=m(t.i,t.x,t.y),x=e.next,i=t.prev;return e.next=t,t.prev=e,n.next=x,x.prev=n,r.next=n,n.prev=r,i.next=r,r.prev=i,r}function w(e,t,n,r){const x=m(e,t,n);return r?(x.next=r.next,x.prev=r,r.next.prev=x,r.next=x):(x.prev=x,x.next=x),x}function z(e){e.next.prev=e.prev,e.prev.next=e.next,e.prevZ&&(e.prevZ.nextZ=e.nextZ),e.nextZ&&(e.nextZ.prevZ=e.prevZ)}function m(e,t,n){return{i:e,x:t,y:n,prev:null,next:null,z:0,prevZ:null,nextZ:null,steiner:!1}}function j(e,t,n,r){let x=0;for(let i=t,o=n-r;i<n;i+=r)x+=(e[o]-e[i])*(e[i+1]+e[o+1]),o=i;return x}e.default=function(e,n,x=2){const i=n&&n.length,o=i?n[0]*x:e.length;let u=t(e,0,o,x,!0);const c=[];if(!u||u.next===u.prev)return c;let y,s,v;if(i&&(u=function(e,n,r,x){const i=[];for(let r=0,o=n.length;r<o;r++){const u=t(e,n[r]*x,r<o-1?n[r+1]*x:e.length,x,!1);u===u.next&&(u.steiner=!0),i.push(p(u))}i.sort(f);for(let e=0;e<i.length;e++)r=l(i[e],r);return r}(e,n,u,x)),e.length>80*x){y=1/0,s=1/0;let t=-1/0,n=-1/0;for(let r=x;r<o;r+=x){const x=e[r],i=e[r+1];x<y&&(y=x),i<s&&(s=i),x>t&&(t=x),i>n&&(n=i)}v=Math.max(t-y,n-s),v=0!==v?32767/v:0}return r(u,c,x,y,s,v,0),c},e.deviation=function(e,t,n,r){const x=t&&t.length,i=x?t[0]*n:e.length;let o=Math.abs(j(e,0,i,n));if(x)for(let r=0,x=t.length;r<x;r++){const i=t[r]*n,u=r<x-1?t[r+1]*n:e.length;o-=Math.abs(j(e,i,u,n))}let u=0;for(let t=0;t<r.length;t+=3){const x=r[t]*n,i=r[t+1]*n,o=r[t+2]*n;u+=Math.abs((e[x]-e[o])*(e[i+1]-e[x+1])-(e[x]-e[i])*(e[o+1]-e[x+1]))}return 0===o&&0===u?0:Math.abs((u-o)/o)},e.flatten=function(e){const t=[],n=[],r=e[0][0].length;let x=0,i=0;for(const o of e){for(const e of o)for(let n=0;n<r;n++)t.push(e[n]);i&&(x+=i,n.push(x)),i=o.length}return{vertices:t,holes:n,dimensions:r}},Object.defineProperty(e,"__esModule",{value:!0})}));
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).earcut={})}(this,function(e){"use strict";function t(e,t,n,r,x){let i;if(x===k(e,t,n,r)>0)for(let x=t;x<n;x+=r)i=w(x/r|0,e[x],e[x+1],i);else for(let x=n-r;x>=t;x-=r)i=w(x/r|0,e[x],e[x+1],i);return i&&Z(i,i.next)&&(z(i),i=i.next),i}function n(e,t){if(!e)return e;t||(t=e);let n,r=e;do{if(n=!1,r.steiner||!Z(r,r.next)&&0!==a(r.prev,r,r.next))r=r.next;else{if(z(r),r=t=r.prev,r===r.next)break;n=!0}}while(n||r!==t);return t}function r(e,t,f,l,c,p,s){if(!e)return;!s&&p&&function(e,t,n,r){let x=e;do{0===x.z&&(x.z=y(x.x,x.y,t,n,r)),x.prevZ=x.prev,x.nextZ=x.next,x=x.next}while(x!==e);x.prevZ.nextZ=null,x.prevZ=null,function(e){let t,n=1;do{let r,x=e;e=null;let i=null;for(t=0;x;){t++;let o=x,u=0;for(let e=0;e<n&&(u++,o=o.nextZ,o);e++);let f=n;for(;u>0||f>0&&o;)0!==u&&(0===f||!o||x.z<=o.z)?(r=x,x=x.nextZ,u--):(r=o,o=o.nextZ,f--),i?i.nextZ=r:e=r,r.prevZ=i,i=r;x=o}i.nextZ=null,n*=2}while(t>1)}(x)}(e,l,c,p);let v=e;for(;e.prev!==e.next;){const y=e.prev,h=e.next;if(p?i(e,l,c,p):x(e))t.push(y.i,e.i,h.i),z(e),e=h.next,v=h.next;else if((e=h)===v){s?1===s?r(e=o(n(e),t),t,f,l,c,p,2):2===s&&u(e,t,f,l,c,p):r(n(e),t,f,l,c,p,1);break}}}function x(e){const t=e.prev,n=e,r=e.next;if(a(t,n,r)>=0)return!1;const x=t.x,i=n.x,o=r.x,u=t.y,f=n.y,l=r.y,c=Math.min(x,i,o),y=Math.min(u,f,l),p=Math.max(x,i,o),s=Math.max(u,f,l);let h=r.next;for(;h!==t;){if(h.x>=c&&h.x<=p&&h.y>=y&&h.y<=s&&v(x,u,i,f,o,l,h.x,h.y)&&a(h.prev,h,h.next)>=0)return!1;h=h.next}return!0}function i(e,t,n,r){const x=e.prev,i=e,o=e.next;if(a(x,i,o)>=0)return!1;const u=x.x,f=i.x,l=o.x,c=x.y,p=i.y,s=o.y,h=Math.min(u,f,l),Z=Math.min(c,p,s),d=Math.max(u,f,l),M=Math.max(c,p,s),m=y(h,Z,t,n,r),g=y(d,M,t,n,r);let b=e.prevZ,w=e.nextZ;for(;b&&b.z>=m&&w&&w.z<=g;){if(b.x>=h&&b.x<=d&&b.y>=Z&&b.y<=M&&b!==x&&b!==o&&v(u,c,f,p,l,s,b.x,b.y)&&a(b.prev,b,b.next)>=0)return!1;if(b=b.prevZ,w.x>=h&&w.x<=d&&w.y>=Z&&w.y<=M&&w!==x&&w!==o&&v(u,c,f,p,l,s,w.x,w.y)&&a(w.prev,w,w.next)>=0)return!1;w=w.nextZ}for(;b&&b.z>=m;){if(b.x>=h&&b.x<=d&&b.y>=Z&&b.y<=M&&b!==x&&b!==o&&v(u,c,f,p,l,s,b.x,b.y)&&a(b.prev,b,b.next)>=0)return!1;b=b.prevZ}for(;w&&w.z<=g;){if(w.x>=h&&w.x<=d&&w.y>=Z&&w.y<=M&&w!==x&&w!==o&&v(u,c,f,p,l,s,w.x,w.y)&&a(w.prev,w,w.next)>=0)return!1;w=w.nextZ}return!0}function o(e,t){let r=e;do{const n=r.prev,x=r.next.next;!Z(n,x)&&d(n,r,r.next,x)&&g(n,x)&&g(x,n)&&(t.push(n.i,r.i,x.i),z(r),z(r.next),r=e=x),r=r.next}while(r!==e);return n(r)}function u(e,t,x,i,o,u){let f=e;do{let e=f.next.next;for(;e!==f.prev;){if(f.i!==e.i&&h(f,e)){let l=b(f,e);return f=n(f,f.next),l=n(l,l.next),r(f,t,x,i,o,u,0),void r(l,t,x,i,o,u,0)}e=e.next}f=f.next}while(f!==e)}function f(e,t){let n=e.x-t.x;if(0===n&&(n=e.y-t.y,0===n)){n=(e.next.y-e.y)/(e.next.x-e.x)-(t.next.y-t.y)/(t.next.x-t.x)}return n}function l(e,t){const r=function(e,t){let n=t;const r=e.x,x=e.y;let i,o=-1/0;if(Z(e,n))return n;do{if(Z(e,n.next))return n.next;if(x<=n.y&&x>=n.next.y&&n.next.y!==n.y){const e=n.x+(x-n.y)*(n.next.x-n.x)/(n.next.y-n.y);if(e<=r&&e>o&&(o=e,i=n.x<n.next.x?n:n.next,e===r))return i}n=n.next}while(n!==t);if(!i)return null;const u=i,f=i.x,l=i.y;let y=1/0;n=i;do{if(r>=n.x&&n.x>=f&&r!==n.x&&s(x<l?r:o,x,f,l,x<l?o:r,x,n.x,n.y)){const t=Math.abs(x-n.y)/(r-n.x);g(n,e)&&(t<y||t===y&&(n.x>i.x||n.x===i.x&&c(i,n)))&&(i=n,y=t)}n=n.next}while(n!==u);return i}(e,t);if(!r)return t;const x=b(r,e);return n(x,x.next),n(r,r.next)}function c(e,t){return a(e.prev,e,t.prev)<0&&a(t.next,e,e.next)<0}function y(e,t,n,r,x){return(e=1431655765&((e=858993459&((e=252645135&((e=16711935&((e=(e-n)*x|0)|e<<8))|e<<4))|e<<2))|e<<1))|(t=1431655765&((t=858993459&((t=252645135&((t=16711935&((t=(t-r)*x|0)|t<<8))|t<<4))|t<<2))|t<<1))<<1}function p(e){let t=e,n=e;do{(t.x<n.x||t.x===n.x&&t.y<n.y)&&(n=t),t=t.next}while(t!==e);return n}function s(e,t,n,r,x,i,o,u){return(x-o)*(t-u)>=(e-o)*(i-u)&&(e-o)*(r-u)>=(n-o)*(t-u)&&(n-o)*(i-u)>=(x-o)*(r-u)}function v(e,t,n,r,x,i,o,u){return!(e===o&&t===u)&&s(e,t,n,r,x,i,o,u)}function h(e,t){return e.next.i!==t.i&&e.prev.i!==t.i&&!function(e,t){let n=e;do{if(n.i!==e.i&&n.next.i!==e.i&&n.i!==t.i&&n.next.i!==t.i&&d(n,n.next,e,t))return!0;n=n.next}while(n!==e);return!1}(e,t)&&(g(e,t)&&g(t,e)&&function(e,t){let n=e,r=!1;const x=(e.x+t.x)/2,i=(e.y+t.y)/2;do{n.y>i!=n.next.y>i&&n.next.y!==n.y&&x<(n.next.x-n.x)*(i-n.y)/(n.next.y-n.y)+n.x&&(r=!r),n=n.next}while(n!==e);return r}(e,t)&&(a(e.prev,e,t.prev)||a(e,t.prev,t))||Z(e,t)&&a(e.prev,e,e.next)>0&&a(t.prev,t,t.next)>0)}function a(e,t,n){return(t.y-e.y)*(n.x-t.x)-(t.x-e.x)*(n.y-t.y)}function Z(e,t){return e.x===t.x&&e.y===t.y}function d(e,t,n,r){const x=m(a(e,t,n)),i=m(a(e,t,r)),o=m(a(n,r,e)),u=m(a(n,r,t));return x!==i&&o!==u||(!(0!==x||!M(e,n,t))||(!(0!==i||!M(e,r,t))||(!(0!==o||!M(n,e,r))||!(0!==u||!M(n,t,r)))))}function M(e,t,n){return t.x<=Math.max(e.x,n.x)&&t.x>=Math.min(e.x,n.x)&&t.y<=Math.max(e.y,n.y)&&t.y>=Math.min(e.y,n.y)}function m(e){return e>0?1:e<0?-1:0}function g(e,t){return a(e.prev,e,e.next)<0?a(e,t,e.next)>=0&&a(e,e.prev,t)>=0:a(e,t,e.prev)<0||a(e,e.next,t)<0}function b(e,t){const n=j(e.i,e.x,e.y),r=j(t.i,t.x,t.y),x=e.next,i=t.prev;return e.next=t,t.prev=e,n.next=x,x.prev=n,r.next=n,n.prev=r,i.next=r,r.prev=i,r}function w(e,t,n,r){const x=j(e,t,n);return r?(x.next=r.next,x.prev=r,r.next.prev=x,r.next=x):(x.prev=x,x.next=x),x}function z(e){e.next.prev=e.prev,e.prev.next=e.next,e.prevZ&&(e.prevZ.nextZ=e.nextZ),e.nextZ&&(e.nextZ.prevZ=e.prevZ)}function j(e,t,n){return{i:e,x:t,y:n,prev:null,next:null,z:0,prevZ:null,nextZ:null,steiner:!1}}function k(e,t,n,r){let x=0;for(let i=t,o=n-r;i<n;i+=r)x+=(e[o]-e[i])*(e[i+1]+e[o+1]),o=i;return x}e.default=function(e,n,x=2){const i=n&&n.length,o=i?n[0]*x:e.length;let u=t(e,0,o,x,!0);const c=[];if(!u||u.next===u.prev)return c;let y,s,v;if(i&&(u=function(e,n,r,x){const i=[];for(let r=0,o=n.length;r<o;r++){const u=t(e,n[r]*x,r<o-1?n[r+1]*x:e.length,x,!1);u===u.next&&(u.steiner=!0),i.push(p(u))}i.sort(f);for(let e=0;e<i.length;e++)r=l(i[e],r);return r}(e,n,u,x)),e.length>80*x){y=e[0],s=e[1];let t=y,n=s;for(let r=x;r<o;r+=x){const x=e[r],i=e[r+1];x<y&&(y=x),i<s&&(s=i),x>t&&(t=x),i>n&&(n=i)}v=Math.max(t-y,n-s),v=0!==v?32767/v:0}return r(u,c,x,y,s,v,0),c},e.deviation=function(e,t,n,r){const x=t&&t.length,i=x?t[0]*n:e.length;let o=Math.abs(k(e,0,i,n));if(x)for(let r=0,x=t.length;r<x;r++){const i=t[r]*n,u=r<x-1?t[r+1]*n:e.length;o-=Math.abs(k(e,i,u,n))}let u=0;for(let t=0;t<r.length;t+=3){const x=r[t]*n,i=r[t+1]*n,o=r[t+2]*n;u+=Math.abs((e[x]-e[o])*(e[i+1]-e[x+1])-(e[x]-e[i])*(e[o+1]-e[x+1]))}return 0===o&&0===u?0:Math.abs((u-o)/o)},e.flatten=function(e){const t=[],n=[],r=e[0][0].length;let x=0,i=0;for(const o of e){for(const e of o)for(let n=0;n<r;n++)t.push(e[n]);i&&(x+=i,n.push(x)),i=o.length}return{vertices:t,holes:n,dimensions:r}},Object.defineProperty(e,"__esModule",{value:!0})});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "earcut",
3
- "version": "3.0.0",
3
+ "version": "3.0.2",
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",
@@ -22,12 +22,9 @@
22
22
  "devDependencies": {
23
23
  "@rollup/plugin-terser": "^0.4.4",
24
24
  "benchmark": "^2.1.4",
25
- "coveralls": "^3.1.1",
26
- "eslint": "^9.5.0",
27
- "eslint-config-mourner": "^4.0.1",
28
- "rollup": "^4.18.0",
29
- "uglify-js": "^3.18.0",
30
- "watchify": "^4.0.0"
25
+ "eslint": "^9.31.0",
26
+ "eslint-config-mourner": "^4.1.0",
27
+ "rollup": "^4.45.1"
31
28
  },
32
29
  "eslintConfig": {
33
30
  "extends": "mourner",
package/src/earcut.js CHANGED
@@ -14,10 +14,10 @@ export default function earcut(data, holeIndices, dim = 2) {
14
14
 
15
15
  // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
16
16
  if (data.length > 80 * dim) {
17
- minX = Infinity;
18
- minY = Infinity;
19
- let maxX = -Infinity;
20
- let maxY = -Infinity;
17
+ minX = data[0];
18
+ minY = data[1];
19
+ let maxX = minX;
20
+ let maxY = minY;
21
21
 
22
22
  for (let i = dim; i < outerLen; i += dim) {
23
23
  const x = data[i];
@@ -140,16 +140,16 @@ function isEar(ear) {
140
140
  // now make sure we don't have other points inside the potential ear
141
141
  const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y;
142
142
 
143
- // triangle bbox; min & max are calculated like this for speed
144
- const x0 = ax < bx ? (ax < cx ? ax : cx) : (bx < cx ? bx : cx),
145
- y0 = ay < by ? (ay < cy ? ay : cy) : (by < cy ? by : cy),
146
- x1 = ax > bx ? (ax > cx ? ax : cx) : (bx > cx ? bx : cx),
147
- y1 = ay > by ? (ay > cy ? ay : cy) : (by > cy ? by : cy);
143
+ // triangle bbox
144
+ const x0 = Math.min(ax, bx, cx),
145
+ y0 = Math.min(ay, by, cy),
146
+ x1 = Math.max(ax, bx, cx),
147
+ y1 = Math.max(ay, by, cy);
148
148
 
149
149
  let p = c.next;
150
150
  while (p !== a) {
151
151
  if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 &&
152
- pointInTriangle(ax, ay, bx, by, cx, cy, p.x, p.y) &&
152
+ pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, p.x, p.y) &&
153
153
  area(p.prev, p, p.next) >= 0) return false;
154
154
  p = p.next;
155
155
  }
@@ -166,11 +166,11 @@ function isEarHashed(ear, minX, minY, invSize) {
166
166
 
167
167
  const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y;
168
168
 
169
- // triangle bbox; min & max are calculated like this for speed
170
- const x0 = ax < bx ? (ax < cx ? ax : cx) : (bx < cx ? bx : cx),
171
- y0 = ay < by ? (ay < cy ? ay : cy) : (by < cy ? by : cy),
172
- x1 = ax > bx ? (ax > cx ? ax : cx) : (bx > cx ? bx : cx),
173
- y1 = ay > by ? (ay > cy ? ay : cy) : (by > cy ? by : cy);
169
+ // triangle bbox
170
+ const x0 = Math.min(ax, bx, cx),
171
+ y0 = Math.min(ay, by, cy),
172
+ x1 = Math.max(ax, bx, cx),
173
+ y1 = Math.max(ay, by, cy);
174
174
 
175
175
  // z-order range for the current triangle bbox;
176
176
  const minZ = zOrder(x0, y0, minX, minY, invSize),
@@ -182,25 +182,25 @@ function isEarHashed(ear, minX, minY, invSize) {
182
182
  // look for points inside the triangle in both directions
183
183
  while (p && p.z >= minZ && n && n.z <= maxZ) {
184
184
  if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c &&
185
- pointInTriangle(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false;
185
+ pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false;
186
186
  p = p.prevZ;
187
187
 
188
188
  if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c &&
189
- pointInTriangle(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false;
189
+ pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false;
190
190
  n = n.nextZ;
191
191
  }
192
192
 
193
193
  // look for remaining points in decreasing z-order
194
194
  while (p && p.z >= minZ) {
195
195
  if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c &&
196
- pointInTriangle(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false;
196
+ pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false;
197
197
  p = p.prevZ;
198
198
  }
199
199
 
200
200
  // look for remaining points in increasing z-order
201
201
  while (n && n.z <= maxZ) {
202
202
  if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c &&
203
- pointInTriangle(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false;
203
+ pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false;
204
204
  n = n.nextZ;
205
205
  }
206
206
 
@@ -268,7 +268,7 @@ function eliminateHoles(data, holeIndices, outerNode, dim) {
268
268
  queue.push(getLeftmost(list));
269
269
  }
270
270
 
271
- queue.sort(compareX);
271
+ queue.sort(compareXYSlope);
272
272
 
273
273
  // process holes from left to right
274
274
  for (let i = 0; i < queue.length; i++) {
@@ -278,11 +278,22 @@ function eliminateHoles(data, holeIndices, outerNode, dim) {
278
278
  return outerNode;
279
279
  }
280
280
 
281
- function compareX(a, b) {
282
- return a.x - b.x;
281
+ function compareXYSlope(a, b) {
282
+ let result = a.x - b.x;
283
+ // when the left-most point of 2 holes meet at a vertex, sort the holes counterclockwise so that when we find
284
+ // the bridge to the outer shell is always the point that they meet at.
285
+ if (result === 0) {
286
+ result = a.y - b.y;
287
+ if (result === 0) {
288
+ const aSlope = (a.next.y - a.y) / (a.next.x - a.x);
289
+ const bSlope = (b.next.y - b.y) / (b.next.x - b.x);
290
+ result = aSlope - bSlope;
291
+ }
292
+ }
293
+ return result;
283
294
  }
284
295
 
285
- // find a bridge between vertices that connects hole with an outer ring and and link it
296
+ // find a bridge between vertices that connects hole with an outer ring and link it
286
297
  function eliminateHole(hole, outerNode) {
287
298
  const bridge = findHoleBridge(hole, outerNode);
288
299
  if (!bridge) {
@@ -306,8 +317,11 @@ function findHoleBridge(hole, outerNode) {
306
317
 
307
318
  // find a segment intersected by a ray from the hole's leftmost point to the left;
308
319
  // segment's endpoint with lesser x will be potential connection point
320
+ // unless they intersect at a vertex, then choose the vertex
321
+ if (equals(hole, p)) return p;
309
322
  do {
310
- if (hy <= p.y && hy >= p.next.y && p.next.y !== p.y) {
323
+ if (equals(hole, p.next)) return p.next;
324
+ else if (hy <= p.y && hy >= p.next.y && p.next.y !== p.y) {
311
325
  const x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y);
312
326
  if (x <= hx && x > qx) {
313
327
  qx = x;
@@ -463,9 +477,14 @@ function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) {
463
477
  (bx - px) * (cy - py) >= (cx - px) * (by - py);
464
478
  }
465
479
 
480
+ // check if a point lies within a convex triangle but false if its equal to the first point of the triangle
481
+ function pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, px, py) {
482
+ return !(ax === px && ay === py) && pointInTriangle(ax, ay, bx, by, cx, cy, px, py);
483
+ }
484
+
466
485
  // check if a diagonal between two polygon nodes is valid (lies in polygon interior)
467
486
  function isValidDiagonal(a, b) {
468
- return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) && // dones't intersect other edges
487
+ return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) && // doesn't intersect other edges
469
488
  (locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b) && // locally visible
470
489
  (area(a.prev, a, b.prev) || area(a, b.prev, b)) || // does not create opposite-facing sectors
471
490
  equals(a, b) && area(a.prev, a, a.next) > 0 && area(b.prev, b, b.next) > 0); // special zero-length case