@math.gl/polygon 3.6.2 → 4.0.0-alpha.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/dist/cut-by-grid.d.ts +4 -1
- package/dist/cut-by-grid.d.ts.map +1 -1
- package/dist/{esm/cut-by-grid.js → cut-by-grid.js} +10 -11
- package/dist/cut-by-grid.js.map +1 -0
- package/dist/{esm/cut-by-mercator-bounds.js → cut-by-mercator-bounds.js} +2 -2
- package/dist/cut-by-mercator-bounds.js.map +1 -0
- package/dist/earcut.d.ts +3 -1
- package/dist/earcut.d.ts.map +1 -1
- package/dist/{esm/earcut.js → earcut.js} +96 -65
- package/dist/earcut.js.map +1 -0
- package/dist/index.cjs +1129 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/lineclip.d.ts +1 -1
- package/dist/lineclip.d.ts.map +1 -1
- package/dist/{esm/lineclip.js → lineclip.js} +1 -1
- package/dist/lineclip.js.map +1 -0
- package/dist/polygon-utils.d.ts +26 -4
- package/dist/polygon-utils.d.ts.map +1 -1
- package/dist/{esm/polygon-utils.js → polygon-utils.js} +15 -4
- package/dist/polygon-utils.js.map +1 -0
- package/dist/polygon.d.ts +1 -1
- package/dist/polygon.d.ts.map +1 -1
- package/dist/{esm/polygon.js → polygon.js} +1 -1
- package/dist/polygon.js.map +1 -0
- package/dist/{esm/utils.js → utils.js} +0 -0
- package/dist/utils.js.map +1 -0
- package/package.json +6 -5
- package/src/cut-by-grid.ts +26 -9
- package/src/earcut.ts +143 -77
- package/src/polygon-utils.ts +42 -8
- package/dist/es5/cut-by-grid.js +0 -276
- package/dist/es5/cut-by-grid.js.map +0 -1
- package/dist/es5/cut-by-mercator-bounds.js +0 -194
- package/dist/es5/cut-by-mercator-bounds.js.map +0 -1
- package/dist/es5/earcut.js +0 -540
- package/dist/es5/earcut.js.map +0 -1
- package/dist/es5/index.js +0 -104
- package/dist/es5/index.js.map +0 -1
- package/dist/es5/lineclip.js +0 -150
- package/dist/es5/lineclip.js.map +0 -1
- package/dist/es5/polygon-utils.js +0 -154
- package/dist/es5/polygon-utils.js.map +0 -1
- package/dist/es5/polygon.js +0 -79
- package/dist/es5/polygon.js.map +0 -1
- package/dist/es5/utils.js +0 -54
- package/dist/es5/utils.js.map +0 -1
- package/dist/esm/cut-by-grid.js.map +0 -1
- package/dist/esm/cut-by-mercator-bounds.js.map +0 -1
- package/dist/esm/earcut.js.map +0 -1
- package/dist/esm/index.js +0 -8
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/lineclip.js.map +0 -1
- package/dist/esm/polygon-utils.js.map +0 -1
- package/dist/esm/polygon.js.map +0 -1
- package/dist/esm/utils.js.map +0 -1
package/src/earcut.ts
CHANGED
|
@@ -22,9 +22,12 @@
|
|
|
22
22
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
+
// @ts-nocheck
|
|
26
|
+
|
|
25
27
|
/* eslint-disable */
|
|
26
28
|
|
|
27
|
-
import {
|
|
29
|
+
import type {NumericArray} from '@math.gl/core';
|
|
30
|
+
import {getPolygonSignedArea, DimIndex, Plane2D} from './polygon-utils';
|
|
28
31
|
|
|
29
32
|
/**
|
|
30
33
|
* Computes a triangulation of a polygon
|
|
@@ -36,14 +39,15 @@ import {getPolygonSignedArea} from './polygon-utils';
|
|
|
36
39
|
* Adapted from https://github.com/mapbox/earcut
|
|
37
40
|
*/
|
|
38
41
|
export function earcut(
|
|
39
|
-
positions:
|
|
40
|
-
holeIndices?:
|
|
42
|
+
positions: NumericArray,
|
|
43
|
+
holeIndices?: NumericArray,
|
|
41
44
|
dim: number = 2,
|
|
42
|
-
areas?:
|
|
45
|
+
areas?: NumericArray,
|
|
46
|
+
plane: Plane2D = 'xy'
|
|
43
47
|
): number[] {
|
|
44
48
|
const hasHoles = holeIndices && holeIndices.length;
|
|
45
49
|
const outerLen = hasHoles ? holeIndices[0] * dim : positions.length;
|
|
46
|
-
let outerNode = linkedList(positions, 0, outerLen, dim, true, areas && areas[0]);
|
|
50
|
+
let outerNode = linkedList(positions, 0, outerLen, dim, true, areas && areas[0], plane);
|
|
47
51
|
const triangles = [];
|
|
48
52
|
|
|
49
53
|
if (!outerNode || outerNode.next === outerNode.prev) return triangles;
|
|
@@ -56,7 +60,7 @@ export function earcut(
|
|
|
56
60
|
let x;
|
|
57
61
|
let y;
|
|
58
62
|
|
|
59
|
-
if (hasHoles) outerNode = eliminateHoles(positions, holeIndices, outerNode, dim, areas);
|
|
63
|
+
if (hasHoles) outerNode = eliminateHoles(positions, holeIndices, outerNode, dim, areas, plane);
|
|
60
64
|
|
|
61
65
|
// if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
|
|
62
66
|
if (positions.length > 80 * dim) {
|
|
@@ -74,29 +78,40 @@ export function earcut(
|
|
|
74
78
|
|
|
75
79
|
// minX, minY and invSize are later used to transform coords into integers for z-order calculation
|
|
76
80
|
invSize = Math.max(maxX - minX, maxY - minY);
|
|
77
|
-
invSize = invSize !== 0 ?
|
|
81
|
+
invSize = invSize !== 0 ? 32767 / invSize : 0;
|
|
78
82
|
}
|
|
79
83
|
|
|
80
|
-
earcutLinked(outerNode, triangles, dim, minX, minY, invSize);
|
|
84
|
+
earcutLinked(outerNode, triangles, dim, minX, minY, invSize, 0);
|
|
81
85
|
|
|
82
86
|
return triangles;
|
|
83
87
|
}
|
|
84
88
|
|
|
85
89
|
// create a circular doubly linked list from polygon points in the specified winding order
|
|
86
|
-
function linkedList(
|
|
90
|
+
function linkedList(
|
|
91
|
+
data: NumericArray,
|
|
92
|
+
start: number,
|
|
93
|
+
end: number,
|
|
94
|
+
dim: number,
|
|
95
|
+
clockwise: boolean,
|
|
96
|
+
area: number | undefined,
|
|
97
|
+
plane: Plane2D
|
|
98
|
+
): Vertex {
|
|
87
99
|
let i;
|
|
88
100
|
let last;
|
|
89
101
|
if (area === undefined) {
|
|
90
|
-
area = getPolygonSignedArea(data, {start, end, size: dim});
|
|
102
|
+
area = getPolygonSignedArea(data, {start, end, size: dim, plane});
|
|
91
103
|
}
|
|
92
104
|
|
|
105
|
+
let i0 = DimIndex[plane[0]];
|
|
106
|
+
let i1 = DimIndex[plane[1]];
|
|
93
107
|
// Note that the signed area calculation in math.gl
|
|
94
108
|
// has the opposite sign to that which was originally
|
|
95
109
|
// present in earcut, thus the `< 0` is reversed
|
|
96
110
|
if (clockwise === area < 0) {
|
|
97
|
-
for (i = start; i < end; i += dim) last = insertNode(i, data[i], data[i +
|
|
111
|
+
for (i = start; i < end; i += dim) last = insertNode(i, data[i + i0], data[i + i1], last);
|
|
98
112
|
} else {
|
|
99
|
-
for (i = end - dim; i >= start; i -= dim)
|
|
113
|
+
for (i = end - dim; i >= start; i -= dim)
|
|
114
|
+
last = insertNode(i, data[i + i0], data[i + i1], last);
|
|
100
115
|
}
|
|
101
116
|
|
|
102
117
|
if (last && equals(last, last.next)) {
|
|
@@ -148,9 +163,9 @@ function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass?) {
|
|
|
148
163
|
|
|
149
164
|
if (invSize ? isEarHashed(ear, minX, minY, invSize) : isEar(ear)) {
|
|
150
165
|
// cut off the triangle
|
|
151
|
-
triangles.push(prev.i / dim);
|
|
152
|
-
triangles.push(ear.i / dim);
|
|
153
|
-
triangles.push(next.i / dim);
|
|
166
|
+
triangles.push((prev.i / dim) | 0);
|
|
167
|
+
triangles.push((ear.i / dim) | 0);
|
|
168
|
+
triangles.push((next.i / dim) | 0);
|
|
154
169
|
|
|
155
170
|
removeNode(ear);
|
|
156
171
|
|
|
@@ -193,10 +208,29 @@ function isEar(ear) {
|
|
|
193
208
|
if (area(a, b, c) >= 0) return false; // reflex, can't be an ear
|
|
194
209
|
|
|
195
210
|
// now make sure we don't have other points inside the potential ear
|
|
196
|
-
|
|
211
|
+
const ax = a.x;
|
|
212
|
+
const bx = b.x;
|
|
213
|
+
const cx = c.x;
|
|
214
|
+
const ay = a.y;
|
|
215
|
+
const by = b.y;
|
|
216
|
+
const cy = c.y;
|
|
217
|
+
|
|
218
|
+
// triangle bbox; min & max are calculated like this for speed
|
|
219
|
+
const x0 = ax < bx ? (ax < cx ? ax : cx) : bx < cx ? bx : cx;
|
|
220
|
+
const y0 = ay < by ? (ay < cy ? ay : cy) : by < cy ? by : cy;
|
|
221
|
+
const x1 = ax > bx ? (ax > cx ? ax : cx) : bx > cx ? bx : cx;
|
|
222
|
+
const y1 = ay > by ? (ay > cy ? ay : cy) : by > cy ? by : cy;
|
|
197
223
|
|
|
198
|
-
|
|
199
|
-
|
|
224
|
+
let p = c.next;
|
|
225
|
+
while (p !== a) {
|
|
226
|
+
if (
|
|
227
|
+
p.x >= x0 &&
|
|
228
|
+
p.x <= x1 &&
|
|
229
|
+
p.y >= y0 &&
|
|
230
|
+
p.y <= y1 &&
|
|
231
|
+
pointInTriangle(ax, ay, bx, by, cx, cy, p.x, p.y) &&
|
|
232
|
+
area(p.prev, p, p.next) >= 0
|
|
233
|
+
)
|
|
200
234
|
return false;
|
|
201
235
|
p = p.next;
|
|
202
236
|
}
|
|
@@ -211,15 +245,22 @@ function isEarHashed(ear, minX, minY, invSize) {
|
|
|
211
245
|
|
|
212
246
|
if (area(a, b, c) >= 0) return false; // reflex, can't be an ear
|
|
213
247
|
|
|
248
|
+
const ax = a.x;
|
|
249
|
+
const bx = b.x;
|
|
250
|
+
const cx = c.x;
|
|
251
|
+
const ay = a.y;
|
|
252
|
+
const by = b.y;
|
|
253
|
+
const cy = c.y;
|
|
254
|
+
|
|
214
255
|
// triangle bbox; min & max are calculated like this for speed
|
|
215
|
-
const
|
|
216
|
-
const
|
|
217
|
-
const
|
|
218
|
-
const
|
|
256
|
+
const x0 = ax < bx ? (ax < cx ? ax : cx) : bx < cx ? bx : cx;
|
|
257
|
+
const y0 = ay < by ? (ay < cy ? ay : cy) : by < cy ? by : cy;
|
|
258
|
+
const x1 = ax > bx ? (ax > cx ? ax : cx) : bx > cx ? bx : cx;
|
|
259
|
+
const y1 = ay > by ? (ay > cy ? ay : cy) : by > cy ? by : cy;
|
|
219
260
|
|
|
220
261
|
// z-order range for the current triangle bbox;
|
|
221
|
-
const minZ = zOrder(
|
|
222
|
-
const maxZ = zOrder(
|
|
262
|
+
const minZ = zOrder(x0, y0, minX, minY, invSize);
|
|
263
|
+
const maxZ = zOrder(x1, y1, minX, minY, invSize);
|
|
223
264
|
|
|
224
265
|
let p = ear.prevZ;
|
|
225
266
|
let n = ear.nextZ;
|
|
@@ -227,18 +268,26 @@ function isEarHashed(ear, minX, minY, invSize) {
|
|
|
227
268
|
// look for points inside the triangle in both directions
|
|
228
269
|
while (p && p.z >= minZ && n && n.z <= maxZ) {
|
|
229
270
|
if (
|
|
230
|
-
p
|
|
231
|
-
p
|
|
232
|
-
|
|
271
|
+
p.x >= x0 &&
|
|
272
|
+
p.x <= x1 &&
|
|
273
|
+
p.y >= y0 &&
|
|
274
|
+
p.y <= y1 &&
|
|
275
|
+
p !== a &&
|
|
276
|
+
p !== c &&
|
|
277
|
+
pointInTriangle(ax, ay, bx, by, cx, cy, p.x, p.y) &&
|
|
233
278
|
area(p.prev, p, p.next) >= 0
|
|
234
279
|
)
|
|
235
280
|
return false;
|
|
236
281
|
p = p.prevZ;
|
|
237
282
|
|
|
238
283
|
if (
|
|
239
|
-
n
|
|
240
|
-
n
|
|
241
|
-
|
|
284
|
+
n.x >= x0 &&
|
|
285
|
+
n.x <= x1 &&
|
|
286
|
+
n.y >= y0 &&
|
|
287
|
+
n.y <= y1 &&
|
|
288
|
+
n !== a &&
|
|
289
|
+
n !== c &&
|
|
290
|
+
pointInTriangle(ax, ay, bx, by, cx, cy, n.x, n.y) &&
|
|
242
291
|
area(n.prev, n, n.next) >= 0
|
|
243
292
|
)
|
|
244
293
|
return false;
|
|
@@ -248,9 +297,13 @@ function isEarHashed(ear, minX, minY, invSize) {
|
|
|
248
297
|
// look for remaining points in decreasing z-order
|
|
249
298
|
while (p && p.z >= minZ) {
|
|
250
299
|
if (
|
|
251
|
-
p
|
|
252
|
-
p
|
|
253
|
-
|
|
300
|
+
p.x >= x0 &&
|
|
301
|
+
p.x <= x1 &&
|
|
302
|
+
p.y >= y0 &&
|
|
303
|
+
p.y <= y1 &&
|
|
304
|
+
p !== a &&
|
|
305
|
+
p !== c &&
|
|
306
|
+
pointInTriangle(ax, ay, bx, by, cx, cy, p.x, p.y) &&
|
|
254
307
|
area(p.prev, p, p.next) >= 0
|
|
255
308
|
)
|
|
256
309
|
return false;
|
|
@@ -260,9 +313,13 @@ function isEarHashed(ear, minX, minY, invSize) {
|
|
|
260
313
|
// look for remaining points in increasing z-order
|
|
261
314
|
while (n && n.z <= maxZ) {
|
|
262
315
|
if (
|
|
263
|
-
n
|
|
264
|
-
n
|
|
265
|
-
|
|
316
|
+
n.x >= x0 &&
|
|
317
|
+
n.x <= x1 &&
|
|
318
|
+
n.y >= y0 &&
|
|
319
|
+
n.y <= y1 &&
|
|
320
|
+
n !== a &&
|
|
321
|
+
n !== c &&
|
|
322
|
+
pointInTriangle(ax, ay, bx, by, cx, cy, n.x, n.y) &&
|
|
266
323
|
area(n.prev, n, n.next) >= 0
|
|
267
324
|
)
|
|
268
325
|
return false;
|
|
@@ -285,9 +342,9 @@ function cureLocalIntersections(start, triangles, dim) {
|
|
|
285
342
|
locallyInside(a, b) &&
|
|
286
343
|
locallyInside(b, a)
|
|
287
344
|
) {
|
|
288
|
-
triangles.push(a.i / dim);
|
|
289
|
-
triangles.push(p.i / dim);
|
|
290
|
-
triangles.push(b.i / dim);
|
|
345
|
+
triangles.push((a.i / dim) | 0);
|
|
346
|
+
triangles.push((p.i / dim) | 0);
|
|
347
|
+
triangles.push((b.i / dim) | 0);
|
|
291
348
|
|
|
292
349
|
// remove two nodes involved
|
|
293
350
|
removeNode(p);
|
|
@@ -317,8 +374,8 @@ function splitEarcut(start, triangles, dim, minX, minY, invSize) {
|
|
|
317
374
|
c = filterPoints(c, c.next);
|
|
318
375
|
|
|
319
376
|
// run earcut on each half
|
|
320
|
-
earcutLinked(a, triangles, dim, minX, minY, invSize);
|
|
321
|
-
earcutLinked(c, triangles, dim, minX, minY, invSize);
|
|
377
|
+
earcutLinked(a, triangles, dim, minX, minY, invSize, 0);
|
|
378
|
+
earcutLinked(c, triangles, dim, minX, minY, invSize, 0);
|
|
322
379
|
return;
|
|
323
380
|
}
|
|
324
381
|
b = b.next;
|
|
@@ -328,7 +385,14 @@ function splitEarcut(start, triangles, dim, minX, minY, invSize) {
|
|
|
328
385
|
}
|
|
329
386
|
|
|
330
387
|
// link every hole into the outer loop, producing a single-ring polygon without holes
|
|
331
|
-
function eliminateHoles(
|
|
388
|
+
function eliminateHoles(
|
|
389
|
+
data: NumericArray,
|
|
390
|
+
holeIndices: NumericArray,
|
|
391
|
+
outerNode: Vertex,
|
|
392
|
+
dim: number,
|
|
393
|
+
areas: NumericArray | undefined,
|
|
394
|
+
plane: Plane2D
|
|
395
|
+
): Vertex {
|
|
332
396
|
const queue = [];
|
|
333
397
|
let i;
|
|
334
398
|
let len;
|
|
@@ -339,7 +403,7 @@ function eliminateHoles(data, holeIndices, outerNode, dim, areas) {
|
|
|
339
403
|
for (i = 0, len = holeIndices.length; i < len; i++) {
|
|
340
404
|
start = holeIndices[i] * dim;
|
|
341
405
|
end = i < len - 1 ? holeIndices[i + 1] * dim : data.length;
|
|
342
|
-
list = linkedList(data, start, end, dim, false, areas && areas[i + 1]);
|
|
406
|
+
list = linkedList(data, start, end, dim, false, areas && areas[i + 1], plane);
|
|
343
407
|
if (list === list.next) list.steiner = true;
|
|
344
408
|
queue.push(getLeftmost(list));
|
|
345
409
|
}
|
|
@@ -348,8 +412,7 @@ function eliminateHoles(data, holeIndices, outerNode, dim, areas) {
|
|
|
348
412
|
|
|
349
413
|
// process holes from left to right
|
|
350
414
|
for (i = 0; i < queue.length; i++) {
|
|
351
|
-
eliminateHole(queue[i], outerNode);
|
|
352
|
-
outerNode = filterPoints(outerNode, outerNode.next);
|
|
415
|
+
outerNode = eliminateHole(queue[i], outerNode);
|
|
353
416
|
}
|
|
354
417
|
|
|
355
418
|
return outerNode;
|
|
@@ -361,14 +424,16 @@ function compareX(a, b) {
|
|
|
361
424
|
|
|
362
425
|
// find a bridge between vertices that connects hole with an outer ring and and link it
|
|
363
426
|
function eliminateHole(hole, outerNode) {
|
|
364
|
-
|
|
365
|
-
if (
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
// filter collinear points around the cuts
|
|
369
|
-
filterPoints(outerNode, outerNode.next);
|
|
370
|
-
filterPoints(b, b.next);
|
|
427
|
+
const bridge = findHoleBridge(hole, outerNode);
|
|
428
|
+
if (!bridge) {
|
|
429
|
+
return outerNode;
|
|
371
430
|
}
|
|
431
|
+
|
|
432
|
+
const bridgeReverse = splitPolygon(bridge, hole);
|
|
433
|
+
|
|
434
|
+
// filter collinear points around the cuts
|
|
435
|
+
filterPoints(bridgeReverse, bridgeReverse.next);
|
|
436
|
+
return filterPoints(bridge, bridge.next);
|
|
372
437
|
}
|
|
373
438
|
|
|
374
439
|
// David Eberly's algorithm for finding a bridge between hole and outer polygon
|
|
@@ -386,11 +451,8 @@ function findHoleBridge(hole, outerNode) {
|
|
|
386
451
|
const x = p.x + ((hy - p.y) * (p.next.x - p.x)) / (p.next.y - p.y);
|
|
387
452
|
if (x <= hx && x > qx) {
|
|
388
453
|
qx = x;
|
|
389
|
-
if (x === hx) {
|
|
390
|
-
if (hy === p.y) return p;
|
|
391
|
-
if (hy === p.next.y) return p.next;
|
|
392
|
-
}
|
|
393
454
|
m = p.x < p.next.x ? p : p.next;
|
|
455
|
+
if (x === hx) return m; // hole touches outer segment; pick leftmost endpoint
|
|
394
456
|
}
|
|
395
457
|
}
|
|
396
458
|
p = p.next;
|
|
@@ -398,8 +460,6 @@ function findHoleBridge(hole, outerNode) {
|
|
|
398
460
|
|
|
399
461
|
if (!m) return null;
|
|
400
462
|
|
|
401
|
-
if (hx === qx) return m; // hole touches outer segment; pick leftmost endpoint
|
|
402
|
-
|
|
403
463
|
// look for points inside the triangle of hole point, segment intersection and endpoint;
|
|
404
464
|
// if there are no points found, we have a valid connection;
|
|
405
465
|
// otherwise choose the point of the minimum angle with the ray as connection point
|
|
@@ -446,7 +506,7 @@ function sectorContainsSector(m, p) {
|
|
|
446
506
|
function indexCurve(start, minX, minY, invSize) {
|
|
447
507
|
let p = start;
|
|
448
508
|
do {
|
|
449
|
-
if (p.z ===
|
|
509
|
+
if (p.z === 0) p.z = zOrder(p.x, p.y, minX, minY, invSize);
|
|
450
510
|
p.prevZ = p.prev;
|
|
451
511
|
p.nextZ = p.next;
|
|
452
512
|
p = p.next;
|
|
@@ -519,8 +579,8 @@ function sortLinked(list) {
|
|
|
519
579
|
// z-order of a point given coords and inverse of the longer side of data bbox
|
|
520
580
|
function zOrder(x, y, minX, minY, invSize) {
|
|
521
581
|
// coords are transformed into non-negative 15-bit integer range
|
|
522
|
-
x =
|
|
523
|
-
y =
|
|
582
|
+
x = ((x - minX) * invSize) | 0;
|
|
583
|
+
y = ((y - minY) * invSize) | 0;
|
|
524
584
|
|
|
525
585
|
x = (x | (x << 8)) & 0x00ff00ff;
|
|
526
586
|
x = (x | (x << 4)) & 0x0f0f0f0f;
|
|
@@ -550,9 +610,9 @@ function getLeftmost(start) {
|
|
|
550
610
|
// check if a point lies within a convex triangle
|
|
551
611
|
function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) {
|
|
552
612
|
return (
|
|
553
|
-
(cx - px) * (ay - py)
|
|
554
|
-
(ax - px) * (by - py)
|
|
555
|
-
(bx - px) * (cy - py)
|
|
613
|
+
(cx - px) * (ay - py) >= (ax - px) * (cy - py) &&
|
|
614
|
+
(ax - px) * (by - py) >= (bx - px) * (ay - py) &&
|
|
615
|
+
(bx - px) * (cy - py) >= (cx - px) * (by - py)
|
|
556
616
|
);
|
|
557
617
|
}
|
|
558
618
|
|
|
@@ -658,8 +718,8 @@ function middleInside(a, b) {
|
|
|
658
718
|
// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two;
|
|
659
719
|
// if one belongs to the outer ring and another to a hole, it merges it into a single ring
|
|
660
720
|
function splitPolygon(a, b) {
|
|
661
|
-
const a2 = new
|
|
662
|
-
const b2 = new
|
|
721
|
+
const a2 = new Vertex(a.i, a.x, a.y);
|
|
722
|
+
const b2 = new Vertex(b.i, b.x, b.y);
|
|
663
723
|
const an = a.next;
|
|
664
724
|
const bp = b.prev;
|
|
665
725
|
|
|
@@ -680,7 +740,7 @@ function splitPolygon(a, b) {
|
|
|
680
740
|
|
|
681
741
|
// create a node and optionally link it with previous one (in a circular doubly linked list)
|
|
682
742
|
function insertNode(i, x, y, last) {
|
|
683
|
-
const p = new
|
|
743
|
+
const p = new Vertex(i, x, y);
|
|
684
744
|
|
|
685
745
|
if (!last) {
|
|
686
746
|
p.prev = p;
|
|
@@ -702,25 +762,31 @@ function removeNode(p) {
|
|
|
702
762
|
if (p.nextZ) p.nextZ.prevZ = p.prevZ;
|
|
703
763
|
}
|
|
704
764
|
|
|
705
|
-
|
|
765
|
+
class Vertex {
|
|
706
766
|
// vertex index in coordinates array
|
|
707
|
-
|
|
767
|
+
i: number;
|
|
708
768
|
|
|
709
769
|
// vertex coordinates
|
|
710
|
-
|
|
711
|
-
|
|
770
|
+
x: number;
|
|
771
|
+
y: number;
|
|
712
772
|
|
|
713
773
|
// previous and next vertex nodes in a polygon ring
|
|
714
|
-
|
|
715
|
-
|
|
774
|
+
prev: Vertex = null;
|
|
775
|
+
next: Vertex = null;
|
|
716
776
|
|
|
717
777
|
// z-order curve value
|
|
718
|
-
|
|
778
|
+
z: number = 0;
|
|
719
779
|
|
|
720
780
|
// previous and next nodes in z-order
|
|
721
|
-
|
|
722
|
-
|
|
781
|
+
prevZ: Vertex = null;
|
|
782
|
+
nextZ: Vertex = null;
|
|
723
783
|
|
|
724
784
|
// indicates whether this is a steiner point
|
|
725
|
-
|
|
785
|
+
steiner: boolean = false;
|
|
786
|
+
|
|
787
|
+
constructor(i: number, x: number, y: number) {
|
|
788
|
+
this.i = i;
|
|
789
|
+
this.x = x;
|
|
790
|
+
this.y = y;
|
|
791
|
+
}
|
|
726
792
|
}
|
package/src/polygon-utils.ts
CHANGED
|
@@ -29,12 +29,34 @@ export type SegmentVisitorPoints = (
|
|
|
29
29
|
i2: number
|
|
30
30
|
) => void;
|
|
31
31
|
|
|
32
|
+
export type Plane2D = 'xy' | 'yz' | 'xz';
|
|
33
|
+
|
|
32
34
|
/** Parameters of a polygon. */
|
|
33
35
|
type PolygonParams = {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Start index of the polygon in the array of positions.
|
|
38
|
+
* @default `0`
|
|
39
|
+
*/
|
|
40
|
+
start?: number;
|
|
41
|
+
/**
|
|
42
|
+
* End index of the polygon in the array of positions.
|
|
43
|
+
* @default number of positions
|
|
44
|
+
*/
|
|
45
|
+
end?: number;
|
|
46
|
+
/**
|
|
47
|
+
* Size of a point, 2 (XZ) or 3 (XYZ). Affects only polygons stored in flat arrays.
|
|
48
|
+
* @default `2`
|
|
49
|
+
*/
|
|
50
|
+
size?: number;
|
|
51
|
+
/**
|
|
52
|
+
* Indicates that the first point of the polygon is equal to the last point, and additional checks should be ommited.
|
|
53
|
+
*/
|
|
54
|
+
isClosed?: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* The 2D projection plane on which to calculate the area of a 3D polygon.
|
|
57
|
+
* @default `'xy'`
|
|
58
|
+
*/
|
|
59
|
+
plane?: Plane2D;
|
|
38
60
|
};
|
|
39
61
|
|
|
40
62
|
/**
|
|
@@ -71,6 +93,12 @@ export function getPolygonWindingDirection(
|
|
|
71
93
|
return Math.sign(getPolygonSignedArea(points, options));
|
|
72
94
|
}
|
|
73
95
|
|
|
96
|
+
export const DimIndex: Record<string, number> = {
|
|
97
|
+
x: 0,
|
|
98
|
+
y: 1,
|
|
99
|
+
z: 2
|
|
100
|
+
} as const;
|
|
101
|
+
|
|
74
102
|
/**
|
|
75
103
|
* Returns signed area of the polygon.
|
|
76
104
|
* @param points An array that represents points of the polygon.
|
|
@@ -79,11 +107,14 @@ export function getPolygonWindingDirection(
|
|
|
79
107
|
* https://en.wikipedia.org/wiki/Shoelace_formula
|
|
80
108
|
*/
|
|
81
109
|
export function getPolygonSignedArea(points: NumericArray, options: PolygonParams = {}): number {
|
|
82
|
-
const {start = 0, end = points.length} = options;
|
|
110
|
+
const {start = 0, end = points.length, plane = 'xy'} = options;
|
|
83
111
|
const dim = options.size || 2;
|
|
84
112
|
let area = 0;
|
|
113
|
+
const i0 = DimIndex[plane[0]];
|
|
114
|
+
const i1 = DimIndex[plane[1]];
|
|
115
|
+
|
|
85
116
|
for (let i = start, j = end - dim; i < end; i += dim) {
|
|
86
|
-
area += (points[i] - points[j]) * (points[i +
|
|
117
|
+
area += (points[i + i0] - points[j + i0]) * (points[i + i1] + points[j + i1]);
|
|
87
118
|
j = i;
|
|
88
119
|
}
|
|
89
120
|
return area / 2;
|
|
@@ -196,10 +227,13 @@ export function getPolygonSignedAreaPoints(
|
|
|
196
227
|
options: PolygonParams = {}
|
|
197
228
|
): number {
|
|
198
229
|
// https://en.wikipedia.org/wiki/Shoelace_formula
|
|
199
|
-
const {start = 0, end = points.length} = options;
|
|
230
|
+
const {start = 0, end = points.length, plane = 'xy'} = options;
|
|
200
231
|
let area = 0;
|
|
232
|
+
const i0 = DimIndex[plane[0]];
|
|
233
|
+
const i1 = DimIndex[plane[1]];
|
|
234
|
+
|
|
201
235
|
for (let i = start, j = end - 1; i < end; ++i) {
|
|
202
|
-
area += (points[i][
|
|
236
|
+
area += (points[i][i0] - points[j][i0]) * (points[i][i1] + points[j][i1]);
|
|
203
237
|
j = i;
|
|
204
238
|
}
|
|
205
239
|
return area / 2;
|