@js-draw/math 1.21.3 → 1.23.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. package/build-config.json +1 -1
  2. package/dist/cjs/Color4.d.ts +24 -1
  3. package/dist/cjs/Color4.js +35 -3
  4. package/dist/cjs/Mat33.d.ts +21 -11
  5. package/dist/cjs/Mat33.js +28 -24
  6. package/dist/cjs/Vec3.d.ts +12 -3
  7. package/dist/cjs/Vec3.js +20 -9
  8. package/dist/cjs/lib.d.ts +3 -0
  9. package/dist/cjs/lib.js +3 -0
  10. package/dist/cjs/shapes/BezierJSWrapper.d.ts +2 -0
  11. package/dist/cjs/shapes/BezierJSWrapper.js +22 -13
  12. package/dist/cjs/shapes/LineSegment2.js +13 -17
  13. package/dist/cjs/shapes/Parameterized2DShape.js +1 -1
  14. package/dist/cjs/shapes/Path.d.ts +1 -0
  15. package/dist/cjs/shapes/Path.js +50 -47
  16. package/dist/cjs/shapes/QuadraticBezier.d.ts +19 -2
  17. package/dist/cjs/shapes/QuadraticBezier.js +26 -3
  18. package/dist/cjs/shapes/Rect2.d.ts +13 -0
  19. package/dist/cjs/shapes/Rect2.js +35 -16
  20. package/dist/cjs/shapes/Triangle.js +4 -5
  21. package/dist/cjs/utils/convexHull2Of.js +3 -3
  22. package/dist/mjs/Color4.d.ts +24 -1
  23. package/dist/mjs/Color4.mjs +35 -3
  24. package/dist/mjs/Mat33.d.ts +21 -11
  25. package/dist/mjs/Mat33.mjs +28 -24
  26. package/dist/mjs/Vec3.d.ts +12 -3
  27. package/dist/mjs/Vec3.mjs +20 -9
  28. package/dist/mjs/lib.d.ts +3 -0
  29. package/dist/mjs/lib.mjs +3 -0
  30. package/dist/mjs/shapes/BezierJSWrapper.d.ts +2 -0
  31. package/dist/mjs/shapes/BezierJSWrapper.mjs +22 -13
  32. package/dist/mjs/shapes/LineSegment2.mjs +13 -17
  33. package/dist/mjs/shapes/Parameterized2DShape.mjs +1 -1
  34. package/dist/mjs/shapes/Path.d.ts +1 -0
  35. package/dist/mjs/shapes/Path.mjs +50 -47
  36. package/dist/mjs/shapes/QuadraticBezier.d.ts +19 -2
  37. package/dist/mjs/shapes/QuadraticBezier.mjs +26 -3
  38. package/dist/mjs/shapes/Rect2.d.ts +13 -0
  39. package/dist/mjs/shapes/Rect2.mjs +35 -16
  40. package/dist/mjs/shapes/Triangle.mjs +4 -5
  41. package/dist/mjs/utils/convexHull2Of.mjs +3 -3
  42. package/dist-test/test_imports/test-require.cjs +1 -1
  43. package/package.json +3 -3
  44. package/src/Color4.test.ts +21 -21
  45. package/src/Color4.ts +61 -18
  46. package/src/Mat33.fromCSSMatrix.test.ts +32 -46
  47. package/src/Mat33.test.ts +64 -102
  48. package/src/Mat33.ts +81 -104
  49. package/src/Vec2.test.ts +3 -3
  50. package/src/Vec3.test.ts +2 -3
  51. package/src/Vec3.ts +46 -61
  52. package/src/lib.ts +3 -2
  53. package/src/polynomial/solveQuadratic.test.ts +39 -13
  54. package/src/polynomial/solveQuadratic.ts +5 -6
  55. package/src/rounding/cleanUpNumber.test.ts +1 -1
  56. package/src/rounding/constants.ts +1 -3
  57. package/src/rounding/getLenAfterDecimal.ts +1 -2
  58. package/src/rounding/lib.ts +1 -2
  59. package/src/rounding/toRoundedString.test.ts +1 -1
  60. package/src/rounding/toStringOfSamePrecision.test.ts +1 -2
  61. package/src/rounding/toStringOfSamePrecision.ts +1 -1
  62. package/src/shapes/BezierJSWrapper.ts +56 -37
  63. package/src/shapes/CubicBezier.ts +3 -3
  64. package/src/shapes/LineSegment2.test.ts +24 -17
  65. package/src/shapes/LineSegment2.ts +26 -29
  66. package/src/shapes/Parameterized2DShape.ts +5 -4
  67. package/src/shapes/Path.fromString.test.ts +5 -5
  68. package/src/shapes/Path.test.ts +122 -120
  69. package/src/shapes/Path.toString.test.ts +7 -7
  70. package/src/shapes/Path.ts +379 -352
  71. package/src/shapes/PointShape2D.ts +3 -3
  72. package/src/shapes/QuadraticBezier.test.ts +27 -21
  73. package/src/shapes/QuadraticBezier.ts +26 -11
  74. package/src/shapes/Rect2.test.ts +44 -75
  75. package/src/shapes/Rect2.ts +47 -35
  76. package/src/shapes/Triangle.test.ts +31 -29
  77. package/src/shapes/Triangle.ts +17 -18
  78. package/src/utils/convexHull2Of.test.ts +54 -15
  79. package/src/utils/convexHull2Of.ts +9 -7
  80. package/tsconfig.json +1 -3
  81. 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
