@js-draw/math
Version:
A math library for js-draw.
225 lines (192 loc) • 9.03 kB
text/typescript
import Rect2 from './Rect2';
import { Vec2 } from '../Vec2';
import Mat33 from '../Mat33';
describe('Rect2', () => {
it('width, height should always be positive', () => {
expect(new Rect2(-1, -2, -3, 4)).objEq(new Rect2(-4, -2, 3, 4));
expect(new Rect2(0, 0, 0, 0).size).objEq(Vec2.zero);
expect(Rect2.fromCorners(Vec2.of(-3, -3), Vec2.of(-1, -1))).objEq(new Rect2(-3, -3, 2, 2));
});
it('bounding boxes should be correctly computed', () => {
expect(Rect2.bboxOf([Vec2.zero])).objEq(Rect2.empty);
expect(Rect2.bboxOf([Vec2.of(-1, -1), Vec2.of(1, 2), Vec2.of(3, 4), Vec2.of(1, -4)])).objEq(
new Rect2(-1, -4, 4, 8),
);
expect(Rect2.bboxOf([Vec2.zero], 10)).objEq(new Rect2(-10, -10, 20, 20));
});
it('"union"s should contain both composite rectangles.', () => {
expect(new Rect2(0, 0, 1, 1).union(new Rect2(1, 1, 2, 2))).objEq(new Rect2(0, 0, 3, 3));
expect(Rect2.empty.union(Rect2.empty)).objEq(Rect2.empty);
});
it('should handle empty unions', () => {
expect(Rect2.union()).toStrictEqual(Rect2.empty);
});
it('should correctly union multiple rectangles', () => {
expect(Rect2.union(new Rect2(0, 0, 1, 1), new Rect2(1, 1, 2, 2))).objEq(new Rect2(0, 0, 3, 3));
expect(
Rect2.union(new Rect2(-1, 0, 1, 1), new Rect2(1, 1, 2, 2), new Rect2(1, 10, 1, 0.1)),
).objEq(new Rect2(-1, 0, 4, 10.1));
expect(
Rect2.union(new Rect2(-1, 0, 1, 1), new Rect2(1, -11.1, 2, 2), new Rect2(1, 10, 1, 0.1)),
).objEq(new Rect2(-1, -11.1, 4, 21.2));
});
it('should contain points that are within a rectangle', () => {
expect(new Rect2(-1, -1, 2, 2).containsPoint(Vec2.zero)).toBe(true);
expect(new Rect2(-1, -1, 0, 0).containsPoint(Vec2.zero)).toBe(false);
expect(new Rect2(1, 2, 3, 4).containsRect(Rect2.empty)).toBe(false);
expect(new Rect2(1, 2, 3, 4).containsRect(new Rect2(1, 2, 1, 2))).toBe(true);
expect(new Rect2(-2, -2, 4, 4).containsRect(new Rect2(-1, 0, 1, 1))).toBe(true);
expect(new Rect2(-2, -2, 4, 4).containsRect(new Rect2(-1, 0, 10, 1))).toBe(false);
});
it('.center should be the center of a rectangle', () => {
expect(new Rect2(-1, -1, 2, 3).center).objEq(Vec2.of(0, 0.5));
expect(new Rect2(-1, -1, 2, 2).center).objEq(Vec2.zero);
});
describe('containsRect', () => {
it('a rectangle should contain itself', () => {
const rect = new Rect2(1 / 3, 1 / 4, 1 / 5, 1 / 6);
expect(rect.containsRect(rect)).toBe(true);
});
it('empty rect should not contain a larger rect', () => {
expect(Rect2.empty.containsRect(new Rect2(-1, -1, 3, 3))).toBe(false);
});
it('should correctly contain rectangles', () => {
const testRect = new Rect2(4, -10, 50, 100);
expect(testRect.containsRect(new Rect2(4.1, 0, 1, 1))).toBe(true);
expect(testRect.containsRect(new Rect2(48, 0, 1, 1))).toBe(true);
expect(testRect.containsRect(new Rect2(48, -9, 1, 1))).toBe(true);
expect(testRect.containsRect(new Rect2(48, -9, 1, 91))).toBe(true);
});
});
it('intersecting rectangles should be identified as intersecting', () => {
expect(new Rect2(-1, -1, 2, 2).intersects(Rect2.empty)).toBe(true);
expect(new Rect2(-1, -1, 2, 2).intersects(new Rect2(0, 0, 1, 1))).toBe(true);
expect(new Rect2(-1, -1, 2, 2).intersects(new Rect2(0, 0, 10, 10))).toBe(true);
expect(new Rect2(-1, -1, 2, 2).intersects(new Rect2(3, 3, 10, 10))).toBe(false);
expect(new Rect2(-1, -1, 2, 2).intersects(new Rect2(0.2, 0.1, 0, 0))).toBe(true);
expect(new Rect2(-100, -1, 200, 2).intersects(new Rect2(-5, -5, 10, 30))).toBe(true);
expect(new Rect2(-100, -1, 200, 2).intersects(new Rect2(-5, 50, 10, 30))).toBe(false);
});
it('should correctly compute the intersection of one rectangle and several others', () => {
const mainRect = new Rect2(334, 156, 333, 179);
const shouldIntersect = [
new Rect2(400.8, 134.8, 8.4, 161.4),
new Rect2(324.8, 93, 164.4, 75.2),
new Rect2(435.8, 146.8, 213.2, 192.6),
new Rect2(550.8, 211.8, 3.4, 3.4),
new Rect2(478.8, 93.8, 212.4, 95.4),
];
const shouldNotIntersect = [new Rect2(200, 200, 1, 1)];
for (const rect of shouldIntersect) {
expect(mainRect.intersects(rect)).toBe(true);
}
for (const rect of shouldNotIntersect) {
expect(mainRect.intersects(rect)).toBe(false);
}
});
it('intersecting rectangles should have their intersections correctly computed', () => {
expect(new Rect2(-1, -1, 2, 2).intersection(Rect2.empty)).objEq(Rect2.empty);
expect(new Rect2(-1, -1, 2, 2).intersection(new Rect2(0, 0, 3, 3))).objEq(
new Rect2(0, 0, 1, 1),
);
expect(new Rect2(-2, 0, 1, 2).intersection(new Rect2(-3, 0, 2, 2))).objEq(
new Rect2(-2, 0, 1, 2),
);
expect(new Rect2(-1, -1, 2, 2).intersection(new Rect2(3, 3, 10, 10))).toBe(null);
});
it('A transformed bounding box', () => {
expect(Rect2.unitSquare.transformedBoundingBox(Mat33.scaling2D(2))).objEq(
new Rect2(0, 0, 2, 2),
);
const rotationMat = Mat33.zRotation(Math.PI / 4);
const rect = Rect2.unitSquare.translatedBy(Vec2.of(-0.5, -0.5));
const transformedBBox = rect.transformedBoundingBox(rotationMat);
expect(transformedBBox.containsPoint(Vec2.of(0.5, 0.5)));
expect(transformedBBox.containsRect(rect)).toBe(true);
});
it('.grownBy should expand a rectangle by the given margin', () => {
expect(Rect2.empty.grownBy(0)).toBe(Rect2.empty);
// Should add padding to all sides.
expect(new Rect2(1, 2, 3, 4).grownBy(1)).objEq(new Rect2(0, 1, 5, 6));
// Shrinking should not result in negative widths/heights and
// should adjust x/y appropriately
expect(new Rect2(1, 2, 1, 2).grownBy(-1)).objEq(new Rect2(1.5, 3, 0, 0));
expect(new Rect2(1, 2, 4, 4).grownBy(-1)).objEq(new Rect2(2, 3, 2, 2));
expect(new Rect2(1, 2, 2, 8).grownBy(-2)).objEq(new Rect2(2, 4, 0, 4));
});
it('.grownToSize should grow the rectangle to the given minimum size', () => {
expect(Rect2.empty.grownToSize(Vec2.of(10, 10))).objEq(new Rect2(-5, -5, 10, 10));
expect(Rect2.empty.grownToSize(Vec2.of(10, 4))).objEq(new Rect2(-5, -2, 10, 4));
expect(Rect2.unitSquare.grownToSize(Vec2.of(0.5, 0.5))).toBe(Rect2.unitSquare);
expect(new Rect2(0, 0, 2, 2).grownToSize(Vec2.of(4, 0.5))).objEq(new Rect2(-1, 0, 4, 2));
});
describe('should correctly expand to include a given point', () => {
it('Growing an empty rectange to include (1, 0)', () => {
const originalRect = Rect2.empty;
const grownRect = originalRect.grownToPoint(Vec2.unitX);
expect(grownRect).objEq(new Rect2(0, 0, 1, 0));
});
it('Growing the unit rectangle to include (-5, 1), with a margin', () => {
const originalRect = Rect2.unitSquare;
const grownRect = originalRect.grownToPoint(Vec2.of(-5, 1), 4);
expect(grownRect).objEq(new Rect2(-9, -3, 10, 8));
});
it('Growing to include a point just above', () => {
const original = Rect2.unitSquare;
const grown = original.grownToPoint(Vec2.of(-1, -1));
expect(grown).objEq(new Rect2(-1, -1, 2, 2));
});
it('Growing to include a point just below', () => {
const original = Rect2.unitSquare;
const grown = original.grownToPoint(Vec2.of(2, 2));
expect(grown).objEq(new Rect2(0, 0, 2, 2));
});
});
describe('divideIntoGrid', () => {
it('division of unit square', () => {
expect(Rect2.unitSquare.divideIntoGrid(2, 2)).toMatchObject([
new Rect2(0, 0, 0.5, 0.5),
new Rect2(0.5, 0, 0.5, 0.5),
new Rect2(0, 0.5, 0.5, 0.5),
new Rect2(0.5, 0.5, 0.5, 0.5),
]);
expect(Rect2.unitSquare.divideIntoGrid(0, 0).length).toBe(0);
expect(Rect2.unitSquare.divideIntoGrid(100, 0).length).toBe(0);
expect(Rect2.unitSquare.divideIntoGrid(4, 1)).toMatchObject([
new Rect2(0, 0, 0.25, 1),
new Rect2(0.25, 0, 0.25, 1),
new Rect2(0.5, 0, 0.25, 1),
new Rect2(0.75, 0, 0.25, 1),
]);
});
it('division of translated square', () => {
expect(new Rect2(3, -3, 4, 4).divideIntoGrid(2, 1)).toMatchObject([
new Rect2(3, -3, 2, 4),
new Rect2(5, -3, 2, 4),
]);
});
it('division of empty square', () => {
expect(Rect2.empty.divideIntoGrid(1000, 10000).length).toBe(1);
});
it('division of rectangle', () => {
expect(new Rect2(0, 0, 2, 1).divideIntoGrid(2, 2)).toMatchObject([
new Rect2(0, 0, 1, 0.5),
new Rect2(1, 0, 1, 0.5),
new Rect2(0, 0.5, 1, 0.5),
new Rect2(1, 0.5, 1, 0.5),
]);
});
});
describe('should correctly return the closest point on the edge of a rectangle', () => {
it('with the unit square', () => {
const rect = Rect2.unitSquare;
expect(rect.getClosestPointOnBoundaryTo(Vec2.zero)).objEq(Vec2.zero);
expect(rect.getClosestPointOnBoundaryTo(Vec2.of(-1, -1))).objEq(Vec2.zero);
expect(rect.getClosestPointOnBoundaryTo(Vec2.of(-1, 0.5))).objEq(Vec2.of(0, 0.5));
expect(rect.getClosestPointOnBoundaryTo(Vec2.of(1, 0.5))).objEq(Vec2.of(1, 0.5));
expect(rect.getClosestPointOnBoundaryTo(Vec2.of(0.6, 0.6))).objEq(Vec2.of(1, 0.6));
expect(rect.getClosestPointOnBoundaryTo(Vec2.of(2, 0.5))).objEq(Vec2.of(1, 0.5));
expect(rect.getClosestPointOnBoundaryTo(Vec2.of(0.6, 0.6))).objEq(Vec2.of(1, 0.6));
});
});
});