earcut 2.2.3 → 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 +22 -148
- package/dist/earcut.dev.js +167 -181
- package/dist/earcut.min.js +1 -1
- package/package.json +17 -20
- package/src/earcut.js +156 -177
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;
|
|
@@ -34,22 +30,22 @@ function earcut(data, holeIndices, dim) {
|
|
|
34
30
|
|
|
35
31
|
// minX, minY and invSize are later used to transform coords into integers for z-order calculation
|
|
36
32
|
invSize = Math.max(maxX - minX, maxY - minY);
|
|
37
|
-
invSize = invSize !== 0 ?
|
|
33
|
+
invSize = invSize !== 0 ? 32767 / invSize : 0;
|
|
38
34
|
}
|
|
39
35
|
|
|
40
|
-
earcutLinked(outerNode, triangles, dim, minX, minY, invSize);
|
|
36
|
+
earcutLinked(outerNode, triangles, dim, minX, minY, invSize, 0);
|
|
41
37
|
|
|
42
38
|
return triangles;
|
|
43
39
|
}
|
|
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);
|
|
105
|
-
triangles.push(ear.i / dim);
|
|
106
|
-
triangles.push(next.i / dim);
|
|
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,17 +131,25 @@ 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
|
-
|
|
152
|
-
|
|
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);
|
|
148
|
+
|
|
149
|
+
let p = c.next;
|
|
150
|
+
while (p !== a) {
|
|
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) &&
|
|
153
153
|
area(p.prev, p, p.next) >= 0) return false;
|
|
154
154
|
p = p.next;
|
|
155
155
|
}
|
|
@@ -158,51 +158,49 @@ function isEar(ear) {
|
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
function isEarHashed(ear, minX, minY, invSize) {
|
|
161
|
-
|
|
161
|
+
const a = ear.prev,
|
|
162
162
|
b = ear,
|
|
163
163
|
c = ear.next;
|
|
164
164
|
|
|
165
165
|
if (area(a, b, c) >= 0) return false; // reflex, can't be an ear
|
|
166
166
|
|
|
167
|
+
const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y;
|
|
168
|
+
|
|
167
169
|
// triangle bbox; min & max are calculated like this for speed
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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);
|
|
172
174
|
|
|
173
175
|
// z-order range for the current triangle bbox;
|
|
174
|
-
|
|
175
|
-
maxZ = zOrder(
|
|
176
|
+
const minZ = zOrder(x0, y0, minX, minY, invSize),
|
|
177
|
+
maxZ = zOrder(x1, y1, minX, minY, invSize);
|
|
176
178
|
|
|
177
|
-
|
|
179
|
+
let p = ear.prevZ,
|
|
178
180
|
n = ear.nextZ;
|
|
179
181
|
|
|
180
182
|
// look for points inside the triangle in both directions
|
|
181
183
|
while (p && p.z >= minZ && n && n.z <= maxZ) {
|
|
182
|
-
if (p
|
|
183
|
-
pointInTriangle(
|
|
184
|
-
area(p.prev, p, p.next) >= 0) return false;
|
|
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
186
|
p = p.prevZ;
|
|
186
187
|
|
|
187
|
-
if (n
|
|
188
|
-
pointInTriangle(
|
|
189
|
-
area(n.prev, n, n.next) >= 0) return false;
|
|
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;
|
|
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
|
-
if (p
|
|
196
|
-
pointInTriangle(
|
|
197
|
-
area(p.prev, p, p.next) >= 0) return false;
|
|
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;
|
|
198
197
|
p = p.prevZ;
|
|
199
198
|
}
|
|
200
199
|
|
|
201
200
|
// look for remaining points in increasing z-order
|
|
202
201
|
while (n && n.z <= maxZ) {
|
|
203
|
-
if (n
|
|
204
|
-
pointInTriangle(
|
|
205
|
-
area(n.prev, n, n.next) >= 0) return false;
|
|
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;
|
|
206
204
|
n = n.nextZ;
|
|
207
205
|
}
|
|
208
206
|
|
|
@@ -210,17 +208,15 @@ function isEarHashed(ear, minX, minY, invSize) {
|
|
|
210
208
|
}
|
|
211
209
|
|
|
212
210
|
// go through all polygon nodes and cure small local self-intersections
|
|
213
|
-
function cureLocalIntersections(start, triangles
|
|
214
|
-
|
|
211
|
+
function cureLocalIntersections(start, triangles) {
|
|
212
|
+
let p = start;
|
|
215
213
|
do {
|
|
216
|
-
|
|
214
|
+
const a = p.prev,
|
|
217
215
|
b = p.next.next;
|
|
218
216
|
|
|
219
217
|
if (!equals(a, b) && intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) {
|
|
220
218
|
|
|
221
|
-
triangles.push(a.i
|
|
222
|
-
triangles.push(p.i / dim);
|
|
223
|
-
triangles.push(b.i / dim);
|
|
219
|
+
triangles.push(a.i, p.i, b.i);
|
|
224
220
|
|
|
225
221
|
// remove two nodes involved
|
|
226
222
|
removeNode(p);
|
|
@@ -237,21 +233,21 @@ function cureLocalIntersections(start, triangles, dim) {
|
|
|
237
233
|
// try splitting polygon into two and triangulate them independently
|
|
238
234
|
function splitEarcut(start, triangles, dim, minX, minY, invSize) {
|
|
239
235
|
// look for a valid diagonal that divides the polygon into two
|
|
240
|
-
|
|
236
|
+
let a = start;
|
|
241
237
|
do {
|
|
242
|
-
|
|
238
|
+
let b = a.next.next;
|
|
243
239
|
while (b !== a.prev) {
|
|
244
240
|
if (a.i !== b.i && isValidDiagonal(a, b)) {
|
|
245
241
|
// split the polygon in two by the diagonal
|
|
246
|
-
|
|
242
|
+
let c = splitPolygon(a, b);
|
|
247
243
|
|
|
248
244
|
// filter colinear points around the cuts
|
|
249
245
|
a = filterPoints(a, a.next);
|
|
250
246
|
c = filterPoints(c, c.next);
|
|
251
247
|
|
|
252
248
|
// run earcut on each half
|
|
253
|
-
earcutLinked(a, triangles, dim, minX, minY, invSize);
|
|
254
|
-
earcutLinked(c, triangles, dim, minX, minY, invSize);
|
|
249
|
+
earcutLinked(a, triangles, dim, minX, minY, invSize, 0);
|
|
250
|
+
earcutLinked(c, triangles, dim, minX, minY, invSize, 0);
|
|
255
251
|
return;
|
|
256
252
|
}
|
|
257
253
|
b = b.next;
|
|
@@ -262,13 +258,12 @@ function splitEarcut(start, triangles, dim, minX, minY, invSize) {
|
|
|
262
258
|
|
|
263
259
|
// link every hole into the outer loop, producing a single-ring polygon without holes
|
|
264
260
|
function eliminateHoles(data, holeIndices, outerNode, dim) {
|
|
265
|
-
|
|
266
|
-
i, len, start, end, list;
|
|
261
|
+
const queue = [];
|
|
267
262
|
|
|
268
|
-
for (i = 0, len = holeIndices.length; i < len; i++) {
|
|
269
|
-
start = holeIndices[i] * dim;
|
|
270
|
-
end = i < len - 1 ? holeIndices[i + 1] * dim : data.length;
|
|
271
|
-
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);
|
|
272
267
|
if (list === list.next) list.steiner = true;
|
|
273
268
|
queue.push(getLeftmost(list));
|
|
274
269
|
}
|
|
@@ -276,9 +271,8 @@ function eliminateHoles(data, holeIndices, outerNode, dim) {
|
|
|
276
271
|
queue.sort(compareX);
|
|
277
272
|
|
|
278
273
|
// process holes from left to right
|
|
279
|
-
for (i = 0; i < queue.length; i++) {
|
|
274
|
+
for (let i = 0; i < queue.length; i++) {
|
|
280
275
|
outerNode = eliminateHole(queue[i], outerNode);
|
|
281
|
-
outerNode = filterPoints(outerNode, outerNode.next);
|
|
282
276
|
}
|
|
283
277
|
|
|
284
278
|
return outerNode;
|
|
@@ -290,41 +284,35 @@ function compareX(a, b) {
|
|
|
290
284
|
|
|
291
285
|
// find a bridge between vertices that connects hole with an outer ring and and link it
|
|
292
286
|
function eliminateHole(hole, outerNode) {
|
|
293
|
-
|
|
287
|
+
const bridge = findHoleBridge(hole, outerNode);
|
|
294
288
|
if (!bridge) {
|
|
295
289
|
return outerNode;
|
|
296
290
|
}
|
|
297
291
|
|
|
298
|
-
|
|
292
|
+
const bridgeReverse = splitPolygon(bridge, hole);
|
|
299
293
|
|
|
300
294
|
// filter collinear points around the cuts
|
|
301
|
-
var filteredBridge = filterPoints(bridge, bridge.next);
|
|
302
295
|
filterPoints(bridgeReverse, bridgeReverse.next);
|
|
303
|
-
|
|
304
|
-
// Check if input node was removed by the filtering
|
|
305
|
-
return outerNode === bridge ? filteredBridge : outerNode;
|
|
296
|
+
return filterPoints(bridge, bridge.next);
|
|
306
297
|
}
|
|
307
298
|
|
|
308
299
|
// David Eberly's algorithm for finding a bridge between hole and outer polygon
|
|
309
300
|
function findHoleBridge(hole, outerNode) {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
301
|
+
let p = outerNode;
|
|
302
|
+
const hx = hole.x;
|
|
303
|
+
const hy = hole.y;
|
|
304
|
+
let qx = -Infinity;
|
|
305
|
+
let m;
|
|
315
306
|
|
|
316
307
|
// find a segment intersected by a ray from the hole's leftmost point to the left;
|
|
317
308
|
// segment's endpoint with lesser x will be potential connection point
|
|
318
309
|
do {
|
|
319
310
|
if (hy <= p.y && hy >= p.next.y && p.next.y !== p.y) {
|
|
320
|
-
|
|
311
|
+
const x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y);
|
|
321
312
|
if (x <= hx && x > qx) {
|
|
322
313
|
qx = x;
|
|
323
|
-
if (x === hx) {
|
|
324
|
-
if (hy === p.y) return p;
|
|
325
|
-
if (hy === p.next.y) return p.next;
|
|
326
|
-
}
|
|
327
314
|
m = p.x < p.next.x ? p : p.next;
|
|
315
|
+
if (x === hx) return m; // hole touches outer segment; pick leftmost endpoint
|
|
328
316
|
}
|
|
329
317
|
}
|
|
330
318
|
p = p.next;
|
|
@@ -332,17 +320,14 @@ function findHoleBridge(hole, outerNode) {
|
|
|
332
320
|
|
|
333
321
|
if (!m) return null;
|
|
334
322
|
|
|
335
|
-
if (hx === qx) return m; // hole touches outer segment; pick leftmost endpoint
|
|
336
|
-
|
|
337
323
|
// look for points inside the triangle of hole point, segment intersection and endpoint;
|
|
338
324
|
// if there are no points found, we have a valid connection;
|
|
339
325
|
// otherwise choose the point of the minimum angle with the ray as connection point
|
|
340
326
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
tan;
|
|
327
|
+
const stop = m;
|
|
328
|
+
const mx = m.x;
|
|
329
|
+
const my = m.y;
|
|
330
|
+
let tanMin = Infinity;
|
|
346
331
|
|
|
347
332
|
p = m;
|
|
348
333
|
|
|
@@ -350,7 +335,7 @@ function findHoleBridge(hole, outerNode) {
|
|
|
350
335
|
if (hx >= p.x && p.x >= mx && hx !== p.x &&
|
|
351
336
|
pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y)) {
|
|
352
337
|
|
|
353
|
-
tan = Math.abs(hy - p.y) / (hx - p.x); // tangential
|
|
338
|
+
const tan = Math.abs(hy - p.y) / (hx - p.x); // tangential
|
|
354
339
|
|
|
355
340
|
if (locallyInside(p, hole) &&
|
|
356
341
|
(tan < tanMin || (tan === tanMin && (p.x > m.x || (p.x === m.x && sectorContainsSector(m, p)))))) {
|
|
@@ -372,9 +357,9 @@ function sectorContainsSector(m, p) {
|
|
|
372
357
|
|
|
373
358
|
// interlink polygon nodes in z-order
|
|
374
359
|
function indexCurve(start, minX, minY, invSize) {
|
|
375
|
-
|
|
360
|
+
let p = start;
|
|
376
361
|
do {
|
|
377
|
-
if (p.z ===
|
|
362
|
+
if (p.z === 0) p.z = zOrder(p.x, p.y, minX, minY, invSize);
|
|
378
363
|
p.prevZ = p.prev;
|
|
379
364
|
p.nextZ = p.next;
|
|
380
365
|
p = p.next;
|
|
@@ -389,25 +374,26 @@ function indexCurve(start, minX, minY, invSize) {
|
|
|
389
374
|
// Simon Tatham's linked list merge sort algorithm
|
|
390
375
|
// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
|
|
391
376
|
function sortLinked(list) {
|
|
392
|
-
|
|
393
|
-
|
|
377
|
+
let numMerges;
|
|
378
|
+
let inSize = 1;
|
|
394
379
|
|
|
395
380
|
do {
|
|
396
|
-
p = list;
|
|
381
|
+
let p = list;
|
|
382
|
+
let e;
|
|
397
383
|
list = null;
|
|
398
|
-
tail = null;
|
|
384
|
+
let tail = null;
|
|
399
385
|
numMerges = 0;
|
|
400
386
|
|
|
401
387
|
while (p) {
|
|
402
388
|
numMerges++;
|
|
403
|
-
q = p;
|
|
404
|
-
pSize = 0;
|
|
405
|
-
for (i = 0; i < inSize; i++) {
|
|
389
|
+
let q = p;
|
|
390
|
+
let pSize = 0;
|
|
391
|
+
for (let i = 0; i < inSize; i++) {
|
|
406
392
|
pSize++;
|
|
407
393
|
q = q.nextZ;
|
|
408
394
|
if (!q) break;
|
|
409
395
|
}
|
|
410
|
-
qSize = inSize;
|
|
396
|
+
let qSize = inSize;
|
|
411
397
|
|
|
412
398
|
while (pSize > 0 || (qSize > 0 && q)) {
|
|
413
399
|
|
|
@@ -442,8 +428,8 @@ function sortLinked(list) {
|
|
|
442
428
|
// z-order of a point given coords and inverse of the longer side of data bbox
|
|
443
429
|
function zOrder(x, y, minX, minY, invSize) {
|
|
444
430
|
// coords are transformed into non-negative 15-bit integer range
|
|
445
|
-
x =
|
|
446
|
-
y =
|
|
431
|
+
x = (x - minX) * invSize | 0;
|
|
432
|
+
y = (y - minY) * invSize | 0;
|
|
447
433
|
|
|
448
434
|
x = (x | (x << 8)) & 0x00FF00FF;
|
|
449
435
|
x = (x | (x << 4)) & 0x0F0F0F0F;
|
|
@@ -460,7 +446,7 @@ function zOrder(x, y, minX, minY, invSize) {
|
|
|
460
446
|
|
|
461
447
|
// find the leftmost node of a polygon ring
|
|
462
448
|
function getLeftmost(start) {
|
|
463
|
-
|
|
449
|
+
let p = start,
|
|
464
450
|
leftmost = start;
|
|
465
451
|
do {
|
|
466
452
|
if (p.x < leftmost.x || (p.x === leftmost.x && p.y < leftmost.y)) leftmost = p;
|
|
@@ -472,9 +458,9 @@ function getLeftmost(start) {
|
|
|
472
458
|
|
|
473
459
|
// check if a point lies within a convex triangle
|
|
474
460
|
function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) {
|
|
475
|
-
return (cx - px) * (ay - py)
|
|
476
|
-
(ax - px) * (by - py)
|
|
477
|
-
(bx - px) * (cy - py)
|
|
461
|
+
return (cx - px) * (ay - py) >= (ax - px) * (cy - py) &&
|
|
462
|
+
(ax - px) * (by - py) >= (bx - px) * (ay - py) &&
|
|
463
|
+
(bx - px) * (cy - py) >= (cx - px) * (by - py);
|
|
478
464
|
}
|
|
479
465
|
|
|
480
466
|
// check if a diagonal between two polygon nodes is valid (lies in polygon interior)
|
|
@@ -497,10 +483,10 @@ function equals(p1, p2) {
|
|
|
497
483
|
|
|
498
484
|
// check if two segments intersect
|
|
499
485
|
function intersects(p1, q1, p2, q2) {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
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));
|
|
504
490
|
|
|
505
491
|
if (o1 !== o2 && o3 !== o4) return true; // general case
|
|
506
492
|
|
|
@@ -523,7 +509,7 @@ function sign(num) {
|
|
|
523
509
|
|
|
524
510
|
// check if a polygon diagonal intersects any polygon segments
|
|
525
511
|
function intersectsPolygon(a, b) {
|
|
526
|
-
|
|
512
|
+
let p = a;
|
|
527
513
|
do {
|
|
528
514
|
if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i &&
|
|
529
515
|
intersects(p, p.next, a, b)) return true;
|
|
@@ -542,10 +528,10 @@ function locallyInside(a, b) {
|
|
|
542
528
|
|
|
543
529
|
// check if the middle point of a polygon diagonal is inside the polygon
|
|
544
530
|
function middleInside(a, b) {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
531
|
+
let p = a;
|
|
532
|
+
let inside = false;
|
|
533
|
+
const px = (a.x + b.x) / 2;
|
|
534
|
+
const py = (a.y + b.y) / 2;
|
|
549
535
|
do {
|
|
550
536
|
if (((p.y > py) !== (p.next.y > py)) && p.next.y !== p.y &&
|
|
551
537
|
(px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x))
|
|
@@ -559,8 +545,8 @@ function middleInside(a, b) {
|
|
|
559
545
|
// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two;
|
|
560
546
|
// if one belongs to the outer ring and another to a hole, it merges it into a single ring
|
|
561
547
|
function splitPolygon(a, b) {
|
|
562
|
-
|
|
563
|
-
b2 =
|
|
548
|
+
const a2 = createNode(a.i, a.x, a.y),
|
|
549
|
+
b2 = createNode(b.i, b.x, b.y),
|
|
564
550
|
an = a.next,
|
|
565
551
|
bp = b.prev;
|
|
566
552
|
|
|
@@ -581,7 +567,7 @@ function splitPolygon(a, b) {
|
|
|
581
567
|
|
|
582
568
|
// create a node and optionally link it with previous one (in a circular doubly linked list)
|
|
583
569
|
function insertNode(i, x, y, last) {
|
|
584
|
-
|
|
570
|
+
const p = createNode(i, x, y);
|
|
585
571
|
|
|
586
572
|
if (!last) {
|
|
587
573
|
p.prev = p;
|
|
@@ -604,49 +590,39 @@ function removeNode(p) {
|
|
|
604
590
|
if (p.nextZ) p.nextZ.prevZ = p.prevZ;
|
|
605
591
|
}
|
|
606
592
|
|
|
607
|
-
function
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
// z-order curve value
|
|
620
|
-
this.z = null;
|
|
621
|
-
|
|
622
|
-
// previous and next nodes in z-order
|
|
623
|
-
this.prevZ = null;
|
|
624
|
-
this.nextZ = null;
|
|
625
|
-
|
|
626
|
-
// indicates whether this is a steiner point
|
|
627
|
-
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
|
+
};
|
|
628
604
|
}
|
|
629
605
|
|
|
630
606
|
// return a percentage difference between the polygon area and its triangulation area;
|
|
631
607
|
// used to verify correctness of triangulation
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
608
|
+
export function deviation(data, holeIndices, dim, triangles) {
|
|
609
|
+
const hasHoles = holeIndices && holeIndices.length;
|
|
610
|
+
const outerLen = hasHoles ? holeIndices[0] * dim : data.length;
|
|
635
611
|
|
|
636
|
-
|
|
612
|
+
let polygonArea = Math.abs(signedArea(data, 0, outerLen, dim));
|
|
637
613
|
if (hasHoles) {
|
|
638
|
-
for (
|
|
639
|
-
|
|
640
|
-
|
|
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;
|
|
641
617
|
polygonArea -= Math.abs(signedArea(data, start, end, dim));
|
|
642
618
|
}
|
|
643
619
|
}
|
|
644
620
|
|
|
645
|
-
|
|
646
|
-
for (i = 0; i < triangles.length; i += 3) {
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
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;
|
|
650
626
|
trianglesArea += Math.abs(
|
|
651
627
|
(data[a] - data[c]) * (data[b + 1] - data[a + 1]) -
|
|
652
628
|
(data[a] - data[b]) * (data[c + 1] - data[a + 1]));
|
|
@@ -654,11 +630,11 @@ earcut.deviation = function (data, holeIndices, dim, triangles) {
|
|
|
654
630
|
|
|
655
631
|
return polygonArea === 0 && trianglesArea === 0 ? 0 :
|
|
656
632
|
Math.abs((trianglesArea - polygonArea) / polygonArea);
|
|
657
|
-
}
|
|
633
|
+
}
|
|
658
634
|
|
|
659
635
|
function signedArea(data, start, end, dim) {
|
|
660
|
-
|
|
661
|
-
for (
|
|
636
|
+
let sum = 0;
|
|
637
|
+
for (let i = start, j = end - dim; i < end; i += dim) {
|
|
662
638
|
sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]);
|
|
663
639
|
j = i;
|
|
664
640
|
}
|
|
@@ -666,19 +642,22 @@ function signedArea(data, start, end, dim) {
|
|
|
666
642
|
}
|
|
667
643
|
|
|
668
644
|
// turn a polygon in a multi-dimensional array form (e.g. as in GeoJSON) into a form Earcut accepts
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
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]);
|
|
677
655
|
}
|
|
678
|
-
if (
|
|
679
|
-
holeIndex +=
|
|
680
|
-
|
|
656
|
+
if (prevLen) {
|
|
657
|
+
holeIndex += prevLen;
|
|
658
|
+
holes.push(holeIndex);
|
|
681
659
|
}
|
|
660
|
+
prevLen = ring.length;
|
|
682
661
|
}
|
|
683
|
-
return
|
|
684
|
-
}
|
|
662
|
+
return {vertices, holes, dimensions};
|
|
663
|
+
}
|