@math.gl/polygon 3.6.3 → 4.0.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/cut-by-grid.d.ts +3 -0
  2. package/dist/cut-by-grid.d.ts.map +1 -1
  3. package/dist/{esm/cut-by-grid.js → cut-by-grid.js} +10 -11
  4. package/dist/cut-by-grid.js.map +1 -0
  5. package/dist/{esm/cut-by-mercator-bounds.js → cut-by-mercator-bounds.js} +2 -2
  6. package/dist/cut-by-mercator-bounds.js.map +1 -0
  7. package/dist/earcut.d.ts +3 -1
  8. package/dist/earcut.d.ts.map +1 -1
  9. package/dist/{esm/earcut.js → earcut.js} +96 -65
  10. package/dist/earcut.js.map +1 -0
  11. package/dist/index.cjs +1129 -0
  12. package/dist/index.d.ts +3 -2
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +8 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/{esm/lineclip.js → lineclip.js} +1 -1
  17. package/dist/lineclip.js.map +1 -0
  18. package/dist/polygon-utils.d.ts +22 -0
  19. package/dist/polygon-utils.d.ts.map +1 -1
  20. package/dist/{esm/polygon-utils.js → polygon-utils.js} +15 -4
  21. package/dist/polygon-utils.js.map +1 -0
  22. package/dist/polygon.d.ts +1 -1
  23. package/dist/polygon.d.ts.map +1 -1
  24. package/dist/{esm/polygon.js → polygon.js} +2 -2
  25. package/dist/polygon.js.map +1 -0
  26. package/dist/utils.js.map +1 -0
  27. package/package.json +6 -5
  28. package/src/cut-by-grid.ts +26 -9
  29. package/src/earcut.ts +143 -77
  30. package/src/index.ts +3 -3
  31. package/src/polygon-utils.ts +42 -8
  32. package/src/polygon.ts +1 -1
  33. package/dist/es5/cut-by-grid.js +0 -276
  34. package/dist/es5/cut-by-grid.js.map +0 -1
  35. package/dist/es5/cut-by-mercator-bounds.js +0 -194
  36. package/dist/es5/cut-by-mercator-bounds.js.map +0 -1
  37. package/dist/es5/earcut.js +0 -540
  38. package/dist/es5/earcut.js.map +0 -1
  39. package/dist/es5/index.js +0 -104
  40. package/dist/es5/index.js.map +0 -1
  41. package/dist/es5/lineclip.js +0 -150
  42. package/dist/es5/lineclip.js.map +0 -1
  43. package/dist/es5/polygon-utils.js +0 -154
  44. package/dist/es5/polygon-utils.js.map +0 -1
  45. package/dist/es5/polygon.js +0 -79
  46. package/dist/es5/polygon.js.map +0 -1
  47. package/dist/es5/utils.js +0 -54
  48. package/dist/es5/utils.js.map +0 -1
  49. package/dist/esm/cut-by-grid.js.map +0 -1
  50. package/dist/esm/cut-by-mercator-bounds.js.map +0 -1
  51. package/dist/esm/earcut.js.map +0 -1
  52. package/dist/esm/index.js +0 -8
  53. package/dist/esm/index.js.map +0 -1
  54. package/dist/esm/lineclip.js.map +0 -1
  55. package/dist/esm/polygon-utils.js.map +0 -1
  56. package/dist/esm/polygon.js.map +0 -1
  57. package/dist/esm/utils.js.map +0 -1
  58. /package/dist/{esm/utils.js → utils.js} +0 -0
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 {getPolygonSignedArea} from './polygon-utils';
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: number[],
40
- holeIndices?: number[],
42
+ positions: NumericArray,
43
+ holeIndices?: NumericArray,
41
44
  dim: number = 2,
