@js-draw/math 1.0.0 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cjs/Color4.test.d.ts +1 -0
  3. package/dist/cjs/Mat33.test.d.ts +1 -0
  4. package/dist/cjs/Vec2.test.d.ts +1 -0
  5. package/dist/cjs/Vec3.test.d.ts +1 -0
  6. package/dist/cjs/polynomial/solveQuadratic.test.d.ts +1 -0
  7. package/dist/cjs/rounding.test.d.ts +1 -0
  8. package/dist/cjs/shapes/LineSegment2.test.d.ts +1 -0
  9. package/dist/cjs/shapes/Path.fromString.test.d.ts +1 -0
  10. package/dist/cjs/shapes/Path.test.d.ts +1 -0
  11. package/dist/cjs/shapes/Path.toString.test.d.ts +1 -0
  12. package/dist/cjs/shapes/QuadraticBezier.test.d.ts +1 -0
  13. package/dist/cjs/shapes/Rect2.test.d.ts +1 -0
  14. package/dist/cjs/shapes/Triangle.test.d.ts +1 -0
  15. package/dist/mjs/Color4.test.d.ts +1 -0
  16. package/dist/mjs/Mat33.test.d.ts +1 -0
  17. package/dist/mjs/Vec2.test.d.ts +1 -0
  18. package/dist/mjs/Vec3.test.d.ts +1 -0
  19. package/dist/mjs/polynomial/solveQuadratic.test.d.ts +1 -0
  20. package/dist/mjs/rounding.test.d.ts +1 -0
  21. package/dist/mjs/shapes/LineSegment2.test.d.ts +1 -0
  22. package/dist/mjs/shapes/Path.fromString.test.d.ts +1 -0
  23. package/dist/mjs/shapes/Path.test.d.ts +1 -0
  24. package/dist/mjs/shapes/Path.toString.test.d.ts +1 -0
  25. package/dist/mjs/shapes/QuadraticBezier.test.d.ts +1 -0
  26. package/dist/mjs/shapes/Rect2.test.d.ts +1 -0
  27. package/dist/mjs/shapes/Triangle.test.d.ts +1 -0
  28. package/dist-test/test_imports/package-lock.json +13 -0
  29. package/dist-test/test_imports/package.json +12 -0
  30. package/dist-test/test_imports/test-imports.js +15 -0
  31. package/dist-test/test_imports/test-require.cjs +15 -0
  32. package/package.json +4 -3
  33. package/src/Color4.test.ts +52 -0
  34. package/src/Color4.ts +318 -0
  35. package/src/Mat33.test.ts +244 -0
  36. package/src/Mat33.ts +450 -0
  37. package/src/Vec2.test.ts +30 -0
  38. package/src/Vec2.ts +49 -0
  39. package/src/Vec3.test.ts +51 -0
  40. package/src/Vec3.ts +245 -0
  41. package/src/lib.ts +42 -0
  42. package/src/polynomial/solveQuadratic.test.ts +39 -0
  43. package/src/polynomial/solveQuadratic.ts +43 -0
  44. package/src/rounding.test.ts +65 -0
  45. package/src/rounding.ts +167 -0
  46. package/src/shapes/Abstract2DShape.ts +63 -0
  47. package/src/shapes/BezierJSWrapper.ts +93 -0
  48. package/src/shapes/CubicBezier.ts +35 -0
  49. package/src/shapes/LineSegment2.test.ts +99 -0
  50. package/src/shapes/LineSegment2.ts +232 -0
  51. package/src/shapes/Path.fromString.test.ts +223 -0
  52. package/src/shapes/Path.test.ts +309 -0
  53. package/src/shapes/Path.toString.test.ts +77 -0
  54. package/src/shapes/Path.ts +963 -0
  55. package/src/shapes/PointShape2D.ts +33 -0
  56. package/src/shapes/QuadraticBezier.test.ts +31 -0
  57. package/src/shapes/QuadraticBezier.ts +142 -0
  58. package/src/shapes/Rect2.test.ts +209 -0
  59. package/src/shapes/Rect2.ts +346 -0
  60. package/src/shapes/Triangle.test.ts +61 -0
  61. package/src/shapes/Triangle.ts +139 -0
