@immugio/three-math-extensions 0.2.33 → 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,18 @@ 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.33](https://github.com/Immugio/three-math-extensions/compare/0.2.32...0.2.33)
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
11
22
 
12
23
  ### Commits
13
24
 
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.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;
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");
@@ -40,3 +40,11 @@ var extendOrTrimPolylinesAtIntersections_1 = require("./extendOrTrimPolylinesAtI
40
40
  Object.defineProperty(exports, "extendOrTrimPolylinesAtIntersections", { enumerable: true, get: function () { return extendOrTrimPolylinesAtIntersections_1.extendOrTrimPolylinesAtIntersections; } });
41
41
  var sortLinesByConnections_1 = require("./sortLinesByConnections");
42
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;
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
@@ -17,3 +17,7 @@ export { polygonPerimeter } from "./polygonPerimeter";
17
17
  export { offsetPolyline } from "./offsetPolyline";
18
18
  export { extendOrTrimPolylinesAtIntersections } from "./extendOrTrimPolylinesAtIntersections";
19
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@immugio/three-math-extensions",
3
- "version": "0.2.33",
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/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
@@ -18,4 +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";
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
+ }
@@ -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
@@ -19,3 +19,7 @@ export { polygonPerimeter } from "./polygonPerimeter";
19
19
  export { offsetPolyline } from "./offsetPolyline";
20
20
  export { extendOrTrimPolylinesAtIntersections } from "./extendOrTrimPolylinesAtIntersections";
21
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;