@immugio/three-math-extensions 0.3.4 → 0.3.6
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 +17 -1
- package/cjs/Line2D.js +90 -13
- package/docs/classes/BoundingBox.md +121 -121
- package/docs/classes/Line2D.md +1366 -1366
- package/docs/classes/Line3D.md +831 -831
- package/docs/classes/Polygon.md +297 -297
- package/docs/classes/Rectangle.md +291 -291
- package/docs/classes/Size2.md +55 -55
- package/docs/classes/Vec2.md +282 -282
- package/docs/classes/Vec3.md +338 -338
- package/docs/interfaces/Point2.md +30 -30
- package/docs/interfaces/Point3.md +41 -41
- package/docs/modules.md +209 -209
- package/eslint.config.mjs +111 -111
- package/esm/Line2D.js +90 -13
- package/package.json +62 -62
- package/src/BoundingBox.ts +13 -13
- package/src/Line2D.ts +951 -857
- package/src/Line3D.ts +586 -586
- package/src/MathConstants.ts +1 -1
- package/src/Point2.ts +3 -3
- package/src/Point3.ts +4 -4
- package/src/Polygon.ts +286 -286
- package/src/Rectangle.ts +92 -92
- package/src/Size2.ts +3 -3
- package/src/Vec2.ts +124 -124
- package/src/Vec3.ts +167 -167
- package/src/containsPoint.ts +65 -65
- package/src/directions.ts +9 -9
- package/src/directions2d.ts +7 -7
- package/src/ensurePolygonClockwise.ts +9 -9
- package/src/extendOrTrimPolylinesAtIntersections.ts +10 -10
- package/src/getPolygonArea.ts +21 -21
- package/src/index.ts +24 -24
- package/src/isContinuousClosedShape.ts +24 -24
- package/src/isPointInPolygon.ts +23 -23
- package/src/isPolygonClockwise.ts +15 -15
- package/src/normalizeAngleDegrees.ts +6 -6
- package/src/normalizeAngleRadians.ts +14 -14
- package/src/offsetPolyline.ts +26 -26
- package/src/polygonPerimeter.ts +13 -13
- package/src/sortLinesByConnections.ts +45 -45
- package/types/Line2D.d.ts +12 -5
package/src/MathConstants.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const TwoPI = 2 * Math.PI;
|
|
1
|
+
export const TwoPI = 2 * Math.PI;
|
|
2
2
|
export const HalfPI = Math.PI / 2;
|
package/src/Point2.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export interface Point2 {
|
|
2
|
-
x: number,
|
|
3
|
-
y: number,
|
|
1
|
+
export interface Point2 {
|
|
2
|
+
x: number,
|
|
3
|
+
y: number,
|
|
4
4
|
}
|
package/src/Point3.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export interface Point3 {
|
|
2
|
-
x: number,
|
|
3
|
-
y: number,
|
|
4
|
-
z: number
|
|
1
|
+
export interface Point3 {
|
|
2
|
+
x: number,
|
|
3
|
+
y: number,
|
|
4
|
+
z: number
|
|
5
5
|
}
|
package/src/Polygon.ts
CHANGED
|
@@ -1,287 +1,287 @@
|
|
|
1
|
-
import { Point2 } from "./Point2";
|
|
2
|
-
import { Vec2 } from "./Vec2";
|
|
3
|
-
import { Rectangle } from "./Rectangle";
|
|
4
|
-
import { BoundingBox } from "./BoundingBox";
|
|
5
|
-
import { polygonPerimeter } from "./polygonPerimeter";
|
|
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";
|
|
11
|
-
|
|
12
|
-
export class Polygon {
|
|
13
|
-
|
|
14
|
-
constructor(public contour: Vec2[], public holes?: Vec2[][]) {
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
public static fromPoints(contour: Point2[], holes?: Point2[][]): Polygon {
|
|
18
|
-
return new Polygon(contour.map(p => Vec2.fromPoint(p)), holes?.map(h => h.map(p => Vec2.fromPoint(p))));
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
public static fromSize(width: number, height: number): Polygon {
|
|
22
|
-
return new Polygon([
|
|
23
|
-
new Vec2(0, 0),
|
|
24
|
-
new Vec2(width, 0),
|
|
25
|
-
new Vec2(width, height),
|
|
26
|
-
new Vec2(0, height),
|
|
27
|
-
]);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
public get size(): Vec2 {
|
|
31
|
-
const { minX, maxX, minY, maxY } = this.boundingBox();
|
|
32
|
-
return new Vec2(maxX - minX, maxY - minY);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
public centerOnOrigin(): this {
|
|
36
|
-
const center = this.center();
|
|
37
|
-
|
|
38
|
-
function centerPoints(points: Vec2[]): void {
|
|
39
|
-
for (const point of points) {
|
|
40
|
-
point.x -= center.x;
|
|
41
|
-
point.y -= center.y;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
centerPoints(this.contour);
|
|
46
|
-
|
|
47
|
-
for (const hole of this.holes || []) {
|
|
48
|
-
centerPoints(hole);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return this;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
public center(): Vec2 {
|
|
55
|
-
const { minX, maxX, minY, maxY } = this.boundingBox();
|
|
56
|
-
|
|
57
|
-
const x = (maxX + minX) / 2;
|
|
58
|
-
const y = (maxY + minY) / 2;
|
|
59
|
-
|
|
60
|
-
return new Vec2(x, y);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
public ensureLastPoint(): this {
|
|
64
|
-
function ensure(points: Vec2[]): void {
|
|
65
|
-
if (!points[0].equals(points.at(-1))) {
|
|
66
|
-
points.push(points[0].clone());
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
ensure(this.contour);
|
|
71
|
-
|
|
72
|
-
for (const hole of this.holes || []) {
|
|
73
|
-
ensure(hole);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return this;
|
|
77
|
-
}
|
|
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
|
-
|
|
99
|
-
public boundingBox(): BoundingBox {
|
|
100
|
-
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
|
|
101
|
-
|
|
102
|
-
for (const p of this.contour) {
|
|
103
|
-
if (minX > p.x) minX = p.x;
|
|
104
|
-
if (maxX < p.x) maxX = p.x;
|
|
105
|
-
if (minY > p.y) minY = p.y;
|
|
106
|
-
if (maxY < p.y) maxY = p.y;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return new BoundingBox(minX, maxX, minY, maxY);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
public toBoundingPolygon(): Polygon {
|
|
113
|
-
const bounding = this.boundingBox();
|
|
114
|
-
return new Polygon([
|
|
115
|
-
new Vec2(bounding.minX, bounding.minY),
|
|
116
|
-
new Vec2(bounding.maxX, bounding.minY),
|
|
117
|
-
new Vec2(bounding.maxX, bounding.maxY),
|
|
118
|
-
new Vec2(bounding.minX, bounding.maxY),
|
|
119
|
-
]);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
public flip(): this {
|
|
123
|
-
const centerX = this.center().x;
|
|
124
|
-
this.flipSingle(centerX, this.contour);
|
|
125
|
-
this.holes?.forEach(hole => this.flipSingle(centerX, hole));
|
|
126
|
-
return this;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
public perimeter(): number {
|
|
130
|
-
return polygonPerimeter(this.contour);
|
|
131
|
-
}
|
|
132
|
-
|
|
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: Point2[]): boolean {
|
|
143
|
-
return points.every(point => containsPoint(this.contour, point)) &&
|
|
144
|
-
(this.holes || []).every(hole => !points.some(point => containsPoint(hole, point)));
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
private flipSingle(centerX: number, poly: Vec2[]): void {
|
|
148
|
-
for (const point of poly) {
|
|
149
|
-
const xDistanceToCenter = Math.abs(centerX - point.x);
|
|
150
|
-
point.x = point.x < centerX ? centerX + xDistanceToCenter : centerX - xDistanceToCenter;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
public translate(translate: Vec2): this {
|
|
155
|
-
this.contour.forEach(p => p.add(translate));
|
|
156
|
-
this.holes?.forEach(hole => hole.forEach(p => p.add(translate)));
|
|
157
|
-
return this;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Translates the polygon so that the lowest x and y values are 0.
|
|
162
|
-
*/
|
|
163
|
-
public shiftToZero(): this {
|
|
164
|
-
let xMin = Infinity, yMin = Infinity; // Find the diff between the lowest x & y & 0
|
|
165
|
-
for (const point of this.contour) {
|
|
166
|
-
xMin = Math.min(xMin, point.x);
|
|
167
|
-
yMin = Math.min(yMin, point.y);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (xMin !== 0 || yMin !== 0) { // Make the poly to start at 0
|
|
171
|
-
this.translate(new Vec2(-xMin, -yMin));
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return this;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
public offsetContour(offset: number): this {
|
|
178
|
-
if (!this.contour[0].equals(this.contour.at(-1))) {
|
|
179
|
-
console.error("The contour should be closed");
|
|
180
|
-
return this;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const lines = Line2D
|
|
184
|
-
.fromPolygon(this.contour)
|
|
185
|
-
.map(line => line.translateLeft(offset));
|
|
186
|
-
|
|
187
|
-
for (let i = 0; i < lines.length; i++) {
|
|
188
|
-
const line = lines[i];
|
|
189
|
-
const next = lines[(i + 1) % lines.length];
|
|
190
|
-
line.extendToOrTrimAtIntersection(next);
|
|
191
|
-
next.extendToOrTrimAtIntersection(line);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
for (let i = 0; i < this.contour.length; i++) {
|
|
195
|
-
this.contour[i].copy(lines[i % lines.length].start);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return this;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
public translateContourLine(index: number, offset: number): this {
|
|
202
|
-
if (index < 0 || index > this.contour.length - 2) {
|
|
203
|
-
console.error(`Index out of bounds: ${index}`);
|
|
204
|
-
return this;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
if (!this.contour[0].equals(this.contour.at(-1))) {
|
|
208
|
-
console.error("The contour should be closed");
|
|
209
|
-
return this;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const line = Line2D.fromPoints(this.contour[index], this.contour[index + 1]);
|
|
213
|
-
const prev = Line2D.fromPoints(this.contour[(index - 1 + this.contour.length) % this.contour.length], this.contour[index]);
|
|
214
|
-
const next = Line2D.fromPoints(this.contour[index + 1], this.contour[(index + 2) % this.contour.length]);
|
|
215
|
-
|
|
216
|
-
line.translateLeft(offset);
|
|
217
|
-
line.extendToOrTrimAtIntersection(prev);
|
|
218
|
-
line.extendToOrTrimAtIntersection(next);
|
|
219
|
-
|
|
220
|
-
this.contour[index].copy(line.start);
|
|
221
|
-
this.contour[index + 1].copy(line.end);
|
|
222
|
-
|
|
223
|
-
if (index === 0) {
|
|
224
|
-
this.contour.at(-1).copy(line.start);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
if (index === this.contour.length - 2) {
|
|
228
|
-
this.contour[0].copy(line.end);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
return this;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
public rotate(angle: number, center = this.center()): this {
|
|
235
|
-
this.contour.forEach(p => p.rotateAround(center, angle));
|
|
236
|
-
this.holes?.forEach(hole => hole.forEach(p => p.rotateAround(center, angle)));
|
|
237
|
-
return this;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
public toRectangle(): Rectangle {
|
|
241
|
-
const bounding = this.boundingBox();
|
|
242
|
-
return new Rectangle(bounding.minX, bounding.maxX, bounding.minY, bounding.maxY);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
public clone(): Polygon {
|
|
246
|
-
return new Polygon(this.contour.map(p => p.clone()), this.holes?.map(h => h.map(p => p.clone())));
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
public roundIfCloseToInteger(max: number = 0.000000000001): this {
|
|
250
|
-
this.contour.forEach(p => p.roundIfCloseToInteger(max));
|
|
251
|
-
this.holes?.forEach(h => h.forEach(p => p.roundIfCloseToInteger(max)));
|
|
252
|
-
return this;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
public equals(other: Polygon): boolean {
|
|
256
|
-
if (this.contour.length !== other.contour.length) {
|
|
257
|
-
return false;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
for (let i = 0; i < this.contour.length; i++) {
|
|
261
|
-
if (!this.contour[i].equals(other.contour[i])) {
|
|
262
|
-
return false;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
if (this.holes?.length !== other.holes?.length) {
|
|
267
|
-
return false;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
for (let i = 0; i < this.holes?.length; i++) {
|
|
271
|
-
const hole = this.holes[i];
|
|
272
|
-
const otherHole = other.holes[i];
|
|
273
|
-
|
|
274
|
-
if (hole.length !== otherHole.length) {
|
|
275
|
-
return false;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
for (let j = 0; j < hole.length; j++) {
|
|
279
|
-
if (!hole[j].equals(otherHole[j])) {
|
|
280
|
-
return false;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
return true;
|
|
286
|
-
}
|
|
1
|
+
import { Point2 } from "./Point2";
|
|
2
|
+
import { Vec2 } from "./Vec2";
|
|
3
|
+
import { Rectangle } from "./Rectangle";
|
|
4
|
+
import { BoundingBox } from "./BoundingBox";
|
|
5
|
+
import { polygonPerimeter } from "./polygonPerimeter";
|
|
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";
|
|
11
|
+
|
|
12
|
+
export class Polygon {
|
|
13
|
+
|
|
14
|
+
constructor(public contour: Vec2[], public holes?: Vec2[][]) {
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public static fromPoints(contour: Point2[], holes?: Point2[][]): Polygon {
|
|
18
|
+
return new Polygon(contour.map(p => Vec2.fromPoint(p)), holes?.map(h => h.map(p => Vec2.fromPoint(p))));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public static fromSize(width: number, height: number): Polygon {
|
|
22
|
+
return new Polygon([
|
|
23
|
+
new Vec2(0, 0),
|
|
24
|
+
new Vec2(width, 0),
|
|
25
|
+
new Vec2(width, height),
|
|
26
|
+
new Vec2(0, height),
|
|
27
|
+
]);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public get size(): Vec2 {
|
|
31
|
+
const { minX, maxX, minY, maxY } = this.boundingBox();
|
|
32
|
+
return new Vec2(maxX - minX, maxY - minY);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public centerOnOrigin(): this {
|
|
36
|
+
const center = this.center();
|
|
37
|
+
|
|
38
|
+
function centerPoints(points: Vec2[]): void {
|
|
39
|
+
for (const point of points) {
|
|
40
|
+
point.x -= center.x;
|
|
41
|
+
point.y -= center.y;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
centerPoints(this.contour);
|
|
46
|
+
|
|
47
|
+
for (const hole of this.holes || []) {
|
|
48
|
+
centerPoints(hole);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return this;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public center(): Vec2 {
|
|
55
|
+
const { minX, maxX, minY, maxY } = this.boundingBox();
|
|
56
|
+
|
|
57
|
+
const x = (maxX + minX) / 2;
|
|
58
|
+
const y = (maxY + minY) / 2;
|
|
59
|
+
|
|
60
|
+
return new Vec2(x, y);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public ensureLastPoint(): this {
|
|
64
|
+
function ensure(points: Vec2[]): void {
|
|
65
|
+
if (!points[0].equals(points.at(-1))) {
|
|
66
|
+
points.push(points[0].clone());
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
ensure(this.contour);
|
|
71
|
+
|
|
72
|
+
for (const hole of this.holes || []) {
|
|
73
|
+
ensure(hole);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return this;
|
|
77
|
+
}
|
|
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
|
+
|
|
99
|
+
public boundingBox(): BoundingBox {
|
|
100
|
+
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
|
|
101
|
+
|
|
102
|
+
for (const p of this.contour) {
|
|
103
|
+
if (minX > p.x) minX = p.x;
|
|
104
|
+
if (maxX < p.x) maxX = p.x;
|
|
105
|
+
if (minY > p.y) minY = p.y;
|
|
106
|
+
if (maxY < p.y) maxY = p.y;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return new BoundingBox(minX, maxX, minY, maxY);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
public toBoundingPolygon(): Polygon {
|
|
113
|
+
const bounding = this.boundingBox();
|
|
114
|
+
return new Polygon([
|
|
115
|
+
new Vec2(bounding.minX, bounding.minY),
|
|
116
|
+
new Vec2(bounding.maxX, bounding.minY),
|
|
117
|
+
new Vec2(bounding.maxX, bounding.maxY),
|
|
118
|
+
new Vec2(bounding.minX, bounding.maxY),
|
|
119
|
+
]);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
public flip(): this {
|
|
123
|
+
const centerX = this.center().x;
|
|
124
|
+
this.flipSingle(centerX, this.contour);
|
|
125
|
+
this.holes?.forEach(hole => this.flipSingle(centerX, hole));
|
|
126
|
+
return this;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
public perimeter(): number {
|
|
130
|
+
return polygonPerimeter(this.contour);
|
|
131
|
+
}
|
|
132
|
+
|
|
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: Point2[]): boolean {
|
|
143
|
+
return points.every(point => containsPoint(this.contour, point)) &&
|
|
144
|
+
(this.holes || []).every(hole => !points.some(point => containsPoint(hole, point)));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private flipSingle(centerX: number, poly: Vec2[]): void {
|
|
148
|
+
for (const point of poly) {
|
|
149
|
+
const xDistanceToCenter = Math.abs(centerX - point.x);
|
|
150
|
+
point.x = point.x < centerX ? centerX + xDistanceToCenter : centerX - xDistanceToCenter;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
public translate(translate: Vec2): this {
|
|
155
|
+
this.contour.forEach(p => p.add(translate));
|
|
156
|
+
this.holes?.forEach(hole => hole.forEach(p => p.add(translate)));
|
|
157
|
+
return this;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Translates the polygon so that the lowest x and y values are 0.
|
|
162
|
+
*/
|
|
163
|
+
public shiftToZero(): this {
|
|
164
|
+
let xMin = Infinity, yMin = Infinity; // Find the diff between the lowest x & y & 0
|
|
165
|
+
for (const point of this.contour) {
|
|
166
|
+
xMin = Math.min(xMin, point.x);
|
|
167
|
+
yMin = Math.min(yMin, point.y);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (xMin !== 0 || yMin !== 0) { // Make the poly to start at 0
|
|
171
|
+
this.translate(new Vec2(-xMin, -yMin));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return this;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
public offsetContour(offset: number): this {
|
|
178
|
+
if (!this.contour[0].equals(this.contour.at(-1))) {
|
|
179
|
+
console.error("The contour should be closed");
|
|
180
|
+
return this;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const lines = Line2D
|
|
184
|
+
.fromPolygon(this.contour)
|
|
185
|
+
.map(line => line.translateLeft(offset));
|
|
186
|
+
|
|
187
|
+
for (let i = 0; i < lines.length; i++) {
|
|
188
|
+
const line = lines[i];
|
|
189
|
+
const next = lines[(i + 1) % lines.length];
|
|
190
|
+
line.extendToOrTrimAtIntersection(next);
|
|
191
|
+
next.extendToOrTrimAtIntersection(line);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
for (let i = 0; i < this.contour.length; i++) {
|
|
195
|
+
this.contour[i].copy(lines[i % lines.length].start);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return this;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
public translateContourLine(index: number, offset: number): this {
|
|
202
|
+
if (index < 0 || index > this.contour.length - 2) {
|
|
203
|
+
console.error(`Index out of bounds: ${index}`);
|
|
204
|
+
return this;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (!this.contour[0].equals(this.contour.at(-1))) {
|
|
208
|
+
console.error("The contour should be closed");
|
|
209
|
+
return this;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const line = Line2D.fromPoints(this.contour[index], this.contour[index + 1]);
|
|
213
|
+
const prev = Line2D.fromPoints(this.contour[(index - 1 + this.contour.length) % this.contour.length], this.contour[index]);
|
|
214
|
+
const next = Line2D.fromPoints(this.contour[index + 1], this.contour[(index + 2) % this.contour.length]);
|
|
215
|
+
|
|
216
|
+
line.translateLeft(offset);
|
|
217
|
+
line.extendToOrTrimAtIntersection(prev);
|
|
218
|
+
line.extendToOrTrimAtIntersection(next);
|
|
219
|
+
|
|
220
|
+
this.contour[index].copy(line.start);
|
|
221
|
+
this.contour[index + 1].copy(line.end);
|
|
222
|
+
|
|
223
|
+
if (index === 0) {
|
|
224
|
+
this.contour.at(-1).copy(line.start);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (index === this.contour.length - 2) {
|
|
228
|
+
this.contour[0].copy(line.end);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return this;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
public rotate(angle: number, center = this.center()): this {
|
|
235
|
+
this.contour.forEach(p => p.rotateAround(center, angle));
|
|
236
|
+
this.holes?.forEach(hole => hole.forEach(p => p.rotateAround(center, angle)));
|
|
237
|
+
return this;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
public toRectangle(): Rectangle {
|
|
241
|
+
const bounding = this.boundingBox();
|
|
242
|
+
return new Rectangle(bounding.minX, bounding.maxX, bounding.minY, bounding.maxY);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
public clone(): Polygon {
|
|
246
|
+
return new Polygon(this.contour.map(p => p.clone()), this.holes?.map(h => h.map(p => p.clone())));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
public roundIfCloseToInteger(max: number = 0.000000000001): this {
|
|
250
|
+
this.contour.forEach(p => p.roundIfCloseToInteger(max));
|
|
251
|
+
this.holes?.forEach(h => h.forEach(p => p.roundIfCloseToInteger(max)));
|
|
252
|
+
return this;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
public equals(other: Polygon): boolean {
|
|
256
|
+
if (this.contour.length !== other.contour.length) {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
for (let i = 0; i < this.contour.length; i++) {
|
|
261
|
+
if (!this.contour[i].equals(other.contour[i])) {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (this.holes?.length !== other.holes?.length) {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
for (let i = 0; i < this.holes?.length; i++) {
|
|
271
|
+
const hole = this.holes[i];
|
|
272
|
+
const otherHole = other.holes[i];
|
|
273
|
+
|
|
274
|
+
if (hole.length !== otherHole.length) {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
for (let j = 0; j < hole.length; j++) {
|
|
279
|
+
if (!hole[j].equals(otherHole[j])) {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
287
|
}
|