@@ -0,0 +1,33 @@
1
+ import { Point2 } from '../Vec2';
2
+ import Vec3 from '../Vec3';
3
+ import Abstract2DShape from './Abstract2DShape';
4
+ import LineSegment2 from './LineSegment2';
5
+ import Rect2 from './Rect2';
6
+
7
+ /**
8
+ * Like a {@link Point2}, but with additional functionality (e.g. SDF).
9
+ *
10
+ * Access the internal `Point2` using the `p` property.
11
+ */
12
+ class PointShape2D extends Abstract2DShape {
13
+ public constructor(public readonly p: Point2) {
14
+ super();
15
+ }
16
+
17
+ public override signedDistance(point: Vec3): number {
18
+ return this.p.minus(point).magnitude();
19
+ }
20
+
21
+ public override intersectsLineSegment(lineSegment: LineSegment2, epsilon?: number): Vec3[] {
22
+ if (lineSegment.containsPoint(this.p, epsilon)) {
23
+ return [ this.p ];
24
+ }
25
+ return [ ];
26
+ }
27
+
28
+ public override getTightBoundingBox(): Rect2 {
29
+ return new Rect2(this.p.x, this.p.y, 0, 0);
30
+ }
31
+ }
32
+
33
+ export default PointShape2D;
@@ -0,0 +1,31 @@
1
+ import { Vec2 } from '../Vec2';
2
+ import QuadraticBezier from './QuadraticBezier';
3
+
4
+ describe('QuadraticBezier', () => {
5
+ it('approxmiateDistance should approximately return the distance to the curve', () => {
6
+ const curves = [
7
+ new QuadraticBezier(Vec2.zero, Vec2.of(10, 0), Vec2.of(20, 0)),
8
+ new QuadraticBezier(Vec2.of(-10, 0), Vec2.of(2, 10), Vec2.of(20, 0)),
9
+ new QuadraticBezier(Vec2.of(0, 0), Vec2.of(4, -10), Vec2.of(20, 60)),
10
+ new QuadraticBezier(Vec2.of(0, 0), Vec2.of(4, -10), Vec2.of(-20, 60)),
11
+ ];
12
+ const testPoints = [
13
+ Vec2.of(1, 1),
14
+ Vec2.of(-1, 1),
15
+ Vec2.of(100, 0),
16
+ Vec2.of(20, 3),
17
+ Vec2.of(4, -30),
18
+ Vec2.of(5, 0),
19
+ ];
20
+
21
+ for (const curve of curves) {
22
+ for (const point of testPoints) {
23
+ const actualDist = curve.distance(point);
24
+ const approxDist = curve.approximateDistance(point);
25
+
26
+ expect(approxDist).toBeGreaterThan(actualDist * 0.6 - 0.25);
27
+ expect(approxDist).toBeLessThan(actualDist * 1.5 + 2.6);
28
+ }
29
+ }
30
+ });
31
+ });
@@ -0,0 +1,142 @@
1
+ import { Point2, Vec2 } from '../Vec2';
2
+ import solveQuadratic from '../polynomial/solveQuadratic';
3
+ import BezierJSWrapper from './BezierJSWrapper';
4
+ import Rect2 from './Rect2';
5
+
6
+ /**
7
+ * A wrapper around `bezier-js`'s quadratic Bézier.
8
+ *
9
+ * This wrappper lazy-loads `bezier-js`'s Bézier and can perform some operations
10
+ * without loading it at all (e.g. `normal`, `at`, and `approximateDistance`).
11
+ */
12
+ export class QuadraticBezier extends BezierJSWrapper {
13
+ public constructor(
14
+ public readonly p0: Point2,
15
+ public readonly p1: Point2,
16
+ public readonly p2: Point2
17
+ ) {
18
+ super();
19
+ }
20
+
21
+ /**
22
+ * Returns a component of a quadratic Bézier curve at t, where p0,p1,p2 are either all x or
23
+ * all y components of the target curve.
24
+ */
25
+ private static componentAt(t: number, p0: number, p1: number, p2: number) {
26
+ return p0 + t * (-2 * p0 + 2 * p1) + t * t * (p0 - 2 * p1 + p2);
27
+ }
28
+
29
+ private static derivativeComponentAt(t: number, p0: number, p1: number, p2: number) {
30
+ return -2 * p0 + 2 * p1 + 2 * t * (p0 - 2 * p1 + p2);
31
+ }
32
+
33
+ /**
34
+ * @returns the curve evaluated at `t`.
35
+ */
36
+ public override at(t: number): Point2 {
37
+ const p0 = this.p0;
38
+ const p1 = this.p1;
39
+ const p2 = this.p2;
40
+ return Vec2.of(
41
+ QuadraticBezier.componentAt(t, p0.x, p1.x, p2.x),
42
+ QuadraticBezier.componentAt(t, p0.y, p1.y, p2.y),
43
+ );
44
+ }
45
+
46
+ public override derivativeAt(t: number): Point2 {
47
+ const p0 = this.p0;
48
+ const p1 = this.p1;
49
+ const p2 = this.p2;
50
+ return Vec2.of(
51
+ QuadraticBezier.derivativeComponentAt(t, p0.x, p1.x, p2.x),
52
+ QuadraticBezier.derivativeComponentAt(t, p0.y, p1.y, p2.y),
53
+ );
54
+ }
55
+
56
+ public override normal(t: number): Vec2 {
57
+ const tangent = this.derivativeAt(t);
58
+ return tangent.orthog().normalized();
59
+ }
60
+
61
+ /** @returns an overestimate of this shape's bounding box. */
62
+ public override getLooseBoundingBox(): Rect2 {
63
+ return Rect2.bboxOf([ this.p0, this.p1, this.p2 ]);
64
+ }
65
+
66
+ /**
67
+ * @returns the *approximate* distance from `point` to this curve.
68
+ */
69
+ public approximateDistance(point: Point2): number {
70
+ // We want to minimize f(t) = |B(t) - p|².
71
+ // Expanding,
72
+ // f(t) = (Bₓ(t) - pₓ)² + (Bᵧ(t) - pᵧ)²
73
+ // ⇒ f'(t) = Dₜ(Bₓ(t) - pₓ)² + Dₜ(Bᵧ(t) - pᵧ)²
74
+ //
75
+ // Considering just one component,
76
+ // Dₜ(Bₓ(t) - pₓ)² = 2(Bₓ(t) - pₓ)(DₜBₓ(t))
77
+ // = 2(Bₓ(t)DₜBₓ(t) - pₓBₓ(t))
78
+ // = 2(p0ₓ + (t)(-2p0ₓ + 2p1ₓ) + (t²)(p0ₓ - 2p1ₓ + p2ₓ) - pₓ)((-2p0ₓ + 2p1ₓ) + 2(t)(p0ₓ - 2p1ₓ + p2ₓ))
79
+ // - (pₓ)((-2p0ₓ + 2p1ₓ) + (t)(p0ₓ - 2p1ₓ + p2ₓ))
80
+ const A = this.p0.x - point.x;
81
+ const B = -2 * this.p0.x + 2 * this.p1.x;
82
+ const C = this.p0.x - 2 * this.p1.x + this.p2.x;
83
+ // Let A = p0ₓ - pₓ, B = -2p0ₓ + 2p1ₓ, C = p0ₓ - 2p1ₓ + p2ₓ. We then have,
84
+ // Dₜ(Bₓ(t) - pₓ)²
85
+ // = 2(A + tB + t²C)(B + 2tC) - (pₓ)(B + 2tC)
86
+ // = 2(AB + tB² + t²BC + 2tCA + 2tCtB + 2tCt²C) - pₓB - pₓ2tC
87
+ // = 2(AB + tB² + 2tCA + t²BC + 2t²CB + 2C²t³) - pₓB - pₓ2tC
88
+ // = 2AB + 2t(B² + 2CA) + 2t²(BC + 2CB) + 4C²t³ - pₓB - pₓ2tC
89
+ // = 2AB + 2t(B² + 2CA - pₓC) + 2t²(BC + 2CB) + 4C²t³ - pₓB
90
+ //
91
+
92
+ const D = this.p0.y - point.y;
93
+ const E = -2 * this.p0.y + 2 * this.p1.y;
94
+ const F = this.p0.y - 2 * this.p1.y + this.p2.y;
95
+ // Using D = p0ᵧ - pᵧ, E = -2p0ᵧ + 2p1ᵧ, F = p0ᵧ - 2p1ᵧ + p2ᵧ, we thus have,
96
+ // f'(t) = 2AB + 2t(B² + 2CA - pₓC) + 2t²(BC + 2CB) + 4C²t³ - pₓB
97
+ // + 2DE + 2t(E² + 2FD - pᵧF) + 2t²(EF + 2FE) + 4F²t³ - pᵧE
98
+ const a = 2 * A * B + 2 * D * E - point.x * B - point.y * E;
99
+ const b = 2 * B * B + 2 * E * E + 2 * C * A + 2 * F * D - point.x * C - point.y * F;
100
+ const c = 2 * E * F + 2 * B * C + 2 * C * B + 2 * F * E;
101
+ //const d = 4 * C * C + 4 * F * F;
102
+
103
+ // Thus,
104
+ // f'(t) = a + bt + ct² + dt³
105
+ const fDerivAtZero = a;
106
+ const f2ndDerivAtZero = b;
107
+ const f3rdDerivAtZero = 2 * c;
108
+
109
+
110
+ // Using the first few terms of a Maclaurin series to approximate f'(t),
111
+ // f'(t) ≈ f'(0) + t f''(0) + t² f'''(0) / 2
112
+ let [ min1, min2 ] = solveQuadratic(
113
+ f3rdDerivAtZero / 2,
114
+ f2ndDerivAtZero,
115
+ fDerivAtZero,
116
+ );
117
+
118
+ // If the quadratic has no solutions, approximate.
119
+ if (isNaN(min1)) {
120
+ min1 = 0.25;
121
+ }
122
+
123
+ if (isNaN(min2)) {
124
+ min2 = 0.75;
125
+ }
126
+
127
+ const at1 = this.at(min1);
128
+ const at2 = this.at(min2);
129
+ const sqrDist1 = at1.minus(point).magnitudeSquared();
130
+ const sqrDist2 = at2.minus(point).magnitudeSquared();
131
+ const sqrDist3 = this.at(0).minus(point).magnitudeSquared();
132
+ const sqrDist4 = this.at(1).minus(point).magnitudeSquared();
133
+
134
+
135
+ return Math.sqrt(Math.min(sqrDist1, sqrDist2, sqrDist3, sqrDist4));
136
+ }
137
+
138
+ public override getPoints() {
139
+ return [ this.p0, this.p1, this.p2 ];
140
+ }
141
+ }
142
+ export default QuadraticBezier;
@@ -0,0 +1,209 @@
1
+
2
+ import Rect2 from './Rect2';
3
+ import { Vec2 } from '../Vec2';
4
+ import Mat33 from '../Mat33';
5
+
6
+ describe('Rect2', () => {
7
+ it('width, height should always be positive', () => {
8
+ expect(new Rect2(-1, -2, -3, 4)).objEq(new Rect2(-4, -2, 3, 4));
9
+ 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
+ ));
17
+ });
18
+
19
+ 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
+ ));
40
+ });
41
+
42
+ 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
+ );
46
+ expect(Rect2.empty.union(Rect2.empty)).objEq(Rect2.empty);
47
+ });
48
+
49
+ it('should handle empty unions', () => {
50
+ expect(Rect2.union()).toStrictEqual(Rect2.empty);
51
+ });
52
+
53
+ 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
+ );
57
+
58
+ 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
+ );
63
+
64
+ 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
+ );
69
+ });
70
+
71
+ it('should contain points that are within a rectangle', () => {
72
+ expect(new Rect2(-1, -1, 2, 2).containsPoint(Vec2.zero)).toBe(true);
73
+ expect(new Rect2(-1, -1, 0, 0).containsPoint(Vec2.zero)).toBe(false);
74
+ expect(new Rect2(1, 2, 3, 4).containsRect(Rect2.empty)).toBe(false);
75
+ expect(new Rect2(1, 2, 3, 4).containsRect(new Rect2(1, 2, 1, 2))).toBe(true);
76
+ expect(new Rect2(-2, -2, 4, 4).containsRect(new Rect2(-1, 0, 1, 1))).toBe(true);
77
+ expect(new Rect2(-2, -2, 4, 4).containsRect(new Rect2(-1, 0, 10, 1))).toBe(false);
78
+ });
79
+
80
+ it('.center should be the center of a rectangle', () => {
81
+ expect(new Rect2(-1, -1, 2, 3).center).objEq(Vec2.of(0, 0.5));
82
+ expect(new Rect2(-1, -1, 2, 2).center).objEq(Vec2.zero);
83
+ });
84
+
85
+ describe('containsRect', () => {
86
+ it('a rectangle should contain itself', () => {
87
+ const rect = new Rect2(1 / 3, 1 / 4, 1 / 5, 1 / 6);
88
+ expect(rect.containsRect(rect)).toBe(true);
89
+ });
90
+
91
+ it('empty rect should not contain a larger rect', () => {
92
+ expect(Rect2.empty.containsRect(new Rect2(-1, -1, 3, 3))).toBe(false);
93
+ });
94
+
95
+ it('should correctly contain rectangles', () => {
96
+ const testRect = new Rect2(4, -10, 50, 100);
97
+ expect(testRect.containsRect(new Rect2(4.1, 0, 1, 1))).toBe(true);
98
+ expect(testRect.containsRect(new Rect2(48, 0, 1, 1))).toBe(true);
99
+ expect(testRect.containsRect(new Rect2(48, -9, 1, 1))).toBe(true);
100
+ expect(testRect.containsRect(new Rect2(48, -9, 1, 91))).toBe(true);
101
+ });
102
+ });
103
+
104
+ it('intersecting rectangles should be identified as intersecting', () => {
105
+ expect(new Rect2(-1, -1, 2, 2).intersects(Rect2.empty)).toBe(true);
106
+ expect(new Rect2(-1, -1, 2, 2).intersects(new Rect2(0, 0, 1, 1))).toBe(true);
107
+ expect(new Rect2(-1, -1, 2, 2).intersects(new Rect2(0, 0, 10, 10))).toBe(true);
108
+ expect(new Rect2(-1, -1, 2, 2).intersects(new Rect2(3, 3, 10, 10))).toBe(false);
109
+ expect(new Rect2(-1, -1, 2, 2).intersects(new Rect2(0.2, 0.1, 0, 0))).toBe(true);
110
+ expect(new Rect2(-100, -1, 200, 2).intersects(new Rect2(-5, -5, 10, 30))).toBe(true);
111
+ expect(new Rect2(-100, -1, 200, 2).intersects(new Rect2(-5, 50, 10, 30))).toBe(false);
112
+ });
113
+
114
+ it('intersecting rectangles should have their intersections correctly computed', () => {
115
+ expect(new Rect2(-1, -1, 2, 2).intersection(Rect2.empty)).objEq(Rect2.empty);
116
+ expect(new Rect2(-1, -1, 2, 2).intersection(new Rect2(0, 0, 3, 3))).objEq(
117
+ new Rect2(0, 0, 1, 1)
118
+ );
119
+ expect(new Rect2(-2, 0, 1, 2).intersection(new Rect2(-3, 0, 2, 2))).objEq(
120
+ new Rect2(-2, 0, 1, 2)
121
+ );
122
+ expect(new Rect2(-1, -1, 2, 2).intersection(new Rect2(3, 3, 10, 10))).toBe(null);
123
+ });
124
+
125
+ it('A transformed bounding box', () => {
126
+ const rotationMat = Mat33.zRotation(Math.PI / 4);
127
+ const rect = Rect2.unitSquare.translatedBy(Vec2.of(-0.5, -0.5));
128
+ const transformedBBox = rect.transformedBoundingBox(rotationMat);
129
+ expect(transformedBBox.containsPoint(Vec2.of(0.5, 0.5)));
130
+ expect(transformedBBox.containsRect(rect)).toBe(true);
131
+ });
132
+
133
+ describe('should correctly expand to include a given point', () => {
134
+ it('Growing an empty rectange to include (1, 0)', () => {
135
+ const originalRect = Rect2.empty;
136
+ const grownRect = originalRect.grownToPoint(Vec2.unitX);
137
+ expect(grownRect).objEq(new Rect2(0, 0, 1, 0));
138
+ });
139
+
140
+ it('Growing the unit rectangle to include (-5, 1), with a margin', () => {
141
+ const originalRect = Rect2.unitSquare;
142
+ const grownRect = originalRect.grownToPoint(Vec2.of(-5, 1), 4);
143
+ expect(grownRect).objEq(new Rect2(-9, -3, 10, 8));
144
+ });
145
+
146
+ it('Growing to include a point just above', () => {
147
+ const original = Rect2.unitSquare;
148
+ const grown = original.grownToPoint(Vec2.of(-1, -1));
149
+ expect(grown).objEq(new Rect2(-1, -1, 2, 2));
150
+ });
151
+
152
+ it('Growing to include a point just below', () => {
153
+ const original = Rect2.unitSquare;
154
+ const grown = original.grownToPoint(Vec2.of(2, 2));
155
+ expect(grown).objEq(new Rect2(0, 0, 2, 2));
156
+ });
157
+ });
158
+
159
+ describe('divideIntoGrid', () => {
160
+ it('division of unit square', () => {
161
+ expect(Rect2.unitSquare.divideIntoGrid(2, 2)).toMatchObject(
162
+ [
163
+ new Rect2(0, 0, 0.5, 0.5), new Rect2(0.5, 0, 0.5, 0.5),
164
+ new Rect2(0, 0.5, 0.5, 0.5), new Rect2(0.5, 0.5, 0.5, 0.5),
165
+ ]
166
+ );
167
+ expect(Rect2.unitSquare.divideIntoGrid(0, 0).length).toBe(0);
168
+ expect(Rect2.unitSquare.divideIntoGrid(100, 0).length).toBe(0);
169
+ expect(Rect2.unitSquare.divideIntoGrid(4, 1)).toMatchObject(
170
+ [
171
+ new Rect2(0, 0, 0.25, 1), new Rect2(0.25, 0, 0.25, 1),
172
+ new Rect2(0.5, 0, 0.25, 1), new Rect2(0.75, 0, 0.25, 1),
173
+ ]
174
+ );
175
+ });
176
+ it('division of translated square', () => {
177
+ expect(new Rect2(3, -3, 4, 4).divideIntoGrid(2, 1)).toMatchObject(
178
+ [
179
+ new Rect2(3, -3, 2, 4), new Rect2(5, -3, 2, 4),
180
+ ]
181
+ );
182
+ });
183
+ it('division of empty square', () => {
184
+ expect(Rect2.empty.divideIntoGrid(1000, 10000).length).toBe(1);
185
+ });
186
+
187
+ it('division of rectangle', () => {
188
+ expect(new Rect2(0, 0, 2, 1).divideIntoGrid(2, 2)).toMatchObject(
189
+ [
190
+ new Rect2(0, 0, 1, 0.5), new Rect2(1, 0, 1, 0.5),
191
+ new Rect2(0, 0.5, 1, 0.5), new Rect2(1, 0.5, 1, 0.5),
192
+ ]
193
+ );
194
+ });
195
+ });
196
+
197
+ describe('should correctly return the closest point on the edge of a rectangle', () => {
198
+ it('with the unit square', () => {
199
+ const rect = Rect2.unitSquare;
200
+ expect(rect.getClosestPointOnBoundaryTo(Vec2.zero)).objEq(Vec2.zero);
201
+ expect(rect.getClosestPointOnBoundaryTo(Vec2.of(-1, -1))).objEq(Vec2.zero);
202
+ expect(rect.getClosestPointOnBoundaryTo(Vec2.of(-1, 0.5))).objEq(Vec2.of(0, 0.5));
203
+ expect(rect.getClosestPointOnBoundaryTo(Vec2.of(1, 0.5))).objEq(Vec2.of(1, 0.5));
204
+ expect(rect.getClosestPointOnBoundaryTo(Vec2.of(0.6, 0.6))).objEq(Vec2.of(1, 0.6));
205
+ expect(rect.getClosestPointOnBoundaryTo(Vec2.of(2, 0.5))).objEq(Vec2.of(1, 0.5));
206
+ expect(rect.getClosestPointOnBoundaryTo(Vec2.of(0.6, 0.6))).objEq(Vec2.of(1, 0.6));
207
+ });
208
+ });
209
+ });