@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.
- package/LICENSE +21 -0
- package/dist/cjs/Color4.test.d.ts +1 -0
- package/dist/cjs/Mat33.test.d.ts +1 -0
- package/dist/cjs/Vec2.test.d.ts +1 -0
- package/dist/cjs/Vec3.test.d.ts +1 -0
- package/dist/cjs/polynomial/solveQuadratic.test.d.ts +1 -0
- package/dist/cjs/rounding.test.d.ts +1 -0
- package/dist/cjs/shapes/LineSegment2.test.d.ts +1 -0
- package/dist/cjs/shapes/Path.fromString.test.d.ts +1 -0
- package/dist/cjs/shapes/Path.test.d.ts +1 -0
- package/dist/cjs/shapes/Path.toString.test.d.ts +1 -0
- package/dist/cjs/shapes/QuadraticBezier.test.d.ts +1 -0
- package/dist/cjs/shapes/Rect2.test.d.ts +1 -0
- package/dist/cjs/shapes/Triangle.test.d.ts +1 -0
- package/dist/mjs/Color4.test.d.ts +1 -0
- package/dist/mjs/Mat33.test.d.ts +1 -0
- package/dist/mjs/Vec2.test.d.ts +1 -0
- package/dist/mjs/Vec3.test.d.ts +1 -0
- package/dist/mjs/polynomial/solveQuadratic.test.d.ts +1 -0
- package/dist/mjs/rounding.test.d.ts +1 -0
- package/dist/mjs/shapes/LineSegment2.test.d.ts +1 -0
- package/dist/mjs/shapes/Path.fromString.test.d.ts +1 -0
- package/dist/mjs/shapes/Path.test.d.ts +1 -0
- package/dist/mjs/shapes/Path.toString.test.d.ts +1 -0
- package/dist/mjs/shapes/QuadraticBezier.test.d.ts +1 -0
- package/dist/mjs/shapes/Rect2.test.d.ts +1 -0
- package/dist/mjs/shapes/Triangle.test.d.ts +1 -0
- package/dist-test/test_imports/package-lock.json +13 -0
- package/dist-test/test_imports/package.json +12 -0
- package/dist-test/test_imports/test-imports.js +15 -0
- package/dist-test/test_imports/test-require.cjs +15 -0
- package/package.json +4 -3
- package/src/Color4.test.ts +52 -0
- package/src/Color4.ts +318 -0
- package/src/Mat33.test.ts +244 -0
- package/src/Mat33.ts +450 -0
- package/src/Vec2.test.ts +30 -0
- package/src/Vec2.ts +49 -0
- package/src/Vec3.test.ts +51 -0
- package/src/Vec3.ts +245 -0
- package/src/lib.ts +42 -0
- package/src/polynomial/solveQuadratic.test.ts +39 -0
- package/src/polynomial/solveQuadratic.ts +43 -0
- package/src/rounding.test.ts +65 -0
- package/src/rounding.ts +167 -0
- package/src/shapes/Abstract2DShape.ts +63 -0
- package/src/shapes/BezierJSWrapper.ts +93 -0
- package/src/shapes/CubicBezier.ts +35 -0
- package/src/shapes/LineSegment2.test.ts +99 -0
- package/src/shapes/LineSegment2.ts +232 -0
- package/src/shapes/Path.fromString.test.ts +223 -0
- package/src/shapes/Path.test.ts +309 -0
- package/src/shapes/Path.toString.test.ts +77 -0
- package/src/shapes/Path.ts +963 -0
- package/src/shapes/PointShape2D.ts +33 -0
- package/src/shapes/QuadraticBezier.test.ts +31 -0
- package/src/shapes/QuadraticBezier.ts +142 -0
- package/src/shapes/Rect2.test.ts +209 -0
- package/src/shapes/Rect2.ts +346 -0
- package/src/shapes/Triangle.test.ts +61 -0
- 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
|
+
});
|