@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 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.38](https://github.com/Immugio/three-math-extensions/compare/0.2.37...0.2.38)
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
- const p1 = this.movePointOnThisLine(this.start, amount);
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
- const p2 = this.movePointOnThisLine(this.end, amount);
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 == 0)
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
- const toProcess = lines.slice();
365
- const result = [];
366
- while (toProcess.length > 0) {
367
- const current = toProcess.pop();
368
- let joined = false;
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
- const other = result[i];
371
- const joinedLine = Line2D.joinLine(current, other);
372
- if (joinedLine) {
373
- result[i] = joinedLine;
374
- joined = true;
375
- break;
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 == 0)
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((neighbor) => {
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((line) => {
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((neighbor) => {
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((line) => {
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, Vector2 } from "three";
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
- const p1 = this.movePointOnThisLine(this.start, amount);
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
- const p2 = this.movePointOnThisLine(this.end, amount);
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 == 0)
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
- const toProcess = lines.slice();
362
- const result = [];
363
- while (toProcess.length > 0) {
364
- const current = toProcess.pop();
365
- let joined = false;
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
- const other = result[i];
368
- const joinedLine = Line2D.joinLine(current, other);
369
- if (joinedLine) {
370
- result[i] = joinedLine;
371
- joined = true;
372
- break;
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 == 0)
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((neighbor) => {
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((line) => {
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((neighbor) => {
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((line) => {
434
+ lines.forEach(line => {
435
435
  if (!visited.has(line)) {
436
436
  const group = [];
437
437
  dfs(line, group);
@@ -1,3 +1,4 @@
1
+ // This matches the signatures on Vec2 and Vec3 (for example, isNear(v: Vector2, maxDistance?: number)).
1
2
  export function isContinuousClosedShape(lines, tolerance = 0) {
2
3
  if (lines.length < 3) {
3
4
  return false; // A shape needs at least 3 lines to be closed
package/package.json CHANGED
@@ -1,56 +1,62 @@
1
1
  {
2
- "name": "@immugio/three-math-extensions",
3
- "version": "0.2.38",
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
- "@types/jest": "^29.5.11",
37
- "@types/offscreencanvas": "2019.7.3",
38
- "@types/three": "0.152.0",
39
- "@typescript-eslint/eslint-plugin": "^6.16.0",
40
- "auto-changelog": "^2.4.0",
41
- "jest": "^29.7.0",
42
- "rimraf": "^5.0.5",
43
- "ts-jest": "^29.1.1",
44
- "typedoc": "^0.25.4",
45
- "typedoc-plugin-markdown": "^3.17.1",
46
- "typescript": "5.3.3",
47
- "eslint": "^8.56.0"
48
- },
49
- "peerDependencies": {
50
- "three": ">=0.152.0"
51
- },
52
- "repository": {
53
- "type": "git",
54
- "url": "git+https://github.com/Immugio/three-math-extensions.git"
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
  }
@@ -1,14 +1,14 @@
1
1
  import { Vec2 } from "./Vec2";
2
2
 
3
3
  export class BoundingBox {
4
- constructor(public minX: number, public maxX: number, public minY: number, public maxY: number) {
5
- }
4
+ constructor(public minX: number, public maxX: number, public minY: number, public maxY: number) {
5
+ }
6
6
 
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
- }
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
- public get size(): Vec2 {
12
- return new Vec2(this.maxX - this.minX, this.maxY - this.minY);
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
- const p1 = this.movePointOnThisLine(this.start, amount);
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
- const p2 = this.movePointOnThisLine(this.end, amount);
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 == 0) return false;
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
- const toProcess = lines.slice();
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
- const current = toProcess.pop();
430
- let joined = false;
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
- const other = result[i];
434
- const joinedLine = Line2D.joinLine(current, other);
435
- if (joinedLine) {
436
- result[i] = joinedLine;
437
- joined = true;
438
- break;
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 == 0) return Infinity;
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((neighbor) => {
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((line) => {
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((neighbor) => {
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((line) => {
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
@@ -2,5 +2,4 @@ export interface Point3 {
2
2
  x: number,
3
3
  y: number,
4
4
  z: number
5
- }
6
-
5
+ }
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
@@ -56,7 +56,6 @@ export class Vec3 extends Vector3 {
56
56
  return this.moveTowards(target, this.distanceTo(target) / 2);
57
57
  }
58
58
 
59
-
60
59
  /**
61
60
  * Adds y amount to this Vec3 instance and return this
62
61
  * @param y
@@ -1,7 +1,8 @@
1
- import { Line3D } from "./Line3D";
2
- import { Line2D } from "./Line2D";
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
- export function isContinuousClosedShape<T extends Line2D | Line3D>(lines: T[], tolerance: number = 0): boolean {
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 as any, tolerance)) {
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 as any, tolerance);
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
- import { Line3D } from "./Line3D";
2
- import { Line2D } from "./Line2D";
3
- export declare function isContinuousClosedShape<T extends Line2D | Line3D>(lines: T[], tolerance?: number): boolean;
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 {};