@js-draw/math 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ });