@immugio/three-math-extensions 0.2.32 → 0.2.34

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/CHANGELOG.md CHANGED
@@ -7,7 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
9
9
 
10
- ## [0.2.32](https://github.com/Immugio/three-math-extensions/compare/0.2.31...0.2.32)
10
+ ## [0.2.34](https://github.com/Immugio/three-math-extensions/compare/0.2.33...0.2.34)
11
+
12
+ ### Commits
13
+
14
+ - Add containsPoint [`8e15aa2`](https://github.com/Immugio/three-math-extensions/commit/8e15aa275efa2b8c939bb3c85e456a269c360054)
15
+ - Add isPolygonClockwise [`9fb7f1c`](https://github.com/Immugio/three-math-extensions/commit/9fb7f1c6f9f185ffacdf933158618588a12e4b33)
16
+ - Add getPolygonArea [`f2f2c32`](https://github.com/Immugio/three-math-extensions/commit/f2f2c32ca19790972520c1d8827d76f20052a1fe)
17
+ - Improve Polygon.containsPoint [`805822d`](https://github.com/Immugio/three-math-extensions/commit/805822df97009d4f4004227e642562760607bc9f)
18
+ - Add Polygon.ensureOpen [`12d96bc`](https://github.com/Immugio/three-math-extensions/commit/12d96bcef0e06da62927b2fd336f821040a6c6c5)
19
+ - Add ensurePolygonClockwise [`a8b1f03`](https://github.com/Immugio/three-math-extensions/commit/a8b1f03e85d86d56b341c2636e4e92eeb652af77)
20
+
21
+ ## [0.2.33](https://github.com/Immugio/three-math-extensions/compare/0.2.32...0.2.33) - 2024-10-21
22
+
23
+ ### Commits
24
+
25
+ - Line2D.groupConnectedLines improve sort [`fa56c1e`](https://github.com/Immugio/three-math-extensions/commit/fa56c1e13b2b49f369034224985f31f069e3079f)
26
+
27
+ ## [0.2.32](https://github.com/Immugio/three-math-extensions/compare/0.2.31...0.2.32) - 2024-10-18
11
28
 
12
29
  ### Commits
13
30
 
@@ -274,29 +291,48 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
274
291
 
275
292
  - Add Vec2 documentation [`f625e66`](https://github.com/Immugio/three-math-extensions/commit/f625e66b60ee0d90c5c788a80989f64013d60265)
276
293
 
277
- ## [0.0.11](https://github.com/Immugio/three-math-extensions/compare/0.0.6...0.0.11) - 2022-12-20
294
+ ## [0.0.11](https://github.com/Immugio/three-math-extensions/compare/0.0.10...0.0.11) - 2022-12-20
278
295
 
279
296
  ### Commits
280
297
 
298
+ - Improve API, minor bug fix, improve test coverage [`881a5f0`](https://github.com/Immugio/three-math-extensions/commit/881a5f096823f0d5fd90faa76cd602f076291bc5)
281
299
  - Add Line2D.extendToOrTrimAtIntersection [`ab82a36`](https://github.com/Immugio/three-math-extensions/commit/ab82a36db52b9ae83cf8bfb848362d295140d073)
282
300
  - Added "isNear" to Vec2 & Vec3 as a shorthand for distanceTo <= maxDistance [`a27cff8`](https://github.com/Immugio/three-math-extensions/commit/a27cff8421472f625ed240439b9e6a7d2c430db8)
301
+ - Add .npmignore [`f58329a`](https://github.com/Immugio/three-math-extensions/commit/f58329a86b96589bb574d6ebed37d4cc4474663a)
283
302
  - Add Vec2.moveTowards [`b46ba12`](https://github.com/Immugio/three-math-extensions/commit/b46ba1286f9924cd9bfecfb37200664202e964ba)
284
303
  - Add Line2D.extendToOrTrimAtIntersection [`b22aa12`](https://github.com/Immugio/three-math-extensions/commit/b22aa120b5eb02562cebc4573374ebb61e2dba14)
304
+ - Correct change log version [`c6244bf`](https://github.com/Immugio/three-math-extensions/commit/c6244bf1488ad21bcc5589d1dff62c41d8182d48)
305
+
306
+ ## [0.0.10](https://github.com/Immugio/three-math-extensions/compare/0.0.9...0.0.10) - 2022-11-21
307
+
308
+ ### Commits
309
+
310
+ - Release 0.0.4 - tag pattern 6 [`c1c5454`](https://github.com/Immugio/three-math-extensions/commit/c1c54541ed400e1ad49bf42fe8926cd5293efefd)
311
+
312
+ ## [0.0.9](https://github.com/Immugio/three-math-extensions/compare/0.0.8...0.0.9) - 2022-11-21
313
+
314
+ ### Commits
315
+
316
+ - Release 0.0.4 - tag pattern 5 [`5245eed`](https://github.com/Immugio/three-math-extensions/commit/5245eed60195103e989c0fadf9bd642f39ef4589)
317
+
318
+ ## [0.0.8](https://github.com/Immugio/three-math-extensions/compare/0.0.7...0.0.8) - 2022-11-21
319
+
320
+ ### Commits
321
+
322
+ - Release 0.0.4 - tag pattern 4 [`ab9ad4e`](https://github.com/Immugio/three-math-extensions/commit/ab9ad4e49477a991482ed6d4d3a3ca4687a70b44)
323
+
324
+ ## [0.0.7](https://github.com/Immugio/three-math-extensions/compare/0.0.6...0.0.7) - 2022-11-21
285
325
 
286
326
  ## [0.0.6](https://github.com/Immugio/three-math-extensions/compare/0.0.5...0.0.6) - 2022-11-24
287
327
 
288
- ## 0.0.5 - 2023-09-01
328
+ ## [0.0.5](https://github.com/Immugio/three-math-extensions/compare/0.0.4...0.0.5) - 2023-09-01
289
329
 
290
330
  ### Commits
291
331
 
292
- - Automatic publish with change log [`a70c99d`](https://github.com/Immugio/three-math-extensions/commit/a70c99d0d42e60411ce01bcdb8dd4f3f489bb949)
293
- - Switch to npm [`ed47a17`](https://github.com/Immugio/three-math-extensions/commit/ed47a1787522db3bde5a2112bfd02480fe2edc54)
294
- - Initial commit [`3d1bf9e`](https://github.com/Immugio/three-math-extensions/commit/3d1bf9ef015570830007c9be99140c8c8d760d7f)
295
332
  - Generate documentation using TSDoc [`304c3a8`](https://github.com/Immugio/three-math-extensions/commit/304c3a84b5dcc49183db57083f550d134ae641b2)
296
333
  - Add eslint [`98e4912`](https://github.com/Immugio/three-math-extensions/commit/98e4912d637b42ba80e2f3267638b43296113019)
297
334
  - Update jest packages [`af23b4f`](https://github.com/Immugio/three-math-extensions/commit/af23b4f08154bba3407f05b773865215e7e1cba8)
298
335
  - Add Rectangle, update Polygon [`58ee875`](https://github.com/Immugio/three-math-extensions/commit/58ee87539af8f9ade186e5250cba9e01926da514)
299
- - Add Line2D [`9a1dd0f`](https://github.com/Immugio/three-math-extensions/commit/9a1dd0f58352b7b25828693c688aa4770e95c174)
300
336
  - Add isPointInPolygon [`a59eb4b`](https://github.com/Immugio/three-math-extensions/commit/a59eb4be026f17a3106070ae626a0588cd4f4411)
301
337
  - Improve Line2D.closestPointToPoint, add Vec2.signedAngle [`151f214`](https://github.com/Immugio/three-math-extensions/commit/151f21462e0358057ad8e9d75d5782563a1061f6)
302
338
  - Improve documentation [`d0fcb51`](https://github.com/Immugio/three-math-extensions/commit/d0fcb5132f127b4382ac5f7291575a061b8ec121)
@@ -310,7 +346,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
310
346
  - Add Vec2.signedAngle [`863c8f2`](https://github.com/Immugio/three-math-extensions/commit/863c8f27f11288cbda535e21bb688206259269ed)
311
347
  - Add intersect method to Line3D [`6fe47de`](https://github.com/Immugio/three-math-extensions/commit/6fe47de7caaa1807b47a4363e551510c463757d7)
312
348
  - Add Line2D.extendToOrTrimAtIntersection [`ab82a36`](https://github.com/Immugio/three-math-extensions/commit/ab82a36db52b9ae83cf8bfb848362d295140d073)
313
- - Readme, npm info [`dda4a28`](https://github.com/Immugio/three-math-extensions/commit/dda4a282a71a00308dcae858ffe53d67d4185be8)
314
349
  - Add Vec2.fromPoints to accept multiple points [`a261402`](https://github.com/Immugio/three-math-extensions/commit/a2614027cf5fb8263189b48f7e0bb9a23a552c15)
315
350
  - Improve documentation for Vec2 and Vec3 [`6a2373d`](https://github.com/Immugio/three-math-extensions/commit/6a2373d8b6754a87720dcaea5d98336bfa7bc5b5)
316
351
  - Add Vec2.parallelTo [`989874d`](https://github.com/Immugio/three-math-extensions/commit/989874dcfe122d3ee84d8d56d79cb88e4e441736)
@@ -322,11 +357,8 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
322
357
  - Add .npmignore [`f58329a`](https://github.com/Immugio/three-math-extensions/commit/f58329a86b96589bb574d6ebed37d4cc4474663a)
323
358
  - Polygon from bounding size [`eae6701`](https://github.com/Immugio/three-math-extensions/commit/eae67012f57f426f8b5259b765000447ce06d608)
324
359
  - Add Vec2 documentation [`f625e66`](https://github.com/Immugio/three-math-extensions/commit/f625e66b60ee0d90c5c788a80989f64013d60265)
325
- - Run tests in CI on commit [`e3f77ca`](https://github.com/Immugio/three-math-extensions/commit/e3f77ca76e25f4d99eef0130e5779b5e7c5aec6b)
326
360
  - Clean up documentation [`70c8d5e`](https://github.com/Immugio/three-math-extensions/commit/70c8d5efe8b7095d7a03af637df3af5d46615293)
327
- - Remove babel.config.js [`c9a1e16`](https://github.com/Immugio/three-math-extensions/commit/c9a1e1607cffec8a3d74adcca644320a79ca4c43)
328
361
  - Line2D and Line3D to return this instead of specific type [`761ef6a`](https://github.com/Immugio/three-math-extensions/commit/761ef6a9d8cc4e35120b666576794e521aa3b991)
329
- - Initial changelog [`6c98aa6`](https://github.com/Immugio/three-math-extensions/commit/6c98aa6ad631cf5e73d32dd76276f99b3ba5089f)
330
362
  - Line2D.in3DSpace added [`a6ce0ec`](https://github.com/Immugio/three-math-extensions/commit/a6ce0ecb67f5c7b2a75fcc283c28af626153a4af)
331
363
  - Line3D.connectsTo added [`6d2cfa0`](https://github.com/Immugio/three-math-extensions/commit/6d2cfa0f5335c665f325a694a32c57b574ec326d)
332
364
  - Line2D.hasIntersectionWithAngle to support optional tolerance [`b313bbe`](https://github.com/Immugio/three-math-extensions/commit/b313bbe118d435d53750deefd9a9e29ba6ec5c71)
@@ -335,21 +367,36 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
335
367
  - Use Vec2 instead of Vector2 [`7e6a6ea`](https://github.com/Immugio/three-math-extensions/commit/7e6a6ea272f4441ef4bc78b3fdec23fc783fa1db)
336
368
  - Add release instructions into README.md [`03653b1`](https://github.com/Immugio/three-math-extensions/commit/03653b1ffa55be606d3f9cd588e28a6084462c2e)
337
369
  - Documentation improvements [`160db8b`](https://github.com/Immugio/three-math-extensions/commit/160db8ba6d6e5eb63a4e91ed9c40efd6fa70181a)
338
- - Release 0.0.4 [`bdac840`](https://github.com/Immugio/three-math-extensions/commit/bdac840f33261a3f39ef33bc05c80772169cfe87)
339
370
  - Update release instructions [`5b41a2e`](https://github.com/Immugio/three-math-extensions/commit/5b41a2ed7e15450dbb6088a7f7ed0031a013badc)
340
- - Add test badge [`37c62e8`](https://github.com/Immugio/three-math-extensions/commit/37c62e809bdcdfc63f7fe135469fd131f190f950)
341
371
  - Add Polygon to exports [`ed66775`](https://github.com/Immugio/three-math-extensions/commit/ed66775c33e961835b23843222b822cfd9c16b1d)
342
372
  - Vec2.fromPoint and Vec3.fromPoint should accept null [`4b871af`](https://github.com/Immugio/three-math-extensions/commit/4b871af297bdcbe8584f1e2b99d602247b77687c)
343
373
  - Documentation improvements [`2f3e676`](https://github.com/Immugio/three-math-extensions/commit/2f3e6768b3b0139b60688fc151a4084f15566f8d)
344
- - Edit publish script condition [`dc77d89`](https://github.com/Immugio/three-math-extensions/commit/dc77d8978d87daab8732d6429ad1b01ba0ca6f97)
345
- - CI to use node version 16 [`dc42650`](https://github.com/Immugio/three-math-extensions/commit/dc426508939de922cc0316b7652bbd09aeed4610)
346
- - Action rename [`26a1f01`](https://github.com/Immugio/three-math-extensions/commit/26a1f014827faab2c02d30ca8ed18dac8f9ff8af)
347
374
  - Documentation update [`d5c7a07`](https://github.com/Immugio/three-math-extensions/commit/d5c7a0765f6097f5d3a3be01967d4059f19682fb)
348
375
  - Fix peer dependency version [`00dd116`](https://github.com/Immugio/three-math-extensions/commit/00dd1169f578d5769207031fa625f29c96a38c31)
349
376
  - Add Line2D.extendToOrTrimAtIntersection [`b22aa12`](https://github.com/Immugio/three-math-extensions/commit/b22aa120b5eb02562cebc4573374ebb61e2dba14)
350
377
  - Correct change log version [`c6244bf`](https://github.com/Immugio/three-math-extensions/commit/c6244bf1488ad21bcc5589d1dff62c41d8182d48)
351
378
  - Release 0.0.4 - tag pattern 6 [`c1c5454`](https://github.com/Immugio/three-math-extensions/commit/c1c54541ed400e1ad49bf42fe8926cd5293efefd)
352
379
  - Release 0.0.4 - tag pattern 5 [`5245eed`](https://github.com/Immugio/three-math-extensions/commit/5245eed60195103e989c0fadf9bd642f39ef4589)
380
+ - Excluded files from build [`ec70614`](https://github.com/Immugio/three-math-extensions/commit/ec70614bc7df7a98f854c7a6693365118e04faf7)
381
+ - Revert npm ignore [`590b26a`](https://github.com/Immugio/three-math-extensions/commit/590b26a9e57fc41466b51e711f3f5c648e2d56ed)
382
+
383
+ ## 0.0.4 - 2022-11-21
384
+
385
+ ### Commits
386
+
387
+ - Automatic publish with change log [`a70c99d`](https://github.com/Immugio/three-math-extensions/commit/a70c99d0d42e60411ce01bcdb8dd4f3f489bb949)
388
+ - Switch to npm [`ed47a17`](https://github.com/Immugio/three-math-extensions/commit/ed47a1787522db3bde5a2112bfd02480fe2edc54)
389
+ - Initial commit [`3d1bf9e`](https://github.com/Immugio/three-math-extensions/commit/3d1bf9ef015570830007c9be99140c8c8d760d7f)
390
+ - Add Line2D [`9a1dd0f`](https://github.com/Immugio/three-math-extensions/commit/9a1dd0f58352b7b25828693c688aa4770e95c174)
391
+ - Readme, npm info [`dda4a28`](https://github.com/Immugio/three-math-extensions/commit/dda4a282a71a00308dcae858ffe53d67d4185be8)
392
+ - Run tests in CI on commit [`e3f77ca`](https://github.com/Immugio/three-math-extensions/commit/e3f77ca76e25f4d99eef0130e5779b5e7c5aec6b)
393
+ - Remove babel.config.js [`c9a1e16`](https://github.com/Immugio/three-math-extensions/commit/c9a1e1607cffec8a3d74adcca644320a79ca4c43)
394
+ - Initial changelog [`6c98aa6`](https://github.com/Immugio/three-math-extensions/commit/6c98aa6ad631cf5e73d32dd76276f99b3ba5089f)
395
+ - Release 0.0.4 [`bdac840`](https://github.com/Immugio/three-math-extensions/commit/bdac840f33261a3f39ef33bc05c80772169cfe87)
396
+ - Add test badge [`37c62e8`](https://github.com/Immugio/three-math-extensions/commit/37c62e809bdcdfc63f7fe135469fd131f190f950)
397
+ - Edit publish script condition [`dc77d89`](https://github.com/Immugio/three-math-extensions/commit/dc77d8978d87daab8732d6429ad1b01ba0ca6f97)
398
+ - CI to use node version 16 [`dc42650`](https://github.com/Immugio/three-math-extensions/commit/dc426508939de922cc0316b7652bbd09aeed4610)
399
+ - Action rename [`26a1f01`](https://github.com/Immugio/three-math-extensions/commit/26a1f014827faab2c02d30ca8ed18dac8f9ff8af)
353
400
  - Release 0.0.4 - tag pattern 4 [`ab9ad4e`](https://github.com/Immugio/three-math-extensions/commit/ab9ad4e49477a991482ed6d4d3a3ca4687a70b44)
354
401
  - Release 0.0.4 - tag pattern 3 [`d7d4c8f`](https://github.com/Immugio/three-math-extensions/commit/d7d4c8f546e6550868ddd06a5213fdec6c68bcd2)
355
402
  - Release 0.0.4 - tag pattern 2 [`0dcf801`](https://github.com/Immugio/three-math-extensions/commit/0dcf80190bd67a3ca93d45131a403201ab39e00f)
@@ -357,6 +404,4 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
357
404
  - Release 0.0.5 [`c80d6bd`](https://github.com/Immugio/three-math-extensions/commit/c80d6bd12c25ddad0f67610cdf5c804d5759f084)
358
405
  - Status badge update [`370843e`](https://github.com/Immugio/three-math-extensions/commit/370843ed0a28b024761555572b7d51ccbdb4b77d)
359
406
  - Run tests in CI on commit [`aee8c55`](https://github.com/Immugio/three-math-extensions/commit/aee8c556ae1cab9025dbe0984dd17278fe6275a1)
360
- - Excluded files from build [`ec70614`](https://github.com/Immugio/three-math-extensions/commit/ec70614bc7df7a98f854c7a6693365118e04faf7)
361
- - Revert npm ignore [`590b26a`](https://github.com/Immugio/three-math-extensions/commit/590b26a9e57fc41466b51e711f3f5c648e2d56ed)
362
407
  - Add Line2D to index [`a5fb6bd`](https://github.com/Immugio/three-math-extensions/commit/a5fb6bdeee5d9f07f325bfffc31ef96f0ce167d1)
package/cjs/Line2D.js CHANGED
@@ -6,6 +6,7 @@ const Vec2_1 = require("./Vec2");
6
6
  const MathConstants_1 = require("./MathConstants");
7
7
  const Line3D_1 = require("./Line3D");
8
8
  const directions2d_1 = require("./directions2d");
9
+ const sortLinesByConnections_1 = require("./sortLinesByConnections");
9
10
  const _startP = /*@__PURE__*/ new Vec2_1.Vec2();
10
11
  const _startEnd = /*@__PURE__*/ new Vec2_1.Vec2();
11
12
  class Line2D {
@@ -682,21 +683,9 @@ class Line2D {
682
683
  if (!visited.has(line)) {
683
684
  const group = [];
684
685
  dfs(line, group);
685
- connectedLines.push(group);
686
+ connectedLines.push((0, sortLinesByConnections_1.sortLinesByConnections)(group, 1));
686
687
  }
687
688
  });
688
- // Sort each group based on connection order
689
- connectedLines.forEach(group => {
690
- group.sort((a, b) => {
691
- if (a.start.isNear(b.start, tolerance) || a.end.isNear(b.start, tolerance)) {
692
- return -1;
693
- }
694
- if (a.start.isNear(b.end, tolerance) || a.end.isNear(b.end, tolerance)) {
695
- return 1;
696
- }
697
- return 0;
698
- });
699
- });
700
689
  return connectedLines;
701
690
  }
702
691
  /**
package/cjs/Polygon.js CHANGED
@@ -5,8 +5,11 @@ const Vec2_1 = require("./Vec2");
5
5
  const Rectangle_1 = require("./Rectangle");
6
6
  const BoundingBox_1 = require("./BoundingBox");
7
7
  const polygonPerimeter_1 = require("./polygonPerimeter");
8
- const isPointInPolygon_1 = require("./isPointInPolygon");
9
8
  const Line2D_1 = require("./Line2D");
9
+ const isPolygonClockwise_1 = require("./isPolygonClockwise");
10
+ const ensurePolygonClockwise_1 = require("./ensurePolygonClockwise");
11
+ const containsPoint_1 = require("./containsPoint");
12
+ const getPolygonArea_1 = require("./getPolygonArea");
10
13
  class Polygon {
11
14
  contour;
12
15
  holes;
@@ -61,6 +64,21 @@ class Polygon {
61
64
  }
62
65
  return this;
63
66
  }
67
+ ensureOpen() {
68
+ function ensure(points) {
69
+ if (points.length > 2 && points[0].equals(points.at(-1))) {
70
+ points.pop();
71
+ }
72
+ }
73
+ ensure(this.contour);
74
+ for (const hole of this.holes || []) {
75
+ ensure(hole);
76
+ }
77
+ return this;
78
+ }
79
+ get area() {
80
+ return (0, getPolygonArea_1.getPolygonArea)(this.contour);
81
+ }
64
82
  boundingBox() {
65
83
  let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
66
84
  for (const p of this.contour) {
@@ -93,8 +111,16 @@ class Polygon {
93
111
  perimeter() {
94
112
  return (0, polygonPerimeter_1.polygonPerimeter)(this.contour);
95
113
  }
96
- containsPoint(point) {
97
- return (0, isPointInPolygon_1.isPointInPolygon)(this.contour, point) && (this.holes || []).every(hole => !(0, isPointInPolygon_1.isPointInPolygon)(hole, point));
114
+ get isClockwise() {
115
+ return (0, isPolygonClockwise_1.isPolygonClockwise)(this.contour);
116
+ }
117
+ ensureClockwise() {
118
+ (0, ensurePolygonClockwise_1.ensurePolygonClockwise)(this.contour);
119
+ return this;
120
+ }
121
+ containsPoint(...points) {
122
+ return points.every(point => (0, containsPoint_1.containsPoint)(this.contour, point)) &&
123
+ (this.holes || []).every(hole => !points.some(point => (0, containsPoint_1.containsPoint)(hole, point)));
98
124
  }
99
125
  flipSingle(centerX, poly) {
100
126
  for (const point of poly) {
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.containsPoints = exports.containsPoint = void 0;
4
+ /**
5
+ * Return if polygon contains @point or when the point is on the polygon boundary.
6
+ * Works consistently with points on the boundary.
7
+ * Works with simple polygons only.
8
+ * The code is copied from PolyK library.
9
+ * @returns {boolean} depth
10
+ * @param polygon
11
+ * @param point
12
+ */
13
+ function containsPoint(polygon, point) {
14
+ const p = [];
15
+ for (let i = 0; i < polygon.length; i++) {
16
+ p.push(polygon[i].x);
17
+ p.push(polygon[i].y);
18
+ }
19
+ const px = point.x;
20
+ const py = point.y;
21
+ const n = p.length >> 1;
22
+ let ax;
23
+ let ay = p[2 * n - 3] - py;
24
+ let bx = p[2 * n - 2] - px;
25
+ let by = p[2 * n - 1] - py;
26
+ let lup;
27
+ // var lup = by > ay;
28
+ for (let i = 0; i < n; i++) {
29
+ ax = bx;
30
+ ay = by;
31
+ bx = p[2 * i] - px;
32
+ by = p[2 * i + 1] - py;
33
+ if (ay === by)
34
+ continue;
35
+ lup = by > ay;
36
+ }
37
+ let depth = 0;
38
+ for (let i = 0; i < n; i++) {
39
+ ax = bx;
40
+ ay = by;
41
+ bx = p[2 * i] - px;
42
+ by = p[2 * i + 1] - py;
43
+ if (ay < 0 && by < 0)
44
+ continue; // both "up" or both "down"
45
+ if (ay > 0 && by > 0)
46
+ continue; // both "up" or both "down"
47
+ if (ax < 0 && bx < 0)
48
+ continue; // both points on the left
49
+ if (ay === by && Math.min(ax, bx) <= 0)
50
+ return true;
51
+ if (ay === by)
52
+ continue;
53
+ const lx = ax + (bx - ax) * (-ay) / (by - ay);
54
+ if (lx === 0)
55
+ return true; // point on edge
56
+ if (lx > 0)
57
+ depth++;
58
+ if (ay === 0 && lup && by > ay)
59
+ depth--; // hit vertex, both up
60
+ if (ay === 0 && !lup && by < ay)
61
+ depth--; // hit vertex, both down(x < (p[j].x - p[i].x) * (y - p[i].y) / (p[j].y - p[i].y) + p[i].x)) {
62
+ lup = by > ay;
63
+ }
64
+ return (depth & 1) === 1;
65
+ }
66
+ exports.containsPoint = containsPoint;
67
+ function containsPoints(polygon, points) {
68
+ return points.every(p => containsPoint(polygon, p));
69
+ }
70
+ exports.containsPoints = containsPoints;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ensurePolygonClockwise = void 0;
4
+ const isPolygonClockwise_1 = require("./isPolygonClockwise");
5
+ function ensurePolygonClockwise(poly) {
6
+ if (!(0, isPolygonClockwise_1.isPolygonClockwise)(poly)) {
7
+ return poly.reverse();
8
+ }
9
+ return poly;
10
+ }
11
+ exports.ensurePolygonClockwise = ensurePolygonClockwise;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getPolygonArea = void 0;
4
+ /**
5
+ * Returns the area of polygon.
6
+ * @returns {number}
7
+ * @param polygon {Point2[]}
8
+ */
9
+ function getPolygonArea(polygon) {
10
+ const p = [];
11
+ for (const point of polygon) {
12
+ p.push(point.x, point.y);
13
+ }
14
+ if (p.length < 6)
15
+ return 0;
16
+ const l = p.length - 2;
17
+ let sum = 0;
18
+ for (let i = 0; i < l; i += 2) {
19
+ sum += (p[i + 2] - p[i]) * (p[i + 1] + p[i + 3]);
20
+ }
21
+ sum += (p[0] - p[l]) * (p[l + 1] + p[1]);
22
+ return Math.abs(-sum * 0.5); // Handles -0
23
+ }
24
+ exports.getPolygonArea = getPolygonArea;
package/cjs/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.extendOrTrimPolylinesAtIntersections = exports.offsetPolyline = exports.polygonPerimeter = exports.isContinuousClosedShape = exports.directions2d = exports.directions = exports.isPointInPolygon = exports.HalfPI = exports.TwoPI = exports.normalizeAngleRadians = exports.normalizeAngleDegrees = exports.Rectangle = exports.BoundingBox = exports.Polygon = exports.Size2 = exports.Line3D = exports.Line2D = exports.Vec3 = exports.Vec2 = void 0;
3
+ exports.getPolygonArea = exports.containsPoint = exports.ensurePolygonClockwise = exports.isPolygonClockwise = exports.sortLinesByConnections = exports.extendOrTrimPolylinesAtIntersections = exports.offsetPolyline = exports.polygonPerimeter = exports.isContinuousClosedShape = exports.directions2d = exports.directions = exports.isPointInPolygon = exports.HalfPI = exports.TwoPI = exports.normalizeAngleRadians = exports.normalizeAngleDegrees = exports.Rectangle = exports.BoundingBox = exports.Polygon = exports.Size2 = exports.Line3D = exports.Line2D = exports.Vec3 = exports.Vec2 = void 0;
4
4
  var Vec2_1 = require("./Vec2");
5
5
  Object.defineProperty(exports, "Vec2", { enumerable: true, get: function () { return Vec2_1.Vec2; } });
6
6
  var Vec3_1 = require("./Vec3");
@@ -38,3 +38,13 @@ var offsetPolyline_1 = require("./offsetPolyline");
38
38
  Object.defineProperty(exports, "offsetPolyline", { enumerable: true, get: function () { return offsetPolyline_1.offsetPolyline; } });
39
39
  var extendOrTrimPolylinesAtIntersections_1 = require("./extendOrTrimPolylinesAtIntersections");
40
40
  Object.defineProperty(exports, "extendOrTrimPolylinesAtIntersections", { enumerable: true, get: function () { return extendOrTrimPolylinesAtIntersections_1.extendOrTrimPolylinesAtIntersections; } });
41
+ var sortLinesByConnections_1 = require("./sortLinesByConnections");
42
+ Object.defineProperty(exports, "sortLinesByConnections", { enumerable: true, get: function () { return sortLinesByConnections_1.sortLinesByConnections; } });
43
+ var isPolygonClockwise_1 = require("./isPolygonClockwise");
44
+ Object.defineProperty(exports, "isPolygonClockwise", { enumerable: true, get: function () { return isPolygonClockwise_1.isPolygonClockwise; } });
45
+ var ensurePolygonClockwise_1 = require("./ensurePolygonClockwise");
46
+ Object.defineProperty(exports, "ensurePolygonClockwise", { enumerable: true, get: function () { return ensurePolygonClockwise_1.ensurePolygonClockwise; } });
47
+ var containsPoint_1 = require("./containsPoint");
48
+ Object.defineProperty(exports, "containsPoint", { enumerable: true, get: function () { return containsPoint_1.containsPoint; } });
49
+ var getPolygonArea_1 = require("./getPolygonArea");
50
+ Object.defineProperty(exports, "getPolygonArea", { enumerable: true, get: function () { return getPolygonArea_1.getPolygonArea; } });
@@ -1,6 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.isPointInPolygon = void 0;
4
+ /**
5
+ * Check if a point is inside a polygon
6
+ * Warning: The function returns unreliable results for points on the polygon boundary.
7
+ * Obsolete, use containsPoint instead.
8
+ * @param p
9
+ * @param point
10
+ */
4
11
  function isPointInPolygon(p, point) {
5
12
  const x = point.x, y = point.y;
6
13
  let i, j, c = false;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isPolygonClockwise = void 0;
4
+ /*
5
+ * Determines if a polygon is clockwise or counter-clockwise.
6
+ * X increases to the right, Y increases downwards.
7
+ * Based on this answer https://stackoverflow.com/a/18472899/1837173 - the result is inverted, they assume the inverse y-axis
8
+ */
9
+ function isPolygonClockwise(vertices) {
10
+ let sum = 0.0;
11
+ for (let i = 0; i < vertices.length; i++) {
12
+ const v1 = vertices[i];
13
+ const v2 = vertices[(i + 1) % vertices.length];
14
+ sum += (v2.x - v1.x) * (v2.y + v1.y);
15
+ }
16
+ return sum < 0.0;
17
+ }
18
+ exports.isPolygonClockwise = isPolygonClockwise;
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sortLinesByConnections = void 0;
4
+ /**
5
+ * Sort connected lines by their connections.
6
+ * When the polygon is open, the first line must be the line that has no connection at the start.
7
+ * When the polygon is open, the last line must be the line that has no connection at the end.
8
+ * If the lines form a closed polygon, any line can be the first line.
9
+ */
10
+ function sortLinesByConnections(lines, tolerance = 0) {
11
+ const remainingLines = [...lines];
12
+ const startLineIndex = findStartLineIndex(remainingLines, tolerance);
13
+ const sortedLines = [remainingLines.splice(startLineIndex, 1)[0]];
14
+ while (remainingLines.length > 0) {
15
+ const lastLine = sortedLines[sortedLines.length - 1];
16
+ const nextLineIndex = remainingLines.findIndex(line => lastLine.end.isNear(line.start, tolerance));
17
+ if (nextLineIndex === -1) {
18
+ console.log("Lines do not form a connected path");
19
+ return [...sortedLines, ...remainingLines];
20
+ }
21
+ sortedLines.push(remainingLines.splice(nextLineIndex, 1)[0]);
22
+ }
23
+ return sortedLines;
24
+ }
25
+ exports.sortLinesByConnections = sortLinesByConnections;
26
+ /**
27
+ * Find the index of the starting line.
28
+ * A starting line is defined as a line that has no other line connected to its start.
29
+ * If such a line does not exist, it means that the lines form a closed polygon, return 0.
30
+ * @param lines
31
+ * @param tolerance
32
+ */
33
+ function findStartLineIndex(lines, tolerance) {
34
+ for (let i = 0; i < lines.length; i++) {
35
+ const startLine = lines[i];
36
+ const isStartLine = !lines.some(line => line.end.isNear(startLine.start, tolerance));
37
+ if (isStartLine) {
38
+ return i;
39
+ }
40
+ }
41
+ return 0;
42
+ }
package/esm/Line2D.js CHANGED
@@ -3,6 +3,7 @@ import { Vec2 } from "./Vec2";
3
3
  import { TwoPI } from "./MathConstants";
4
4
  import { Line3D } from "./Line3D";
5
5
  import { directions2d } from "./directions2d";
6
+ import { sortLinesByConnections } from "./sortLinesByConnections";
6
7
  const _startP = /*@__PURE__*/ new Vec2();
7
8
  const _startEnd = /*@__PURE__*/ new Vec2();
8
9
  export class Line2D {
@@ -679,21 +680,9 @@ export class Line2D {
679
680
  if (!visited.has(line)) {
680
681
  const group = [];
681
682
  dfs(line, group);
682
- connectedLines.push(group);
683
+ connectedLines.push(sortLinesByConnections(group, 1));
683
684
  }
684
685
  });
685
- // Sort each group based on connection order
686
- connectedLines.forEach(group => {
687
- group.sort((a, b) => {
688
- if (a.start.isNear(b.start, tolerance) || a.end.isNear(b.start, tolerance)) {
689
- return -1;
690
- }
691
- if (a.start.isNear(b.end, tolerance) || a.end.isNear(b.end, tolerance)) {
692
- return 1;
693
- }
694
- return 0;
695
- });
696
- });
697
686
  return connectedLines;
698
687
  }
699
688
  /**
package/esm/Polygon.js CHANGED
@@ -2,8 +2,11 @@ import { Vec2 } from "./Vec2";
2
2
  import { Rectangle } from "./Rectangle";
3
3
  import { BoundingBox } from "./BoundingBox";
4
4
  import { polygonPerimeter } from "./polygonPerimeter";
5
- import { isPointInPolygon } from "./isPointInPolygon";
6
5
  import { Line2D } from "./Line2D";
6
+ import { isPolygonClockwise } from "./isPolygonClockwise";
7
+ import { ensurePolygonClockwise } from "./ensurePolygonClockwise";
8
+ import { containsPoint } from "./containsPoint";
9
+ import { getPolygonArea } from "./getPolygonArea";
7
10
  export class Polygon {
8
11
  contour;
9
12
  holes;
@@ -58,6 +61,21 @@ export class Polygon {
58
61
  }
59
62
  return this;
60
63
  }
64
+ ensureOpen() {
65
+ function ensure(points) {
66
+ if (points.length > 2 && points[0].equals(points.at(-1))) {
67
+ points.pop();
68
+ }
69
+ }
70
+ ensure(this.contour);
71
+ for (const hole of this.holes || []) {
72
+ ensure(hole);
73
+ }
74
+ return this;
75
+ }
76
+ get area() {
77
+ return getPolygonArea(this.contour);
78
+ }
61
79
  boundingBox() {
62
80
  let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
63
81
  for (const p of this.contour) {
@@ -90,8 +108,16 @@ export class Polygon {
90
108
  perimeter() {
91
109
  return polygonPerimeter(this.contour);
92
110
  }
93
- containsPoint(point) {
94
- return isPointInPolygon(this.contour, point) && (this.holes || []).every(hole => !isPointInPolygon(hole, point));
111
+ get isClockwise() {
112
+ return isPolygonClockwise(this.contour);
113
+ }
114
+ ensureClockwise() {
115
+ ensurePolygonClockwise(this.contour);
116
+ return this;
117
+ }
118
+ containsPoint(...points) {
119
+ return points.every(point => containsPoint(this.contour, point)) &&
120
+ (this.holes || []).every(hole => !points.some(point => containsPoint(hole, point)));
95
121
  }
96
122
  flipSingle(centerX, poly) {
97
123
  for (const point of poly) {
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Return if polygon contains @point or when the point is on the polygon boundary.
3
+ * Works consistently with points on the boundary.
4
+ * Works with simple polygons only.
5
+ * The code is copied from PolyK library.
6
+ * @returns {boolean} depth
7
+ * @param polygon
8
+ * @param point
9
+ */
10
+ export function containsPoint(polygon, point) {
11
+ const p = [];
12
+ for (let i = 0; i < polygon.length; i++) {
13
+ p.push(polygon[i].x);
14
+ p.push(polygon[i].y);
15
+ }
16
+ const px = point.x;
17
+ const py = point.y;
18
+ const n = p.length >> 1;
19
+ let ax;
20
+ let ay = p[2 * n - 3] - py;
21
+ let bx = p[2 * n - 2] - px;
22
+ let by = p[2 * n - 1] - py;
23
+ let lup;
24
+ // var lup = by > ay;
25
+ for (let i = 0; i < n; i++) {
26
+ ax = bx;
27
+ ay = by;
28
+ bx = p[2 * i] - px;
29
+ by = p[2 * i + 1] - py;
30
+ if (ay === by)
31
+ continue;
32
+ lup = by > ay;
33
+ }
34
+ let depth = 0;
35
+ for (let i = 0; i < n; i++) {
36
+ ax = bx;
37
+ ay = by;
38
+ bx = p[2 * i] - px;
39
+ by = p[2 * i + 1] - py;
40
+ if (ay < 0 && by < 0)
41
+ continue; // both "up" or both "down"
42
+ if (ay > 0 && by > 0)
43
+ continue; // both "up" or both "down"
44
+ if (ax < 0 && bx < 0)
45
+ continue; // both points on the left
46
+ if (ay === by && Math.min(ax, bx) <= 0)
47
+ return true;
48
+ if (ay === by)
49
+ continue;
50
+ const lx = ax + (bx - ax) * (-ay) / (by - ay);
51
+ if (lx === 0)
52
+ return true; // point on edge
53
+ if (lx > 0)
54
+ depth++;
55
+ if (ay === 0 && lup && by > ay)
56
+ depth--; // hit vertex, both up
57
+ if (ay === 0 && !lup && by < ay)
58
+ depth--; // hit vertex, both down(x < (p[j].x - p[i].x) * (y - p[i].y) / (p[j].y - p[i].y) + p[i].x)) {
59
+ lup = by > ay;
60
+ }
61
+ return (depth & 1) === 1;
62
+ }
63
+ export function containsPoints(polygon, points) {
64
+ return points.every(p => containsPoint(polygon, p));
65
+ }
@@ -0,0 +1,7 @@
1
+ import { isPolygonClockwise } from "./isPolygonClockwise";
2
+ export function ensurePolygonClockwise(poly) {
3
+ if (!isPolygonClockwise(poly)) {
4
+ return poly.reverse();
5
+ }
6
+ return poly;
7
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Returns the area of polygon.
3
+ * @returns {number}
4
+ * @param polygon {Point2[]}
5
+ */
6
+ export function getPolygonArea(polygon) {
7
+ const p = [];
8
+ for (const point of polygon) {
9
+ p.push(point.x, point.y);
10
+ }
11
+ if (p.length < 6)
12
+ return 0;
13
+ const l = p.length - 2;
14
+ let sum = 0;
15
+ for (let i = 0; i < l; i += 2) {
16
+ sum += (p[i + 2] - p[i]) * (p[i + 1] + p[i + 3]);
17
+ }
18
+ sum += (p[0] - p[l]) * (p[l + 1] + p[1]);
19
+ return Math.abs(-sum * 0.5); // Handles -0
20
+ }
package/esm/index.js CHANGED
@@ -16,3 +16,8 @@ export { isContinuousClosedShape } from "./isContinuousClosedShape";
16
16
  export { polygonPerimeter } from "./polygonPerimeter";
17
17
  export { offsetPolyline } from "./offsetPolyline";
18
18
  export { extendOrTrimPolylinesAtIntersections } from "./extendOrTrimPolylinesAtIntersections";
19
+ export { sortLinesByConnections } from "./sortLinesByConnections";
20
+ export { isPolygonClockwise } from "./isPolygonClockwise";
21
+ export { ensurePolygonClockwise } from "./ensurePolygonClockwise";
22
+ export { containsPoint } from "./containsPoint";
23
+ export { getPolygonArea } from "./getPolygonArea";
@@ -1,3 +1,10 @@
1
+ /**
2
+ * Check if a point is inside a polygon
3
+ * Warning: The function returns unreliable results for points on the polygon boundary.
4
+ * Obsolete, use containsPoint instead.
5
+ * @param p
6
+ * @param point
7
+ */
1
8
  export function isPointInPolygon(p, point) {
2
9
  const x = point.x, y = point.y;
3
10
  let i, j, c = false;
@@ -0,0 +1,14 @@
1
+ /*
2
+ * Determines if a polygon is clockwise or counter-clockwise.
3
+ * X increases to the right, Y increases downwards.
4
+ * Based on this answer https://stackoverflow.com/a/18472899/1837173 - the result is inverted, they assume the inverse y-axis
5
+ */
6
+ export function isPolygonClockwise(vertices) {
7
+ let sum = 0.0;
8
+ for (let i = 0; i < vertices.length; i++) {
9
+ const v1 = vertices[i];
10
+ const v2 = vertices[(i + 1) % vertices.length];
11
+ sum += (v2.x - v1.x) * (v2.y + v1.y);
12
+ }
13
+ return sum < 0.0;
14
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Sort connected lines by their connections.
3
+ * When the polygon is open, the first line must be the line that has no connection at the start.
4
+ * When the polygon is open, the last line must be the line that has no connection at the end.
5
+ * If the lines form a closed polygon, any line can be the first line.
6
+ */
7
+ export function sortLinesByConnections(lines, tolerance = 0) {
8
+ const remainingLines = [...lines];
9
+ const startLineIndex = findStartLineIndex(remainingLines, tolerance);
10
+ const sortedLines = [remainingLines.splice(startLineIndex, 1)[0]];
11
+ while (remainingLines.length > 0) {
12
+ const lastLine = sortedLines[sortedLines.length - 1];
13
+ const nextLineIndex = remainingLines.findIndex(line => lastLine.end.isNear(line.start, tolerance));
14
+ if (nextLineIndex === -1) {
15
+ console.log("Lines do not form a connected path");
16
+ return [...sortedLines, ...remainingLines];
17
+ }
18
+ sortedLines.push(remainingLines.splice(nextLineIndex, 1)[0]);
19
+ }
20
+ return sortedLines;
21
+ }
22
+ /**
23
+ * Find the index of the starting line.
24
+ * A starting line is defined as a line that has no other line connected to its start.
25
+ * If such a line does not exist, it means that the lines form a closed polygon, return 0.
26
+ * @param lines
27
+ * @param tolerance
28
+ */
29
+ function findStartLineIndex(lines, tolerance) {
30
+ for (let i = 0; i < lines.length; i++) {
31
+ const startLine = lines[i];
32
+ const isStartLine = !lines.some(line => line.end.isNear(startLine.start, tolerance));
33
+ if (isStartLine) {
34
+ return i;
35
+ }
36
+ }
37
+ return 0;
38
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@immugio/three-math-extensions",
3
- "version": "0.2.32",
3
+ "version": "0.2.34",
4
4
  "description": "Set of utilities for 2d and 3d line math built on top of three.js",
5
5
  "author": "Jan Mikeska <janmikeska@gmail.com>",
6
6
  "license": "ISC",
package/src/Line2D.ts CHANGED
@@ -4,6 +4,7 @@ import { Vec2 } from "./Vec2";
4
4
  import { TwoPI } from "./MathConstants";
5
5
  import { Line3D } from "./Line3D";
6
6
  import { directions2d } from "./directions2d";
7
+ import { sortLinesByConnections } from "./sortLinesByConnections";
7
8
 
8
9
  const _startP = /*@__PURE__*/ new Vec2();
9
10
  const _startEnd = /*@__PURE__*/ new Vec2();
@@ -803,23 +804,10 @@ export class Line2D {
803
804
  if (!visited.has(line)) {
804
805
  const group: Line2D[] = [];
805
806
  dfs(line, group);
806
- connectedLines.push(group);
807
+ connectedLines.push(sortLinesByConnections(group, 1));
807
808
  }
808
809
  });
809
810
 
810
- // Sort each group based on connection order
811
- connectedLines.forEach(group => {
812
- group.sort((a, b) => {
813
- if (a.start.isNear(b.start, tolerance) || a.end.isNear(b.start, tolerance)) {
814
- return -1;
815
- }
816
- if (a.start.isNear(b.end, tolerance) || a.end.isNear(b.end, tolerance)) {
817
- return 1;
818
- }
819
- return 0;
820
- });
821
- });
822
-
823
811
  return connectedLines;
824
812
  }
825
813
 
package/src/Polygon.ts CHANGED
@@ -3,8 +3,11 @@ import { Vec2 } from "./Vec2";
3
3
  import { Rectangle } from "./Rectangle";
4
4
  import { BoundingBox } from "./BoundingBox";
5
5
  import { polygonPerimeter } from "./polygonPerimeter";
6
- import { isPointInPolygon } from "./isPointInPolygon";
7
6
  import { Line2D } from "./Line2D";
7
+ import { isPolygonClockwise } from "./isPolygonClockwise";
8
+ import { ensurePolygonClockwise } from "./ensurePolygonClockwise";
9
+ import { containsPoint } from "./containsPoint";
10
+ import { getPolygonArea } from "./getPolygonArea";
8
11
 
9
12
  export class Polygon {
10
13
 
@@ -73,6 +76,26 @@ export class Polygon {
73
76
  return this;
74
77
  }
75
78
 
79
+ public ensureOpen(): this {
80
+ function ensure(points: Vec2[]): void {
81
+ if (points.length > 2 && points[0].equals(points.at(-1))) {
82
+ points.pop();
83
+ }
84
+ }
85
+
86
+ ensure(this.contour);
87
+
88
+ for (const hole of this.holes || []) {
89
+ ensure(hole);
90
+ }
91
+
92
+ return this;
93
+ }
94
+
95
+ public get area(): number {
96
+ return getPolygonArea(this.contour);
97
+ }
98
+
76
99
  public boundingBox(): BoundingBox {
77
100
  let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
78
101
 
@@ -107,8 +130,18 @@ export class Polygon {
107
130
  return polygonPerimeter(this.contour);
108
131
  }
109
132
 
110
- public containsPoint(point: Vec2): boolean {
111
- return isPointInPolygon(this.contour, point) && (this.holes || []).every(hole => !isPointInPolygon(hole, point));
133
+ public get isClockwise(): boolean {
134
+ return isPolygonClockwise(this.contour);
135
+ }
136
+
137
+ public ensureClockwise(): this {
138
+ ensurePolygonClockwise(this.contour);
139
+ return this;
140
+ }
141
+
142
+ public containsPoint(...points: Vec2[]): boolean {
143
+ return points.every(point => containsPoint(this.contour, point)) &&
144
+ (this.holes || []).every(hole => !points.some(point => containsPoint(hole, point)));
112
145
  }
113
146
 
114
147
  private flipSingle(centerX: number, poly: Vec2[]): void {
@@ -0,0 +1,66 @@
1
+ import { Point2 } from "./Point2";
2
+
3
+ /**
4
+ * Return if polygon contains @point or when the point is on the polygon boundary.
5
+ * Works consistently with points on the boundary.
6
+ * Works with simple polygons only.
7
+ * The code is copied from PolyK library.
8
+ * @returns {boolean} depth
9
+ * @param polygon
10
+ * @param point
11
+ */
12
+ export function containsPoint(polygon: Point2[], point: Point2): boolean {
13
+ const p: number[] = [];
14
+ for (let i = 0; i < polygon.length; i++) {
15
+ p.push(polygon[i].x);
16
+ p.push(polygon[i].y);
17
+ }
18
+
19
+ const px = point.x;
20
+ const py = point.y;
21
+ const n = p.length >> 1;
22
+
23
+ let ax: number;
24
+
25
+ let ay = p[2 * n - 3] - py;
26
+ let bx = p[2 * n - 2] - px;
27
+ let by = p[2 * n - 1] - py;
28
+ let lup: boolean;
29
+
30
+ // var lup = by > ay;
31
+ for (let i = 0; i < n; i++) {
32
+ ax = bx;
33
+ ay = by;
34
+ bx = p[2 * i] - px;
35
+ by = p[2 * i + 1] - py;
36
+ if (ay === by) continue;
37
+ lup = by > ay;
38
+ }
39
+
40
+ let depth = 0;
41
+ for (let i = 0; i < n; i++) {
42
+ ax = bx;
43
+ ay = by;
44
+ bx = p[2 * i] - px;
45
+ by = p[2 * i + 1] - py;
46
+ if (ay < 0 && by < 0) continue; // both "up" or both "down"
47
+ if (ay > 0 && by > 0) continue; // both "up" or both "down"
48
+ if (ax < 0 && bx < 0) continue; // both points on the left
49
+
50
+ if (ay === by && Math.min(ax, bx) <= 0) return true;
51
+ if (ay === by) continue;
52
+
53
+ const lx = ax + (bx - ax) * (-ay) / (by - ay);
54
+ if (lx === 0) return true; // point on edge
55
+ if (lx > 0) depth++;
56
+ if (ay === 0 && lup && by > ay) depth--; // hit vertex, both up
57
+ if (ay === 0 && !lup && by < ay) depth--; // hit vertex, both down(x < (p[j].x - p[i].x) * (y - p[i].y) / (p[j].y - p[i].y) + p[i].x)) {
58
+ lup = by > ay;
59
+ }
60
+
61
+ return (depth & 1) === 1;
62
+ }
63
+
64
+ export function containsPoints(polygon: Point2[], points: Point2[]): boolean {
65
+ return points.every(p => containsPoint(polygon, p));
66
+ }
@@ -0,0 +1,10 @@
1
+ import { Point2 } from "./Point2";
2
+ import { isPolygonClockwise } from "./isPolygonClockwise";
3
+
4
+ export function ensurePolygonClockwise<T extends Point2>(poly: T[]): T[] {
5
+ if (!isPolygonClockwise(poly)) {
6
+ return poly.reverse();
7
+ }
8
+
9
+ return poly;
10
+ }
@@ -0,0 +1,22 @@
1
+ import { Point2 } from "./Point2";
2
+
3
+ /**
4
+ * Returns the area of polygon.
5
+ * @returns {number}
6
+ * @param polygon {Point2[]}
7
+ */
8
+ export function getPolygonArea(polygon: Point2[]): number {
9
+ const p: number[] = [];
10
+ for (const point of polygon) {
11
+ p.push(point.x, point.y);
12
+ }
13
+
14
+ if (p.length < 6) return 0;
15
+ const l = p.length - 2;
16
+ let sum = 0;
17
+ for (let i = 0; i < l; i += 2) {
18
+ sum += (p[i + 2] - p[i]) * (p[i + 1] + p[i + 3]);
19
+ }
20
+ sum += (p[0] - p[l]) * (p[l + 1] + p[1]);
21
+ return Math.abs(-sum * 0.5); // Handles -0
22
+ }
package/src/index.ts CHANGED
@@ -17,4 +17,9 @@ export { directions2d } from "./directions2d";
17
17
  export { isContinuousClosedShape } from "./isContinuousClosedShape";
18
18
  export { polygonPerimeter } from "./polygonPerimeter";
19
19
  export { offsetPolyline } from "./offsetPolyline";
20
- export { extendOrTrimPolylinesAtIntersections } from "./extendOrTrimPolylinesAtIntersections";
20
+ export { extendOrTrimPolylinesAtIntersections } from "./extendOrTrimPolylinesAtIntersections";
21
+ export { sortLinesByConnections } from "./sortLinesByConnections";
22
+ export { isPolygonClockwise } from "./isPolygonClockwise";
23
+ export { ensurePolygonClockwise } from "./ensurePolygonClockwise";
24
+ export { containsPoint } from "./containsPoint";
25
+ export { getPolygonArea } from "./getPolygonArea";
@@ -1,13 +1,18 @@
1
1
  import { Point2 } from "./Point2";
2
2
 
3
+ /**
4
+ * Check if a point is inside a polygon
5
+ * Warning: The function returns unreliable results for points on the polygon boundary.
6
+ * Obsolete, use containsPoint instead.
7
+ * @param p
8
+ * @param point
9
+ */
3
10
  export function isPointInPolygon(p: Point2[], point: Point2): boolean {
4
-
5
11
  const x = point.x, y = point.y;
6
12
 
7
13
  let i: number, j: number, c = false;
8
14
 
9
15
  for (i = 0, j = p.length - 1; i < p.length; j = i++) {
10
-
11
16
  if ((((p[i].y <= y) && (y < p[j].y)) ||
12
17
  ((p[j].y <= y) && (y < p[i].y))) &&
13
18
  (x < (p[j].x - p[i].x) * (y - p[i].y) / (p[j].y - p[i].y) + p[i].x)) {
@@ -0,0 +1,16 @@
1
+ import { Point2 } from "./Point2";
2
+
3
+ /*
4
+ * Determines if a polygon is clockwise or counter-clockwise.
5
+ * X increases to the right, Y increases downwards.
6
+ * Based on this answer https://stackoverflow.com/a/18472899/1837173 - the result is inverted, they assume the inverse y-axis
7
+ */
8
+ export function isPolygonClockwise(vertices: Point2[]): boolean {
9
+ let sum = 0.0;
10
+ for (let i = 0; i < vertices.length; i++) {
11
+ const v1 = vertices[i];
12
+ const v2 = vertices[(i + 1) % vertices.length];
13
+ sum += (v2.x - v1.x) * (v2.y + v1.y);
14
+ }
15
+ return sum < 0.0;
16
+ }
@@ -0,0 +1,46 @@
1
+ import { Line2D } from "./Line2D";
2
+
3
+ /**
4
+ * Sort connected lines by their connections.
5
+ * When the polygon is open, the first line must be the line that has no connection at the start.
6
+ * When the polygon is open, the last line must be the line that has no connection at the end.
7
+ * If the lines form a closed polygon, any line can be the first line.
8
+ */
9
+ export function sortLinesByConnections(lines: Line2D[], tolerance: number = 0): Line2D[] {
10
+ const remainingLines = [...lines];
11
+ const startLineIndex = findStartLineIndex(remainingLines, tolerance);
12
+
13
+ const sortedLines: Line2D[] = [remainingLines.splice(startLineIndex, 1)[0]];
14
+
15
+ while (remainingLines.length > 0) {
16
+ const lastLine = sortedLines[sortedLines.length - 1];
17
+ const nextLineIndex = remainingLines.findIndex(line => lastLine.end.isNear(line.start, tolerance));
18
+
19
+ if (nextLineIndex === -1) {
20
+ console.log("Lines do not form a connected path");
21
+ return [...sortedLines, ...remainingLines];
22
+ }
23
+
24
+ sortedLines.push(remainingLines.splice(nextLineIndex, 1)[0]);
25
+ }
26
+
27
+ return sortedLines;
28
+ }
29
+
30
+ /**
31
+ * Find the index of the starting line.
32
+ * A starting line is defined as a line that has no other line connected to its start.
33
+ * If such a line does not exist, it means that the lines form a closed polygon, return 0.
34
+ * @param lines
35
+ * @param tolerance
36
+ */
37
+ function findStartLineIndex(lines: Line2D[], tolerance: number): number {
38
+ for (let i = 0; i < lines.length; i++) {
39
+ const startLine = lines[i];
40
+ const isStartLine = !lines.some(line => line.end.isNear(startLine.start, tolerance));
41
+ if (isStartLine) {
42
+ return i;
43
+ }
44
+ }
45
+ return 0;
46
+ }
@@ -12,11 +12,15 @@ export declare class Polygon {
12
12
  centerOnOrigin(): this;
13
13
  center(): Vec2;
14
14
  ensureLastPoint(): this;
15
+ ensureOpen(): this;
16
+ get area(): number;
15
17
  boundingBox(): BoundingBox;
16
18
  toBoundingPolygon(): Polygon;
17
19
  flip(): this;
18
20
  perimeter(): number;
19
- containsPoint(point: Vec2): boolean;
21
+ get isClockwise(): boolean;
22
+ ensureClockwise(): this;
23
+ containsPoint(...points: Vec2[]): boolean;
20
24
  private flipSingle;
21
25
  translate(translate: Vec2): this;
22
26
  /**
@@ -0,0 +1,12 @@
1
+ import { Point2 } from "./Point2";
2
+ /**
3
+ * Return if polygon contains @point or when the point is on the polygon boundary.
4
+ * Works consistently with points on the boundary.
5
+ * Works with simple polygons only.
6
+ * The code is copied from PolyK library.
7
+ * @returns {boolean} depth
8
+ * @param polygon
9
+ * @param point
10
+ */
11
+ export declare function containsPoint(polygon: Point2[], point: Point2): boolean;
12
+ export declare function containsPoints(polygon: Point2[], points: Point2[]): boolean;
@@ -0,0 +1,2 @@
1
+ import { Point2 } from "./Point2";
2
+ export declare function ensurePolygonClockwise<T extends Point2>(poly: T[]): T[];
@@ -0,0 +1,7 @@
1
+ import { Point2 } from "./Point2";
2
+ /**
3
+ * Returns the area of polygon.
4
+ * @returns {number}
5
+ * @param polygon {Point2[]}
6
+ */
7
+ export declare function getPolygonArea(polygon: Point2[]): number;
package/types/index.d.ts CHANGED
@@ -18,3 +18,8 @@ export { isContinuousClosedShape } from "./isContinuousClosedShape";
18
18
  export { polygonPerimeter } from "./polygonPerimeter";
19
19
  export { offsetPolyline } from "./offsetPolyline";
20
20
  export { extendOrTrimPolylinesAtIntersections } from "./extendOrTrimPolylinesAtIntersections";
21
+ export { sortLinesByConnections } from "./sortLinesByConnections";
22
+ export { isPolygonClockwise } from "./isPolygonClockwise";
23
+ export { ensurePolygonClockwise } from "./ensurePolygonClockwise";
24
+ export { containsPoint } from "./containsPoint";
25
+ export { getPolygonArea } from "./getPolygonArea";
@@ -1,2 +1,9 @@
1
1
  import { Point2 } from "./Point2";
2
+ /**
3
+ * Check if a point is inside a polygon
4
+ * Warning: The function returns unreliable results for points on the polygon boundary.
5
+ * Obsolete, use containsPoint instead.
6
+ * @param p
7
+ * @param point
8
+ */
2
9
  export declare function isPointInPolygon(p: Point2[], point: Point2): boolean;
@@ -0,0 +1,2 @@
1
+ import { Point2 } from "./Point2";
2
+ export declare function isPolygonClockwise(vertices: Point2[]): boolean;
@@ -0,0 +1,8 @@
1
+ import { Line2D } from "./Line2D";
2
+ /**
3
+ * Sort connected lines by their connections.
4
+ * When the polygon is open, the first line must be the line that has no connection at the start.
5
+ * When the polygon is open, the last line must be the line that has no connection at the end.
6
+ * If the lines form a closed polygon, any line can be the first line.
7
+ */
8
+ export declare function sortLinesByConnections(lines: Line2D[], tolerance?: number): Line2D[];