新增math库
This commit is contained in:
212
packages/math/tests/CollisionDetector.test.ts
Normal file
212
packages/math/tests/CollisionDetector.test.ts
Normal file
@@ -0,0 +1,212 @@
|
||||
import { CollisionDetector } from '../src/Collision/CollisionDetector';
|
||||
import { Vector2 } from '../src/Vector2';
|
||||
import { Rectangle } from '../src/Rectangle';
|
||||
import { Circle } from '../src/Circle';
|
||||
|
||||
declare global {
|
||||
var expectFloatsEqual: (actual: number, expected: number, epsilon?: number) => void;
|
||||
}
|
||||
|
||||
describe('CollisionDetector', () => {
|
||||
describe('点与几何体碰撞', () => {
|
||||
test('点与圆形碰撞检测', () => {
|
||||
const circle = new Circle(0, 0, 5);
|
||||
const pointInside = new Vector2(3, 0);
|
||||
const pointOutside = new Vector2(10, 0);
|
||||
const pointOnBoundary = new Vector2(5, 0);
|
||||
|
||||
const collision1 = CollisionDetector.pointCircle(pointInside, circle);
|
||||
expect(collision1.collided).toBe(true);
|
||||
expect(collision1.penetration).toBe(2);
|
||||
|
||||
const collision2 = CollisionDetector.pointCircle(pointOutside, circle);
|
||||
expect(collision2.collided).toBe(false);
|
||||
|
||||
const collision3 = CollisionDetector.pointCircle(pointOnBoundary, circle);
|
||||
expect(collision3.collided).toBe(true);
|
||||
expectFloatsEqual(collision3.penetration!, 0, 1e-10);
|
||||
});
|
||||
|
||||
test('点与矩形碰撞检测', () => {
|
||||
const rect = new Rectangle(10, 10, 20, 20);
|
||||
const pointInside = new Vector2(15, 15);
|
||||
const pointOutside = new Vector2(5, 5);
|
||||
|
||||
const collision1 = CollisionDetector.pointRect(pointInside, rect);
|
||||
expect(collision1.collided).toBe(true);
|
||||
expect(collision1.normal).toBeDefined();
|
||||
|
||||
const collision2 = CollisionDetector.pointRect(pointOutside, rect);
|
||||
expect(collision2.collided).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('圆形碰撞检测', () => {
|
||||
test('圆形与圆形碰撞检测', () => {
|
||||
const circle1 = new Circle(0, 0, 5);
|
||||
const circle2 = new Circle(8, 0, 5); // 相交
|
||||
const circle3 = new Circle(15, 0, 5); // 不相交
|
||||
const circle4 = new Circle(10, 0, 5); // 边界接触
|
||||
|
||||
const collision1 = CollisionDetector.circleCircle(circle1, circle2);
|
||||
expect(collision1.collided).toBe(true);
|
||||
expect(collision1.penetration).toBe(2);
|
||||
|
||||
const collision2 = CollisionDetector.circleCircle(circle1, circle3);
|
||||
expect(collision2.collided).toBe(false);
|
||||
|
||||
const collision3 = CollisionDetector.circleCircle(circle1, circle4);
|
||||
expect(collision3.collided).toBe(true);
|
||||
expectFloatsEqual(collision3.penetration!, 0, 1e-10);
|
||||
});
|
||||
|
||||
test('圆形与矩形碰撞检测', () => {
|
||||
const circle = new Circle(15, 15, 5);
|
||||
const rect1 = new Rectangle(10, 10, 20, 20); // 相交
|
||||
const rect2 = new Rectangle(30, 30, 10, 10); // 不相交
|
||||
|
||||
const collision1 = CollisionDetector.circleRect(circle, rect1);
|
||||
expect(collision1.collided).toBe(true);
|
||||
|
||||
const collision2 = CollisionDetector.circleRect(circle, rect2);
|
||||
expect(collision2.collided).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('矩形碰撞检测', () => {
|
||||
test('矩形与矩形碰撞检测', () => {
|
||||
const rect1 = new Rectangle(10, 10, 20, 20);
|
||||
const rect2 = new Rectangle(15, 15, 20, 20); // 相交
|
||||
const rect3 = new Rectangle(40, 40, 10, 10); // 不相交
|
||||
|
||||
const collision1 = CollisionDetector.rectRect(rect1, rect2);
|
||||
expect(collision1.collided).toBe(true);
|
||||
expect(collision1.penetration).toBeGreaterThan(0);
|
||||
expect(collision1.normal).toBeDefined();
|
||||
|
||||
const collision2 = CollisionDetector.rectRect(rect1, rect3);
|
||||
expect(collision2.collided).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('射线投射', () => {
|
||||
test('射线与圆形相交', () => {
|
||||
const rayOrigin = new Vector2(-10, 0);
|
||||
const rayDirection = new Vector2(1, 0);
|
||||
const circle = new Circle(0, 0, 5);
|
||||
|
||||
const collision = CollisionDetector.rayCircle(rayOrigin, rayDirection, circle);
|
||||
expect(collision.collided).toBe(true);
|
||||
expect(collision.distance).toBe(5);
|
||||
expect(collision.contactPoint!.x).toBe(-5);
|
||||
expect(collision.contactPoint!.y).toBe(0);
|
||||
});
|
||||
|
||||
test('射线与圆形不相交', () => {
|
||||
const rayOrigin = new Vector2(-10, 10);
|
||||
const rayDirection = new Vector2(1, 0);
|
||||
const circle = new Circle(0, 0, 5);
|
||||
|
||||
const collision = CollisionDetector.rayCircle(rayOrigin, rayDirection, circle);
|
||||
expect(collision.collided).toBe(false);
|
||||
});
|
||||
|
||||
test('射线与矩形相交', () => {
|
||||
const rayOrigin = new Vector2(-5, 15);
|
||||
const rayDirection = new Vector2(1, 0);
|
||||
const rect = new Rectangle(10, 10, 20, 20);
|
||||
|
||||
const collision = CollisionDetector.rayRect(rayOrigin, rayDirection, rect);
|
||||
expect(collision.collided).toBe(true);
|
||||
expect(collision.distance).toBe(15);
|
||||
});
|
||||
|
||||
test('射线距离限制', () => {
|
||||
const rayOrigin = new Vector2(-10, 0);
|
||||
const rayDirection = new Vector2(1, 0);
|
||||
const circle = new Circle(0, 0, 5);
|
||||
|
||||
const collision = CollisionDetector.rayCircle(rayOrigin, rayDirection, circle, 3);
|
||||
expect(collision.collided).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('线段相交', () => {
|
||||
test('线段与线段相交', () => {
|
||||
const p1 = new Vector2(0, 0);
|
||||
const p2 = new Vector2(10, 10);
|
||||
const p3 = new Vector2(0, 10);
|
||||
const p4 = new Vector2(10, 0);
|
||||
|
||||
const collision = CollisionDetector.lineSegmentLineSegment(p1, p2, p3, p4);
|
||||
expect(collision.collided).toBe(true);
|
||||
expect(collision.contactPoint!.x).toBe(5);
|
||||
expect(collision.contactPoint!.y).toBe(5);
|
||||
});
|
||||
|
||||
test('线段与线段不相交', () => {
|
||||
const p1 = new Vector2(0, 0);
|
||||
const p2 = new Vector2(5, 5);
|
||||
const p3 = new Vector2(10, 0);
|
||||
const p4 = new Vector2(15, 5);
|
||||
|
||||
const collision = CollisionDetector.lineSegmentLineSegment(p1, p2, p3, p4);
|
||||
expect(collision.collided).toBe(false);
|
||||
});
|
||||
|
||||
test('线段与圆形相交', () => {
|
||||
const lineStart = new Vector2(-10, 0);
|
||||
const lineEnd = new Vector2(10, 0);
|
||||
const circle = new Circle(0, 0, 5);
|
||||
|
||||
const collision = CollisionDetector.lineSegmentCircle(lineStart, lineEnd, circle);
|
||||
expect(collision.collided).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('快速排斥测试', () => {
|
||||
test('AABB包围盒测试', () => {
|
||||
const bounds1 = new Rectangle(10, 10, 20, 20);
|
||||
const bounds2 = new Rectangle(15, 15, 20, 20);
|
||||
const bounds3 = new Rectangle(40, 40, 10, 10);
|
||||
|
||||
expect(CollisionDetector.aabbTest(bounds1, bounds2)).toBe(true);
|
||||
expect(CollisionDetector.aabbTest(bounds1, bounds3)).toBe(false);
|
||||
});
|
||||
|
||||
test('圆形包围盒测试', () => {
|
||||
const center1 = new Vector2(0, 0);
|
||||
const center2 = new Vector2(8, 0);
|
||||
const center3 = new Vector2(15, 0);
|
||||
|
||||
expect(CollisionDetector.circleTest(center1, 5, center2, 5)).toBe(true);
|
||||
expect(CollisionDetector.circleTest(center1, 5, center3, 5)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('边界情况', () => {
|
||||
test('零半径圆形', () => {
|
||||
const point = new Vector2(0, 0);
|
||||
const circle = new Circle(0, 0, 0);
|
||||
|
||||
const collision = CollisionDetector.pointCircle(point, circle);
|
||||
expect(collision.collided).toBe(true);
|
||||
});
|
||||
|
||||
test('零面积矩形', () => {
|
||||
const point = new Vector2(10, 10);
|
||||
const rect = new Rectangle(10, 10, 0, 0);
|
||||
|
||||
const collision = CollisionDetector.pointRect(point, rect);
|
||||
expect(collision.collided).toBe(true);
|
||||
});
|
||||
|
||||
test('同心圆形', () => {
|
||||
const circle1 = new Circle(0, 0, 5);
|
||||
const circle2 = new Circle(0, 0, 3);
|
||||
|
||||
const collision = CollisionDetector.circleCircle(circle1, circle2);
|
||||
expect(collision.collided).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
292
packages/math/tests/Rectangle.test.ts
Normal file
292
packages/math/tests/Rectangle.test.ts
Normal file
@@ -0,0 +1,292 @@
|
||||
import { Rectangle } from '../src/Rectangle';
|
||||
import { Vector2 } from '../src/Vector2';
|
||||
|
||||
declare global {
|
||||
var expectFloatsEqual: (actual: number, expected: number, epsilon?: number) => void;
|
||||
}
|
||||
|
||||
describe('Rectangle', () => {
|
||||
describe('构造函数和基础属性', () => {
|
||||
test('默认构造函数应创建空矩形', () => {
|
||||
const rect = new Rectangle();
|
||||
expect(rect.x).toBe(0);
|
||||
expect(rect.y).toBe(0);
|
||||
expect(rect.width).toBe(0);
|
||||
expect(rect.height).toBe(0);
|
||||
});
|
||||
|
||||
test('应正确设置矩形参数', () => {
|
||||
const rect = new Rectangle(10, 20, 100, 50);
|
||||
expect(rect.x).toBe(10);
|
||||
expect(rect.y).toBe(20);
|
||||
expect(rect.width).toBe(100);
|
||||
expect(rect.height).toBe(50);
|
||||
});
|
||||
|
||||
test('边界属性应正确计算', () => {
|
||||
const rect = new Rectangle(10, 20, 100, 50);
|
||||
expect(rect.left).toBe(10);
|
||||
expect(rect.right).toBe(110);
|
||||
expect(rect.top).toBe(20);
|
||||
expect(rect.bottom).toBe(70);
|
||||
});
|
||||
|
||||
test('中心属性应正确计算', () => {
|
||||
const rect = new Rectangle(10, 20, 100, 50);
|
||||
expect(rect.centerX).toBe(60);
|
||||
expect(rect.centerY).toBe(45);
|
||||
|
||||
const center = rect.center;
|
||||
expect(center.x).toBe(60);
|
||||
expect(center.y).toBe(45);
|
||||
});
|
||||
|
||||
test('角点属性应正确计算', () => {
|
||||
const rect = new Rectangle(10, 20, 100, 50);
|
||||
|
||||
expect(rect.topLeft.x).toBe(10);
|
||||
expect(rect.topLeft.y).toBe(20);
|
||||
expect(rect.topRight.x).toBe(110);
|
||||
expect(rect.topRight.y).toBe(20);
|
||||
expect(rect.bottomLeft.x).toBe(10);
|
||||
expect(rect.bottomLeft.y).toBe(70);
|
||||
expect(rect.bottomRight.x).toBe(110);
|
||||
expect(rect.bottomRight.y).toBe(70);
|
||||
});
|
||||
|
||||
test('面积和周长应正确计算', () => {
|
||||
const rect = new Rectangle(0, 0, 10, 5);
|
||||
expect(rect.area).toBe(50);
|
||||
expect(rect.perimeter).toBe(30);
|
||||
});
|
||||
});
|
||||
|
||||
describe('基础操作', () => {
|
||||
test('set方法应正确设置值', () => {
|
||||
const rect = new Rectangle();
|
||||
rect.set(1, 2, 3, 4);
|
||||
expect(rect.x).toBe(1);
|
||||
expect(rect.y).toBe(2);
|
||||
expect(rect.width).toBe(3);
|
||||
expect(rect.height).toBe(4);
|
||||
});
|
||||
|
||||
test('copy方法应正确复制', () => {
|
||||
const rect1 = new Rectangle(1, 2, 3, 4);
|
||||
const rect2 = new Rectangle();
|
||||
rect2.copy(rect1);
|
||||
expect(rect2.x).toBe(1);
|
||||
expect(rect2.y).toBe(2);
|
||||
expect(rect2.width).toBe(3);
|
||||
expect(rect2.height).toBe(4);
|
||||
});
|
||||
|
||||
test('clone方法应创建相同的新实例', () => {
|
||||
const rect1 = new Rectangle(1, 2, 3, 4);
|
||||
const rect2 = rect1.clone();
|
||||
expect(rect2.x).toBe(1);
|
||||
expect(rect2.y).toBe(2);
|
||||
expect(rect2.width).toBe(3);
|
||||
expect(rect2.height).toBe(4);
|
||||
expect(rect2).not.toBe(rect1);
|
||||
});
|
||||
|
||||
test('setCenter方法应正确设置中心点', () => {
|
||||
const rect = new Rectangle(0, 0, 10, 10);
|
||||
rect.setCenter(50, 60);
|
||||
expect(rect.x).toBe(45);
|
||||
expect(rect.y).toBe(55);
|
||||
expect(rect.centerX).toBe(50);
|
||||
expect(rect.centerY).toBe(60);
|
||||
});
|
||||
});
|
||||
|
||||
describe('变换操作', () => {
|
||||
test('translate方法应正确平移', () => {
|
||||
const rect = new Rectangle(10, 20, 30, 40);
|
||||
rect.translate(5, 10);
|
||||
expect(rect.x).toBe(15);
|
||||
expect(rect.y).toBe(30);
|
||||
expect(rect.width).toBe(30);
|
||||
expect(rect.height).toBe(40);
|
||||
});
|
||||
|
||||
test('scale方法应正确缩放', () => {
|
||||
const rect = new Rectangle(10, 10, 20, 30);
|
||||
const originalCenterX = rect.centerX;
|
||||
const originalCenterY = rect.centerY;
|
||||
|
||||
rect.scale(2, 3);
|
||||
|
||||
expect(rect.width).toBe(40);
|
||||
expect(rect.height).toBe(90);
|
||||
expect(rect.centerX).toBe(originalCenterX);
|
||||
expect(rect.centerY).toBe(originalCenterY);
|
||||
});
|
||||
|
||||
test('inflate方法应正确扩展', () => {
|
||||
const rect = new Rectangle(10, 10, 20, 30);
|
||||
rect.inflate(5);
|
||||
expect(rect.x).toBe(5);
|
||||
expect(rect.y).toBe(5);
|
||||
expect(rect.width).toBe(30);
|
||||
expect(rect.height).toBe(40);
|
||||
});
|
||||
});
|
||||
|
||||
describe('包含检测', () => {
|
||||
const rect = new Rectangle(10, 10, 20, 30);
|
||||
|
||||
test('containsPoint应正确检测点包含', () => {
|
||||
const point1 = new Vector2(15, 15);
|
||||
const point2 = new Vector2(5, 5);
|
||||
const point3 = new Vector2(10, 10); // 边界点
|
||||
|
||||
expect(rect.containsPoint(point1)).toBe(true);
|
||||
expect(rect.containsPoint(point2)).toBe(false);
|
||||
expect(rect.containsPoint(point3)).toBe(true);
|
||||
});
|
||||
|
||||
test('contains方法应正确检测坐标包含', () => {
|
||||
expect(rect.contains(15, 15)).toBe(true);
|
||||
expect(rect.contains(5, 5)).toBe(false);
|
||||
});
|
||||
|
||||
test('containsRect应正确检测矩形包含', () => {
|
||||
const inside = new Rectangle(12, 12, 5, 5);
|
||||
const outside = new Rectangle(5, 5, 5, 5);
|
||||
const overlapping = new Rectangle(8, 8, 5, 5);
|
||||
|
||||
expect(rect.containsRect(inside)).toBe(true);
|
||||
expect(rect.containsRect(outside)).toBe(false);
|
||||
expect(rect.containsRect(overlapping)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('相交检测', () => {
|
||||
const rect1 = new Rectangle(10, 10, 20, 20);
|
||||
|
||||
test('intersects应正确检测相交', () => {
|
||||
const rect2 = new Rectangle(15, 15, 20, 20); // 相交
|
||||
const rect3 = new Rectangle(40, 40, 10, 10); // 不相交
|
||||
const rect4 = new Rectangle(30, 10, 10, 20); // 边界接触
|
||||
|
||||
expect(rect1.intersects(rect2)).toBe(true);
|
||||
expect(rect1.intersects(rect3)).toBe(false);
|
||||
expect(rect1.intersects(rect4)).toBe(false);
|
||||
});
|
||||
|
||||
test('intersection应正确计算相交矩形', () => {
|
||||
const rect2 = new Rectangle(15, 15, 20, 20);
|
||||
const intersection = rect1.intersection(rect2);
|
||||
|
||||
expect(intersection.x).toBe(15);
|
||||
expect(intersection.y).toBe(15);
|
||||
expect(intersection.width).toBe(15);
|
||||
expect(intersection.height).toBe(15);
|
||||
});
|
||||
|
||||
test('union应正确计算并集矩形', () => {
|
||||
const rect2 = new Rectangle(20, 20, 20, 20);
|
||||
const union = rect1.union(rect2);
|
||||
|
||||
expect(union.x).toBe(10);
|
||||
expect(union.y).toBe(10);
|
||||
expect(union.right).toBe(40);
|
||||
expect(union.bottom).toBe(40);
|
||||
});
|
||||
});
|
||||
|
||||
describe('距离计算', () => {
|
||||
const rect = new Rectangle(10, 10, 20, 20);
|
||||
|
||||
test('distanceToPoint应正确计算点到矩形的距离', () => {
|
||||
const insidePoint = new Vector2(15, 15);
|
||||
const outsidePoint = new Vector2(40, 40);
|
||||
|
||||
expect(rect.distanceToPoint(insidePoint)).toBe(0);
|
||||
expectFloatsEqual(rect.distanceToPoint(outsidePoint), Math.sqrt(200));
|
||||
});
|
||||
|
||||
test('closestPointTo应返回最近点', () => {
|
||||
const point = new Vector2(5, 25);
|
||||
const closest = rect.closestPointTo(point);
|
||||
|
||||
expect(closest.x).toBe(10);
|
||||
expect(closest.y).toBe(25);
|
||||
});
|
||||
});
|
||||
|
||||
describe('静态方法', () => {
|
||||
test('fromCenter应从中心点创建矩形', () => {
|
||||
const rect = Rectangle.fromCenter(50, 60, 20, 30);
|
||||
expect(rect.centerX).toBe(50);
|
||||
expect(rect.centerY).toBe(60);
|
||||
expect(rect.width).toBe(20);
|
||||
expect(rect.height).toBe(30);
|
||||
});
|
||||
|
||||
test('fromPoints应从两点创建矩形', () => {
|
||||
const p1 = new Vector2(10, 20);
|
||||
const p2 = new Vector2(30, 15);
|
||||
const rect = Rectangle.fromPoints(p1, p2);
|
||||
|
||||
expect(rect.x).toBe(10);
|
||||
expect(rect.y).toBe(15);
|
||||
expect(rect.width).toBe(20);
|
||||
expect(rect.height).toBe(5);
|
||||
});
|
||||
|
||||
test('fromPointArray应从点数组创建包围矩形', () => {
|
||||
const points = [
|
||||
new Vector2(5, 10),
|
||||
new Vector2(15, 5),
|
||||
new Vector2(20, 25),
|
||||
new Vector2(8, 30)
|
||||
];
|
||||
const rect = Rectangle.fromPointArray(points);
|
||||
|
||||
expect(rect.x).toBe(5);
|
||||
expect(rect.y).toBe(5);
|
||||
expect(rect.right).toBe(20);
|
||||
expect(rect.bottom).toBe(30);
|
||||
});
|
||||
});
|
||||
|
||||
describe('比较操作', () => {
|
||||
test('equals应正确比较矩形', () => {
|
||||
const rect1 = new Rectangle(1, 2, 3, 4);
|
||||
const rect2 = new Rectangle(1, 2, 3, 4);
|
||||
const rect3 = new Rectangle(1.001, 2, 3, 4);
|
||||
|
||||
expect(rect1.equals(rect2)).toBe(true);
|
||||
expect(rect1.equals(rect3, 0.01)).toBe(true);
|
||||
expect(rect1.equals(rect3, 0.0001)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('字符串转换', () => {
|
||||
test('toString应返回正确格式', () => {
|
||||
const rect = new Rectangle(1.234, 2.567, 3.891, 4.012);
|
||||
const str = rect.toString();
|
||||
expect(str).toContain('Rectangle');
|
||||
});
|
||||
|
||||
test('toArray应返回数组', () => {
|
||||
const rect = new Rectangle(1, 2, 3, 4);
|
||||
const arr = rect.toArray();
|
||||
expect(arr).toEqual([1, 2, 3, 4]);
|
||||
});
|
||||
|
||||
test('getVertices应返回四个顶点', () => {
|
||||
const rect = new Rectangle(10, 20, 30, 40);
|
||||
const vertices = rect.getVertices();
|
||||
|
||||
expect(vertices).toHaveLength(4);
|
||||
expect(vertices[0].x).toBe(10); // topLeft
|
||||
expect(vertices[0].y).toBe(20);
|
||||
expect(vertices[1].x).toBe(40); // topRight
|
||||
expect(vertices[1].y).toBe(20);
|
||||
});
|
||||
});
|
||||
});
|
||||
261
packages/math/tests/Vector2.test.ts
Normal file
261
packages/math/tests/Vector2.test.ts
Normal file
@@ -0,0 +1,261 @@
|
||||
import { Vector2 } from '../src/Vector2';
|
||||
|
||||
declare global {
|
||||
var expectFloatsEqual: (actual: number, expected: number, epsilon?: number) => void;
|
||||
}
|
||||
|
||||
describe('Vector2', () => {
|
||||
describe('构造函数和基础属性', () => {
|
||||
test('默认构造函数应创建零向量', () => {
|
||||
const v = new Vector2();
|
||||
expect(v.x).toBe(0);
|
||||
expect(v.y).toBe(0);
|
||||
});
|
||||
|
||||
test('应正确设置x和y值', () => {
|
||||
const v = new Vector2(3, 4);
|
||||
expect(v.x).toBe(3);
|
||||
expect(v.y).toBe(4);
|
||||
});
|
||||
|
||||
test('length属性应正确计算', () => {
|
||||
const v = new Vector2(3, 4);
|
||||
expect(v.length).toBe(5);
|
||||
});
|
||||
|
||||
test('lengthSquared属性应正确计算', () => {
|
||||
const v = new Vector2(3, 4);
|
||||
expect(v.lengthSquared).toBe(25);
|
||||
});
|
||||
|
||||
test('angle属性应正确计算', () => {
|
||||
const v = new Vector2(1, 0);
|
||||
expect(v.angle).toBe(0);
|
||||
|
||||
const v2 = new Vector2(0, 1);
|
||||
expectFloatsEqual(v2.angle, Math.PI / 2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('基础运算', () => {
|
||||
test('set方法应正确设置值', () => {
|
||||
const v = new Vector2();
|
||||
v.set(5, 6);
|
||||
expect(v.x).toBe(5);
|
||||
expect(v.y).toBe(6);
|
||||
});
|
||||
|
||||
test('copy方法应正确复制值', () => {
|
||||
const v1 = new Vector2(1, 2);
|
||||
const v2 = new Vector2(3, 4);
|
||||
v2.copy(v1);
|
||||
expect(v2.x).toBe(1);
|
||||
expect(v2.y).toBe(2);
|
||||
});
|
||||
|
||||
test('clone方法应创建相同的新实例', () => {
|
||||
const v1 = new Vector2(1, 2);
|
||||
const v2 = v1.clone();
|
||||
expect(v2.x).toBe(1);
|
||||
expect(v2.y).toBe(2);
|
||||
expect(v2).not.toBe(v1);
|
||||
});
|
||||
|
||||
test('add方法应正确相加', () => {
|
||||
const v1 = new Vector2(1, 2);
|
||||
const v2 = new Vector2(3, 4);
|
||||
v1.add(v2);
|
||||
expect(v1.x).toBe(4);
|
||||
expect(v1.y).toBe(6);
|
||||
});
|
||||
|
||||
test('subtract方法应正确相减', () => {
|
||||
const v1 = new Vector2(5, 7);
|
||||
const v2 = new Vector2(2, 3);
|
||||
v1.subtract(v2);
|
||||
expect(v1.x).toBe(3);
|
||||
expect(v1.y).toBe(4);
|
||||
});
|
||||
|
||||
test('multiply方法应正确数乘', () => {
|
||||
const v = new Vector2(2, 3);
|
||||
v.multiply(4);
|
||||
expect(v.x).toBe(8);
|
||||
expect(v.y).toBe(12);
|
||||
});
|
||||
|
||||
test('divide方法应正确数除', () => {
|
||||
const v = new Vector2(8, 12);
|
||||
v.divide(4);
|
||||
expect(v.x).toBe(2);
|
||||
expect(v.y).toBe(3);
|
||||
});
|
||||
|
||||
test('divide方法应在除以零时抛出错误', () => {
|
||||
const v = new Vector2(1, 2);
|
||||
expect(() => v.divide(0)).toThrow('不能除以零');
|
||||
});
|
||||
});
|
||||
|
||||
describe('向量运算', () => {
|
||||
test('dot方法应正确计算点积', () => {
|
||||
const v1 = new Vector2(1, 2);
|
||||
const v2 = new Vector2(3, 4);
|
||||
expect(v1.dot(v2)).toBe(11); // 1*3 + 2*4 = 11
|
||||
});
|
||||
|
||||
test('cross方法应正确计算叉积', () => {
|
||||
const v1 = new Vector2(1, 0);
|
||||
const v2 = new Vector2(0, 1);
|
||||
expect(v1.cross(v2)).toBe(1);
|
||||
});
|
||||
|
||||
test('normalize方法应正确归一化向量', () => {
|
||||
const v = new Vector2(3, 4);
|
||||
v.normalize();
|
||||
expectFloatsEqual(v.length, 1);
|
||||
expectFloatsEqual(v.x, 0.6);
|
||||
expectFloatsEqual(v.y, 0.8);
|
||||
});
|
||||
|
||||
test('零向量归一化应保持不变', () => {
|
||||
const v = new Vector2(0, 0);
|
||||
v.normalize();
|
||||
expect(v.x).toBe(0);
|
||||
expect(v.y).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('几何运算', () => {
|
||||
test('distanceTo方法应正确计算距离', () => {
|
||||
const v1 = new Vector2(0, 0);
|
||||
const v2 = new Vector2(3, 4);
|
||||
expect(v1.distanceTo(v2)).toBe(5);
|
||||
});
|
||||
|
||||
test('angleTo方法应正确计算夹角', () => {
|
||||
const v1 = new Vector2(1, 0);
|
||||
const v2 = new Vector2(0, 1);
|
||||
expectFloatsEqual(v1.angleTo(v2), Math.PI / 2);
|
||||
});
|
||||
|
||||
test('projectOnto方法应正确投影', () => {
|
||||
const v1 = new Vector2(2, 2);
|
||||
const v2 = new Vector2(1, 0);
|
||||
const projected = v1.projectOnto(v2);
|
||||
expect(projected.x).toBe(2);
|
||||
expect(projected.y).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('变换操作', () => {
|
||||
test('rotate方法应正确旋转向量', () => {
|
||||
const v = new Vector2(1, 0);
|
||||
v.rotate(Math.PI / 2);
|
||||
expectFloatsEqual(v.x, 0, 1e-10);
|
||||
expectFloatsEqual(v.y, 1, 1e-10);
|
||||
});
|
||||
|
||||
test('reflect方法应正确反射向量', () => {
|
||||
const v = new Vector2(1, 1);
|
||||
const normal = new Vector2(0, 1);
|
||||
v.reflect(normal);
|
||||
expectFloatsEqual(v.x, 1);
|
||||
expectFloatsEqual(v.y, -1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('插值和限制', () => {
|
||||
test('lerp方法应正确插值', () => {
|
||||
const v1 = new Vector2(0, 0);
|
||||
const v2 = new Vector2(10, 10);
|
||||
v1.lerp(v2, 0.5);
|
||||
expect(v1.x).toBe(5);
|
||||
expect(v1.y).toBe(5);
|
||||
});
|
||||
|
||||
test('clampLength方法应正确限制长度', () => {
|
||||
const v = new Vector2(6, 8); // 长度为10
|
||||
v.clampLength(5);
|
||||
expectFloatsEqual(v.length, 5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('比较操作', () => {
|
||||
test('equals方法应正确比较向量', () => {
|
||||
const v1 = new Vector2(1, 2);
|
||||
const v2 = new Vector2(1, 2);
|
||||
const v3 = new Vector2(1.0001, 2);
|
||||
|
||||
expect(v1.equals(v2)).toBe(true);
|
||||
expect(v1.equals(v3, 0.001)).toBe(true);
|
||||
expect(v1.equals(v3, 0.00001)).toBe(false);
|
||||
});
|
||||
|
||||
test('exactEquals方法应检查完全相等', () => {
|
||||
const v1 = new Vector2(1, 2);
|
||||
const v2 = new Vector2(1, 2);
|
||||
const v3 = new Vector2(1.0001, 2);
|
||||
|
||||
expect(v1.exactEquals(v2)).toBe(true);
|
||||
expect(v1.exactEquals(v3)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('静态方法', () => {
|
||||
test('Vector2.add应创建新的相加结果', () => {
|
||||
const v1 = new Vector2(1, 2);
|
||||
const v2 = new Vector2(3, 4);
|
||||
const result = Vector2.add(v1, v2);
|
||||
|
||||
expect(result.x).toBe(4);
|
||||
expect(result.y).toBe(6);
|
||||
expect(v1.x).toBe(1); // 原向量不变
|
||||
expect(v1.y).toBe(2);
|
||||
});
|
||||
|
||||
test('Vector2.fromAngle应从角度创建单位向量', () => {
|
||||
const v = Vector2.fromAngle(Math.PI / 2);
|
||||
expectFloatsEqual(v.x, 0, 1e-10);
|
||||
expectFloatsEqual(v.y, 1, 1e-10);
|
||||
});
|
||||
|
||||
test('Vector2.fromPolar应从极坐标创建向量', () => {
|
||||
const v = Vector2.fromPolar(5, 0);
|
||||
expect(v.x).toBe(5);
|
||||
expectFloatsEqual(v.y, 0, 1e-10);
|
||||
});
|
||||
});
|
||||
|
||||
describe('静态常量', () => {
|
||||
test('静态常量应具有正确的值', () => {
|
||||
expect(Vector2.ZERO.x).toBe(0);
|
||||
expect(Vector2.ZERO.y).toBe(0);
|
||||
expect(Vector2.ONE.x).toBe(1);
|
||||
expect(Vector2.ONE.y).toBe(1);
|
||||
expect(Vector2.RIGHT.x).toBe(1);
|
||||
expect(Vector2.RIGHT.y).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('字符串转换', () => {
|
||||
test('toString应返回正确格式', () => {
|
||||
const v = new Vector2(1.2345, 2.6789);
|
||||
const str = v.toString();
|
||||
expect(str).toContain('1.234');
|
||||
expect(str).toContain('2.679');
|
||||
});
|
||||
|
||||
test('toArray应返回数组', () => {
|
||||
const v = new Vector2(1, 2);
|
||||
const arr = v.toArray();
|
||||
expect(arr).toEqual([1, 2]);
|
||||
});
|
||||
|
||||
test('toObject应返回对象', () => {
|
||||
const v = new Vector2(1, 2);
|
||||
const obj = v.toObject();
|
||||
expect(obj).toEqual({ x: 1, y: 2 });
|
||||
});
|
||||
});
|
||||
});
|
||||
23
packages/math/tests/setup.ts
Normal file
23
packages/math/tests/setup.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Jest测试设置文件
|
||||
* 用于配置全局测试环境
|
||||
*/
|
||||
|
||||
// 设置测试超时
|
||||
jest.setTimeout(10000);
|
||||
|
||||
// 全局数学常量和工具函数
|
||||
(global as any).EPSILON = 1e-10;
|
||||
|
||||
// 浮点数比较助手函数
|
||||
(global as any).expectFloatsEqual = (actual: number, expected: number, epsilon = (global as any).EPSILON) => {
|
||||
expect(Math.abs(actual - expected)).toBeLessThan(epsilon);
|
||||
};
|
||||
|
||||
// 声明全局类型扩展
|
||||
declare global {
|
||||
var EPSILON: number;
|
||||
var expectFloatsEqual: (actual: number, expected: number, epsilon?: number) => void;
|
||||
}
|
||||
|
||||
export {};
|
||||
Reference in New Issue
Block a user