@js-draw/math 1.21.3 → 1.22.0

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.
Files changed (63) hide show
  1. package/build-config.json +1 -1
  2. package/dist/cjs/Color4.js +2 -2
  3. package/dist/cjs/Mat33.d.ts +1 -11
  4. package/dist/cjs/Mat33.js +8 -24
  5. package/dist/cjs/Vec3.js +9 -7
  6. package/dist/cjs/shapes/BezierJSWrapper.js +20 -13
  7. package/dist/cjs/shapes/LineSegment2.js +13 -17
  8. package/dist/cjs/shapes/Parameterized2DShape.js +1 -1
  9. package/dist/cjs/shapes/Path.js +49 -47
  10. package/dist/cjs/shapes/Rect2.js +13 -15
  11. package/dist/cjs/shapes/Triangle.js +4 -5
  12. package/dist/cjs/utils/convexHull2Of.js +3 -3
  13. package/dist/mjs/Color4.mjs +2 -2
  14. package/dist/mjs/Mat33.d.ts +1 -11
  15. package/dist/mjs/Mat33.mjs +8 -24
  16. package/dist/mjs/Vec3.mjs +9 -7
  17. package/dist/mjs/shapes/BezierJSWrapper.mjs +20 -13
  18. package/dist/mjs/shapes/LineSegment2.mjs +13 -17
  19. package/dist/mjs/shapes/Parameterized2DShape.mjs +1 -1
  20. package/dist/mjs/shapes/Path.mjs +49 -47
  21. package/dist/mjs/shapes/Rect2.mjs +13 -15
  22. package/dist/mjs/shapes/Triangle.mjs +4 -5
  23. package/dist/mjs/utils/convexHull2Of.mjs +3 -3
  24. package/dist-test/test_imports/test-require.cjs +1 -1
  25. package/package.json +3 -3
  26. package/src/Color4.test.ts +16 -21
  27. package/src/Color4.ts +22 -17
  28. package/src/Mat33.fromCSSMatrix.test.ts +31 -45
  29. package/src/Mat33.test.ts +58 -96
  30. package/src/Mat33.ts +61 -104
  31. package/src/Vec2.test.ts +3 -3
  32. package/src/Vec3.test.ts +2 -3
  33. package/src/Vec3.ts +34 -58
  34. package/src/lib.ts +0 -2
  35. package/src/polynomial/solveQuadratic.test.ts +39 -13
  36. package/src/polynomial/solveQuadratic.ts +5 -6
  37. package/src/rounding/cleanUpNumber.test.ts +1 -1
  38. package/src/rounding/constants.ts +1 -3
  39. package/src/rounding/getLenAfterDecimal.ts +1 -2
  40. package/src/rounding/lib.ts +1 -2
  41. package/src/rounding/toRoundedString.test.ts +1 -1
  42. package/src/rounding/toStringOfSamePrecision.test.ts +1 -2
  43. package/src/rounding/toStringOfSamePrecision.ts +1 -1
  44. package/src/shapes/BezierJSWrapper.ts +54 -37
  45. package/src/shapes/CubicBezier.ts +3 -3
  46. package/src/shapes/LineSegment2.test.ts +24 -17
  47. package/src/shapes/LineSegment2.ts +26 -29
  48. package/src/shapes/Parameterized2DShape.ts +5 -4
  49. package/src/shapes/Path.fromString.test.ts +5 -5
  50. package/src/shapes/Path.test.ts +122 -120
  51. package/src/shapes/Path.toString.test.ts +7 -7
  52. package/src/shapes/Path.ts +378 -352
  53. package/src/shapes/PointShape2D.ts +3 -3
  54. package/src/shapes/QuadraticBezier.test.ts +27 -21
  55. package/src/shapes/QuadraticBezier.ts +4 -9
  56. package/src/shapes/Rect2.test.ts +44 -75
  57. package/src/shapes/Rect2.ts +30 -35
  58. package/src/shapes/Triangle.test.ts +31 -29
  59. package/src/shapes/Triangle.ts +17 -18
  60. package/src/utils/convexHull2Of.test.ts +54 -15
  61. package/src/utils/convexHull2Of.ts +9 -7
  62. package/tsconfig.json +1 -3
  63. package/typedoc.json +2 -2
