earcut 2.2.4 → 3.0.0
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/README.md +21 -156
- package/dist/earcut.dev.js +134 -145
- package/dist/earcut.min.js +1 -1
- package/package.json +19 -19
- package/src/earcut.js +123 -141
package/dist/earcut.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(e){"object"==typeof exports&&"undefined"!=typeof module?
|
|
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})}));
|
package/package.json
CHANGED
|
@@ -1,40 +1,40 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "earcut",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "The fastest and smallest JavaScript polygon triangulation library for your WebGL apps",
|
|
5
5
|
"main": "src/earcut.js",
|
|
6
|
-
"
|
|
7
|
-
"
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": "./src/earcut.js",
|
|
8
8
|
"files": [
|
|
9
|
+
"src/earcut.js",
|
|
9
10
|
"dist/earcut.min.js",
|
|
10
11
|
"dist/earcut.dev.js"
|
|
11
12
|
],
|
|
12
13
|
"scripts": {
|
|
13
|
-
"pretest": "eslint src test/test.js",
|
|
14
|
-
"test": "
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"prepublishOnly": "npm run build-dev && npm run build-min",
|
|
19
|
-
"cov": "c8 tape test/*.js",
|
|
20
|
-
"coveralls": "npm run cov && c8 report -r lcov && coveralls < ./coverage/lcov.info"
|
|
14
|
+
"pretest": "eslint src test/test.js bench/*.js viz/viz.js",
|
|
15
|
+
"test": "node --test",
|
|
16
|
+
"build": "rollup -c",
|
|
17
|
+
"prepublishOnly": "npm run build",
|
|
18
|
+
"cov": "node --test --experimental-test-coverage"
|
|
21
19
|
},
|
|
22
20
|
"author": "Vladimir Agafonkin",
|
|
23
21
|
"license": "ISC",
|
|
24
22
|
"devDependencies": {
|
|
23
|
+
"@rollup/plugin-terser": "^0.4.4",
|
|
25
24
|
"benchmark": "^2.1.4",
|
|
26
|
-
"browserify": "^17.0.0",
|
|
27
|
-
"c8": "^7.11.3",
|
|
28
25
|
"coveralls": "^3.1.1",
|
|
29
|
-
"eslint": "^
|
|
30
|
-
"eslint-config-mourner": "^
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"uglify-js": "^3.16.2",
|
|
26
|
+
"eslint": "^9.5.0",
|
|
27
|
+
"eslint-config-mourner": "^4.0.1",
|
|
28
|
+
"rollup": "^4.18.0",
|
|
29
|
+
"uglify-js": "^3.18.0",
|
|
34
30
|
"watchify": "^4.0.0"
|
|
35
31
|
},
|
|
36
32
|
"eslintConfig": {
|
|
37
|
-
"extends": "mourner"
|
|
33
|
+
"extends": "mourner",
|
|
34
|
+
"parserOptions": {
|
|
35
|
+
"sourceType": "module",
|
|
36
|
+
"ecmaVersion": 2020
|
|
37
|
+
}
|
|
38
38
|
},
|
|
39
39
|
"repository": {
|
|
40
40
|
"type": "git",
|
package/src/earcut.js
CHANGED
|
@@ -1,31 +1,27 @@
|
|
|
1
|
-
'use strict';
|
|
2
1
|
|
|
3
|
-
|
|
4
|
-
module.exports.default = earcut;
|
|
2
|
+
export default function earcut(data, holeIndices, dim = 2) {
|
|
5
3
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
var hasHoles = holeIndices && holeIndices.length,
|
|
11
|
-
outerLen = hasHoles ? holeIndices[0] * dim : data.length,
|
|
12
|
-
outerNode = linkedList(data, 0, outerLen, dim, true),
|
|
13
|
-
triangles = [];
|
|
4
|
+
const hasHoles = holeIndices && holeIndices.length;
|
|
5
|
+
const outerLen = hasHoles ? holeIndices[0] * dim : data.length;
|
|
6
|
+
let outerNode = linkedList(data, 0, outerLen, dim, true);
|
|
7
|
+
const triangles = [];
|
|
14
8
|
|
|
15
9
|
if (!outerNode || outerNode.next === outerNode.prev) return triangles;
|
|
16
10
|
|
|
17
|
-
|
|
11
|
+
let minX, minY, invSize;
|
|
18
12
|
|
|
19
13
|
if (hasHoles) outerNode = eliminateHoles(data, holeIndices, outerNode, dim);
|
|
20
14
|
|
|
21
15
|
// if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
|
|
22
16
|
if (data.length > 80 * dim) {
|
|
23
|
-
minX =
|
|
24
|
-
minY =
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
17
|
+
minX = Infinity;
|
|
18
|
+
minY = Infinity;
|
|
19
|
+
let maxX = -Infinity;
|
|
20
|
+
let maxY = -Infinity;
|
|
21
|
+
|
|
22
|
+
for (let i = dim; i < outerLen; i += dim) {
|
|
23
|
+
const x = data[i];
|
|
24
|
+
const y = data[i + 1];
|
|
29
25
|
if (x < minX) minX = x;
|
|
30
26
|
if (y < minY) minY = y;
|
|
31
27
|
if (x > maxX) maxX = x;
|
|
@@ -44,12 +40,12 @@ function earcut(data, holeIndices, dim) {
|
|
|
44
40
|
|
|
45
41
|
// create a circular doubly linked list from polygon points in the specified winding order
|
|
46
42
|
function linkedList(data, start, end, dim, clockwise) {
|
|
47
|
-
|
|
43
|
+
let last;
|
|
48
44
|
|
|
49
45
|
if (clockwise === (signedArea(data, start, end, dim) > 0)) {
|
|
50
|
-
for (i = start; i < end; i += dim) last = insertNode(i, data[i], data[i + 1], last);
|
|
46
|
+
for (let i = start; i < end; i += dim) last = insertNode(i / dim | 0, data[i], data[i + 1], last);
|
|
51
47
|
} else {
|
|
52
|
-
for (i = end - dim; i >= start; i -= dim) last = insertNode(i, data[i], data[i + 1], last);
|
|
48
|
+
for (let i = end - dim; i >= start; i -= dim) last = insertNode(i / dim | 0, data[i], data[i + 1], last);
|
|
53
49
|
}
|
|
54
50
|
|
|
55
51
|
if (last && equals(last, last.next)) {
|
|
@@ -65,7 +61,7 @@ function filterPoints(start, end) {
|
|
|
65
61
|
if (!start) return start;
|
|
66
62
|
if (!end) end = start;
|
|
67
63
|
|
|
68
|
-
|
|
64
|
+
let p = start,
|
|
69
65
|
again;
|
|
70
66
|
do {
|
|
71
67
|
again = false;
|
|
@@ -91,19 +87,15 @@ function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) {
|
|
|
91
87
|
// interlink polygon nodes in z-order
|
|
92
88
|
if (!pass && invSize) indexCurve(ear, minX, minY, invSize);
|
|
93
89
|
|
|
94
|
-
|
|
95
|
-
prev, next;
|
|
90
|
+
let stop = ear;
|
|
96
91
|
|
|
97
92
|
// iterate through ears, slicing them one by one
|
|
98
93
|
while (ear.prev !== ear.next) {
|
|
99
|
-
prev = ear.prev;
|
|
100
|
-
next = ear.next;
|
|
94
|
+
const prev = ear.prev;
|
|
95
|
+
const next = ear.next;
|
|
101
96
|
|
|
102
97
|
if (invSize ? isEarHashed(ear, minX, minY, invSize) : isEar(ear)) {
|
|
103
|
-
// cut off the triangle
|
|
104
|
-
triangles.push(prev.i / dim | 0);
|
|
105
|
-
triangles.push(ear.i / dim | 0);
|
|
106
|
-
triangles.push(next.i / dim | 0);
|
|
98
|
+
triangles.push(prev.i, ear.i, next.i); // cut off the triangle
|
|
107
99
|
|
|
108
100
|
removeNode(ear);
|
|
109
101
|
|
|
@@ -124,7 +116,7 @@ function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) {
|
|
|
124
116
|
|
|
125
117
|
// if this didn't work, try curing all small self-intersections locally
|
|
126
118
|
} else if (pass === 1) {
|
|
127
|
-
ear = cureLocalIntersections(filterPoints(ear), triangles
|
|
119
|
+
ear = cureLocalIntersections(filterPoints(ear), triangles);
|
|
128
120
|
earcutLinked(ear, triangles, dim, minX, minY, invSize, 2);
|
|
129
121
|
|
|
130
122
|
// as a last resort, try splitting the remaining polygon into two
|
|
@@ -139,22 +131,22 @@ function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) {
|
|
|
139
131
|
|
|
140
132
|
// check whether a polygon node forms a valid ear with adjacent nodes
|
|
141
133
|
function isEar(ear) {
|
|
142
|
-
|
|
134
|
+
const a = ear.prev,
|
|
143
135
|
b = ear,
|
|
144
136
|
c = ear.next;
|
|
145
137
|
|
|
146
138
|
if (area(a, b, c) >= 0) return false; // reflex, can't be an ear
|
|
147
139
|
|
|
148
140
|
// now make sure we don't have other points inside the potential ear
|
|
149
|
-
|
|
141
|
+
const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y;
|
|
150
142
|
|
|
151
143
|
// triangle bbox; min & max are calculated like this for speed
|
|
152
|
-
|
|
144
|
+
const x0 = ax < bx ? (ax < cx ? ax : cx) : (bx < cx ? bx : cx),
|
|
153
145
|
y0 = ay < by ? (ay < cy ? ay : cy) : (by < cy ? by : cy),
|
|
154
146
|
x1 = ax > bx ? (ax > cx ? ax : cx) : (bx > cx ? bx : cx),
|
|
155
147
|
y1 = ay > by ? (ay > cy ? ay : cy) : (by > cy ? by : cy);
|
|
156
148
|
|
|
157
|
-
|
|
149
|
+
let p = c.next;
|
|
158
150
|
while (p !== a) {
|
|
159
151
|
if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 &&
|
|
160
152
|
pointInTriangle(ax, ay, bx, by, cx, cy, p.x, p.y) &&
|
|
@@ -166,25 +158,25 @@ function isEar(ear) {
|
|
|
166
158
|
}
|
|
167
159
|
|
|
168
160
|
function isEarHashed(ear, minX, minY, invSize) {
|
|
169
|
-
|
|
161
|
+
const a = ear.prev,
|
|
170
162
|
b = ear,
|
|
171
163
|
c = ear.next;
|
|
172
164
|
|
|
173
165
|
if (area(a, b, c) >= 0) return false; // reflex, can't be an ear
|
|
174
166
|
|
|
175
|
-
|
|
167
|
+
const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y;
|
|
176
168
|
|
|
177
169
|
// triangle bbox; min & max are calculated like this for speed
|
|
178
|
-
|
|
170
|
+
const x0 = ax < bx ? (ax < cx ? ax : cx) : (bx < cx ? bx : cx),
|
|
179
171
|
y0 = ay < by ? (ay < cy ? ay : cy) : (by < cy ? by : cy),
|
|
180
172
|
x1 = ax > bx ? (ax > cx ? ax : cx) : (bx > cx ? bx : cx),
|
|
181
173
|
y1 = ay > by ? (ay > cy ? ay : cy) : (by > cy ? by : cy);
|
|
182
174
|
|
|
183
175
|
// z-order range for the current triangle bbox;
|
|
184
|
-
|
|
176
|
+
const minZ = zOrder(x0, y0, minX, minY, invSize),
|
|
185
177
|
maxZ = zOrder(x1, y1, minX, minY, invSize);
|
|
186
178
|
|
|
187
|
-
|
|
179
|
+
let p = ear.prevZ,
|
|
188
180
|
n = ear.nextZ;
|
|
189
181
|
|
|
190
182
|
// look for points inside the triangle in both directions
|
|
@@ -216,17 +208,15 @@ function isEarHashed(ear, minX, minY, invSize) {
|
|
|
216
208
|
}
|
|
217
209
|
|
|
218
210
|
// go through all polygon nodes and cure small local self-intersections
|
|
219
|
-
function cureLocalIntersections(start, triangles
|
|
220
|
-
|
|
211
|
+
function cureLocalIntersections(start, triangles) {
|
|
212
|
+
let p = start;
|
|
221
213
|
do {
|
|
222
|
-
|
|
214
|
+
const a = p.prev,
|
|
223
215
|
b = p.next.next;
|
|
224
216
|
|
|
225
217
|
if (!equals(a, b) && intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) {
|
|
226
218
|
|
|
227
|
-
triangles.push(a.i
|
|
228
|
-
triangles.push(p.i / dim | 0);
|
|
229
|
-
triangles.push(b.i / dim | 0);
|
|
219
|
+
triangles.push(a.i, p.i, b.i);
|
|
230
220
|
|
|
231
221
|
// remove two nodes involved
|
|
232
222
|
removeNode(p);
|
|
@@ -243,13 +233,13 @@ function cureLocalIntersections(start, triangles, dim) {
|
|
|
243
233
|
// try splitting polygon into two and triangulate them independently
|
|
244
234
|
function splitEarcut(start, triangles, dim, minX, minY, invSize) {
|
|
245
235
|
// look for a valid diagonal that divides the polygon into two
|
|
246
|
-
|
|
236
|
+
let a = start;
|
|
247
237
|
do {
|
|
248
|
-
|
|
238
|
+
let b = a.next.next;
|
|
249
239
|
while (b !== a.prev) {
|
|
250
240
|
if (a.i !== b.i && isValidDiagonal(a, b)) {
|
|
251
241
|
// split the polygon in two by the diagonal
|
|
252
|
-
|
|
242
|
+
let c = splitPolygon(a, b);
|
|
253
243
|
|
|
254
244
|
// filter colinear points around the cuts
|
|
255
245
|
a = filterPoints(a, a.next);
|
|
@@ -268,13 +258,12 @@ function splitEarcut(start, triangles, dim, minX, minY, invSize) {
|
|
|
268
258
|
|
|
269
259
|
// link every hole into the outer loop, producing a single-ring polygon without holes
|
|
270
260
|
function eliminateHoles(data, holeIndices, outerNode, dim) {
|
|
271
|
-
|
|
272
|
-
i, len, start, end, list;
|
|
261
|
+
const queue = [];
|
|
273
262
|
|
|
274
|
-
for (i = 0, len = holeIndices.length; i < len; i++) {
|
|
275
|
-
start = holeIndices[i] * dim;
|
|
276
|
-
end = i < len - 1 ? holeIndices[i + 1] * dim : data.length;
|
|
277
|
-
list = linkedList(data, start, end, dim, false);
|
|
263
|
+
for (let i = 0, len = holeIndices.length; i < len; i++) {
|
|
264
|
+
const start = holeIndices[i] * dim;
|
|
265
|
+
const end = i < len - 1 ? holeIndices[i + 1] * dim : data.length;
|
|
266
|
+
const list = linkedList(data, start, end, dim, false);
|
|
278
267
|
if (list === list.next) list.steiner = true;
|
|
279
268
|
queue.push(getLeftmost(list));
|
|
280
269
|
}
|
|
@@ -282,7 +271,7 @@ function eliminateHoles(data, holeIndices, outerNode, dim) {
|
|
|
282
271
|
queue.sort(compareX);
|
|
283
272
|
|
|
284
273
|
// process holes from left to right
|
|
285
|
-
for (i = 0; i < queue.length; i++) {
|
|
274
|
+
for (let i = 0; i < queue.length; i++) {
|
|
286
275
|
outerNode = eliminateHole(queue[i], outerNode);
|
|
287
276
|
}
|
|
288
277
|
|
|
@@ -295,12 +284,12 @@ function compareX(a, b) {
|
|
|
295
284
|
|
|
296
285
|
// find a bridge between vertices that connects hole with an outer ring and and link it
|
|
297
286
|
function eliminateHole(hole, outerNode) {
|
|
298
|
-
|
|
287
|
+
const bridge = findHoleBridge(hole, outerNode);
|
|
299
288
|
if (!bridge) {
|
|
300
289
|
return outerNode;
|
|
301
290
|
}
|
|
302
291
|
|
|
303
|
-
|
|
292
|
+
const bridgeReverse = splitPolygon(bridge, hole);
|
|
304
293
|
|
|
305
294
|
// filter collinear points around the cuts
|
|
306
295
|
filterPoints(bridgeReverse, bridgeReverse.next);
|
|
@@ -309,17 +298,17 @@ function eliminateHole(hole, outerNode) {
|
|
|
309
298
|
|
|
310
299
|
// David Eberly's algorithm for finding a bridge between hole and outer polygon
|
|
311
300
|
function findHoleBridge(hole, outerNode) {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
301
|
+
let p = outerNode;
|
|
302
|
+
const hx = hole.x;
|
|
303
|
+
const hy = hole.y;
|
|
304
|
+
let qx = -Infinity;
|
|
305
|
+
let m;
|
|
317
306
|
|
|
318
307
|
// find a segment intersected by a ray from the hole's leftmost point to the left;
|
|
319
308
|
// segment's endpoint with lesser x will be potential connection point
|
|
320
309
|
do {
|
|
321
310
|
if (hy <= p.y && hy >= p.next.y && p.next.y !== p.y) {
|
|
322
|
-
|
|
311
|
+
const x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y);
|
|
323
312
|
if (x <= hx && x > qx) {
|
|
324
313
|
qx = x;
|
|
325
314
|
m = p.x < p.next.x ? p : p.next;
|
|
@@ -335,11 +324,10 @@ function findHoleBridge(hole, outerNode) {
|
|
|
335
324
|
// if there are no points found, we have a valid connection;
|
|
336
325
|
// otherwise choose the point of the minimum angle with the ray as connection point
|
|
337
326
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
tan;
|
|
327
|
+
const stop = m;
|
|
328
|
+
const mx = m.x;
|
|
329
|
+
const my = m.y;
|
|
330
|
+
let tanMin = Infinity;
|
|
343
331
|
|
|
344
332
|
p = m;
|
|
345
333
|
|
|
@@ -347,7 +335,7 @@ function findHoleBridge(hole, outerNode) {
|
|
|
347
335
|
if (hx >= p.x && p.x >= mx && hx !== p.x &&
|
|
348
336
|
pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y)) {
|
|
349
337
|
|
|
350
|
-
tan = Math.abs(hy - p.y) / (hx - p.x); // tangential
|
|
338
|
+
const tan = Math.abs(hy - p.y) / (hx - p.x); // tangential
|
|
351
339
|
|
|
352
340
|
if (locallyInside(p, hole) &&
|
|
353
341
|
(tan < tanMin || (tan === tanMin && (p.x > m.x || (p.x === m.x && sectorContainsSector(m, p)))))) {
|
|
@@ -369,7 +357,7 @@ function sectorContainsSector(m, p) {
|
|
|
369
357
|
|
|
370
358
|
// interlink polygon nodes in z-order
|
|
371
359
|
function indexCurve(start, minX, minY, invSize) {
|
|
372
|
-
|
|
360
|
+
let p = start;
|
|
373
361
|
do {
|
|
374
362
|
if (p.z === 0) p.z = zOrder(p.x, p.y, minX, minY, invSize);
|
|
375
363
|
p.prevZ = p.prev;
|
|
@@ -386,25 +374,26 @@ function indexCurve(start, minX, minY, invSize) {
|
|
|
386
374
|
// Simon Tatham's linked list merge sort algorithm
|
|
387
375
|
// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
|
|
388
376
|
function sortLinked(list) {
|
|
389
|
-
|
|
390
|
-
|
|
377
|
+
let numMerges;
|
|
378
|
+
let inSize = 1;
|
|
391
379
|
|
|
392
380
|
do {
|
|
393
|
-
p = list;
|
|
381
|
+
let p = list;
|
|
382
|
+
let e;
|
|
394
383
|
list = null;
|
|
395
|
-
tail = null;
|
|
384
|
+
let tail = null;
|
|
396
385
|
numMerges = 0;
|
|
397
386
|
|
|
398
387
|
while (p) {
|
|
399
388
|
numMerges++;
|
|
400
|
-
q = p;
|
|
401
|
-
pSize = 0;
|
|
402
|
-
for (i = 0; i < inSize; i++) {
|
|
389
|
+
let q = p;
|
|
390
|
+
let pSize = 0;
|
|
391
|
+
for (let i = 0; i < inSize; i++) {
|
|
403
392
|
pSize++;
|
|
404
393
|
q = q.nextZ;
|
|
405
394
|
if (!q) break;
|
|
406
395
|
}
|
|
407
|
-
qSize = inSize;
|
|
396
|
+
let qSize = inSize;
|
|
408
397
|
|
|
409
398
|
while (pSize > 0 || (qSize > 0 && q)) {
|
|
410
399
|
|
|
@@ -457,7 +446,7 @@ function zOrder(x, y, minX, minY, invSize) {
|
|
|
457
446
|
|
|
458
447
|
// find the leftmost node of a polygon ring
|
|
459
448
|
function getLeftmost(start) {
|
|
460
|
-
|
|
449
|
+
let p = start,
|
|
461
450
|
leftmost = start;
|
|
462
451
|
do {
|
|
463
452
|
if (p.x < leftmost.x || (p.x === leftmost.x && p.y < leftmost.y)) leftmost = p;
|
|
@@ -494,10 +483,10 @@ function equals(p1, p2) {
|
|
|
494
483
|
|
|
495
484
|
// check if two segments intersect
|
|
496
485
|
function intersects(p1, q1, p2, q2) {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
486
|
+
const o1 = sign(area(p1, q1, p2));
|
|
487
|
+
const o2 = sign(area(p1, q1, q2));
|
|
488
|
+
const o3 = sign(area(p2, q2, p1));
|
|
489
|
+
const o4 = sign(area(p2, q2, q1));
|
|
501
490
|
|
|
502
491
|
if (o1 !== o2 && o3 !== o4) return true; // general case
|
|
503
492
|
|
|
@@ -520,7 +509,7 @@ function sign(num) {
|
|
|
520
509
|
|
|
521
510
|
// check if a polygon diagonal intersects any polygon segments
|
|
522
511
|
function intersectsPolygon(a, b) {
|
|
523
|
-
|
|
512
|
+
let p = a;
|
|
524
513
|
do {
|
|
525
514
|
if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i &&
|
|
526
515
|
intersects(p, p.next, a, b)) return true;
|
|
@@ -539,10 +528,10 @@ function locallyInside(a, b) {
|
|
|
539
528
|
|
|
540
529
|
// check if the middle point of a polygon diagonal is inside the polygon
|
|
541
530
|
function middleInside(a, b) {
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
531
|
+
let p = a;
|
|
532
|
+
let inside = false;
|
|
533
|
+
const px = (a.x + b.x) / 2;
|
|
534
|
+
const py = (a.y + b.y) / 2;
|
|
546
535
|
do {
|
|
547
536
|
if (((p.y > py) !== (p.next.y > py)) && p.next.y !== p.y &&
|
|
548
537
|
(px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x))
|
|
@@ -556,8 +545,8 @@ function middleInside(a, b) {
|
|
|
556
545
|
// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two;
|
|
557
546
|
// if one belongs to the outer ring and another to a hole, it merges it into a single ring
|
|
558
547
|
function splitPolygon(a, b) {
|
|
559
|
-
|
|
560
|
-
b2 =
|
|
548
|
+
const a2 = createNode(a.i, a.x, a.y),
|
|
549
|
+
b2 = createNode(b.i, b.x, b.y),
|
|
561
550
|
an = a.next,
|
|
562
551
|
bp = b.prev;
|
|
563
552
|
|
|
@@ -578,7 +567,7 @@ function splitPolygon(a, b) {
|
|
|
578
567
|
|
|
579
568
|
// create a node and optionally link it with previous one (in a circular doubly linked list)
|
|
580
569
|
function insertNode(i, x, y, last) {
|
|
581
|
-
|
|
570
|
+
const p = createNode(i, x, y);
|
|
582
571
|
|
|
583
572
|
if (!last) {
|
|
584
573
|
p.prev = p;
|
|
@@ -601,49 +590,39 @@ function removeNode(p) {
|
|
|
601
590
|
if (p.nextZ) p.nextZ.prevZ = p.prevZ;
|
|
602
591
|
}
|
|
603
592
|
|
|
604
|
-
function
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
// z-order curve value
|
|
617
|
-
this.z = 0;
|
|
618
|
-
|
|
619
|
-
// previous and next nodes in z-order
|
|
620
|
-
this.prevZ = null;
|
|
621
|
-
this.nextZ = null;
|
|
622
|
-
|
|
623
|
-
// indicates whether this is a steiner point
|
|
624
|
-
this.steiner = false;
|
|
593
|
+
function createNode(i, x, y) {
|
|
594
|
+
return {
|
|
595
|
+
i, // vertex index in coordinates array
|
|
596
|
+
x, y, // vertex coordinates
|
|
597
|
+
prev: null, // previous and next vertex nodes in a polygon ring
|
|
598
|
+
next: null,
|
|
599
|
+
z: 0, // z-order curve value
|
|
600
|
+
prevZ: null, // previous and next nodes in z-order
|
|
601
|
+
nextZ: null,
|
|
602
|
+
steiner: false // indicates whether this is a steiner point
|
|
603
|
+
};
|
|
625
604
|
}
|
|
626
605
|
|
|
627
606
|
// return a percentage difference between the polygon area and its triangulation area;
|
|
628
607
|
// used to verify correctness of triangulation
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
608
|
+
export function deviation(data, holeIndices, dim, triangles) {
|
|
609
|
+
const hasHoles = holeIndices && holeIndices.length;
|
|
610
|
+
const outerLen = hasHoles ? holeIndices[0] * dim : data.length;
|
|
632
611
|
|
|
633
|
-
|
|
612
|
+
let polygonArea = Math.abs(signedArea(data, 0, outerLen, dim));
|
|
634
613
|
if (hasHoles) {
|
|
635
|
-
for (
|
|
636
|
-
|
|
637
|
-
|
|
614
|
+
for (let i = 0, len = holeIndices.length; i < len; i++) {
|
|
615
|
+
const start = holeIndices[i] * dim;
|
|
616
|
+
const end = i < len - 1 ? holeIndices[i + 1] * dim : data.length;
|
|
638
617
|
polygonArea -= Math.abs(signedArea(data, start, end, dim));
|
|
639
618
|
}
|
|
640
619
|
}
|
|
641
620
|
|
|
642
|
-
|
|
643
|
-
for (i = 0; i < triangles.length; i += 3) {
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
621
|
+
let trianglesArea = 0;
|
|
622
|
+
for (let i = 0; i < triangles.length; i += 3) {
|
|
623
|
+
const a = triangles[i] * dim;
|
|
624
|
+
const b = triangles[i + 1] * dim;
|
|
625
|
+
const c = triangles[i + 2] * dim;
|
|
647
626
|
trianglesArea += Math.abs(
|
|
648
627
|
(data[a] - data[c]) * (data[b + 1] - data[a + 1]) -
|
|
649
628
|
(data[a] - data[b]) * (data[c + 1] - data[a + 1]));
|
|
@@ -651,11 +630,11 @@ earcut.deviation = function (data, holeIndices, dim, triangles) {
|
|
|
651
630
|
|
|
652
631
|
return polygonArea === 0 && trianglesArea === 0 ? 0 :
|
|
653
632
|
Math.abs((trianglesArea - polygonArea) / polygonArea);
|
|
654
|
-
}
|
|
633
|
+
}
|
|
655
634
|
|
|
656
635
|
function signedArea(data, start, end, dim) {
|
|
657
|
-
|
|
658
|
-
for (
|
|
636
|
+
let sum = 0;
|
|
637
|
+
for (let i = start, j = end - dim; i < end; i += dim) {
|
|
659
638
|
sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]);
|
|
660
639
|
j = i;
|
|
661
640
|
}
|
|
@@ -663,19 +642,22 @@ function signedArea(data, start, end, dim) {
|
|
|
663
642
|
}
|
|
664
643
|
|
|
665
644
|
// turn a polygon in a multi-dimensional array form (e.g. as in GeoJSON) into a form Earcut accepts
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
645
|
+
export function flatten(data) {
|
|
646
|
+
const vertices = [];
|
|
647
|
+
const holes = [];
|
|
648
|
+
const dimensions = data[0][0].length;
|
|
649
|
+
let holeIndex = 0;
|
|
650
|
+
let prevLen = 0;
|
|
651
|
+
|
|
652
|
+
for (const ring of data) {
|
|
653
|
+
for (const p of ring) {
|
|
654
|
+
for (let d = 0; d < dimensions; d++) vertices.push(p[d]);
|
|
674
655
|
}
|
|
675
|
-
if (
|
|
676
|
-
holeIndex +=
|
|
677
|
-
|
|
656
|
+
if (prevLen) {
|
|
657
|
+
holeIndex += prevLen;
|
|
658
|
+
holes.push(holeIndex);
|
|
678
659
|
}
|
|
660
|
+
prevLen = ring.length;
|
|
679
661
|
}
|
|
680
|
-
return
|
|
681
|
-
}
|
|
662
|
+
return {vertices, holes, dimensions};
|
|
663
|
+
}
|