42
- areas?: number[]
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 ? 1 / 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(data, start, end, dim, clockwise, area) {
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 + 1], last);
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) last = insertNode(i, data[i], data[i + 1], last);
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
- let p = ear.next.next;
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
- while (p !== ear.prev) {
199
- if (pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) && area(p.prev, p, p.next) >= 0)
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 minTX = a.x < b.x ? (a.x < c.x ? a.x : c.x) : b.x < c.x ? b.x : c.x;
216
- const minTY = a.y < b.y ? (a.y < c.y ? a.y : c.y) : b.y < c.y ? b.y : c.y;
217
- const maxTX = a.x > b.x ? (a.x > c.x ? a.x : c.x) : b.x > c.x ? b.x : c.x;
218
- const maxTY = a.y > b.y ? (a.y > c.y ? a.y : c.y) : b.y > c.y ? b.y : c.y;
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(minTX, minTY, minX, minY, invSize);
222
- const maxZ = zOrder(maxTX, maxTY, minX, minY, invSize);
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 !== ear.prev &&
231
- p !== ear.next &&
232
- pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
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 !== ear.prev &&
240
- n !== ear.next &&
241
- pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, n.x, n.y) &&
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 !== ear.prev &&
252
- p !== ear.next &&
253
- pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
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 !== ear.prev &&
264
- n !== ear.next &&
265
- pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, n.x, n.y) &&
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(data, holeIndices, outerNode, dim, areas) {
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
- outerNode = findHoleBridge(hole, outerNode);
365
- if (outerNode) {
366
- const b = splitPolygon(outerNode, hole);
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 === null) p.z = zOrder(p.x, p.y, minX, minY, invSize);
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 = 32767 * (x - minX) * invSize;
523
- y = 32767 * (y - minY) * invSize;
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) - (ax - px) * (cy - py) >= 0 &&
554
- (ax - px) * (by - py) - (bx - px) * (ay - py) >= 0 &&
555
- (bx - px) * (cy - py) - (cx - px) * (by - py) >= 0
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 Node(a.i, a.x, a.y);
662
- const b2 = new Node(b.i, b.x, b.y);
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 Node(i, x, y);
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
- function Node(i, x, y) {
765
+ class Vertex {
706
766
  // vertex index in coordinates array
707
- this.i = i;
767
+ i: number;
708
768
 
709
769
  // vertex coordinates
710
- this.x = x;
711
- this.y = y;
770
+ x: number;
771
+ y: number;
712
772
 
713
773
  // previous and next vertex nodes in a polygon ring
714
- this.prev = null;
715
- this.next = null;
774
+ prev: Vertex = null;
775
+ next: Vertex = null;
716
776
 
717
777
  // z-order curve value
718
- this.z = null;
778
+ z: number = 0;
719
779
 
720
780
  // previous and next nodes in z-order
721
- this.prevZ = null;
722
- this.nextZ = null;
781
+ prevZ: Vertex = null;
782
+ nextZ: Vertex = null;
723
783
 
724
784
  // indicates whether this is a steiner point
725
- this.steiner = false;
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/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- export {default as Polygon} from './polygon';
1
+ export {Polygon} from './polygon';
2
2
 
3
3
  export {
4
4
  getPolygonSignedArea,
@@ -16,5 +16,5 @@ export {cutPolygonByGrid, cutPolylineByGrid} from './cut-by-grid';
16
16
 
17
17
  export {cutPolylineByMercatorBounds, cutPolygonByMercatorBounds} from './cut-by-mercator-bounds';
18
18
 
19
- // DEPRECATED
20
- export {default as _Polygon} from './polygon';
19
+ /** @deprecated */
20
+ export {Polygon as _Polygon} from './polygon';
@@ -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
- start?: number; // Start index of the polygon in the array of positions. Defaults to 0.
35
- end?: number; // End index of the polygon in the array of positions. Defaults to number of positions.
36
- size?: number; // Size of a point, 2 (XZ) or 3 (XYZ). Defaults to 2. Affects only polygons stored in flat arrays.
37
- isClosed?: boolean; // Indicates that the first point of the polygon is equal to the last point, and additional checks should be ommited.
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 + 1] + points[j + 1]);
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][0] - points[j][0]) * (points[i][1] + points[j][1]);
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;
package/src/polygon.ts CHANGED
@@ -19,7 +19,7 @@ export type PolygonOptions = {
19
19
  isClosed?: boolean;
20
20
  };
21
21
 
22
- export default class Polygon {
22
+ export class Polygon {
23
23
  points: NumericArray | number[][];
24
24
  isFlatArray: boolean;
25
25
  options: PolygonOptions;