@@ -20,9 +20,9 @@ class PointShape2D extends Parameterized2DShape {
20
20
 
21
21
  public override argIntersectsLineSegment(lineSegment: LineSegment2, epsilon?: number): number[] {
22
22
  if (lineSegment.containsPoint(this.p, epsilon)) {
23
- return [ 0 ];
23
+ return [0];
24
24
  }
25
- return [ ];
25
+ return [];
26
26
  }
27
27
 
28
28
  public override getTightBoundingBox(): Rect2 {
@@ -57,4 +57,4 @@ class PointShape2D extends Parameterized2DShape {
57
57
  }
58
58
  }
59
59
 
60
- export default PointShape2D;
60
+ export default PointShape2D;
@@ -27,24 +27,27 @@ describe('QuadraticBezier', () => {
27
27
  });
28
28
 
29
29
  test.each([
30
- [ new QuadraticBezier(Vec2.zero, Vec2.unitX, Vec2.unitY), Vec2.zero, 0 ],
31
- [ new QuadraticBezier(Vec2.zero, Vec2.unitX, Vec2.unitY), Vec2.unitY, 1 ],
30
+ [new QuadraticBezier(Vec2.zero, Vec2.unitX, Vec2.unitY), Vec2.zero, 0],
31
+ [new QuadraticBezier(Vec2.zero, Vec2.unitX, Vec2.unitY), Vec2.unitY, 1],
32
32
 
33
- [ new QuadraticBezier(Vec2.zero, Vec2.of(0.5, 0), Vec2.of(1, 0)), Vec2.of(0.4, 0), 0.4],
34
- [ new QuadraticBezier(Vec2.zero, Vec2.of(0, 0.5), Vec2.of(0, 1)), Vec2.of(0, 0.4), 0.4],
35
- [ new QuadraticBezier(Vec2.zero, Vec2.unitX, Vec2.unitY), Vec2.unitX, 0.42514 ],
33
+ [new QuadraticBezier(Vec2.zero, Vec2.of(0.5, 0), Vec2.of(1, 0)), Vec2.of(0.4, 0), 0.4],
34
+ [new QuadraticBezier(Vec2.zero, Vec2.of(0, 0.5), Vec2.of(0, 1)), Vec2.of(0, 0.4), 0.4],
35
+ [new QuadraticBezier(Vec2.zero, Vec2.unitX, Vec2.unitY), Vec2.unitX, 0.42514],
36
36
 
37
37
  // Should not return an out-of-range parameter
38
- [ new QuadraticBezier(Vec2.zero, Vec2.of(0, 0.5), Vec2.unitY), Vec2.of(0, -1000), 0 ],
39
- [ new QuadraticBezier(Vec2.zero, Vec2.of(0, 0.5), Vec2.unitY), Vec2.of(0, 1000), 1 ],
38
+ [new QuadraticBezier(Vec2.zero, Vec2.of(0, 0.5), Vec2.unitY), Vec2.of(0, -1000), 0],
39
+ [new QuadraticBezier(Vec2.zero, Vec2.of(0, 0.5), Vec2.unitY), Vec2.of(0, 1000), 1],
40
40
 
41
41
  // Edge case -- just a point
42
- [ new QuadraticBezier(Vec2.zero, Vec2.zero, Vec2.zero), Vec2.of(0, 1000), 0 ],
43
- ])('nearestPointTo should return the nearest point and parameter value on %s to %s', (bezier, point, expectedParameter) => {
44
- const nearest = bezier.nearestPointTo(point);
45
- expect(nearest.parameterValue).toBeCloseTo(expectedParameter, 0.0001);
46
- expect(nearest.point).objEq(bezier.at(nearest.parameterValue));
47
- });
42
+ [new QuadraticBezier(Vec2.zero, Vec2.zero, Vec2.zero), Vec2.of(0, 1000), 0],
43
+ ])(
44
+ 'nearestPointTo should return the nearest point and parameter value on %s to %s',
45
+ (bezier, point, expectedParameter) => {
46
+ const nearest = bezier.nearestPointTo(point);
47
+ expect(nearest.parameterValue).toBeCloseTo(expectedParameter, 0.0001);
48
+ expect(nearest.point).objEq(bezier.at(nearest.parameterValue));
49
+ },
50
+ );
48
51
 
