@immugio/three-math-extensions 0.3.0 → 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 +21 -1
- package/cjs/Line2D.js +22 -18
- package/cjs/Line3D.js +2 -2
- package/cjs/isContinuousClosedShape.js +1 -0
- package/eslint.config.mjs +112 -0
- package/esm/Line2D.js +22 -18
- 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 +22 -20
- 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/isContinuousClosedShape.d.ts +10 -3
package/CHANGELOG.md
CHANGED
|
@@ -7,7 +7,27 @@ 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.3.
|
|
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
|
|
11
31
|
|
|
12
32
|
### Commits
|
|
13
33
|
|
package/cjs/Line2D.js
CHANGED
|
@@ -250,7 +250,7 @@ class Line2D {
|
|
|
250
250
|
*/
|
|
251
251
|
isPointBesideLineSection(point) {
|
|
252
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)));
|
|
253
|
-
if (l2
|
|
253
|
+
if (l2 === 0)
|
|
254
254
|
return false;
|
|
255
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;
|
|
256
256
|
return (0 <= r) && (r <= 1);
|
|
@@ -353,23 +353,27 @@ class Line2D {
|
|
|
353
353
|
if (lines.length < 2) {
|
|
354
354
|
return lines.map(x => x.clone());
|
|
355
355
|
}
|
|
356
|
-
|
|
357
|
-
const result =
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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
|
|
361
363
|
for (let i = 0; i < result.length; i++) {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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
|
+
}
|
|
368
375
|
}
|
|
369
376
|
}
|
|
370
|
-
if (!joined) {
|
|
371
|
-
result.push(current.clone());
|
|
372
|
-
}
|
|
373
377
|
}
|
|
374
378
|
return result;
|
|
375
379
|
}
|
|
@@ -452,7 +456,7 @@ class Line2D {
|
|
|
452
456
|
*/
|
|
453
457
|
distanceToPointOnInfiniteLine(point) {
|
|
454
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)));
|
|
455
|
-
if (l2
|
|
459
|
+
if (l2 === 0)
|
|
456
460
|
return Infinity;
|
|
457
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;
|
|
458
462
|
return Math.abs(s) * Math.sqrt(l2);
|
|
@@ -673,7 +677,7 @@ class Line2D {
|
|
|
673
677
|
return;
|
|
674
678
|
visited.add(line);
|
|
675
679
|
group.push(line);
|
|
676
|
-
lines.forEach(
|
|
680
|
+
lines.forEach(neighbor => {
|
|
677
681
|
if (!visited.has(neighbor)) {
|
|
678
682
|
if (line.connectsTo(neighbor, tolerance, breakpoints)) {
|
|
679
683
|
dfs(neighbor, group);
|
|
@@ -682,7 +686,7 @@ class Line2D {
|
|
|
682
686
|
});
|
|
683
687
|
};
|
|
684
688
|
const connectedLines = [];
|
|
685
|
-
lines.forEach(
|
|
689
|
+
lines.forEach(line => {
|
|
686
690
|
if (!visited.has(line)) {
|
|
687
691
|
const group = [];
|
|
688
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
|
@@ -247,7 +247,7 @@ export class Line2D {
|
|
|
247
247
|
*/
|
|
248
248
|
isPointBesideLineSection(point) {
|
|
249
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)));
|
|
250
|
-
if (l2
|
|
250
|
+
if (l2 === 0)
|
|
251
251
|
return false;
|
|
252
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;
|
|
253
253
|
return (0 <= r) && (r <= 1);
|
|
@@ -350,23 +350,27 @@ export class Line2D {
|
|
|
350
350
|
if (lines.length < 2) {
|
|
351
351
|
return lines.map(x => x.clone());
|
|
352
352
|
}
|
|
353
|
-
|
|
354
|
-
const result =
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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
|
|
358
360
|
for (let i = 0; i < result.length; i++) {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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
|
+
}
|
|
365
372
|
}
|
|
366
373
|
}
|
|
367
|
-
if (!joined) {
|
|
368
|
-
result.push(current.clone());
|
|
369
|
-
}
|
|
370
374
|
}
|
|
371
375
|
return result;
|
|
372
376
|
}
|
|
@@ -449,7 +453,7 @@ export class Line2D {
|
|
|
449
453
|
*/
|
|
450
454
|
distanceToPointOnInfiniteLine(point) {
|
|
451
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)));
|
|
452
|
-
if (l2
|
|
456
|
+
if (l2 === 0)
|
|
453
457
|
return Infinity;
|
|
454
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;
|
|
455
459
|
return Math.abs(s) * Math.sqrt(l2);
|
|
@@ -670,7 +674,7 @@ export class Line2D {
|
|
|
670
674
|
return;
|
|
671
675
|
visited.add(line);
|
|
672
676
|
group.push(line);
|
|
673
|
-
lines.forEach(
|
|
677
|
+
lines.forEach(neighbor => {
|
|
674
678
|
if (!visited.has(neighbor)) {
|
|
675
679
|
if (line.connectsTo(neighbor, tolerance, breakpoints)) {
|
|
676
680
|
dfs(neighbor, group);
|
|
@@ -679,7 +683,7 @@ export class Line2D {
|
|
|
679
683
|
});
|
|
680
684
|
};
|
|
681
685
|
const connectedLines = [];
|
|
682
|
-
lines.forEach(
|
|
686
|
+
lines.forEach(line => {
|
|
683
687
|
if (!visited.has(line)) {
|
|
684
688
|
const group = [];
|
|
685
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
|
@@ -285,7 +285,7 @@ export class Line2D {
|
|
|
285
285
|
*/
|
|
286
286
|
public isPointBesideLineSection(point: Point2): boolean {
|
|
287
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)));
|
|
288
|
-
if (l2
|
|
288
|
+
if (l2 === 0) return false;
|
|
289
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;
|
|
290
290
|
|
|
291
291
|
return (0 <= r) && (r <= 1);
|
|
@@ -409,27 +409,29 @@ export class Line2D {
|
|
|
409
409
|
return lines.map(x => x.clone());
|
|
410
410
|
}
|
|
411
411
|
|
|
412
|
-
|
|
413
|
-
const result: Line2D[] =
|
|
414
|
-
|
|
415
|
-
while (toProcess.length > 0) {
|
|
412
|
+
// Start with cloned lines as initial result
|
|
413
|
+
const result: Line2D[] = lines.map(x => x.clone());
|
|
416
414
|
|
|
417
|
-
|
|
418
|
-
|
|
415
|
+
// Keep trying to join lines until no more joins are possible
|
|
416
|
+
let joinedInLastPass = true;
|
|
417
|
+
while (joinedInLastPass) {
|
|
418
|
+
joinedInLastPass = false;
|
|
419
419
|
|
|
420
|
+
// Try to join each pair of lines
|
|
420
421
|
for (let i = 0; i < result.length; i++) {
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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
|
+
}
|
|
427
433
|
}
|
|
428
434
|
}
|
|
429
|
-
|
|
430
|
-
if (!joined) {
|
|
431
|
-
result.push(current.clone());
|
|
432
|
-
}
|
|
433
435
|
}
|
|
434
436
|
|
|
435
437
|
return result;
|
|
@@ -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
|
}
|
|
@@ -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 {};
|