@immugio/three-math-extensions 0.2.38 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -1
- package/cjs/Line2D.js +35 -28
- package/cjs/Line3D.js +2 -2
- package/cjs/isContinuousClosedShape.js +1 -0
- package/eslint.config.mjs +112 -0
- package/esm/Line2D.js +36 -29
- package/esm/Line3D.js +2 -2
- package/esm/isContinuousClosedShape.js +1 -0
- package/package.json +60 -54
- package/src/BoundingBox.ts +8 -8
- package/src/Line2D.ts +36 -34
- package/src/Line3D.ts +2 -2
- package/src/Point3.ts +1 -2
- package/src/Polygon.ts +2 -3
- package/src/Vec2.ts +4 -4
- package/src/Vec3.ts +0 -1
- package/src/isContinuousClosedShape.ts +6 -5
- package/types/Line2D.d.ts +1 -1
- package/types/isContinuousClosedShape.d.ts +10 -3
package/CHANGELOG.md
CHANGED
|
@@ -7,7 +7,33 @@ 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
|
|
10
|
+
## [0.3.2](https://github.com/Immugio/three-math-extensions/compare/0.3.1...0.3.2)
|
|
11
|
+
|
|
12
|
+
### Merged
|
|
13
|
+
|
|
14
|
+
- Update GitHub Actions to latest versions [`#5`](https://github.com/Immugio/three-math-extensions/pull/5)
|
|
15
|
+
|
|
16
|
+
## [0.3.1](https://github.com/Immugio/three-math-extensions/compare/0.3.0...0.3.1) - 2025-11-20
|
|
17
|
+
|
|
18
|
+
### Merged
|
|
19
|
+
|
|
20
|
+
- GitHub Actions - Update workflow to Node 20.x [`#4`](https://github.com/Immugio/three-math-extensions/pull/4)
|
|
21
|
+
- Line2D.spec.ts - Split tests into individual test files per method being tested [`#3`](https://github.com/Immugio/three-math-extensions/pull/3)
|
|
22
|
+
|
|
23
|
+
### Commits
|
|
24
|
+
|
|
25
|
+
- Update eslint and fix linting errors [`718ec97`](https://github.com/Immugio/three-math-extensions/commit/718ec978cf0d79cba66f87ac2978007860edfb34)
|
|
26
|
+
- Improve Line2D.joinLines robustness [`a17fd31`](https://github.com/Immugio/three-math-extensions/commit/a17fd3107768e99deb561131581822079bdab2b1)
|
|
27
|
+
- Update isContinuousClosedShape types to avoid using any [`25028b7`](https://github.com/Immugio/three-math-extensions/commit/25028b7ee7b389edaae2c04493e775fd685e4a22)
|
|
28
|
+
- Fix isContinuousClosedShape signature for compatibility with Line2D and Line3D [`1d569e7`](https://github.com/Immugio/three-math-extensions/commit/1d569e7b0a7ba5f92eef1ce2493589fe2a080847)
|
|
29
|
+
|
|
30
|
+
## [0.3.0](https://github.com/Immugio/three-math-extensions/compare/0.2.38...0.3.0) - 2024-12-11
|
|
31
|
+
|
|
32
|
+
### Commits
|
|
33
|
+
|
|
34
|
+
- Add Line2D.split [`8f218fb`](https://github.com/Immugio/three-math-extensions/commit/8f218fb6d8682e65f1da49c9084d56ec3e9a68b7)
|
|
35
|
+
|
|
36
|
+
## [0.2.38](https://github.com/Immugio/three-math-extensions/compare/0.2.37...0.2.38) - 2024-12-09
|
|
11
37
|
|
|
12
38
|
### Commits
|
|
13
39
|
|
package/cjs/Line2D.js
CHANGED
|
@@ -96,8 +96,7 @@ class Line2D {
|
|
|
96
96
|
* Modifies this line.
|
|
97
97
|
*/
|
|
98
98
|
moveStartPoint(amount) {
|
|
99
|
-
|
|
100
|
-
this.start.copy(p1);
|
|
99
|
+
this.start.add(this.direction.multiplyScalar(-amount));
|
|
101
100
|
return this;
|
|
102
101
|
}
|
|
103
102
|
/**
|
|
@@ -105,16 +104,9 @@ class Line2D {
|
|
|
105
104
|
* Modifies this line.
|
|
106
105
|
*/
|
|
107
106
|
moveEndPoint(amount) {
|
|
108
|
-
|
|
109
|
-
this.end.copy(p2);
|
|
107
|
+
this.end.add(this.direction.multiplyScalar(amount));
|
|
110
108
|
return this;
|
|
111
109
|
}
|
|
112
|
-
movePointOnThisLine(point, amount) {
|
|
113
|
-
const vec = new three_1.Vector2(this.center.x - point.x, this.center.y - point.y);
|
|
114
|
-
const length = vec.length();
|
|
115
|
-
vec.normalize().multiplyScalar(length + amount);
|
|
116
|
-
return new Vec2_1.Vec2(this.center.x - vec.x, this.center.y - vec.y);
|
|
117
|
-
}
|
|
118
110
|
/**
|
|
119
111
|
* Set the length of this line. Center and direction remain unchanged.
|
|
120
112
|
* Modifies this line.
|
|
@@ -258,7 +250,7 @@ class Line2D {
|
|
|
258
250
|
*/
|
|
259
251
|
isPointBesideLineSection(point) {
|
|
260
252
|
const l2 = (((this.end.x - this.start.x) * (this.end.x - this.start.x)) + ((this.end.y - this.start.y) * (this.end.y - this.start.y)));
|
|
261
|
-
if (l2
|
|
253
|
+
if (l2 === 0)
|
|
262
254
|
return false;
|
|
263
255
|
const r = (((point.x - this.start.x) * (this.end.x - this.start.x)) + ((point.y - this.start.y) * (this.end.y - this.start.y))) / l2;
|
|
264
256
|
return (0 <= r) && (r <= 1);
|
|
@@ -361,23 +353,27 @@ class Line2D {
|
|
|
361
353
|
if (lines.length < 2) {
|
|
362
354
|
return lines.map(x => x.clone());
|
|
363
355
|
}
|
|
364
|
-
|
|
365
|
-
const result =
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
356
|
+
// Start with cloned lines as initial result
|
|
357
|
+
const result = lines.map(x => x.clone());
|
|
358
|
+
// Keep trying to join lines until no more joins are possible
|
|
359
|
+
let joinedInLastPass = true;
|
|
360
|
+
while (joinedInLastPass) {
|
|
361
|
+
joinedInLastPass = false;
|
|
362
|
+
// Try to join each pair of lines
|
|
369
363
|
for (let i = 0; i < result.length; i++) {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
364
|
+
for (let j = i + 1; j < result.length; j++) {
|
|
365
|
+
const joinedLine = Line2D.joinLine(result[i], result[j]);
|
|
366
|
+
if (joinedLine) {
|
|
367
|
+
// Replace the first line with the joined line and remove the second
|
|
368
|
+
result[i] = joinedLine;
|
|
369
|
+
result.splice(j, 1);
|
|
370
|
+
joinedInLastPass = true;
|
|
371
|
+
// Start over from the beginning since we modified the array
|
|
372
|
+
i = -1;
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
376
375
|
}
|
|
377
376
|
}
|
|
378
|
-
if (!joined) {
|
|
379
|
-
result.push(current.clone());
|
|
380
|
-
}
|
|
381
377
|
}
|
|
382
378
|
return result;
|
|
383
379
|
}
|
|
@@ -419,6 +415,17 @@ class Line2D {
|
|
|
419
415
|
result.push(source);
|
|
420
416
|
return result;
|
|
421
417
|
}
|
|
418
|
+
split(segmentsCount) {
|
|
419
|
+
const result = [];
|
|
420
|
+
const segmentLength = this.length / segmentsCount;
|
|
421
|
+
for (let i = 0; i < segmentsCount; i++) {
|
|
422
|
+
const line = this.clone();
|
|
423
|
+
line.moveStartPoint(-i * segmentLength);
|
|
424
|
+
line.moveEndPoint(-(segmentsCount - i - 1) * segmentLength);
|
|
425
|
+
result.push(line);
|
|
426
|
+
}
|
|
427
|
+
return result;
|
|
428
|
+
}
|
|
422
429
|
/**
|
|
423
430
|
* Returns the closest point on the line to the given point.
|
|
424
431
|
* @param point
|
|
@@ -449,7 +456,7 @@ class Line2D {
|
|
|
449
456
|
*/
|
|
450
457
|
distanceToPointOnInfiniteLine(point) {
|
|
451
458
|
const l2 = (((this.end.x - this.start.x) * (this.end.x - this.start.x)) + ((this.end.y - this.start.y) * (this.end.y - this.start.y)));
|
|
452
|
-
if (l2
|
|
459
|
+
if (l2 === 0)
|
|
453
460
|
return Infinity;
|
|
454
461
|
const s = (((this.start.y - point.y) * (this.end.x - this.start.x)) - ((this.start.x - point.x) * (this.end.y - this.start.y))) / l2;
|
|
455
462
|
return Math.abs(s) * Math.sqrt(l2);
|
|
@@ -670,7 +677,7 @@ class Line2D {
|
|
|
670
677
|
return;
|
|
671
678
|
visited.add(line);
|
|
672
679
|
group.push(line);
|
|
673
|
-
lines.forEach(
|
|
680
|
+
lines.forEach(neighbor => {
|
|
674
681
|
if (!visited.has(neighbor)) {
|
|
675
682
|
if (line.connectsTo(neighbor, tolerance, breakpoints)) {
|
|
676
683
|
dfs(neighbor, group);
|
|
@@ -679,7 +686,7 @@ class Line2D {
|
|
|
679
686
|
});
|
|
680
687
|
};
|
|
681
688
|
const connectedLines = [];
|
|
682
|
-
lines.forEach(
|
|
689
|
+
lines.forEach(line => {
|
|
683
690
|
if (!visited.has(line)) {
|
|
684
691
|
const group = [];
|
|
685
692
|
dfs(line, group);
|
package/cjs/Line3D.js
CHANGED
|
@@ -425,7 +425,7 @@ class Line3D extends three_1.Line3 {
|
|
|
425
425
|
return;
|
|
426
426
|
visited.add(line);
|
|
427
427
|
group.push(line);
|
|
428
|
-
lines.forEach(
|
|
428
|
+
lines.forEach(neighbor => {
|
|
429
429
|
if (!visited.has(neighbor)) {
|
|
430
430
|
if (line.connectsTo(neighbor, tolerance, breakpoints)) {
|
|
431
431
|
dfs(neighbor, group);
|
|
@@ -434,7 +434,7 @@ class Line3D extends three_1.Line3 {
|
|
|
434
434
|
});
|
|
435
435
|
};
|
|
436
436
|
const connectedLines = [];
|
|
437
|
-
lines.forEach(
|
|
437
|
+
lines.forEach(line => {
|
|
438
438
|
if (!visited.has(line)) {
|
|
439
439
|
const group = [];
|
|
440
440
|
dfs(line, group);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.isContinuousClosedShape = void 0;
|
|
4
|
+
// This matches the signatures on Vec2 and Vec3 (for example, isNear(v: Vector2, maxDistance?: number)).
|
|
4
5
|
function isContinuousClosedShape(lines, tolerance = 0) {
|
|
5
6
|
if (lines.length < 3) {
|
|
6
7
|
return false; // A shape needs at least 3 lines to be closed
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import react from "eslint-plugin-react";
|
|
2
|
+
import typescriptEslint from "@typescript-eslint/eslint-plugin";
|
|
3
|
+
import globals from "globals";
|
|
4
|
+
import tsParser from "@typescript-eslint/parser";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import js from "@eslint/js";
|
|
8
|
+
import { FlatCompat } from "@eslint/eslintrc";
|
|
9
|
+
import stylistic from "@stylistic/eslint-plugin";
|
|
10
|
+
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = path.dirname(__filename);
|
|
13
|
+
const compat = new FlatCompat({
|
|
14
|
+
baseDirectory: __dirname,
|
|
15
|
+
recommendedConfig: js.configs.recommended,
|
|
16
|
+
allConfig: js.configs.all
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export default [
|
|
20
|
+
{
|
|
21
|
+
ignores: ["cjs/", "esm/", "types/"],
|
|
22
|
+
},
|
|
23
|
+
...compat.extends("eslint:recommended",
|
|
24
|
+
"plugin:react/recommended",
|
|
25
|
+
"plugin:@typescript-eslint/recommended"),
|
|
26
|
+
{
|
|
27
|
+
plugins: {
|
|
28
|
+
react,
|
|
29
|
+
"@typescript-eslint": typescriptEslint,
|
|
30
|
+
"@stylistic": stylistic
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
languageOptions: {
|
|
34
|
+
globals: {
|
|
35
|
+
...globals.browser,
|
|
36
|
+
...globals.jest,
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
parser: tsParser,
|
|
40
|
+
ecmaVersion: 11,
|
|
41
|
+
sourceType: "module",
|
|
42
|
+
|
|
43
|
+
parserOptions: {
|
|
44
|
+
ecmaFeatures: {
|
|
45
|
+
jsx: true,
|
|
46
|
+
},
|
|
47
|
+
lib: ["esnext", "dom"]
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
settings: {
|
|
52
|
+
react: {
|
|
53
|
+
pragma: "dom",
|
|
54
|
+
version: "17.0"
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
rules: {
|
|
59
|
+
"@stylistic/indent": ["warn", 4, { "SwitchCase": 1 }],
|
|
60
|
+
"@stylistic/linebreak-style": ["error", "windows"],
|
|
61
|
+
"@stylistic/quotes": ["error", "double"],
|
|
62
|
+
"@stylistic/semi": ["error", "always"],
|
|
63
|
+
"@stylistic/no-extra-semi": ["error"],
|
|
64
|
+
"@stylistic/no-trailing-spaces": ["error"],
|
|
65
|
+
"@stylistic/no-multiple-empty-lines": ["error", { max: 1 }],
|
|
66
|
+
"@stylistic/space-in-parens": ["error", "never"],
|
|
67
|
+
"@stylistic/space-infix-ops": ["error", { int32Hint: false }],
|
|
68
|
+
"@stylistic/no-mixed-spaces-and-tabs": ["error"],
|
|
69
|
+
"@stylistic/space-before-blocks": ["error", "always"],
|
|
70
|
+
"@stylistic/function-call-spacing": ["error", "never"],
|
|
71
|
+
"@stylistic/key-spacing": ["error", { "beforeColon": false, "afterColon": true }],
|
|
72
|
+
"@stylistic/array-bracket-spacing": ["error", "never"],
|
|
73
|
+
"@stylistic/arrow-spacing": ["error", { "before": true, "after": true }],
|
|
74
|
+
"@stylistic/block-spacing": ["error", "always"],
|
|
75
|
+
"@stylistic/brace-style": ["error", "1tbs", { "allowSingleLine": true }],
|
|
76
|
+
"@stylistic/semi-spacing": ["error", { "before": false, "after": true }],
|
|
77
|
+
"@stylistic/space-before-function-paren": ["error", "never"],
|
|
78
|
+
"@stylistic/switch-colon-spacing": ["error", { "after": true, "before": false }],
|
|
79
|
+
"@stylistic/template-curly-spacing": ["error", "never"],
|
|
80
|
+
"@stylistic/type-annotation-spacing": ["error", { "after": true }],
|
|
81
|
+
"@stylistic/object-curly-spacing": ["error", "always"],
|
|
82
|
+
"@stylistic/keyword-spacing": ["error", { "before": true, "after": true }],
|
|
83
|
+
"no-undef": "error",
|
|
84
|
+
"consistent-return": "error",
|
|
85
|
+
"no-invalid-this": "error",
|
|
86
|
+
"arrow-parens": ["error", "as-needed"],
|
|
87
|
+
"eol-last": ["error", "never"],
|
|
88
|
+
"@typescript-eslint/no-inferrable-types": "off",
|
|
89
|
+
"@typescript-eslint/no-empty-interface": "off",
|
|
90
|
+
"@typescript-eslint/no-this-alias": "off",
|
|
91
|
+
"no-inv": "off",
|
|
92
|
+
"no-prototype-builtins": "off",
|
|
93
|
+
"react/react-in-jsx-scope": "off",
|
|
94
|
+
"react/no-unknown-property": "off",
|
|
95
|
+
"react/no-unescaped-entities": "off",
|
|
96
|
+
"react/jsx-key": "off",
|
|
97
|
+
"react/prop-types": "off",
|
|
98
|
+
"react/no-is-mounted": "off",
|
|
99
|
+
"@typescript-eslint/explicit-member-accessibility": ["error", {
|
|
100
|
+
"accessibility": "explicit",
|
|
101
|
+
"overrides": {
|
|
102
|
+
"constructors": "no-public"
|
|
103
|
+
}
|
|
104
|
+
}],
|
|
105
|
+
"react/self-closing-comp": ["warn", {
|
|
106
|
+
"component": true,
|
|
107
|
+
"html": true
|
|
108
|
+
}],
|
|
109
|
+
"@typescript-eslint/explicit-module-boundary-types": "error",
|
|
110
|
+
"eqeqeq": ["error", "always"]
|
|
111
|
+
},
|
|
112
|
+
}];
|
package/esm/Line2D.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MathUtils
|
|
1
|
+
import { MathUtils } from "three";
|
|
2
2
|
import { Vec2 } from "./Vec2";
|
|
3
3
|
import { TwoPI } from "./MathConstants";
|
|
4
4
|
import { Line3D } from "./Line3D";
|
|
@@ -93,8 +93,7 @@ export class Line2D {
|
|
|
93
93
|
* Modifies this line.
|
|
94
94
|
*/
|
|
95
95
|
moveStartPoint(amount) {
|
|
96
|
-
|
|
97
|
-
this.start.copy(p1);
|
|
96
|
+
this.start.add(this.direction.multiplyScalar(-amount));
|
|
98
97
|
return this;
|
|
99
98
|
}
|
|
100
99
|
/**
|
|
@@ -102,16 +101,9 @@ export class Line2D {
|
|
|
102
101
|
* Modifies this line.
|
|
103
102
|
*/
|
|
104
103
|
moveEndPoint(amount) {
|
|
105
|
-
|
|
106
|
-
this.end.copy(p2);
|
|
104
|
+
this.end.add(this.direction.multiplyScalar(amount));
|
|
107
105
|
return this;
|
|
108
106
|
}
|
|
109
|
-
movePointOnThisLine(point, amount) {
|
|
110
|
-
const vec = new Vector2(this.center.x - point.x, this.center.y - point.y);
|
|
111
|
-
const length = vec.length();
|
|
112
|
-
vec.normalize().multiplyScalar(length + amount);
|
|
113
|
-
return new Vec2(this.center.x - vec.x, this.center.y - vec.y);
|
|
114
|
-
}
|
|
115
107
|
/**
|
|
116
108
|
* Set the length of this line. Center and direction remain unchanged.
|
|
117
109
|
* Modifies this line.
|
|
@@ -255,7 +247,7 @@ export class Line2D {
|
|
|
255
247
|
*/
|
|
256
248
|
isPointBesideLineSection(point) {
|
|
257
249
|
const l2 = (((this.end.x - this.start.x) * (this.end.x - this.start.x)) + ((this.end.y - this.start.y) * (this.end.y - this.start.y)));
|
|
258
|
-
if (l2
|
|
250
|
+
if (l2 === 0)
|
|
259
251
|
return false;
|
|
260
252
|
const r = (((point.x - this.start.x) * (this.end.x - this.start.x)) + ((point.y - this.start.y) * (this.end.y - this.start.y))) / l2;
|
|
261
253
|
return (0 <= r) && (r <= 1);
|
|
@@ -358,23 +350,27 @@ export class Line2D {
|
|
|
358
350
|
if (lines.length < 2) {
|
|
359
351
|
return lines.map(x => x.clone());
|
|
360
352
|
}
|
|
361
|
-
|
|
362
|
-
const result =
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
353
|
+
// Start with cloned lines as initial result
|
|
354
|
+
const result = lines.map(x => x.clone());
|
|
355
|
+
// Keep trying to join lines until no more joins are possible
|
|
356
|
+
let joinedInLastPass = true;
|
|
357
|
+
while (joinedInLastPass) {
|
|
358
|
+
joinedInLastPass = false;
|
|
359
|
+
// Try to join each pair of lines
|
|
366
360
|
for (let i = 0; i < result.length; i++) {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
361
|
+
for (let j = i + 1; j < result.length; j++) {
|
|
362
|
+
const joinedLine = Line2D.joinLine(result[i], result[j]);
|
|
363
|
+
if (joinedLine) {
|
|
364
|
+
// Replace the first line with the joined line and remove the second
|
|
365
|
+
result[i] = joinedLine;
|
|
366
|
+
result.splice(j, 1);
|
|
367
|
+
joinedInLastPass = true;
|
|
368
|
+
// Start over from the beginning since we modified the array
|
|
369
|
+
i = -1;
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
373
372
|
}
|
|
374
373
|
}
|
|
375
|
-
if (!joined) {
|
|
376
|
-
result.push(current.clone());
|
|
377
|
-
}
|
|
378
374
|
}
|
|
379
375
|
return result;
|
|
380
376
|
}
|
|
@@ -416,6 +412,17 @@ export class Line2D {
|
|
|
416
412
|
result.push(source);
|
|
417
413
|
return result;
|
|
418
414
|
}
|
|
415
|
+
split(segmentsCount) {
|
|
416
|
+
const result = [];
|
|
417
|
+
const segmentLength = this.length / segmentsCount;
|
|
418
|
+
for (let i = 0; i < segmentsCount; i++) {
|
|
419
|
+
const line = this.clone();
|
|
420
|
+
line.moveStartPoint(-i * segmentLength);
|
|
421
|
+
line.moveEndPoint(-(segmentsCount - i - 1) * segmentLength);
|
|
422
|
+
result.push(line);
|
|
423
|
+
}
|
|
424
|
+
return result;
|
|
425
|
+
}
|
|
419
426
|
/**
|
|
420
427
|
* Returns the closest point on the line to the given point.
|
|
421
428
|
* @param point
|
|
@@ -446,7 +453,7 @@ export class Line2D {
|
|
|
446
453
|
*/
|
|
447
454
|
distanceToPointOnInfiniteLine(point) {
|
|
448
455
|
const l2 = (((this.end.x - this.start.x) * (this.end.x - this.start.x)) + ((this.end.y - this.start.y) * (this.end.y - this.start.y)));
|
|
449
|
-
if (l2
|
|
456
|
+
if (l2 === 0)
|
|
450
457
|
return Infinity;
|
|
451
458
|
const s = (((this.start.y - point.y) * (this.end.x - this.start.x)) - ((this.start.x - point.x) * (this.end.y - this.start.y))) / l2;
|
|
452
459
|
return Math.abs(s) * Math.sqrt(l2);
|
|
@@ -667,7 +674,7 @@ export class Line2D {
|
|
|
667
674
|
return;
|
|
668
675
|
visited.add(line);
|
|
669
676
|
group.push(line);
|
|
670
|
-
lines.forEach(
|
|
677
|
+
lines.forEach(neighbor => {
|
|
671
678
|
if (!visited.has(neighbor)) {
|
|
672
679
|
if (line.connectsTo(neighbor, tolerance, breakpoints)) {
|
|
673
680
|
dfs(neighbor, group);
|
|
@@ -676,7 +683,7 @@ export class Line2D {
|
|
|
676
683
|
});
|
|
677
684
|
};
|
|
678
685
|
const connectedLines = [];
|
|
679
|
-
lines.forEach(
|
|
686
|
+
lines.forEach(line => {
|
|
680
687
|
if (!visited.has(line)) {
|
|
681
688
|
const group = [];
|
|
682
689
|
dfs(line, group);
|
package/esm/Line3D.js
CHANGED
|
@@ -422,7 +422,7 @@ export class Line3D extends Line3 {
|
|
|
422
422
|
return;
|
|
423
423
|
visited.add(line);
|
|
424
424
|
group.push(line);
|
|
425
|
-
lines.forEach(
|
|
425
|
+
lines.forEach(neighbor => {
|
|
426
426
|
if (!visited.has(neighbor)) {
|
|
427
427
|
if (line.connectsTo(neighbor, tolerance, breakpoints)) {
|
|
428
428
|
dfs(neighbor, group);
|
|
@@ -431,7 +431,7 @@ export class Line3D extends Line3 {
|
|
|
431
431
|
});
|
|
432
432
|
};
|
|
433
433
|
const connectedLines = [];
|
|
434
|
-
lines.forEach(
|
|
434
|
+
lines.forEach(line => {
|
|
435
435
|
if (!visited.has(line)) {
|
|
436
436
|
const group = [];
|
|
437
437
|
dfs(line, group);
|
package/package.json
CHANGED
|
@@ -1,56 +1,62 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
2
|
+
"name": "@immugio/three-math-extensions",
|
|
3
|
+
"version": "0.3.2",
|
|
4
|
+
"description": "Set of utilities for 2d and 3d line math built on top of three.js",
|
|
5
|
+
"author": "Jan Mikeska <janmikeska@gmail.com>",
|
|
6
|
+
"license": "ISC",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"threejs",
|
|
9
|
+
"three",
|
|
10
|
+
"math"
|
|
11
|
+
],
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/Immugio/three-math-extensions/issues"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/Immugio/three-math-extensions#readme",
|
|
16
|
+
"sideEffects": false,
|
|
17
|
+
"source": "src/index.ts",
|
|
18
|
+
"main": "cjs/index.js",
|
|
19
|
+
"module": "esm/index.js",
|
|
20
|
+
"types": "types/index.d.ts",
|
|
21
|
+
"auto-changelog": {
|
|
22
|
+
"commitLimit": false,
|
|
23
|
+
"template": "keepachangelog"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"test": "npx jest",
|
|
27
|
+
"build:esm": "tsc",
|
|
28
|
+
"build:cjs": "tsc -p tsconfig-cjs.json",
|
|
29
|
+
"clean": "rimraf types cjs esm",
|
|
30
|
+
"build": "npm run clean && npm run build:esm && npm run build:cjs",
|
|
31
|
+
"preversion": "npm run clean && npm run build && npm run test",
|
|
32
|
+
"version": "auto-changelog -p && git add CHANGELOG.md",
|
|
33
|
+
"postversion": "git push && git push --tags"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@eslint/eslintrc": "^3.3.1",
|
|
37
|
+
"@eslint/js": "^9.39.1",
|
|
38
|
+
"@stylistic/eslint-plugin": "^5.6.1",
|
|
39
|
+
"@types/jest": "^29.5.11",
|
|
40
|
+
"@types/offscreencanvas": "2019.7.3",
|
|
41
|
+
"@types/three": "0.152.0",
|
|
42
|
+
"@typescript-eslint/eslint-plugin": "^8.47.0",
|
|
43
|
+
"@typescript-eslint/parser": "^8.47.0",
|
|
44
|
+
"auto-changelog": "^2.5.0",
|
|
45
|
+
"eslint": "^9.39.1",
|
|
46
|
+
"eslint-plugin-react": "^7.37.5",
|
|
47
|
+
"globals": "^16.5.0",
|
|
48
|
+
"jest": "^30.2.0",
|
|
49
|
+
"rimraf": "^6.1.2",
|
|
50
|
+
"ts-jest": "^29.4.5",
|
|
51
|
+
"typedoc": "^0.28.14",
|
|
52
|
+
"typedoc-plugin-markdown": "^4.9.0",
|
|
53
|
+
"typescript": "5.3.3"
|
|
54
|
+
},
|
|
55
|
+
"peerDependencies": {
|
|
56
|
+
"three": ">=0.152.0"
|
|
57
|
+
},
|
|
58
|
+
"repository": {
|
|
59
|
+
"type": "git",
|
|
60
|
+
"url": "git+https://github.com/Immugio/three-math-extensions.git"
|
|
61
|
+
}
|
|
56
62
|
}
|
package/src/BoundingBox.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { Vec2 } from "./Vec2";
|
|
2
2
|
|
|
3
3
|
export class BoundingBox {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
constructor(public minX: number, public maxX: number, public minY: number, public maxY: number) {
|
|
5
|
+
}
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
public equals(other: BoundingBox): boolean {
|
|
8
|
+
return this.minX === other.minX && this.maxX === other.maxX && this.minY === other.minY && this.maxY === other.maxY;
|
|
9
|
+
}
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
public get size(): Vec2 {
|
|
12
|
+
return new Vec2(this.maxX - this.minX, this.maxY - this.minY);
|
|
13
|
+
}
|
|
14
14
|
}
|
package/src/Line2D.ts
CHANGED
|
@@ -107,9 +107,7 @@ export class Line2D {
|
|
|
107
107
|
* Modifies this line.
|
|
108
108
|
*/
|
|
109
109
|
public moveStartPoint(amount: number): this {
|
|
110
|
-
|
|
111
|
-
this.start.copy(p1);
|
|
112
|
-
|
|
110
|
+
this.start.add(this.direction.multiplyScalar(-amount));
|
|
113
111
|
return this;
|
|
114
112
|
}
|
|
115
113
|
|
|
@@ -118,20 +116,10 @@ export class Line2D {
|
|
|
118
116
|
* Modifies this line.
|
|
119
117
|
*/
|
|
120
118
|
public moveEndPoint(amount: number): this {
|
|
121
|
-
|
|
122
|
-
this.end.copy(p2);
|
|
123
|
-
|
|
119
|
+
this.end.add(this.direction.multiplyScalar(amount));
|
|
124
120
|
return this;
|
|
125
121
|
}
|
|
126
122
|
|
|
127
|
-
private movePointOnThisLine(point: Point2, amount: number): Vec2 {
|
|
128
|
-
const vec = new Vector2(this.center.x - point.x, this.center.y - point.y);
|
|
129
|
-
const length = vec.length();
|
|
130
|
-
vec.normalize().multiplyScalar(length + amount);
|
|
131
|
-
|
|
132
|
-
return new Vec2(this.center.x - vec.x,this.center.y - vec.y);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
123
|
/**
|
|
136
124
|
* Set the length of this line. Center and direction remain unchanged.
|
|
137
125
|
* Modifies this line.
|
|
@@ -297,7 +285,7 @@ export class Line2D {
|
|
|
297
285
|
*/
|
|
298
286
|
public isPointBesideLineSection(point: Point2): boolean {
|
|
299
287
|
const l2 = (((this.end.x - this.start.x) * (this.end.x - this.start.x)) + ((this.end.y - this.start.y) * (this.end.y - this.start.y)));
|
|
300
|
-
if (l2
|
|
288
|
+
if (l2 === 0) return false;
|
|
301
289
|
const r = (((point.x - this.start.x) * (this.end.x - this.start.x)) + ((point.y - this.start.y) * (this.end.y - this.start.y))) / l2;
|
|
302
290
|
|
|
303
291
|
return (0 <= r) && (r <= 1);
|
|
@@ -421,27 +409,29 @@ export class Line2D {
|
|
|
421
409
|
return lines.map(x => x.clone());
|
|
422
410
|
}
|
|
423
411
|
|
|
424
|
-
|
|
425
|
-
const result: Line2D[] =
|
|
426
|
-
|
|
427
|
-
while (toProcess.length > 0) {
|
|
412
|
+
// Start with cloned lines as initial result
|
|
413
|
+
const result: Line2D[] = lines.map(x => x.clone());
|
|
428
414
|
|
|
429
|
-
|
|
430
|
-
|
|
415
|
+
// Keep trying to join lines until no more joins are possible
|
|
416
|
+
let joinedInLastPass = true;
|
|
417
|
+
while (joinedInLastPass) {
|
|
418
|
+
joinedInLastPass = false;
|
|
431
419
|
|
|
420
|
+
// Try to join each pair of lines
|
|
432
421
|
for (let i = 0; i < result.length; i++) {
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
422
|
+
for (let j = i + 1; j < result.length; j++) {
|
|
423
|
+
const joinedLine = Line2D.joinLine(result[i], result[j]);
|
|
424
|
+
if (joinedLine) {
|
|
425
|
+
// Replace the first line with the joined line and remove the second
|
|
426
|
+
result[i] = joinedLine;
|
|
427
|
+
result.splice(j, 1);
|
|
428
|
+
joinedInLastPass = true;
|
|
429
|
+
// Start over from the beginning since we modified the array
|
|
430
|
+
i = -1;
|
|
431
|
+
break;
|
|
432
|
+
}
|
|
439
433
|
}
|
|
440
434
|
}
|
|
441
|
-
|
|
442
|
-
if (!joined) {
|
|
443
|
-
result.push(current.clone());
|
|
444
|
-
}
|
|
445
435
|
}
|
|
446
436
|
|
|
447
437
|
return result;
|
|
@@ -490,6 +480,18 @@ export class Line2D {
|
|
|
490
480
|
return result;
|
|
491
481
|
}
|
|
492
482
|
|
|
483
|
+
public split(segmentsCount: number): Line2D[] {
|
|
484
|
+
const result: Line2D[] = [];
|
|
485
|
+
const segmentLength = this.length / segmentsCount;
|
|
486
|
+
for (let i = 0; i < segmentsCount; i++) {
|
|
487
|
+
const line = this.clone();
|
|
488
|
+
line.moveStartPoint(-i * segmentLength);
|
|
489
|
+
line.moveEndPoint(-(segmentsCount - i - 1) * segmentLength);
|
|
490
|
+
result.push(line);
|
|
491
|
+
}
|
|
492
|
+
return result;
|
|
493
|
+
}
|
|
494
|
+
|
|
493
495
|
/**
|
|
494
496
|
* Returns the closest point on the line to the given point.
|
|
495
497
|
* @param point
|
|
@@ -527,7 +529,7 @@ export class Line2D {
|
|
|
527
529
|
*/
|
|
528
530
|
public distanceToPointOnInfiniteLine(point: Point2): number {
|
|
529
531
|
const l2 = (((this.end.x - this.start.x) * (this.end.x - this.start.x)) + ((this.end.y - this.start.y) * (this.end.y - this.start.y)));
|
|
530
|
-
if (l2
|
|
532
|
+
if (l2 === 0) return Infinity;
|
|
531
533
|
const s = (((this.start.y - point.y) * (this.end.x - this.start.x)) - ((this.start.x - point.x) * (this.end.y - this.start.y))) / l2;
|
|
532
534
|
return Math.abs(s) * Math.sqrt(l2);
|
|
533
535
|
}
|
|
@@ -789,7 +791,7 @@ export class Line2D {
|
|
|
789
791
|
visited.add(line);
|
|
790
792
|
group.push(line);
|
|
791
793
|
|
|
792
|
-
lines.forEach(
|
|
794
|
+
lines.forEach(neighbor => {
|
|
793
795
|
if (!visited.has(neighbor)) {
|
|
794
796
|
if (line.connectsTo(neighbor, tolerance, breakpoints)) {
|
|
795
797
|
dfs(neighbor, group);
|
|
@@ -800,7 +802,7 @@ export class Line2D {
|
|
|
800
802
|
|
|
801
803
|
const connectedLines: Line2D[][] = [];
|
|
802
804
|
|
|
803
|
-
lines.forEach(
|
|
805
|
+
lines.forEach(line => {
|
|
804
806
|
if (!visited.has(line)) {
|
|
805
807
|
const group: Line2D[] = [];
|
|
806
808
|
dfs(line, group);
|
package/src/Line3D.ts
CHANGED
|
@@ -521,7 +521,7 @@ export class Line3D extends Line3 {
|
|
|
521
521
|
visited.add(line);
|
|
522
522
|
group.push(line);
|
|
523
523
|
|
|
524
|
-
lines.forEach(
|
|
524
|
+
lines.forEach(neighbor => {
|
|
525
525
|
if (!visited.has(neighbor)) {
|
|
526
526
|
if (
|
|
527
527
|
line.connectsTo(neighbor, tolerance, breakpoints)
|
|
@@ -534,7 +534,7 @@ export class Line3D extends Line3 {
|
|
|
534
534
|
|
|
535
535
|
const connectedLines: Line3D[][] = [];
|
|
536
536
|
|
|
537
|
-
lines.forEach(
|
|
537
|
+
lines.forEach(line => {
|
|
538
538
|
if (!visited.has(line)) {
|
|
539
539
|
const group: Line3D[] = [];
|
|
540
540
|
dfs(line, group);
|
package/src/Point3.ts
CHANGED
package/src/Polygon.ts
CHANGED
|
@@ -15,7 +15,7 @@ export class Polygon {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
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)))
|
|
18
|
+
return new Polygon(contour.map(p => Vec2.fromPoint(p)), holes?.map(h => h.map(p => Vec2.fromPoint(p))));
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
public static fromSize(width: number, height: number): Polygon {
|
|
@@ -284,5 +284,4 @@ export class Polygon {
|
|
|
284
284
|
|
|
285
285
|
return true;
|
|
286
286
|
}
|
|
287
|
-
}
|
|
288
|
-
|
|
287
|
+
}
|
package/src/Vec2.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {Vector2} from "three";
|
|
2
|
-
import {Vec3} from "./Vec3";
|
|
3
|
-
import {Point2} from "./Point2";
|
|
4
|
-
import {normalizeAngleRadians} from "./normalizeAngleRadians";
|
|
1
|
+
import { Vector2 } from "three";
|
|
2
|
+
import { Vec3 } from "./Vec3";
|
|
3
|
+
import { Point2 } from "./Point2";
|
|
4
|
+
import { normalizeAngleRadians } from "./normalizeAngleRadians";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Vec2 represents a 2D vector. It extends `Vector2` from the `threejs` library.
|
package/src/Vec3.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// The point type P must implement an isNear method that accepts the same point type and an optional tolerance.
|
|
2
|
+
type Point<P extends { isNear(other: P, tolerance?: number): boolean }> = { isNear(other: P, tolerance?: number): boolean };
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
// This matches the signatures on Vec2 and Vec3 (for example, isNear(v: Vector2, maxDistance?: number)).
|
|
5
|
+
export function isContinuousClosedShape<P extends Point<P>>(lines: { start: P; end: P }[], tolerance: number = 0): boolean {
|
|
5
6
|
if (lines.length < 3) {
|
|
6
7
|
return false; // A shape needs at least 3 lines to be closed
|
|
7
8
|
}
|
|
@@ -11,7 +12,7 @@ export function isContinuousClosedShape<T extends Line2D | Line3D>(lines: T[], t
|
|
|
11
12
|
const endCurrent = lines[i].end;
|
|
12
13
|
const startNext = lines[i + 1].start;
|
|
13
14
|
|
|
14
|
-
if (!endCurrent.isNear(startNext
|
|
15
|
+
if (!endCurrent.isNear(startNext, tolerance)) {
|
|
15
16
|
return false; // If the end of the current line and start of the next line are not close, return false
|
|
16
17
|
}
|
|
17
18
|
}
|
|
@@ -20,5 +21,5 @@ export function isContinuousClosedShape<T extends Line2D | Line3D>(lines: T[], t
|
|
|
20
21
|
const endLast = lines[lines.length - 1].end;
|
|
21
22
|
const startFirst = lines[0].start;
|
|
22
23
|
|
|
23
|
-
return endLast.isNear(startFirst
|
|
24
|
+
return endLast.isNear(startFirst, tolerance);
|
|
24
25
|
}
|
package/types/Line2D.d.ts
CHANGED
|
@@ -38,7 +38,6 @@ export declare class Line2D {
|
|
|
38
38
|
* Modifies this line.
|
|
39
39
|
*/
|
|
40
40
|
moveEndPoint(amount: number): this;
|
|
41
|
-
private movePointOnThisLine;
|
|
42
41
|
/**
|
|
43
42
|
* Set the length of this line. Center and direction remain unchanged.
|
|
44
43
|
* Modifies this line.
|
|
@@ -177,6 +176,7 @@ export declare class Line2D {
|
|
|
177
176
|
* @param maxSegmentLength number
|
|
178
177
|
*/
|
|
179
178
|
chunk(maxSegmentLength: number): Line2D[];
|
|
179
|
+
split(segmentsCount: number): Line2D[];
|
|
180
180
|
/**
|
|
181
181
|
* Returns the closest point on the line to the given point.
|
|
182
182
|
* @param point
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
type Point<P extends {
|
|
2
|
+
isNear(other: P, tolerance?: number): boolean;
|
|
3
|
+
}> = {
|
|
4
|
+
isNear(other: P, tolerance?: number): boolean;
|
|
5
|
+
};
|
|
6
|
+
export declare function isContinuousClosedShape<P extends Point<P>>(lines: {
|
|
7
|
+
start: P;
|
|
8
|
+
end: P;
|
|
9
|
+
}[], tolerance?: number): boolean;
|
|
10
|
+
export {};
|