49
52
  test('.normalAt should return a unit normal vector at the given parameter value', () => {
50
53
  const curves = [
@@ -72,17 +75,20 @@ describe('QuadraticBezier', () => {
72
75
  new QuadraticBezier(Vec2.zero, Vec2.unitY, Vec2.unitY.times(2)),
73
76
  new QuadraticBezier(Vec2.zero, Vec2.unitX, Vec2.unitY),
74
77
  new QuadraticBezier(Vec2.zero, Vec2.unitY, Vec2.unitX),
75
- ])('.derivativeAt should return a derivative vector with the correct direction (curve: %s)', (curve) => {
76
- for (let t = 0; t < 1; t += 0.1) {
77
- const derivative = curve.derivativeAt(t);
78
- const derivativeApprox = curve.at(t + 0.001).minus(curve.at(t - 0.001));
79
- expect(derivativeApprox.normalized()).objEq(derivative.normalized(), 0.01);
80
- }
81
- });
78
+ ])(
79
+ '.derivativeAt should return a derivative vector with the correct direction (curve: %s)',
80
+ (curve) => {
81
+ for (let t = 0; t < 1; t += 0.1) {
82
+ const derivative = curve.derivativeAt(t);
83
+ const derivativeApprox = curve.at(t + 0.001).minus(curve.at(t - 0.001));
84
+ expect(derivativeApprox.normalized()).objEq(derivative.normalized(), 0.01);
85
+ }
86
+ },
87
+ );
82
88
 
83
89
  test('should support Bezier-Bezier intersections', () => {
84
90
  const b1 = new QuadraticBezier(Vec2.zero, Vec2.unitX, Vec2.unitY);
85
91
  const b2 = new QuadraticBezier(Vec2.of(-1, 0.5), Vec2.of(0, 0.6), Vec2.of(1, 0.4));
86
92
  expect(b1.intersectsBezier(b2)).toHaveLength(1);
87
93
  });
88
- });
94
+ });
@@ -12,7 +12,7 @@ export class QuadraticBezier extends BezierJSWrapper {
12
12
  public constructor(
13
13
  public readonly p0: Point2,
14
14
  public readonly p1: Point2,
15
- public readonly p2: Point2
15
+ public readonly p2: Point2,
16
16
  ) {
17
17
  super();
18
18
  }
@@ -78,7 +78,7 @@ export class QuadraticBezier extends BezierJSWrapper {
78
78
 
79
79
  /** @returns an overestimate of this shape's bounding box. */
80
80
  public override getLooseBoundingBox(): Rect2 {
81
- return Rect2.bboxOf([ this.p0, this.p1, this.p2 ]);
81
+ return Rect2.bboxOf([this.p0, this.p1, this.p2]);
82
82
  }
83
83
 
84
84
  /**
@@ -124,14 +124,9 @@ export class QuadraticBezier extends BezierJSWrapper {
124
124
  const f2ndDerivAtZero = b;
125
125
  const f3rdDerivAtZero = 2 * c;
126
126
 
127
-
128
127
  // Using the first few terms of a Maclaurin series to approximate f'(t),
129
128
  // f'(t) ≈ f'(0) + t f''(0) + t² f'''(0) / 2
130
- let [ min1, min2 ] = solveQuadratic(
131
- f3rdDerivAtZero / 2,
132
- f2ndDerivAtZero,
133
- fDerivAtZero,
134
- );
129
+ let [min1, min2] = solveQuadratic(f3rdDerivAtZero / 2, f2ndDerivAtZero, fDerivAtZero);
135
130
 
136
131
  // If the quadratic has no solutions, approximate.
137
132
  if (isNaN(min1)) {
@@ -153,7 +148,7 @@ export class QuadraticBezier extends BezierJSWrapper {
153
148
  }
154
149
 
155
150
  public override getPoints() {
156
- return [ this.p0, this.p1, this.p2 ];
151
+ return [this.p0, this.p1, this.p2];
157
152
  }
158
153
  }
159
154
  export default QuadraticBezier;
@@ -1,4 +1,3 @@
1
-
2
1
  import Rect2 from './Rect2';
3
2
  import { Vec2 } from '../Vec2';
4
3
  import Mat33 from '../Mat33';
@@ -7,42 +6,21 @@ describe('Rect2', () => {
7
6
  it('width, height should always be positive', () => {
8
7
  expect(new Rect2(-1, -2, -3, 4)).objEq(new Rect2(-4, -2, 3, 4));
9
8
  expect(new Rect2(0, 0, 0, 0).size).objEq(Vec2.zero);
10
- expect(Rect2.fromCorners(
11
- Vec2.of(-3, -3),
12
- Vec2.of(-1, -1)
13
- )).objEq(new Rect2(
14
- -3, -3,
15
- 2, 2
16
- ));
9
+ expect(Rect2.fromCorners(Vec2.of(-3, -3), Vec2.of(-1, -1))).objEq(new Rect2(-3, -3, 2, 2));
17
10
  });
18
11
 
19
12
  it('bounding boxes should be correctly computed', () => {
20
- expect(Rect2.bboxOf([
21
- Vec2.zero,
22
- ])).objEq(Rect2.empty);
23
-
24
- expect(Rect2.bboxOf([
25
- Vec2.of(-1, -1),
26
- Vec2.of(1, 2),
27
- Vec2.of(3, 4),
28
- Vec2.of(1, -4),
29
- ])).objEq(new Rect2(
30
- -1, -4,
31
- 4, 8
32
- ));
33
-
34
- expect(Rect2.bboxOf([
35
- Vec2.zero,
36
- ], 10)).objEq(new Rect2(
37
- -10, -10,
38
- 20, 20
39
- ));
13
+ expect(Rect2.bboxOf([Vec2.zero])).objEq(Rect2.empty);
14
+
15
+ expect(Rect2.bboxOf([Vec2.of(-1, -1), Vec2.of(1, 2), Vec2.of(3, 4), Vec2.of(1, -4)])).objEq(
16
+ new Rect2(-1, -4, 4, 8),
17
+ );
18
+
19
+ expect(Rect2.bboxOf([Vec2.zero], 10)).objEq(new Rect2(-10, -10, 20, 20));
40
20
  });
41
21
 
42
22
  it('"union"s should contain both composite rectangles.', () => {
43
- expect(new Rect2(0, 0, 1, 1).union(new Rect2(1, 1, 2, 2))).objEq(
44
- new Rect2(0, 0, 3, 3)
45
- );
23
+ expect(new Rect2(0, 0, 1, 1).union(new Rect2(1, 1, 2, 2))).objEq(new Rect2(0, 0, 3, 3));
46
24
  expect(Rect2.empty.union(Rect2.empty)).objEq(Rect2.empty);
47
25
  });
48
26
 
@@ -51,21 +29,15 @@ describe('Rect2', () => {
51
29
  });
52
30
 
53
31
  it('should correctly union multiple rectangles', () => {
54
- expect(Rect2.union(new Rect2(0, 0, 1, 1), new Rect2(1, 1, 2, 2))).objEq(
55
- new Rect2(0, 0, 3, 3)
56
- );
32
+ expect(Rect2.union(new Rect2(0, 0, 1, 1), new Rect2(1, 1, 2, 2))).objEq(new Rect2(0, 0, 3, 3));
57
33
 
58
34
  expect(
59
- Rect2.union(new Rect2(-1, 0, 1, 1), new Rect2(1, 1, 2, 2), new Rect2(1, 10, 1, 0.1))
60
- ).objEq(
61
- new Rect2(-1, 0, 4, 10.1)
62
- );
35
+ Rect2.union(new Rect2(-1, 0, 1, 1), new Rect2(1, 1, 2, 2), new Rect2(1, 10, 1, 0.1)),
36
+ ).objEq(new Rect2(-1, 0, 4, 10.1));
63
37
 
64
38
  expect(
65
- Rect2.union(new Rect2(-1, 0, 1, 1), new Rect2(1, -11.1, 2, 2), new Rect2(1, 10, 1, 0.1))
66
- ).objEq(
67
- new Rect2(-1, -11.1, 4, 21.2)
68
- );
39
+ Rect2.union(new Rect2(-1, 0, 1, 1), new Rect2(1, -11.1, 2, 2), new Rect2(1, 10, 1, 0.1)),
40
+ ).objEq(new Rect2(-1, -11.1, 4, 21.2));
69
41
  });
70
42
 
71
43
  it('should contain points that are within a rectangle', () => {
@@ -112,17 +84,15 @@ describe('Rect2', () => {
112
84
  });
113
85
 
114
86
  it('should correctly compute the intersection of one rectangle and several others', () => {
115
- const mainRect = new Rect2(334,156,333,179);
87
+ const mainRect = new Rect2(334, 156, 333, 179);
116
88
  const shouldIntersect = [
117
89
  new Rect2(400.8, 134.8, 8.4, 161.4),
118
- new Rect2(324.8,93,164.4,75.2),
119
- new Rect2(435.8,146.8,213.2,192.6),
120
- new Rect2(550.8,211.8,3.4,3.4),
121
- new Rect2(478.8,93.8,212.4,95.4),
122
- ];
123
- const shouldNotIntersect = [
124
- new Rect2(200, 200, 1, 1),
90
+ new Rect2(324.8, 93, 164.4, 75.2),
91
+ new Rect2(435.8, 146.8, 213.2, 192.6),
92
+ new Rect2(550.8, 211.8, 3.4, 3.4),
93
+ new Rect2(478.8, 93.8, 212.4, 95.4),
125
94
  ];
95
+ const shouldNotIntersect = [new Rect2(200, 200, 1, 1)];
126
96
 
127
97
  for (const rect of shouldIntersect) {
128
98
  expect(mainRect.intersects(rect)).toBe(true);
@@ -135,10 +105,10 @@ describe('Rect2', () => {
135
105
  it('intersecting rectangles should have their intersections correctly computed', () => {
136
106
  expect(new Rect2(-1, -1, 2, 2).intersection(Rect2.empty)).objEq(Rect2.empty);
137
107
  expect(new Rect2(-1, -1, 2, 2).intersection(new Rect2(0, 0, 3, 3))).objEq(
138
- new Rect2(0, 0, 1, 1)
108
+ new Rect2(0, 0, 1, 1),
139
109
  );
140
110
  expect(new Rect2(-2, 0, 1, 2).intersection(new Rect2(-3, 0, 2, 2))).objEq(
141
- new Rect2(-2, 0, 1, 2)
111
+ new Rect2(-2, 0, 1, 2),
142
112
  );
143
113
  expect(new Rect2(-1, -1, 2, 2).intersection(new Rect2(3, 3, 10, 10))).toBe(null);
144
114
  });
@@ -192,39 +162,38 @@ describe('Rect2', () => {
192
162
 
193
163
  describe('divideIntoGrid', () => {
194
164
  it('division of unit square', () => {
195
- expect(Rect2.unitSquare.divideIntoGrid(2, 2)).toMatchObject(
196
- [
197
- new Rect2(0, 0, 0.5, 0.5), new Rect2(0.5, 0, 0.5, 0.5),
198
- new Rect2(0, 0.5, 0.5, 0.5), new Rect2(0.5, 0.5, 0.5, 0.5),
199
- ]
200
- );
165
+ expect(Rect2.unitSquare.divideIntoGrid(2, 2)).toMatchObject([
166
+ new Rect2(0, 0, 0.5, 0.5),
167
+ new Rect2(0.5, 0, 0.5, 0.5),
168
+ new Rect2(0, 0.5, 0.5, 0.5),
169
+ new Rect2(0.5, 0.5, 0.5, 0.5),
170
+ ]);
201
171
  expect(Rect2.unitSquare.divideIntoGrid(0, 0).length).toBe(0);
202
172
  expect(Rect2.unitSquare.divideIntoGrid(100, 0).length).toBe(0);
203
- expect(Rect2.unitSquare.divideIntoGrid(4, 1)).toMatchObject(
204
- [
205
- new Rect2(0, 0, 0.25, 1), new Rect2(0.25, 0, 0.25, 1),
206
- new Rect2(0.5, 0, 0.25, 1), new Rect2(0.75, 0, 0.25, 1),
207
- ]
208
- );
173
+ expect(Rect2.unitSquare.divideIntoGrid(4, 1)).toMatchObject([
174
+ new Rect2(0, 0, 0.25, 1),
175
+ new Rect2(0.25, 0, 0.25, 1),
176
+ new Rect2(0.5, 0, 0.25, 1),
177
+ new Rect2(0.75, 0, 0.25, 1),
178
+ ]);
209
179
  });
210
180
  it('division of translated square', () => {
211
- expect(new Rect2(3, -3, 4, 4).divideIntoGrid(2, 1)).toMatchObject(
212
- [
213
- new Rect2(3, -3, 2, 4), new Rect2(5, -3, 2, 4),
214
- ]
215
- );
181
+ expect(new Rect2(3, -3, 4, 4).divideIntoGrid(2, 1)).toMatchObject([
182
+ new Rect2(3, -3, 2, 4),
183
+ new Rect2(5, -3, 2, 4),
184
+ ]);
216
185
  });
217
186
  it('division of empty square', () => {
218
187
  expect(Rect2.empty.divideIntoGrid(1000, 10000).length).toBe(1);
219
188
  });
220
189
 
221
190
  it('division of rectangle', () => {
222
- expect(new Rect2(0, 0, 2, 1).divideIntoGrid(2, 2)).toMatchObject(
223
- [
224
- new Rect2(0, 0, 1, 0.5), new Rect2(1, 0, 1, 0.5),
225
- new Rect2(0, 0.5, 1, 0.5), new Rect2(1, 0.5, 1, 0.5),
226
- ]
227
- );
191
+ expect(new Rect2(0, 0, 2, 1).divideIntoGrid(2, 2)).toMatchObject([
192
+ new Rect2(0, 0, 1, 0.5),
193
+ new Rect2(1, 0, 1, 0.5),
194
+ new Rect2(0, 0.5, 1, 0.5),
195
+ new Rect2(1, 0.5, 1, 0.5),
196
+ ]);
228
197
  });
229
198
  });
230
199
 
@@ -31,7 +31,7 @@ export class Rect2 extends Abstract2DShape {
31
31
  public readonly x: number,
32
32
  public readonly y: number,
33
33
  public readonly w: number,
34
- public readonly h: number
34
+ public readonly h: number,
35
35
  ) {
36
36
  super();
37
37
 
@@ -61,14 +61,21 @@ export class Rect2 extends Abstract2DShape {
61
61
  }
62
62
 
63
63
  public override containsPoint(other: Point2): boolean {
64
- return this.x <= other.x && this.y <= other.y
65
- && this.x + this.w >= other.x && this.y + this.h >= other.y;
64
+ return (
65
+ this.x <= other.x &&
66
+ this.y <= other.y &&
67
+ this.x + this.w >= other.x &&
68
+ this.y + this.h >= other.y
69
+ );
66
70
  }
67
71
 
68
72
  public containsRect(other: Rect2): boolean {
69
- return this.x <= other.x && this.y <= other.y
70
- && this.x + this.w >= other.x + other.w
71
- && this.y + this.h >= other.y + other.h;
73
+ return (
74
+ this.x <= other.x &&
75
+ this.y <= other.y &&
76
+ this.x + this.w >= other.x + other.w &&
77
+ this.y + this.h >= other.y + other.h
78
+ );
72
79
  }
73
80
 
74
81
  /**
@@ -85,7 +92,6 @@ export class Rect2 extends Abstract2DShape {
85
92
  return false;
86
93
  }
87
94
 
88
-
89
95
  const thisMinY = this.y;
90
96
  const thisMaxY = thisMinY + this.h;
91
97
  const otherMinY = other.y;
@@ -100,7 +106,7 @@ export class Rect2 extends Abstract2DShape {
100
106
 
101
107
  // Returns the overlap of this and [other], or null, if no such
102
108
  // overlap exists
103
- public intersection(other: Rect2): Rect2|null {
109
+ public intersection(other: Rect2): Rect2 | null {
104
110
  if (!this.intersects(other)) {
105
111
  return null;
106
112
  }
@@ -151,10 +157,7 @@ export class Rect2 extends Abstract2DShape {
151
157
  // [margin] is the minimum distance between the new point and the edge
152
158
  // of the resultant rectangle.
153
159
  public grownToPoint(point: Point2, margin: number = 0): Rect2 {
154
- const otherRect = new Rect2(
155
- point.x - margin, point.y - margin,
156
- margin * 2, margin * 2
157
- );
160
+ const otherRect = new Rect2(point.x - margin, point.y - margin, margin * 2, margin * 2);
158
161
  return this.union(otherRect);
159
162
  }
160
163
 
@@ -170,23 +173,23 @@ export class Rect2 extends Abstract2DShape {
170
173
  const yMargin = -Math.min(-margin, this.h / 2);
171
174
 
172
175
  return new Rect2(
173
- this.x - xMargin, this.y - yMargin,
174
- this.w + xMargin * 2, this.h + yMargin * 2,
176
+ this.x - xMargin,
177
+ this.y - yMargin,
178
+ this.w + xMargin * 2,
179
+ this.h + yMargin * 2,
175
180
  );
176
181
  }
177
182
 
178
- return new Rect2(
179
- this.x - margin, this.y - margin, this.w + margin * 2, this.h + margin * 2
180
- );
183
+ return new Rect2(this.x - margin, this.y - margin, this.w + margin * 2, this.h + margin * 2);
181
184
  }
182
185
 
183
186
  public getClosestPointOnBoundaryTo(target: Point2) {
184
- const closestEdgePoints = this.getEdges().map(edge => {
187
+ const closestEdgePoints = this.getEdges().map((edge) => {
185
188
  return edge.closestPointTo(target);
186
189
  });
187
190
 
188
- let closest: Point2|null = null;
189
- let closestDist: number|null = null;
191
+ let closest: Point2 | null = null;
192
+ let closestDist: number | null = null;
190
193
  for (const point of closestEdgePoints) {
191
194
  const dist = point.distanceTo(target);
192
195
  if (closestDist === null || dist < closestDist) {
@@ -211,16 +214,11 @@ export class Rect2 extends Abstract2DShape {
211
214
  }
212
215
 
213
216
  const squareRadius = radius * radius;
214
- return this.corners.every(corner => corner.minus(point).magnitudeSquared() < squareRadius);
217
+ return this.corners.every((corner) => corner.minus(point).magnitudeSquared() < squareRadius);
215
218
  }
216
219
 
217
220
  public get corners(): Point2[] {
218
- return [
219
- this.bottomRight,
220
- this.topRight,
221
- this.topLeft,
222
- this.bottomLeft,
223
- ];
221
+ return [this.bottomRight, this.topRight, this.topLeft, this.bottomLeft];
224
222
  }
225
223
 
226
224
  public get maxDimension() {
@@ -272,7 +270,7 @@ export class Rect2 extends Abstract2DShape {
272
270
 
273
271
  for (const edge of this.getEdges()) {
274
272
  const intersection = edge.intersectsLineSegment(lineSegment);
275
- intersection.forEach(point => result.push(point));
273
+ intersection.forEach((point) => result.push(point));
276
274
  }
277
275
 
278
276
  return result;
@@ -295,7 +293,7 @@ export class Rect2 extends Abstract2DShape {
295
293
  // [affineTransform] is a transformation matrix that both scales and **translates**.
296
294
  // the bounding box of this' four corners after transformed by the given affine transformation.
297
295
  public transformedBoundingBox(affineTransform: Mat33): Rect2 {
298
- return Rect2.bboxOf(this.corners.map(corner => affineTransform.transformVec2(corner)));
296
+ return Rect2.bboxOf(this.corners.map((corner) => affineTransform.transformVec2(corner)));
299
297
  }
300
298
 
301
299
  /** @return true iff this is equal to `other ± tolerance` */
@@ -307,13 +305,12 @@ export class Rect2 extends Abstract2DShape {
307
305
  return `Rect(point(${this.x}, ${this.y}), size(${this.w}, ${this.h}))`;
308
306
  }
309
307
 
310
-
311
308
  public static fromCorners(corner1: Point2, corner2: Point2) {
312
309
  return new Rect2(
313
310
  Math.min(corner1.x, corner2.x),
314
311
  Math.min(corner1.y, corner2.y),
315
312
  Math.abs(corner1.x - corner2.x),
316
- Math.abs(corner1.y - corner2.y)
313
+ Math.abs(corner1.y - corner2.y),
317
314
  );
318
315
  }
319
316
 
@@ -344,7 +341,7 @@ export class Rect2 extends Abstract2DShape {
344
341
 
345
342
  return Rect2.fromCorners(
346
343
  Vec2.of(minX - margin, minY - margin),
347
- Vec2.of(maxX + margin, maxY + margin)
344
+ Vec2.of(maxX + margin, maxY + margin),
348
345
  );
349
346
  }
350
347
 
@@ -369,9 +366,7 @@ export class Rect2 extends Abstract2DShape {
369
366
  maxY = Math.max(maxY, rect.y + rect.h);
370
367
  }
371
368
 
372
- return new Rect2(
373
- minX, minY, maxX - minX, maxY - minY,
374
- );
369
+ return new Rect2(minX, minY, maxX - minX, maxY - minY);
375
370
  }
376
371
 
377
372
  public static of(template: RectTemplate) {
@@ -19,43 +19,45 @@ describe('Triangle', () => {
19
19
  }
20
20
  });
21
21
 
22
- it('signed distance function should be the negative distance to the edge '
23
- + 'of the triangle on the interior of a shape, same as distance outside of shape', () => {
24
- const testTriangle = Triangle.fromVertices(Vec2.of(-1, -1), Vec2.of(0, 1), Vec2.of(1, -1));
25
-
26
- // A point vertically above the triangle: Outside, so positive SDF
27
- expect(testTriangle.signedDistance(Vec2.of(0, 2))).toBeCloseTo(1);
22
+ it(
23
+ 'signed distance function should be the negative distance to the edge ' +
24
+ 'of the triangle on the interior of a shape, same as distance outside of shape',
25
+ () => {
26
+ const testTriangle = Triangle.fromVertices(Vec2.of(-1, -1), Vec2.of(0, 1), Vec2.of(1, -1));
28
27
 
29
- // Similarly, a point vertically below the triangle is outside, so should have positive SDF
30
- expect(testTriangle.signedDistance(Vec2.of(0, -2))).toBeCloseTo(1);
28
+ // A point vertically above the triangle: Outside, so positive SDF
29
+ expect(testTriangle.signedDistance(Vec2.of(0, 2))).toBeCloseTo(1);
31
30
 
32
- // A point just above the left side (and outside the triangle) should also have positive SDF
33
- expect(testTriangle.signedDistance(Vec2.of(-0.8, 0.8))).toBeGreaterThan(0);
31
+ // Similarly, a point vertically below the triangle is outside, so should have positive SDF
32
+ expect(testTriangle.signedDistance(Vec2.of(0, -2))).toBeCloseTo(1);
34
33
 
34
+ // A point just above the left side (and outside the triangle) should also have positive SDF
35
+ expect(testTriangle.signedDistance(Vec2.of(-0.8, 0.8))).toBeGreaterThan(0);
35
36
 
36
- const firstSide = testTriangle.getEdges()[0];
37
- const firstSideMidpoint = firstSide.at(0.5);
38
- const firstSideNormal = firstSide.direction.orthog();
37
+ const firstSide = testTriangle.getEdges()[0];
38
+ const firstSideMidpoint = firstSide.at(0.5);
39
+ const firstSideNormal = firstSide.direction.orthog();
39
40
 
40
- // Move a point towards the first side
41
- for (let t = 0.5; t > -0.5; t -= 0.1) {
42
- const point = firstSideMidpoint.minus(firstSideNormal.times(t));
43
- const distFromSide1 = firstSide.distance(point);
44
- const signedDist = testTriangle.signedDistance(point);
41
+ // Move a point towards the first side
42
+ for (let t = 0.5; t > -0.5; t -= 0.1) {
43
+ const point = firstSideMidpoint.minus(firstSideNormal.times(t));
44
+ const distFromSide1 = firstSide.distance(point);
45
+ const signedDist = testTriangle.signedDistance(point);
45
46
 
46
- // Inside the shape
47
- if (t > 0) {
48
47
  // Inside the shape
49
- expect(testTriangle.containsPoint(point)).toBe(true);
48
+ if (t > 0) {
49
+ // Inside the shape
50
+ expect(testTriangle.containsPoint(point)).toBe(true);
50
51
 
51
- expect(signedDist).toBeCloseTo(-distFromSide1);
52
- } else {
53
- // Outside the shape
54
- expect(testTriangle.containsPoint(point)).toBe(false);
52
+ expect(signedDist).toBeCloseTo(-distFromSide1);
53
+ } else {
54
+ // Outside the shape
55
+ expect(testTriangle.containsPoint(point)).toBe(false);
55
56
 
56
- expect(signedDist).toBeCloseTo(distFromSide1);
57
+ expect(signedDist).toBeCloseTo(distFromSide1);
58
+ }
57
59
  }
58
- }
59
- });
60
+ },
61
+ );
60
62
  });
