@js-draw/math 1.21.2 → 1.22.0

Sign up to get free protection for your applications and to get access to all the features.
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