+ });
@@ -4,15 +4,35 @@ import BezierJSWrapper from './BezierJSWrapper';
4
4
  import Rect2 from './Rect2';
5
5
 
6
6
  /**
7
- * Represents a 2D Bézier curve.
7
+ * Represents a 2D [Bézier curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve).
8
8
  *
9
- * **Note**: Many Bézier operations use `bezier-js`'s.
9
+ * Example:
10
+ * ```ts,runnable,console
11
+ * import { QuadraticBezier, Vec2 } from '@js-draw/math';
12
+ *
13
+ * const startPoint = Vec2.of(4, 3);
14
+ * const controlPoint = Vec2.of(1, 1);
15
+ * const endPoint = Vec2.of(1, 3);
16
+ *
17
+ * const curve = new QuadraticBezier(
18
+ * startPoint,
19
+ * controlPoint,
20
+ * endPoint,
21
+ * );
22
+ *
23
+ * console.log('Curve:', curve);
24
+ * ```
25
+ *
26
+ * **Note**: Some Bézier operations internally use the `bezier-js` library.
10
27
  */
11
28
  export class QuadraticBezier extends BezierJSWrapper {
12
29
  public constructor(
30
+ // Start point
13
31
  public readonly p0: Point2,
32
+ // Control point
14
33
  public readonly p1: Point2,
15
- public readonly p2: Point2
34
+ // End point
35
+ public readonly p2: Point2,
16
36
  ) {
17
37
  super();
18
38
  }
@@ -78,7 +98,7 @@ export class QuadraticBezier extends BezierJSWrapper {
78
98
 
79
99
  /** @returns an overestimate of this shape's bounding box. */
80
100
  public override getLooseBoundingBox(): Rect2 {
81
- return Rect2.bboxOf([ this.p0, this.p1, this.p2 ]);
101
+ return Rect2.bboxOf([this.p0, this.p1, this.p2]);
82
102
  }
83
103
 
84
104
  /**
@@ -124,14 +144,9 @@ export class QuadraticBezier extends BezierJSWrapper {
124
144
  const f2ndDerivAtZero = b;
125
145
  const f3rdDerivAtZero = 2 * c;
126
146
 
127
-
128
147
  // Using the first few terms of a Maclaurin series to approximate f'(t),
129
148
  // f'(t) ≈ f'(0) + t f''(0) + t² f'''(0) / 2
130
- let [ min1, min2 ] = solveQuadratic(
131
- f3rdDerivAtZero / 2,
132
- f2ndDerivAtZero,
133
- fDerivAtZero,
134
- );
149
+ let [min1, min2] = solveQuadratic(f3rdDerivAtZero / 2, f2ndDerivAtZero, fDerivAtZero);
135
150
 
136
151
  // If the quadratic has no solutions, approximate.
137
152
  if (isNaN(min1)) {
@@ -153,7 +168,7 @@ export class QuadraticBezier extends BezierJSWrapper {
153
168
  }
154
169
 
155
170
  public override getPoints() {
156
- return [ this.p0, this.p1, this.p2 ];
171
+ return [this.p0, this.p1, this.p2];
157
172
  }
158
173
  }
159
174
  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
 
@@ -17,6 +17,18 @@ export interface RectTemplate {
17
17
  /**
18
18
  * Represents a rectangle in 2D space, parallel to the XY axes.
19
19
  *
20
+ * **Example**:
21
+ * ```ts,runnable,console
22
+ * import { Rect2, Vec2 } from '@js-draw/math';
23
+ *
24
+ * const rect = Rect2.fromCorners(
25
+ * Vec2.of(0, 0),
26
+ * Vec2.of(10, 10),
27
+ * );
28
+ * console.log('area', rect.area);
29
+ * console.log('topLeft', rect.topLeft);
30
+ * ```
31
+ *
20
32
  * `invariant: w ≥ 0, h ≥ 0, immutable`
21
33
  */
22
34
  export class Rect2 extends Abstract2DShape {
@@ -28,10 +40,14 @@ export class Rect2 extends Abstract2DShape {
28
40
  public readonly area: number;
29
41
 
30
42
  public constructor(
43
+ // Top left x coordinate
31
44
  public readonly x: number,
45
+ // Top left y coordinate
32
46
  public readonly y: number,
47
+ // Width
33
48
  public readonly w: number,
34
- public readonly h: number
49
+ // Height
50
+ public readonly h: number,
35
51
  ) {
36
52
  super();
37
53
 
@@ -61,14 +77,22 @@ export class Rect2 extends Abstract2DShape {
61
77
  }
62
78
 
63
79
  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;
80
+ return (
81
+ this.x <= other.x &&
82
+ this.y <= other.y &&
83
+ this.x + this.w >= other.x &&
84
+ this.y + this.h >= other.y
85
+ );
66
86
  }
67
87
 
88
+ /** @returns true iff `other` is completely within this `Rect2`. */
68
89
  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;
90
+ return (
91
+ this.x <= other.x &&
92
+ this.y <= other.y &&
93
+ this.x + this.w >= other.x + other.w &&
94
+ this.y + this.h >= other.y + other.h
95
+ );
72
96
  }
73
97
 
74
98
  /**
@@ -85,7 +109,6 @@ export class Rect2 extends Abstract2DShape {
85
109
  return false;
86
110
  }
87
111
 
88
-
89
112
  const thisMinY = this.y;
90
113
  const thisMaxY = thisMinY + this.h;
91
114
  const otherMinY = other.y;
@@ -100,7 +123,7 @@ export class Rect2 extends Abstract2DShape {
100
123
 
101
124
  // Returns the overlap of this and [other], or null, if no such
102
125
  // overlap exists
103
- public intersection(other: Rect2): Rect2|null {
126
+ public intersection(other: Rect2): Rect2 | null {
104
127
  if (!this.intersects(other)) {
105
128
  return null;
106
129
  }
@@ -151,10 +174,7 @@ export class Rect2 extends Abstract2DShape {
151
174
  // [margin] is the minimum distance between the new point and the edge
152
175
  // of the resultant rectangle.
153
176
  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
- );
177
+ const otherRect = new Rect2(point.x - margin, point.y - margin, margin * 2, margin * 2);
158
178
  return this.union(otherRect);
159
179
  }
160
180
 
@@ -170,23 +190,23 @@ export class Rect2 extends Abstract2DShape {
170
190
  const yMargin = -Math.min(-margin, this.h / 2);
171
191
 
172
192
  return new Rect2(
173
- this.x - xMargin, this.y - yMargin,
174
- this.w + xMargin * 2, this.h + yMargin * 2,
193
+ this.x - xMargin,
194
+ this.y - yMargin,
195
+ this.w + xMargin * 2,
196
+ this.h + yMargin * 2,
175
197
  );
176
198
  }
177
199
 
178
- return new Rect2(
179
- this.x - margin, this.y - margin, this.w + margin * 2, this.h + margin * 2
180
- );
200
+ return new Rect2(this.x - margin, this.y - margin, this.w + margin * 2, this.h + margin * 2);
181
201
  }
182
202
 
183
203
  public getClosestPointOnBoundaryTo(target: Point2) {
184
- const closestEdgePoints = this.getEdges().map(edge => {
204
+ const closestEdgePoints = this.getEdges().map((edge) => {
185
205
  return edge.closestPointTo(target);
186
206
  });
187
207
 
188
- let closest: Point2|null = null;
189
- let closestDist: number|null = null;
208
+ let closest: Point2 | null = null;
209
+ let closestDist: number | null = null;
190
210
  for (const point of closestEdgePoints) {
191
211
  const dist = point.distanceTo(target);
192
212
  if (closestDist === null || dist < closestDist) {
@@ -211,16 +231,11 @@ export class Rect2 extends Abstract2DShape {
211
231
  }
212
232
 
213
233
  const squareRadius = radius * radius;
214
- return this.corners.every(corner => corner.minus(point).magnitudeSquared() < squareRadius);
234
+ return this.corners.every((corner) => corner.minus(point).magnitudeSquared() < squareRadius);
215
235
  }
216
236
 
217
237
  public get corners(): Point2[] {
218
- return [
219
- this.bottomRight,
220
- this.topRight,
221
- this.topLeft,
222
- this.bottomLeft,
223
- ];
238
+ return [this.bottomRight, this.topRight, this.topLeft, this.bottomLeft];
224
239
  }
225
240
 
226
241
  public get maxDimension() {
@@ -272,7 +287,7 @@ export class Rect2 extends Abstract2DShape {
272
287
 
273
288
  for (const edge of this.getEdges()) {
274
289
  const intersection = edge.intersectsLineSegment(lineSegment);
275
- intersection.forEach(point => result.push(point));
290
+ intersection.forEach((point) => result.push(point));
276
291
  }
277
292
 
278
293
  return result;
@@ -295,7 +310,7 @@ export class Rect2 extends Abstract2DShape {
295
310
  // [affineTransform] is a transformation matrix that both scales and **translates**.
296
311
  // the bounding box of this' four corners after transformed by the given affine transformation.
297
312
  public transformedBoundingBox(affineTransform: Mat33): Rect2 {
298
- return Rect2.bboxOf(this.corners.map(corner => affineTransform.transformVec2(corner)));
313
+ return Rect2.bboxOf(this.corners.map((corner) => affineTransform.transformVec2(corner)));
299
314
  }
300
315
 
301
316
  /** @return true iff this is equal to `other ± tolerance` */
@@ -307,13 +322,12 @@ export class Rect2 extends Abstract2DShape {
307
322
  return `Rect(point(${this.x}, ${this.y}), size(${this.w}, ${this.h}))`;
308
323
  }
309
324
 
310
-
311
325
  public static fromCorners(corner1: Point2, corner2: Point2) {
312
326
  return new Rect2(
313
327
  Math.min(corner1.x, corner2.x),
314
328
  Math.min(corner1.y, corner2.y),
315
329
  Math.abs(corner1.x - corner2.x),
316
- Math.abs(corner1.y - corner2.y)
330
+ Math.abs(corner1.y - corner2.y),
317
331
  );
318
332
  }
319
333
 
@@ -344,7 +358,7 @@ export class Rect2 extends Abstract2DShape {
344
358
 
345
359
  return Rect2.fromCorners(
346
360
  Vec2.of(minX - margin, minY - margin),
347
- Vec2.of(maxX + margin, maxY + margin)
361
+ Vec2.of(maxX + margin, maxY + margin),
348
362
  );
349
363
  }
350
364
 
@@ -369,9 +383,7 @@ export class Rect2 extends Abstract2DShape {
369
383
  maxY = Math.max(maxY, rect.y + rect.h);
370
384
  }
371
385
 
372
- return new Rect2(
373
- minX, minY, maxX - minX, maxY - minY,
374
- );
386
+ return new Rect2(minX, minY, maxX - minX, maxY - minY);
375
387
  }
376
388
 
377
389
  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
+ });