61
- });
63
+ });
@@ -5,7 +5,7 @@ import Abstract2DShape from './Abstract2DShape';
5
5
  import LineSegment2 from './LineSegment2';
6
6
  import Rect2 from './Rect2';
7
7
 
8
- type TriangleBoundary = [ LineSegment2, LineSegment2, LineSegment2 ];
8
+ type TriangleBoundary = [LineSegment2, LineSegment2, LineSegment2];
9
9
 
10
10
  export default class Triangle extends Abstract2DShape {
11
11
  /**
@@ -27,30 +27,26 @@ export default class Triangle extends Abstract2DShape {
27
27
  return new Triangle(vertex1, vertex2, vertex3);
28
28
  }
29
29
 
30
- public get vertices(): [ Point2, Point2, Point2 ] {
31
- return [ this.vertex1, this.vertex2, this.vertex3 ];
30
+ public get vertices(): [Point2, Point2, Point2] {
31
+ return [this.vertex1, this.vertex2, this.vertex3];
32
32
  }
33
33
 
34
- public map(mapping: (vertex: Vec3)=>Vec3): Triangle {
35
- return new Triangle(
36
- mapping(this.vertex1),
37
- mapping(this.vertex2),
38
- mapping(this.vertex3),
39
- );
34
+ public map(mapping: (vertex: Vec3) => Vec3): Triangle {
35
+ return new Triangle(mapping(this.vertex1), mapping(this.vertex2), mapping(this.vertex3));
40
36
  }
41
37
 
42
38
  // Transform, treating this as composed of 2D points.
43
39
  public transformed2DBy(affineTransform: Mat33) {
44
- return this.map(vertex => affineTransform.transformVec2(vertex));
40
+ return this.map((vertex) => affineTransform.transformVec2(vertex));
45
41
  }
46
42
 
47
43
  // Transforms this by a linear transform --- verticies are treated as
48
44
  // 3D points.
49
45
  public transformedBy(linearTransform: Mat33) {
50
- return this.map(vertex => linearTransform.transformVec3(vertex));
46
+ return this.map((vertex) => linearTransform.transformVec3(vertex));
51
47
  }
52
48
 
53
- #sides: TriangleBoundary|undefined = undefined;
49
+ #sides: TriangleBoundary | undefined = undefined;
54
50
 
55
51
  /**
56
52
  * Returns the sides of this triangle, as an array of `LineSegment2`s.
@@ -67,7 +63,7 @@ export default class Triangle extends Abstract2DShape {
67
63
  const side2 = new LineSegment2(this.vertex2, this.vertex3);
68
64
  const side3 = new LineSegment2(this.vertex3, this.vertex1);
69
65
 
70
- const sides: TriangleBoundary = [ side1, side2, side3 ];
66
+ const sides: TriangleBoundary = [side1, side2, side3];
71
67
  this.#sides = sides;
72
68
  return sides;
73
69
  }
@@ -76,15 +72,17 @@ export default class Triangle extends Abstract2DShape {
76
72
  const result: Point2[] = [];
77
73
 
78
74
  for (const edge of this.getEdges()) {
79
- edge.intersectsLineSegment(lineSegment)
80
- .forEach(point => result.push(point));
75
+ edge.intersectsLineSegment(lineSegment).forEach((point) => result.push(point));
81
76
  }
82
77
 
83
78
  return result;
84
79
  }
85
80
 
86
81
  /** @inheritdoc */
87
- public override containsPoint(point: Vec3, epsilon: number = Abstract2DShape.smallValue): boolean {
82
+ public override containsPoint(
83
+ point: Vec3,
84
+ epsilon: number = Abstract2DShape.smallValue,
85
+ ): boolean {
88
86
  // Project `point` onto normals to each of this' sides.
89
87
  // Uses the Separating Axis Theorem (https://en.wikipedia.org/wiki/Hyperplane_separation_theorem#Use_in_collision_detection)
90
88
  const sides = this.getEdges();
@@ -103,7 +101,8 @@ export default class Triangle extends Abstract2DShape {
103
101
 
104
102
  const projPoint = orthog.dot(point);
105
103
 
106
- const inProjection = projPoint >= minProjVertex - epsilon && projPoint <= maxProjVertex + epsilon;
104
+ const inProjection =
105
+ projPoint >= minProjVertex - epsilon && projPoint <= maxProjVertex + epsilon;
107
106
  if (!inProjection) {
108
107
  return false;
109
108
  }
@@ -120,7 +119,7 @@ export default class Triangle extends Abstract2DShape {
120
119
  */
121
120
  public override signedDistance(point: Vec3): number {
122
121
  const sides = this.getEdges();
123
- const distances = sides.map(side => side.distance(point));
122
+ const distances = sides.map((side) => side.distance(point));
124
123
  const distance = Math.min(...distances);
125
124
 
126
125
  // If the point is in this' interior, signedDistance must return a negative