@immugio/three-math-extensions 0.0.11 → 0.0.13
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 +13 -1
- package/README.md +11 -3
- package/cjs/Line2D.js +566 -566
- package/cjs/Line3D.js +392 -392
- package/cjs/Point2.js +2 -2
- package/cjs/Point3.js +2 -2
- package/cjs/Vec2.js +62 -34
- package/cjs/Vec3.js +63 -63
- package/cjs/index.js +11 -11
- package/esm/Line2D.js +562 -562
- package/esm/Line3D.js +388 -388
- package/esm/Point2.js +1 -1
- package/esm/Point3.js +1 -1
- package/esm/Vec2.js +58 -30
- package/esm/Vec3.js +59 -59
- package/esm/index.js +4 -4
- package/package.json +52 -52
- package/src/Line2D.ts +660 -660
- package/src/Line3D.ts +470 -470
- package/src/Point2.ts +3 -3
- package/src/Point3.ts +6 -6
- package/src/Vec2.ts +65 -37
- package/src/Vec3.ts +77 -77
- package/src/index.ts +3 -3
- package/types/Line2D.d.ts +222 -222
- package/types/Line3D.d.ts +151 -151
- package/types/Point2.d.ts +4 -4
- package/types/Point3.d.ts +5 -5
- package/types/Vec2.d.ts +38 -10
- package/types/Vec3.d.ts +18 -18
- package/types/index.d.ts +4 -4
package/cjs/Line3D.js
CHANGED
|
@@ -1,392 +1,392 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Line3D = void 0;
|
|
4
|
-
const three_1 = require("three");
|
|
5
|
-
const Vec3_1 = require("./Vec3");
|
|
6
|
-
const Line2D_1 = require("./Line2D");
|
|
7
|
-
class Line3D extends three_1.Line3 {
|
|
8
|
-
#target;
|
|
9
|
-
constructor(start, end) {
|
|
10
|
-
super(start, end);
|
|
11
|
-
this.#target = new Vec3_1.Vec3();
|
|
12
|
-
}
|
|
13
|
-
static fromPoints(start, end) {
|
|
14
|
-
return new Line3D(new Vec3_1.Vec3(start.x, start.y, start.z), new Vec3_1.Vec3(end.x, end.y, end.z));
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Creates a polygon formed by an array of lines from points provided.
|
|
18
|
-
* The polygon will only be closed if either
|
|
19
|
-
* 1) the first and last points are the same or 2) `forceClosedPolygon` is true.
|
|
20
|
-
*/
|
|
21
|
-
static fromPolygon(polygon, forceClosedPolygon = false) {
|
|
22
|
-
if (!polygon || polygon.length < 2) {
|
|
23
|
-
return [];
|
|
24
|
-
}
|
|
25
|
-
if (forceClosedPolygon && (polygon[0].x !== polygon.at(-1).x || polygon[0].y !== polygon.at(-1).y || polygon[0].z !== polygon.at(-1).z)) {
|
|
26
|
-
polygon = [...polygon, polygon[0]];
|
|
27
|
-
}
|
|
28
|
-
const lines = [];
|
|
29
|
-
for (let i = 0; i < polygon.length - 1; i++) {
|
|
30
|
-
lines.push(Line3D.fromPoints(polygon[i], polygon[i + 1]));
|
|
31
|
-
}
|
|
32
|
-
return lines;
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Returns lines that are the result of clipping this line by the @other line.
|
|
36
|
-
* Clips must be parallel to this line.
|
|
37
|
-
* Clones the line, does not modify this.
|
|
38
|
-
* @param other
|
|
39
|
-
* @param parallelTolerance
|
|
40
|
-
*/
|
|
41
|
-
clipLine(other, parallelTolerance = Number.EPSILON) {
|
|
42
|
-
other = this.getParallelLineInTheSameDirection(other, parallelTolerance);
|
|
43
|
-
// 1) Lines aren't parallel
|
|
44
|
-
if (!other) {
|
|
45
|
-
return [this.clone()];
|
|
46
|
-
}
|
|
47
|
-
const left = this.clone();
|
|
48
|
-
left.end.copy(other.start);
|
|
49
|
-
const right = this.clone();
|
|
50
|
-
right.start.copy(other.end);
|
|
51
|
-
return [left, right].filter(x => x.direction.manhattanDistanceTo(this.direction) <= parallelTolerance);
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Returns lines that are the result of clipping this line by the @clips.
|
|
55
|
-
* Clips must be parallel to this line.
|
|
56
|
-
* Clones the line, does not modify this.
|
|
57
|
-
* @param clips
|
|
58
|
-
* @param distanceTolerance
|
|
59
|
-
* @param parallelTolerance
|
|
60
|
-
*/
|
|
61
|
-
clipLines(clips, distanceTolerance = 0, parallelTolerance = Number.EPSILON) {
|
|
62
|
-
const free = [];
|
|
63
|
-
const sources = [this.clone()];
|
|
64
|
-
while (sources.length > 0) {
|
|
65
|
-
let isFree = true;
|
|
66
|
-
const tested = sources.pop();
|
|
67
|
-
for (const clip of clips) {
|
|
68
|
-
if (tested.overlaps(clip, distanceTolerance, parallelTolerance)) {
|
|
69
|
-
isFree = false;
|
|
70
|
-
const subtracted = tested.clipLine(clip, parallelTolerance);
|
|
71
|
-
sources.push(...subtracted);
|
|
72
|
-
break;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
if (isFree)
|
|
76
|
-
free.push(tested);
|
|
77
|
-
}
|
|
78
|
-
return free;
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Joins a copy of this line with the @other line.
|
|
82
|
-
* Other must be parallel to this line.
|
|
83
|
-
* Returns null if there is no overlap
|
|
84
|
-
* Clones the line, does not modify this.
|
|
85
|
-
* @param other
|
|
86
|
-
* @param distanceTolerance
|
|
87
|
-
* @param parallelTolerance
|
|
88
|
-
*/
|
|
89
|
-
joinLine(other, distanceTolerance = 0, parallelTolerance = Number.EPSILON) {
|
|
90
|
-
// 6 possible cases:
|
|
91
|
-
const otherParallel = this.getParallelLineInTheSameDirection(other, parallelTolerance);
|
|
92
|
-
// 1) Lines aren't parallel
|
|
93
|
-
if (!otherParallel) {
|
|
94
|
-
return null;
|
|
95
|
-
}
|
|
96
|
-
const thisContainsOtherStartPoint = this.containsPoint(otherParallel.start, distanceTolerance);
|
|
97
|
-
const thisContainsOtherEndPoint = this.containsPoint(otherParallel.end, distanceTolerance);
|
|
98
|
-
const otherContainsThisStartPoint = otherParallel.containsPoint(this.start, distanceTolerance);
|
|
99
|
-
const otherContainsThisEndPoint = otherParallel.containsPoint(this.end, distanceTolerance);
|
|
100
|
-
// 2) Lines don't overlap at all
|
|
101
|
-
if (!thisContainsOtherStartPoint &&
|
|
102
|
-
!thisContainsOtherEndPoint &&
|
|
103
|
-
!otherContainsThisStartPoint &&
|
|
104
|
-
!otherContainsThisEndPoint) {
|
|
105
|
-
return null;
|
|
106
|
-
}
|
|
107
|
-
// 3) This line entirely covers the other line
|
|
108
|
-
if (thisContainsOtherStartPoint && thisContainsOtherEndPoint) {
|
|
109
|
-
return this.clone();
|
|
110
|
-
}
|
|
111
|
-
// 4) The other line entirely covers this line
|
|
112
|
-
if (otherContainsThisStartPoint && otherContainsThisEndPoint) {
|
|
113
|
-
return otherParallel.clone();
|
|
114
|
-
}
|
|
115
|
-
// 5) This line is overlapped by the start of the other line
|
|
116
|
-
if (thisContainsOtherStartPoint && !thisContainsOtherEndPoint) {
|
|
117
|
-
return new Line3D(this.start, otherParallel.end);
|
|
118
|
-
}
|
|
119
|
-
// 6) This line is overlapped by the end of the other line
|
|
120
|
-
if (thisContainsOtherEndPoint && !thisContainsOtherStartPoint) {
|
|
121
|
-
return new Line3D(otherParallel.start, this.end);
|
|
122
|
-
}
|
|
123
|
-
return null;
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* Joins provided lines into several joined lines.
|
|
127
|
-
* Lines must be parallel for joining.
|
|
128
|
-
* @param lines
|
|
129
|
-
* @param distanceTolerance
|
|
130
|
-
* @param parallelTolerance
|
|
131
|
-
*/
|
|
132
|
-
static joinLines(lines, distanceTolerance = 0, parallelTolerance = Number.EPSILON) {
|
|
133
|
-
if (lines.length < 2) {
|
|
134
|
-
return lines.map(x => x.clone());
|
|
135
|
-
}
|
|
136
|
-
const toProcess = lines.slice();
|
|
137
|
-
const result = [];
|
|
138
|
-
while (toProcess.length > 0) {
|
|
139
|
-
const current = toProcess.pop();
|
|
140
|
-
let joinedLine;
|
|
141
|
-
for (let i = 0; i < result.length; i++) {
|
|
142
|
-
const other = result[i];
|
|
143
|
-
joinedLine = current.joinLine(other, distanceTolerance, parallelTolerance);
|
|
144
|
-
if (joinedLine) {
|
|
145
|
-
result.splice(i, 1);
|
|
146
|
-
toProcess.push(joinedLine);
|
|
147
|
-
break;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
if (!joinedLine) {
|
|
151
|
-
result.push(current.clone());
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
return result;
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* Returns true if this line section completely overlaps the @other line section.
|
|
158
|
-
* @param other
|
|
159
|
-
* @param tolerance
|
|
160
|
-
*/
|
|
161
|
-
covers(other, tolerance = 0) {
|
|
162
|
-
return this.containsPoint(other.start, tolerance) && this.containsPoint(other.end, tolerance);
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* Returns true if there is any overlap between this line and the @other line section.
|
|
166
|
-
* @param other
|
|
167
|
-
* @param distanceTolerance
|
|
168
|
-
* @param parallelTolerance
|
|
169
|
-
*/
|
|
170
|
-
overlaps(other, distanceTolerance = 0, parallelTolerance = Number.EPSILON) {
|
|
171
|
-
// Special case
|
|
172
|
-
if (this.equals(other, distanceTolerance)) {
|
|
173
|
-
return true;
|
|
174
|
-
}
|
|
175
|
-
// Always have to be parallel
|
|
176
|
-
if (this.isParallelTo(other, parallelTolerance)) {
|
|
177
|
-
// To pass as overlapping, at least one point has to be within the other line, but they mush not be equal - "touching" is not considered overlapping
|
|
178
|
-
const otherStartEqualsToAnyOfThisPoint = other.start.distanceTo(this.start) <= distanceTolerance || other.start.distanceTo(this.end) <= distanceTolerance;
|
|
179
|
-
if (this.containsPoint(other.start, distanceTolerance) && !otherStartEqualsToAnyOfThisPoint) {
|
|
180
|
-
return true;
|
|
181
|
-
}
|
|
182
|
-
const otherEndEqualsToAnyOfThisPoint = other.end.distanceTo(this.start) <= distanceTolerance || other.end.distanceTo(this.end) <= distanceTolerance;
|
|
183
|
-
if (this.containsPoint(other.end, distanceTolerance) && !otherEndEqualsToAnyOfThisPoint) {
|
|
184
|
-
return true;
|
|
185
|
-
}
|
|
186
|
-
const thisStartEqualsToAnyOfOtherPoint = this.start.distanceTo(other.start) <= distanceTolerance || this.start.distanceTo(other.end) <= distanceTolerance;
|
|
187
|
-
if (other.containsPoint(this.start, distanceTolerance) && !thisStartEqualsToAnyOfOtherPoint) {
|
|
188
|
-
return true;
|
|
189
|
-
}
|
|
190
|
-
const thisEndEqualsToAnyOfOtherPoint = this.end.distanceTo(other.start) <= distanceTolerance || this.end.distanceTo(other.end) <= distanceTolerance;
|
|
191
|
-
if (other.containsPoint(this.end, distanceTolerance) && !thisEndEqualsToAnyOfOtherPoint) {
|
|
192
|
-
return true;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
return false;
|
|
196
|
-
}
|
|
197
|
-
/**
|
|
198
|
-
* Returns this line's length.
|
|
199
|
-
*/
|
|
200
|
-
get length() {
|
|
201
|
-
return this.start.distanceTo(this.end);
|
|
202
|
-
}
|
|
203
|
-
/**
|
|
204
|
-
* Set the length of this line. Center and direction remain unchanged.
|
|
205
|
-
* @param length
|
|
206
|
-
*/
|
|
207
|
-
setLength(length) {
|
|
208
|
-
const diff = length - this.length;
|
|
209
|
-
return this.resize(diff);
|
|
210
|
-
}
|
|
211
|
-
/**
|
|
212
|
-
* Returns the direction of this line.
|
|
213
|
-
*/
|
|
214
|
-
get direction() {
|
|
215
|
-
return this.end.clone().sub(this.start).normalize();
|
|
216
|
-
}
|
|
217
|
-
/**
|
|
218
|
-
* Returns the center of this line
|
|
219
|
-
*/
|
|
220
|
-
get center() {
|
|
221
|
-
return this.getCenter(new Vec3_1.Vec3());
|
|
222
|
-
}
|
|
223
|
-
/**
|
|
224
|
-
* Set the center of the line to the provided point. Length and direction remain unchanged.
|
|
225
|
-
* @param value
|
|
226
|
-
*/
|
|
227
|
-
setCenter(value) {
|
|
228
|
-
const current = this.center;
|
|
229
|
-
const diffX = current.x - value.x;
|
|
230
|
-
const diffY = current.y - value.y;
|
|
231
|
-
const diffZ = current.z - value.z;
|
|
232
|
-
this.start.x -= diffX;
|
|
233
|
-
this.start.y -= diffY;
|
|
234
|
-
this.start.z -= diffZ;
|
|
235
|
-
this.end.x -= diffX;
|
|
236
|
-
this.end.y -= diffY;
|
|
237
|
-
this.end.z -= diffZ;
|
|
238
|
-
return this;
|
|
239
|
-
}
|
|
240
|
-
/** Returns the start and end points of the line as an array. */
|
|
241
|
-
get endpoints() {
|
|
242
|
-
return [this.start, this.end];
|
|
243
|
-
}
|
|
244
|
-
/**
|
|
245
|
-
* Check that this line section contains provided point.
|
|
246
|
-
* @param p
|
|
247
|
-
* @param tolerance
|
|
248
|
-
*/
|
|
249
|
-
containsPoint(p, tolerance = 0) {
|
|
250
|
-
const closestPointToPoint = this.closestPointToPoint(p, true, this.#target);
|
|
251
|
-
return closestPointToPoint.distanceTo(p) <= tolerance;
|
|
252
|
-
}
|
|
253
|
-
/**
|
|
254
|
-
* Distance from this line to provided point.
|
|
255
|
-
* @param p
|
|
256
|
-
* @param clampToLine
|
|
257
|
-
*/
|
|
258
|
-
distanceToPoint(p, clampToLine = true) {
|
|
259
|
-
const closestPointToPoint = this.closestPointToPoint(p, clampToLine, this.#target);
|
|
260
|
-
return closestPointToPoint.distanceTo(p);
|
|
261
|
-
}
|
|
262
|
-
/**
|
|
263
|
-
* Returns a copy of @other line, the direction of @other is reversed if needed.
|
|
264
|
-
* Returns null if lines are not parallel.
|
|
265
|
-
* @param other
|
|
266
|
-
* @param tolerance
|
|
267
|
-
*/
|
|
268
|
-
getParallelLineInTheSameDirection(other, tolerance = Number.EPSILON) {
|
|
269
|
-
const direction = this.direction;
|
|
270
|
-
const areTheSameDirection = direction.manhattanDistanceTo(other.direction) < tolerance;
|
|
271
|
-
if (areTheSameDirection) {
|
|
272
|
-
return other.clone();
|
|
273
|
-
}
|
|
274
|
-
const otherLineOppositeDirection = new Line3D(other.end, other.start);
|
|
275
|
-
if (otherLineOppositeDirection.direction.manhattanDistanceTo(direction) < tolerance) {
|
|
276
|
-
return otherLineOppositeDirection;
|
|
277
|
-
}
|
|
278
|
-
return null;
|
|
279
|
-
}
|
|
280
|
-
/**
|
|
281
|
-
* Check if @other is parallel to this line.
|
|
282
|
-
* @param other
|
|
283
|
-
* @param tolerance
|
|
284
|
-
*/
|
|
285
|
-
isParallelTo(other, tolerance = Number.EPSILON) {
|
|
286
|
-
const direction = this.direction;
|
|
287
|
-
const otherDirection = other.direction;
|
|
288
|
-
const areTheSameDirection = direction.manhattanDistanceTo(otherDirection) <= tolerance;
|
|
289
|
-
if (areTheSameDirection) {
|
|
290
|
-
return true;
|
|
291
|
-
}
|
|
292
|
-
return direction.negate().manhattanDistanceTo(otherDirection) < tolerance;
|
|
293
|
-
}
|
|
294
|
-
/*
|
|
295
|
-
* Extends or reduces the line to the given length while keeping the center of the line constant.
|
|
296
|
-
*/
|
|
297
|
-
resize(amount) {
|
|
298
|
-
this.moveStartPoint(amount / 2);
|
|
299
|
-
this.moveEndPoint(amount / 2);
|
|
300
|
-
return this;
|
|
301
|
-
}
|
|
302
|
-
/*
|
|
303
|
-
* Moves start on the line by the given amount. Plus values move the point further away from the center.
|
|
304
|
-
*/
|
|
305
|
-
moveStartPoint(amount) {
|
|
306
|
-
const start = this.movePointOnThisLine(this.start, amount);
|
|
307
|
-
this.start.x = start.x;
|
|
308
|
-
this.start.y = start.y;
|
|
309
|
-
this.start.z = start.z;
|
|
310
|
-
return this;
|
|
311
|
-
}
|
|
312
|
-
/*
|
|
313
|
-
* Moves end on the line by the given amount in the current direction. Plus values move the point further away from the center.
|
|
314
|
-
*/
|
|
315
|
-
moveEndPoint(amount) {
|
|
316
|
-
const end = this.movePointOnThisLine(this.end, amount);
|
|
317
|
-
this.end.x = end.x;
|
|
318
|
-
this.end.y = end.y;
|
|
319
|
-
this.end.z = end.z;
|
|
320
|
-
return this;
|
|
321
|
-
}
|
|
322
|
-
/**
|
|
323
|
-
* Returns a new line that is the projection of this line onto @other. Uses `closestPointToPoint` to find the projection.
|
|
324
|
-
* @param other
|
|
325
|
-
* @param clampToLine
|
|
326
|
-
*/
|
|
327
|
-
projectOn(other, clampToLine) {
|
|
328
|
-
const p1 = other.closestPointToPoint(this.start, clampToLine, new Vec3_1.Vec3());
|
|
329
|
-
const p2 = other.closestPointToPoint(this.end, clampToLine, new Vec3_1.Vec3());
|
|
330
|
-
return p1.distanceTo(this.start) < p2.distanceTo(this.start) ? new Line3D(p1, p2) : new Line3D(p2, p1);
|
|
331
|
-
}
|
|
332
|
-
/**
|
|
333
|
-
* Divides the Line3D into a number of segments of the given length.
|
|
334
|
-
* @param maxSegmentLength number
|
|
335
|
-
*/
|
|
336
|
-
chunk(maxSegmentLength) {
|
|
337
|
-
const source = this.clone();
|
|
338
|
-
const result = [];
|
|
339
|
-
while (source.length > maxSegmentLength) {
|
|
340
|
-
const chunk = source.clone();
|
|
341
|
-
chunk.moveEndPoint(-(chunk.length - maxSegmentLength));
|
|
342
|
-
result.push(chunk);
|
|
343
|
-
source.start.copy(chunk.end);
|
|
344
|
-
}
|
|
345
|
-
if (source.length > 0) {
|
|
346
|
-
result.push(source);
|
|
347
|
-
}
|
|
348
|
-
return result;
|
|
349
|
-
}
|
|
350
|
-
/**
|
|
351
|
-
* Note that this works well for moving the endpoints as it's currently used
|
|
352
|
-
* If it were to be made public, it would need to handle the situation where the point to move is in the center of the line which would require a different approach
|
|
353
|
-
*/
|
|
354
|
-
movePointOnThisLine(point, amount) {
|
|
355
|
-
const center = this.getCenter(this.#target);
|
|
356
|
-
const vec = new Vec3_1.Vec3(center.x - point.x, center.y - point.y, center.z - point.z);
|
|
357
|
-
const length = vec.length();
|
|
358
|
-
vec.normalize().multiplyScalar(length + amount);
|
|
359
|
-
return new Vec3_1.Vec3(center.x - vec.x, center.y - vec.y, center.z - vec.z);
|
|
360
|
-
}
|
|
361
|
-
/**
|
|
362
|
-
* Move this line by the given vector.
|
|
363
|
-
* @param p
|
|
364
|
-
*/
|
|
365
|
-
translate(p) {
|
|
366
|
-
this.start.add(p);
|
|
367
|
-
this.end.add(p);
|
|
368
|
-
return this;
|
|
369
|
-
}
|
|
370
|
-
/**
|
|
371
|
-
* Project the line to 2D space, Y value is dropped
|
|
372
|
-
*/
|
|
373
|
-
onPlan() {
|
|
374
|
-
return new Line2D_1.Line2D(this.start.onPlan(), this.end.onPlan());
|
|
375
|
-
}
|
|
376
|
-
/**
|
|
377
|
-
* Equals with tolerance
|
|
378
|
-
*/
|
|
379
|
-
equals(other, tolerance = 0) {
|
|
380
|
-
return !!other && this.start.distanceTo(other.start) <= tolerance && this.end.distanceTo(other.end) <= tolerance;
|
|
381
|
-
}
|
|
382
|
-
/**
|
|
383
|
-
* Deep clone of this line
|
|
384
|
-
*/
|
|
385
|
-
clone() {
|
|
386
|
-
return new Line3D(this.start.clone(), this.end.clone());
|
|
387
|
-
}
|
|
388
|
-
toString() {
|
|
389
|
-
return `Line3D { start: ${this.start.x}, ${this.start.y}, ${this.start.z}, end: ${this.end.x}, ${this.end.y}, ${this.end.z}}`;
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
exports.Line3D = Line3D;
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Line3D = void 0;
|
|
4
|
+
const three_1 = require("three");
|
|
5
|
+
const Vec3_1 = require("./Vec3");
|
|
6
|
+
const Line2D_1 = require("./Line2D");
|
|
7
|
+
class Line3D extends three_1.Line3 {
|
|
8
|
+
#target;
|
|
9
|
+
constructor(start, end) {
|
|
10
|
+
super(start, end);
|
|
11
|
+
this.#target = new Vec3_1.Vec3();
|
|
12
|
+
}
|
|
13
|
+
static fromPoints(start, end) {
|
|
14
|
+
return new Line3D(new Vec3_1.Vec3(start.x, start.y, start.z), new Vec3_1.Vec3(end.x, end.y, end.z));
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Creates a polygon formed by an array of lines from points provided.
|
|
18
|
+
* The polygon will only be closed if either
|
|
19
|
+
* 1) the first and last points are the same or 2) `forceClosedPolygon` is true.
|
|
20
|
+
*/
|
|
21
|
+
static fromPolygon(polygon, forceClosedPolygon = false) {
|
|
22
|
+
if (!polygon || polygon.length < 2) {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
if (forceClosedPolygon && (polygon[0].x !== polygon.at(-1).x || polygon[0].y !== polygon.at(-1).y || polygon[0].z !== polygon.at(-1).z)) {
|
|
26
|
+
polygon = [...polygon, polygon[0]];
|
|
27
|
+
}
|
|
28
|
+
const lines = [];
|
|
29
|
+
for (let i = 0; i < polygon.length - 1; i++) {
|
|
30
|
+
lines.push(Line3D.fromPoints(polygon[i], polygon[i + 1]));
|
|
31
|
+
}
|
|
32
|
+
return lines;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Returns lines that are the result of clipping this line by the @other line.
|
|
36
|
+
* Clips must be parallel to this line.
|
|
37
|
+
* Clones the line, does not modify this.
|
|
38
|
+
* @param other
|
|
39
|
+
* @param parallelTolerance
|
|
40
|
+
*/
|
|
41
|
+
clipLine(other, parallelTolerance = Number.EPSILON) {
|
|
42
|
+
other = this.getParallelLineInTheSameDirection(other, parallelTolerance);
|
|
43
|
+
// 1) Lines aren't parallel
|
|
44
|
+
if (!other) {
|
|
45
|
+
return [this.clone()];
|
|
46
|
+
}
|
|
47
|
+
const left = this.clone();
|
|
48
|
+
left.end.copy(other.start);
|
|
49
|
+
const right = this.clone();
|
|
50
|
+
right.start.copy(other.end);
|
|
51
|
+
return [left, right].filter(x => x.direction.manhattanDistanceTo(this.direction) <= parallelTolerance);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Returns lines that are the result of clipping this line by the @clips.
|
|
55
|
+
* Clips must be parallel to this line.
|
|
56
|
+
* Clones the line, does not modify this.
|
|
57
|
+
* @param clips
|
|
58
|
+
* @param distanceTolerance
|
|
59
|
+
* @param parallelTolerance
|
|
60
|
+
*/
|
|
61
|
+
clipLines(clips, distanceTolerance = 0, parallelTolerance = Number.EPSILON) {
|
|
62
|
+
const free = [];
|
|
63
|
+
const sources = [this.clone()];
|
|
64
|
+
while (sources.length > 0) {
|
|
65
|
+
let isFree = true;
|
|
66
|
+
const tested = sources.pop();
|
|
67
|
+
for (const clip of clips) {
|
|
68
|
+
if (tested.overlaps(clip, distanceTolerance, parallelTolerance)) {
|
|
69
|
+
isFree = false;
|
|
70
|
+
const subtracted = tested.clipLine(clip, parallelTolerance);
|
|
71
|
+
sources.push(...subtracted);
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (isFree)
|
|
76
|
+
free.push(tested);
|
|
77
|
+
}
|
|
78
|
+
return free;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Joins a copy of this line with the @other line.
|
|
82
|
+
* Other must be parallel to this line.
|
|
83
|
+
* Returns null if there is no overlap
|
|
84
|
+
* Clones the line, does not modify this.
|
|
85
|
+
* @param other
|
|
86
|
+
* @param distanceTolerance
|
|
87
|
+
* @param parallelTolerance
|
|
88
|
+
*/
|
|
89
|
+
joinLine(other, distanceTolerance = 0, parallelTolerance = Number.EPSILON) {
|
|
90
|
+
// 6 possible cases:
|
|
91
|
+
const otherParallel = this.getParallelLineInTheSameDirection(other, parallelTolerance);
|
|
92
|
+
// 1) Lines aren't parallel
|
|
93
|
+
if (!otherParallel) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
const thisContainsOtherStartPoint = this.containsPoint(otherParallel.start, distanceTolerance);
|
|
97
|
+
const thisContainsOtherEndPoint = this.containsPoint(otherParallel.end, distanceTolerance);
|
|
98
|
+
const otherContainsThisStartPoint = otherParallel.containsPoint(this.start, distanceTolerance);
|
|
99
|
+
const otherContainsThisEndPoint = otherParallel.containsPoint(this.end, distanceTolerance);
|
|
100
|
+
// 2) Lines don't overlap at all
|
|
101
|
+
if (!thisContainsOtherStartPoint &&
|
|
102
|
+
!thisContainsOtherEndPoint &&
|
|
103
|
+
!otherContainsThisStartPoint &&
|
|
104
|
+
!otherContainsThisEndPoint) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
// 3) This line entirely covers the other line
|
|
108
|
+
if (thisContainsOtherStartPoint && thisContainsOtherEndPoint) {
|
|
109
|
+
return this.clone();
|
|
110
|
+
}
|
|
111
|
+
// 4) The other line entirely covers this line
|
|
112
|
+
if (otherContainsThisStartPoint && otherContainsThisEndPoint) {
|
|
113
|
+
return otherParallel.clone();
|
|
114
|
+
}
|
|
115
|
+
// 5) This line is overlapped by the start of the other line
|
|
116
|
+
if (thisContainsOtherStartPoint && !thisContainsOtherEndPoint) {
|
|
117
|
+
return new Line3D(this.start, otherParallel.end);
|
|
118
|
+
}
|
|
119
|
+
// 6) This line is overlapped by the end of the other line
|
|
120
|
+
if (thisContainsOtherEndPoint && !thisContainsOtherStartPoint) {
|
|
121
|
+
return new Line3D(otherParallel.start, this.end);
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Joins provided lines into several joined lines.
|
|
127
|
+
* Lines must be parallel for joining.
|
|
128
|
+
* @param lines
|
|
129
|
+
* @param distanceTolerance
|
|
130
|
+
* @param parallelTolerance
|
|
131
|
+
*/
|
|
132
|
+
static joinLines(lines, distanceTolerance = 0, parallelTolerance = Number.EPSILON) {
|
|
133
|
+
if (lines.length < 2) {
|
|
134
|
+
return lines.map(x => x.clone());
|
|
135
|
+
}
|
|
136
|
+
const toProcess = lines.slice();
|
|
137
|
+
const result = [];
|
|
138
|
+
while (toProcess.length > 0) {
|
|
139
|
+
const current = toProcess.pop();
|
|
140
|
+
let joinedLine;
|
|
141
|
+
for (let i = 0; i < result.length; i++) {
|
|
142
|
+
const other = result[i];
|
|
143
|
+
joinedLine = current.joinLine(other, distanceTolerance, parallelTolerance);
|
|
144
|
+
if (joinedLine) {
|
|
145
|
+
result.splice(i, 1);
|
|
146
|
+
toProcess.push(joinedLine);
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (!joinedLine) {
|
|
151
|
+
result.push(current.clone());
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Returns true if this line section completely overlaps the @other line section.
|
|
158
|
+
* @param other
|
|
159
|
+
* @param tolerance
|
|
160
|
+
*/
|
|
161
|
+
covers(other, tolerance = 0) {
|
|
162
|
+
return this.containsPoint(other.start, tolerance) && this.containsPoint(other.end, tolerance);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Returns true if there is any overlap between this line and the @other line section.
|
|
166
|
+
* @param other
|
|
167
|
+
* @param distanceTolerance
|
|
168
|
+
* @param parallelTolerance
|
|
169
|
+
*/
|
|
170
|
+
overlaps(other, distanceTolerance = 0, parallelTolerance = Number.EPSILON) {
|
|
171
|
+
// Special case
|
|
172
|
+
if (this.equals(other, distanceTolerance)) {
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
// Always have to be parallel
|
|
176
|
+
if (this.isParallelTo(other, parallelTolerance)) {
|
|
177
|
+
// To pass as overlapping, at least one point has to be within the other line, but they mush not be equal - "touching" is not considered overlapping
|
|
178
|
+
const otherStartEqualsToAnyOfThisPoint = other.start.distanceTo(this.start) <= distanceTolerance || other.start.distanceTo(this.end) <= distanceTolerance;
|
|
179
|
+
if (this.containsPoint(other.start, distanceTolerance) && !otherStartEqualsToAnyOfThisPoint) {
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
const otherEndEqualsToAnyOfThisPoint = other.end.distanceTo(this.start) <= distanceTolerance || other.end.distanceTo(this.end) <= distanceTolerance;
|
|
183
|
+
if (this.containsPoint(other.end, distanceTolerance) && !otherEndEqualsToAnyOfThisPoint) {
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
const thisStartEqualsToAnyOfOtherPoint = this.start.distanceTo(other.start) <= distanceTolerance || this.start.distanceTo(other.end) <= distanceTolerance;
|
|
187
|
+
if (other.containsPoint(this.start, distanceTolerance) && !thisStartEqualsToAnyOfOtherPoint) {
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
const thisEndEqualsToAnyOfOtherPoint = this.end.distanceTo(other.start) <= distanceTolerance || this.end.distanceTo(other.end) <= distanceTolerance;
|
|
191
|
+
if (other.containsPoint(this.end, distanceTolerance) && !thisEndEqualsToAnyOfOtherPoint) {
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Returns this line's length.
|
|
199
|
+
*/
|
|
200
|
+
get length() {
|
|
201
|
+
return this.start.distanceTo(this.end);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Set the length of this line. Center and direction remain unchanged.
|
|
205
|
+
* @param length
|
|
206
|
+
*/
|
|
207
|
+
setLength(length) {
|
|
208
|
+
const diff = length - this.length;
|
|
209
|
+
return this.resize(diff);
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Returns the direction of this line.
|
|
213
|
+
*/
|
|
214
|
+
get direction() {
|
|
215
|
+
return this.end.clone().sub(this.start).normalize();
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Returns the center of this line
|
|
219
|
+
*/
|
|
220
|
+
get center() {
|
|
221
|
+
return this.getCenter(new Vec3_1.Vec3());
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Set the center of the line to the provided point. Length and direction remain unchanged.
|
|
225
|
+
* @param value
|
|
226
|
+
*/
|
|
227
|
+
setCenter(value) {
|
|
228
|
+
const current = this.center;
|
|
229
|
+
const diffX = current.x - value.x;
|
|
230
|
+
const diffY = current.y - value.y;
|
|
231
|
+
const diffZ = current.z - value.z;
|
|
232
|
+
this.start.x -= diffX;
|
|
233
|
+
this.start.y -= diffY;
|
|
234
|
+
this.start.z -= diffZ;
|
|
235
|
+
this.end.x -= diffX;
|
|
236
|
+
this.end.y -= diffY;
|
|
237
|
+
this.end.z -= diffZ;
|
|
238
|
+
return this;
|
|
239
|
+
}
|
|
240
|
+
/** Returns the start and end points of the line as an array. */
|
|
241
|
+
get endpoints() {
|
|
242
|
+
return [this.start, this.end];
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Check that this line section contains provided point.
|
|
246
|
+
* @param p
|
|
247
|
+
* @param tolerance
|
|
248
|
+
*/
|
|
249
|
+
containsPoint(p, tolerance = 0) {
|
|
250
|
+
const closestPointToPoint = this.closestPointToPoint(p, true, this.#target);
|
|
251
|
+
return closestPointToPoint.distanceTo(p) <= tolerance;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Distance from this line to provided point.
|
|
255
|
+
* @param p
|
|
256
|
+
* @param clampToLine
|
|
257
|
+
*/
|
|
258
|
+
distanceToPoint(p, clampToLine = true) {
|
|
259
|
+
const closestPointToPoint = this.closestPointToPoint(p, clampToLine, this.#target);
|
|
260
|
+
return closestPointToPoint.distanceTo(p);
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Returns a copy of @other line, the direction of @other is reversed if needed.
|
|
264
|
+
* Returns null if lines are not parallel.
|
|
265
|
+
* @param other
|
|
266
|
+
* @param tolerance
|
|
267
|
+
*/
|
|
268
|
+
getParallelLineInTheSameDirection(other, tolerance = Number.EPSILON) {
|
|
269
|
+
const direction = this.direction;
|
|
270
|
+
const areTheSameDirection = direction.manhattanDistanceTo(other.direction) < tolerance;
|
|
271
|
+
if (areTheSameDirection) {
|
|
272
|
+
return other.clone();
|
|
273
|
+
}
|
|
274
|
+
const otherLineOppositeDirection = new Line3D(other.end, other.start);
|
|
275
|
+
if (otherLineOppositeDirection.direction.manhattanDistanceTo(direction) < tolerance) {
|
|
276
|
+
return otherLineOppositeDirection;
|
|
277
|
+
}
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Check if @other is parallel to this line.
|
|
282
|
+
* @param other
|
|
283
|
+
* @param tolerance
|
|
284
|
+
*/
|
|
285
|
+
isParallelTo(other, tolerance = Number.EPSILON) {
|
|
286
|
+
const direction = this.direction;
|
|
287
|
+
const otherDirection = other.direction;
|
|
288
|
+
const areTheSameDirection = direction.manhattanDistanceTo(otherDirection) <= tolerance;
|
|
289
|
+
if (areTheSameDirection) {
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
return direction.negate().manhattanDistanceTo(otherDirection) < tolerance;
|
|
293
|
+
}
|
|
294
|
+
/*
|
|
295
|
+
* Extends or reduces the line to the given length while keeping the center of the line constant.
|
|
296
|
+
*/
|
|
297
|
+
resize(amount) {
|
|
298
|
+
this.moveStartPoint(amount / 2);
|
|
299
|
+
this.moveEndPoint(amount / 2);
|
|
300
|
+
return this;
|
|
301
|
+
}
|
|
302
|
+
/*
|
|
303
|
+
* Moves start on the line by the given amount. Plus values move the point further away from the center.
|
|
304
|
+
*/
|
|
305
|
+
moveStartPoint(amount) {
|
|
306
|
+
const start = this.movePointOnThisLine(this.start, amount);
|
|
307
|
+
this.start.x = start.x;
|
|
308
|
+
this.start.y = start.y;
|
|
309
|
+
this.start.z = start.z;
|
|
310
|
+
return this;
|
|
311
|
+
}
|
|
312
|
+
/*
|
|
313
|
+
* Moves end on the line by the given amount in the current direction. Plus values move the point further away from the center.
|
|
314
|
+
*/
|
|
315
|
+
moveEndPoint(amount) {
|
|
316
|
+
const end = this.movePointOnThisLine(this.end, amount);
|
|
317
|
+
this.end.x = end.x;
|
|
318
|
+
this.end.y = end.y;
|
|
319
|
+
this.end.z = end.z;
|
|
320
|
+
return this;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Returns a new line that is the projection of this line onto @other. Uses `closestPointToPoint` to find the projection.
|
|
324
|
+
* @param other
|
|
325
|
+
* @param clampToLine
|
|
326
|
+
*/
|
|
327
|
+
projectOn(other, clampToLine) {
|
|
328
|
+
const p1 = other.closestPointToPoint(this.start, clampToLine, new Vec3_1.Vec3());
|
|
329
|
+
const p2 = other.closestPointToPoint(this.end, clampToLine, new Vec3_1.Vec3());
|
|
330
|
+
return p1.distanceTo(this.start) < p2.distanceTo(this.start) ? new Line3D(p1, p2) : new Line3D(p2, p1);
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Divides the Line3D into a number of segments of the given length.
|
|
334
|
+
* @param maxSegmentLength number
|
|
335
|
+
*/
|
|
336
|
+
chunk(maxSegmentLength) {
|
|
337
|
+
const source = this.clone();
|
|
338
|
+
const result = [];
|
|
339
|
+
while (source.length > maxSegmentLength) {
|
|
340
|
+
const chunk = source.clone();
|
|
341
|
+
chunk.moveEndPoint(-(chunk.length - maxSegmentLength));
|
|
342
|
+
result.push(chunk);
|
|
343
|
+
source.start.copy(chunk.end);
|
|
344
|
+
}
|
|
345
|
+
if (source.length > 0) {
|
|
346
|
+
result.push(source);
|
|
347
|
+
}
|
|
348
|
+
return result;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Note that this works well for moving the endpoints as it's currently used
|
|
352
|
+
* If it were to be made public, it would need to handle the situation where the point to move is in the center of the line which would require a different approach
|
|
353
|
+
*/
|
|
354
|
+
movePointOnThisLine(point, amount) {
|
|
355
|
+
const center = this.getCenter(this.#target);
|
|
356
|
+
const vec = new Vec3_1.Vec3(center.x - point.x, center.y - point.y, center.z - point.z);
|
|
357
|
+
const length = vec.length();
|
|
358
|
+
vec.normalize().multiplyScalar(length + amount);
|
|
359
|
+
return new Vec3_1.Vec3(center.x - vec.x, center.y - vec.y, center.z - vec.z);
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Move this line by the given vector.
|
|
363
|
+
* @param p
|
|
364
|
+
*/
|
|
365
|
+
translate(p) {
|
|
366
|
+
this.start.add(p);
|
|
367
|
+
this.end.add(p);
|
|
368
|
+
return this;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Project the line to 2D space, Y value is dropped
|
|
372
|
+
*/
|
|
373
|
+
onPlan() {
|
|
374
|
+
return new Line2D_1.Line2D(this.start.onPlan(), this.end.onPlan());
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Equals with tolerance
|
|
378
|
+
*/
|
|
379
|
+
equals(other, tolerance = 0) {
|
|
380
|
+
return !!other && this.start.distanceTo(other.start) <= tolerance && this.end.distanceTo(other.end) <= tolerance;
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Deep clone of this line
|
|
384
|
+
*/
|
|
385
|
+
clone() {
|
|
386
|
+
return new Line3D(this.start.clone(), this.end.clone());
|
|
387
|
+
}
|
|
388
|
+
toString() {
|
|
389
|
+
return `Line3D { start: ${this.start.x}, ${this.start.y}, ${this.start.z}, end: ${this.end.x}, ${this.end.y}, ${this.end.z}}`;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
exports.Line3D = Line3D;
|