earcut 2.2.4 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +22 -156
- package/dist/earcut.dev.js +170 -162
- package/dist/earcut.min.js +1 -1
- package/package.json +19 -19
- package/src/earcut.js +159 -158
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,25 +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
|
-
// triangle bbox
|
|
152
|
-
|
|
153
|
-
y0 =
|
|
154
|
-
x1 =
|
|
155
|
-
y1 =
|
|
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);
|
|
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
|
+
pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, p.x, p.y) &&
|
|
161
153
|
area(p.prev, p, p.next) >= 0) return false;
|
|
162
154
|
p = p.next;
|
|
163
155
|
}
|
|
@@ -166,49 +158,49 @@ 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
|
-
// triangle bbox
|
|
178
|
-
|
|
179
|
-
y0 =
|
|
180
|
-
x1 =
|
|
181
|
-
y1 =
|
|
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);
|
|
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
|
|
191
183
|
while (p && p.z >= minZ && n && n.z <= maxZ) {
|
|
192
184
|
if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c &&
|
|
193
|
-
|
|
185
|
+
pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false;
|
|
194
186
|
p = p.prevZ;
|
|
195
187
|
|
|
196
188
|
if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c &&
|
|
197
|
-
|
|
189
|
+
pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false;
|
|
198
190
|
n = n.nextZ;
|
|
199
191
|
}
|
|
200
192
|
|
|
201
193
|
// look for remaining points in decreasing z-order
|
|
202
194
|
while (p && p.z >= minZ) {
|
|
203
195
|
if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c &&
|
|
204
|
-
|
|
196
|
+
pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false;
|
|
205
197
|
p = p.prevZ;
|
|
206
198
|
}
|
|
207
199
|
|
|
208
200
|
// look for remaining points in increasing z-order
|
|
209
201
|
while (n && n.z <= maxZ) {
|
|
210
202
|
if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c &&
|
|
211
|
-
|
|
203
|
+
pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false;
|
|
212
204
|
n = n.nextZ;
|
|
213
205
|
}
|
|
214
206
|
|
|
@@ -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,39 +258,49 @@ 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
|
}
|
|
281
270
|
|
|
282
|
-
queue.sort(
|
|
271
|
+
queue.sort(compareXYSlope);
|
|
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
|
|
|
289
278
|
return outerNode;
|
|
290
279
|
}
|
|
291
280
|
|
|
292
|
-
function
|
|
293
|
-
|
|
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;
|
|
294
294
|
}
|
|
295
295
|
|
|
296
296
|
// find a bridge between vertices that connects hole with an outer ring and and link it
|
|
297
297
|
function eliminateHole(hole, outerNode) {
|
|
298
|
-
|
|
298
|
+
const bridge = findHoleBridge(hole, outerNode);
|
|
299
299
|
if (!bridge) {
|
|
300
300
|
return outerNode;
|
|
301
301
|
}
|
|
302
302
|
|
|
303
|
-
|
|
303
|
+
const bridgeReverse = splitPolygon(bridge, hole);
|
|
304
304
|
|
|
305
305
|
// filter collinear points around the cuts
|
|
306
306
|
filterPoints(bridgeReverse, bridgeReverse.next);
|
|
@@ -309,17 +309,20 @@ function eliminateHole(hole, outerNode) {
|
|
|
309
309
|
|
|
310
310
|
// David Eberly's algorithm for finding a bridge between hole and outer polygon
|
|
311
311
|
function findHoleBridge(hole, outerNode) {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
312
|
+
let p = outerNode;
|
|
313
|
+
const hx = hole.x;
|
|
314
|
+
const hy = hole.y;
|
|
315
|
+
let qx = -Infinity;
|
|
316
|
+
let m;
|
|
317
317
|
|
|
318
318
|
// find a segment intersected by a ray from the hole's leftmost point to the left;
|
|
319
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;
|
|
320
322
|
do {
|
|
321
|
-
if (
|
|
322
|
-
|
|
323
|
+
if (equals(hole, p.next)) return p.next;
|
|
324
|
+
else if (hy <= p.y && hy >= p.next.y && p.next.y !== p.y) {
|
|
325
|
+
const x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y);
|
|
323
326
|
if (x <= hx && x > qx) {
|
|
324
327
|
qx = x;
|
|
325
328
|
m = p.x < p.next.x ? p : p.next;
|
|
@@ -335,11 +338,10 @@ function findHoleBridge(hole, outerNode) {
|
|
|
335
338
|
// if there are no points found, we have a valid connection;
|
|
336
339
|
// otherwise choose the point of the minimum angle with the ray as connection point
|
|
337
340
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
tan;
|
|
341
|
+
const stop = m;
|
|
342
|
+
const mx = m.x;
|
|
343
|
+
const my = m.y;
|
|
344
|
+
let tanMin = Infinity;
|
|
343
345
|
|
|
344
346
|
p = m;
|
|
345
347
|
|
|
@@ -347,7 +349,7 @@ function findHoleBridge(hole, outerNode) {
|
|
|
347
349
|
if (hx >= p.x && p.x >= mx && hx !== p.x &&
|
|
348
350
|
pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y)) {
|
|
349
351
|
|
|
350
|
-
tan = Math.abs(hy - p.y) / (hx - p.x); // tangential
|
|
352
|
+
const tan = Math.abs(hy - p.y) / (hx - p.x); // tangential
|
|
351
353
|
|
|
352
354
|
if (locallyInside(p, hole) &&
|
|
353
355
|
(tan < tanMin || (tan === tanMin && (p.x > m.x || (p.x === m.x && sectorContainsSector(m, p)))))) {
|
|
@@ -369,7 +371,7 @@ function sectorContainsSector(m, p) {
|
|
|
369
371
|
|
|
370
372
|
// interlink polygon nodes in z-order
|
|
371
373
|
function indexCurve(start, minX, minY, invSize) {
|
|
372
|
-
|
|
374
|
+
let p = start;
|
|
373
375
|
do {
|
|
374
376
|
if (p.z === 0) p.z = zOrder(p.x, p.y, minX, minY, invSize);
|
|
375
377
|
p.prevZ = p.prev;
|
|
@@ -386,25 +388,26 @@ function indexCurve(start, minX, minY, invSize) {
|
|
|
386
388
|
// Simon Tatham's linked list merge sort algorithm
|
|
387
389
|
// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
|
|
388
390
|
function sortLinked(list) {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
+
let numMerges;
|
|
392
|
+
let inSize = 1;
|
|
391
393
|
|
|
392
394
|
do {
|
|
393
|
-
p = list;
|
|
395
|
+
let p = list;
|
|
396
|
+
let e;
|
|
394
397
|
list = null;
|
|
395
|
-
tail = null;
|
|
398
|
+
let tail = null;
|
|
396
399
|
numMerges = 0;
|
|
397
400
|
|
|
398
401
|
while (p) {
|
|
399
402
|
numMerges++;
|
|
400
|
-
q = p;
|
|
401
|
-
pSize = 0;
|
|
402
|
-
for (i = 0; i < inSize; i++) {
|
|
403
|
+
let q = p;
|
|
404
|
+
let pSize = 0;
|
|
405
|
+
for (let i = 0; i < inSize; i++) {
|
|
403
406
|
pSize++;
|
|
404
407
|
q = q.nextZ;
|
|
405
408
|
if (!q) break;
|
|
406
409
|
}
|
|
407
|
-
qSize = inSize;
|
|
410
|
+
let qSize = inSize;
|
|
408
411
|
|
|
409
412
|
while (pSize > 0 || (qSize > 0 && q)) {
|
|
410
413
|
|
|
@@ -457,7 +460,7 @@ function zOrder(x, y, minX, minY, invSize) {
|
|
|
457
460
|
|
|
458
461
|
// find the leftmost node of a polygon ring
|
|
459
462
|
function getLeftmost(start) {
|
|
460
|
-
|
|
463
|
+
let p = start,
|
|
461
464
|
leftmost = start;
|
|
462
465
|
do {
|
|
463
466
|
if (p.x < leftmost.x || (p.x === leftmost.x && p.y < leftmost.y)) leftmost = p;
|
|
@@ -474,6 +477,11 @@ function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) {
|
|
|
474
477
|
(bx - px) * (cy - py) >= (cx - px) * (by - py);
|
|
475
478
|
}
|
|
476
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
|
+
|
|
477
485
|
// check if a diagonal between two polygon nodes is valid (lies in polygon interior)
|
|
478
486
|
function isValidDiagonal(a, b) {
|
|
479
487
|
return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) && // dones't intersect other edges
|
|
@@ -494,10 +502,10 @@ function equals(p1, p2) {
|
|
|
494
502
|
|
|
495
503
|
// check if two segments intersect
|
|
496
504
|
function intersects(p1, q1, p2, q2) {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
505
|
+
const o1 = sign(area(p1, q1, p2));
|
|
506
|
+
const o2 = sign(area(p1, q1, q2));
|
|
507
|
+
const o3 = sign(area(p2, q2, p1));
|
|
508
|
+
const o4 = sign(area(p2, q2, q1));
|
|
501
509
|
|
|
502
510
|
if (o1 !== o2 && o3 !== o4) return true; // general case
|
|
503
511
|
|
|
@@ -520,7 +528,7 @@ function sign(num) {
|
|
|
520
528
|
|
|
521
529
|
// check if a polygon diagonal intersects any polygon segments
|
|
522
530
|
function intersectsPolygon(a, b) {
|
|
523
|
-
|
|
531
|
+
let p = a;
|
|
524
532
|
do {
|
|
525
533
|
if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i &&
|
|
526
534
|
intersects(p, p.next, a, b)) return true;
|
|
@@ -539,10 +547,10 @@ function locallyInside(a, b) {
|
|
|
539
547
|
|
|
540
548
|
// check if the middle point of a polygon diagonal is inside the polygon
|
|
541
549
|
function middleInside(a, b) {
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
550
|
+
let p = a;
|
|
551
|
+
let inside = false;
|
|
552
|
+
const px = (a.x + b.x) / 2;
|
|
553
|
+
const py = (a.y + b.y) / 2;
|
|
546
554
|
do {
|
|
547
555
|
if (((p.y > py) !== (p.next.y > py)) && p.next.y !== p.y &&
|
|
548
556
|
(px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x))
|
|
@@ -556,8 +564,8 @@ function middleInside(a, b) {
|
|
|
556
564
|
// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two;
|
|
557
565
|
// if one belongs to the outer ring and another to a hole, it merges it into a single ring
|
|
558
566
|
function splitPolygon(a, b) {
|
|
559
|
-
|
|
560
|
-
b2 =
|
|
567
|
+
const a2 = createNode(a.i, a.x, a.y),
|
|
568
|
+
b2 = createNode(b.i, b.x, b.y),
|
|
561
569
|
an = a.next,
|
|
562
570
|
bp = b.prev;
|
|
563
571
|
|
|
@@ -578,7 +586,7 @@ function splitPolygon(a, b) {
|
|
|
578
586
|
|
|
579
587
|
// create a node and optionally link it with previous one (in a circular doubly linked list)
|
|
580
588
|
function insertNode(i, x, y, last) {
|
|
581
|
-
|
|
589
|
+
const p = createNode(i, x, y);
|
|
582
590
|
|
|
583
591
|
if (!last) {
|
|
584
592
|
p.prev = p;
|
|
@@ -601,49 +609,39 @@ function removeNode(p) {
|
|
|
601
609
|
if (p.nextZ) p.nextZ.prevZ = p.prevZ;
|
|
602
610
|
}
|
|
603
611
|
|
|
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;
|
|
612
|
+
function createNode(i, x, y) {
|
|
613
|
+
return {
|
|
614
|
+
i, // vertex index in coordinates array
|
|
615
|
+
x, y, // vertex coordinates
|
|
616
|
+
prev: null, // previous and next vertex nodes in a polygon ring
|
|
617
|
+
next: null,
|
|
618
|
+
z: 0, // z-order curve value
|
|
619
|
+
prevZ: null, // previous and next nodes in z-order
|
|
620
|
+
nextZ: null,
|
|
621
|
+
steiner: false // indicates whether this is a steiner point
|
|
622
|
+
};
|
|
625
623
|
}
|
|
626
624
|
|
|
627
625
|
// return a percentage difference between the polygon area and its triangulation area;
|
|
628
626
|
// used to verify correctness of triangulation
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
627
|
+
export function deviation(data, holeIndices, dim, triangles) {
|
|
628
|
+
const hasHoles = holeIndices && holeIndices.length;
|
|
629
|
+
const outerLen = hasHoles ? holeIndices[0] * dim : data.length;
|
|
632
630
|
|
|
633
|
-
|
|
631
|
+
let polygonArea = Math.abs(signedArea(data, 0, outerLen, dim));
|
|
634
632
|
if (hasHoles) {
|
|
635
|
-
for (
|
|
636
|
-
|
|
637
|
-
|
|
633
|
+
for (let i = 0, len = holeIndices.length; i < len; i++) {
|
|
634
|
+
const start = holeIndices[i] * dim;
|
|
635
|
+
const end = i < len - 1 ? holeIndices[i + 1] * dim : data.length;
|
|
638
636
|
polygonArea -= Math.abs(signedArea(data, start, end, dim));
|
|
639
637
|
}
|
|
640
638
|
}
|
|
641
639
|
|
|
642
|
-
|
|
643
|
-
for (i = 0; i < triangles.length; i += 3) {
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
640
|
+
let trianglesArea = 0;
|
|
641
|
+
for (let i = 0; i < triangles.length; i += 3) {
|
|
642
|
+
const a = triangles[i] * dim;
|
|
643
|
+
const b = triangles[i + 1] * dim;
|
|
644
|
+
const c = triangles[i + 2] * dim;
|
|
647
645
|
trianglesArea += Math.abs(
|
|
648
646
|
(data[a] - data[c]) * (data[b + 1] - data[a + 1]) -
|
|
649
647
|
(data[a] - data[b]) * (data[c + 1] - data[a + 1]));
|
|
@@ -651,11 +649,11 @@ earcut.deviation = function (data, holeIndices, dim, triangles) {
|
|
|
651
649
|
|
|
652
650
|
return polygonArea === 0 && trianglesArea === 0 ? 0 :
|
|
653
651
|
Math.abs((trianglesArea - polygonArea) / polygonArea);
|
|
654
|
-
}
|
|
652
|
+
}
|
|
655
653
|
|
|
656
654
|
function signedArea(data, start, end, dim) {
|
|
657
|
-
|
|
658
|
-
for (
|
|
655
|
+
let sum = 0;
|
|
656
|
+
for (let i = start, j = end - dim; i < end; i += dim) {
|
|
659
657
|
sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]);
|
|
660
658
|
j = i;
|
|
661
659
|
}
|
|
@@ -663,19 +661,22 @@ function signedArea(data, start, end, dim) {
|
|
|
663
661
|
}
|
|
664
662
|
|
|
665
663
|
// 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
|
-
|
|
664
|
+
export function flatten(data) {
|
|
665
|
+
const vertices = [];
|
|
666
|
+
const holes = [];
|
|
667
|
+
const dimensions = data[0][0].length;
|
|
668
|
+
let holeIndex = 0;
|
|
669
|
+
let prevLen = 0;
|
|
670
|
+
|
|
671
|
+
for (const ring of data) {
|
|
672
|
+
for (const p of ring) {
|
|
673
|
+
for (let d = 0; d < dimensions; d++) vertices.push(p[d]);
|
|
674
674
|
}
|
|
675
|
-
if (
|
|
676
|
-
holeIndex +=
|
|
677
|
-
|
|
675
|
+
if (prevLen) {
|
|
676
|
+
holeIndex += prevLen;
|
|
677
|
+
holes.push(holeIndex);
|
|
678
678
|
}
|
|
679
|
+
prevLen = ring.length;
|
|
679
680
|
}
|
|
680
|
-
return
|
|
681
|
-
}
|
|
681
|
+
return {vertices, holes, dimensions};
|
|
682
|
+
}
|