Matou-Garou / convex /util /geometry.test.ts
Jofthomas's picture
Jofthomas HF staff
bulk
ce8b18b
raw
history blame
8.94 kB
import { compressPath, distance, manhattanDistance, normalize, orientationDegrees, pathOverlaps, pathPosition, pointsEqual, vector, vectorLength } from './geometry';
import { Path, Vector } from './types';
describe('distance', () => {
test('should return the correct distance for two points', () => {
const p0 = { x: 0, y: 0 };
const p1 = { x: 3, y: 4 };
const expectedDistance = 5;
const actualDistance = distance(p0, p1);
expect(actualDistance).toBe(expectedDistance);
});
test('should return 0 for the same point', () => {
const p0 = { x: 1, y: 2 };
const expectedDistance = 0;
const actualDistance = distance(p0, p0);
expect(actualDistance).toBe(expectedDistance);
});
test('should return the correct distance for negative points', () => {
const p0 = { x: -2, y: -3 };
const p1 = { x: 1, y: 2 };
const expectedDistance = 5.83;
const actualDistance = distance(p0, p1);
expect(actualDistance).toBeCloseTo(expectedDistance);
});
});
describe('pointsEqual', () => {
test('should return true for identical points', () => {
const p0 = { x: 1, y: 2 };
const p1 = { x: 1, y: 2 };
expect(pointsEqual(p0, p1)).toBe(true);
});
test('should return false for non-idential points', () => {
const p0 = { x: 3, y: 2 };
const p1 = { x: 5, y: 3 };
expect(pointsEqual(p0, p1)).toBe(false);
});
test('should return false for different x coordinates', () => {
const p0 = { x: 1, y: 2 };
const p1 = { x: 2, y: 2 };
expect(pointsEqual(p0, p1)).toBe(false);
});
test('should return false for different y coordinates', () => {
const p0 = { x: 1, y: 2 };
const p1 = { x: 1, y: 3 };
expect(pointsEqual(p0, p1)).toBe(false);
});
});
describe("manhattanDistance", () => {
test("should return correct distance for points on the same axis", () => {
const p0 = { x: 1, y: 0 };
const p1 = { x: 1, y: 2 };
expect(manhattanDistance(p0, p1)).toBe(2);
});
test("should return correct distance for points on different axes", () => {
const p0 = { x: 1, y: 0 };
const p1 = { x: 3, y: 2 };
expect(manhattanDistance(p0, p1)).toBe(4);
});
test("should return correct distance for negative points", () => {
const p0 = { x: -2, y: 0 };
const p1 = { x: 1, y: -2 };
expect(manhattanDistance(p0, p1)).toBe(5);
});
test("should return correct distance for identical points", () => {
const p0 = { x: 1, y: 2 };
const p1 = { x: 1, y: 2 };
expect(manhattanDistance(p0, p1)).toBe(0);
});
});
describe('pathOverlaps', () => {
test('should throw an error if the path does not have 2 entries', () => {
const path: Path = [
[0, 0, 0, 1, 0]
];
const time = 0;
expect(() => pathOverlaps(path, time)).toThrowError('Invalid path: [[0,0,0,1,0]]');
});
test('should return true if the time is within the path', () => {
const path: Path = [
[0, 0, 0, 1, 1],
[0, 2, 0, 1, 2]
];
const time = 1.5;
expect(pathOverlaps(path, time)).toBe(true);
});
test('should return false if the time is before the start of the path', () => {
const path: Path = [
[0, 0, 0, 1, 1],
[0, 2, 0, 1, 2]
];
const time = 0.5;
expect(pathOverlaps(path, time)).toBe(false);
});
test('should return false if the time is after the end of the path', () => {
const path: Path = [
[0, 0, 0, 1, 1],
[0, 2, 0, 1, 2]
];
const time = 2.5;
expect(pathOverlaps(path, time)).toBe(false);
});
});
describe('pathPosition', () => {
test('should throw an error if the path does not have 2 entries', () => {
const path: Path = [
[0, 0, 0, 1, 0]
];
const time = 0;
expect(() => pathPosition(path, time)).toThrowError('Invalid path: [[0,0,0,1,0]]');
});
test('returns the first point when time is less than the start time', () => {
const path: Path = [
[1, 2, 3, 4, 2],
[5, 6, 3, 4, 3]
];
const result = pathPosition(path, 1);
expect(result.position).toEqual({ x: 1, y: 2 });
expect(result.facing).toEqual({ dx: 3, dy: 4 });
expect(result.velocity).toBe(0);
});
test('returns the last point when time is greater than the end time', () => {
const path: Path = [
[1, 2, 3, 4, 2],
[5, 6, 3, 4, 3]
];
const result = pathPosition(path, 4);
expect(result.position).toEqual({ x: 5, y: 6 });
expect(result.facing).toEqual({ dx: 3, dy: 4 });
expect(result.velocity).toBe(0);
});
test('returns the interpolated point for time between two segments', () => {
const path: Path = [
[1, 2, 7, 8, 2],
[5, 6, 7, 8, 3],
[10, 11, 7, 8, 4],
[14, 15, 7, 8, 5]
];
const result = pathPosition(path, 4.5);
expect(result.position).toEqual({ x: 12, y: 13 });
expect(result.facing).toEqual({ dx: 7, dy: 8 });
expect(result.velocity).toBeCloseTo(5.657);
});
});
describe('vector', () => {
test('should return a vector with dx = 1 and dy = 2', () => {
const p0 = { x: 1, y: 2 };
const p1 = { x: 2, y: 4 };
const expected = { dx: 1, dy: 2 };
const actual = vector(p0, p1);
expect(actual).toEqual(expected);
});
test('should return a vector with dx = 0 and dy = 0', () => {
const p0 = { x: 1, y: 2 };
const p1 = { x: 1, y: 2 };
const expected = { dx: 0, dy: 0 };
const actual = vector(p0, p1);
expect(actual).toEqual(expected);
});
test('should return a vector with dx = 0 and dy = -1', () => {
const p0 = { x: 1, y: 2 };
const p1 = { x: 1, y: 1 };
const expected = { dx: 0, dy: -1 };
const actual = vector(p0, p1);
expect(actual).toEqual(expected);
});
});
describe('vectorLength', () => {
test('returns the correct length for a vector', () => {
const vector: Vector = { dx: 3.14, dy: 4 };
expect(vectorLength(vector)).toBeCloseTo(5.09);
});
test('returns the correct length for a vector with negative components', () => {
const vector: Vector = { dx: -3, dy: -4 };
expect(vectorLength(vector)).toBeCloseTo(5);
});
test('returns the correct length for a vector with zero components', () => {
const vector: Vector = { dx: 0, dy: 0 };
expect(vectorLength(vector)).toBeCloseTo(0);
});
});
describe('normalize', () => {
test('should return null for vector length less than EPSILON', () => {
const vector: Vector = { dx: 0, dy: 0 };
const result = normalize(vector);
expect(result).toBeNull();
});
test('should return a normalized vector', () => {
const vector: Vector = { dx: 3, dy: 4 };
const result = normalize(vector);
expect(result).toEqual({ dx: 0.6, dy: 0.8 });
});
});
describe('orientationDegrees', () => {
test('should throw an error for a vector length smaller than EPSILON', () => {
expect(() => orientationDegrees({ dx: 0, dy: 0 })).toThrowError("Can't compute the orientation of too small vector {\"dx\":0,\"dy\":0}");
});
test('should return 0 for a vector pointing to the right', () => {
expect(orientationDegrees({ dx: 1, dy: 0 })).toBe(0);
});
test('should return 90 for a vector pointing up', () => {
expect(orientationDegrees({ dx: 0, dy: 1 })).toBe(90);
});
test('should return 180 for a vector pointing to the left', () => {
expect(orientationDegrees({ dx: -1, dy: 0 })).toBe(180);
});
test('should return 270 for a vector pointing down', () => {
expect(orientationDegrees({ dx: 0, dy: -1 })).toBe(270);
});
});
describe('compressPath', () => {
test('should not compress a path with only 2 entries', () => {
const facing = { dx: 0, dy: 1 };
const compressed = compressPath([
{ position: { x: 0, y: 0 }, facing, t: 0 },
{ position: { x: 0, y: 1 }, facing, t: 1 },
]);
expect(compressed).toEqual([
[0, 0, 0, 1, 0],
[0, 1, 0, 1, 1],
]);
});
test('should compress a line', () => {
const facing = { dx: 0, dy: 1 };
const compressed = compressPath([
{ position: { x: 0, y: 0 }, facing, t: 0 },
{ position: { x: 0, y: 1 }, facing, t: 1 },
{ position: { x: 0, y: 2 }, facing, t: 2 },
{ position: { x: 0, y: 3 }, facing, t: 3 },
{ position: { x: 0, y: 4 }, facing, t: 4 },
]);
expect(compressed).toEqual([
[0, 0, 0, 1, 0],
[0, 4, 0, 1, 4],
]);
});
test('should compress a line with a turn', () => {
const facingUp = { dx: 0, dy: 1 };
const facingRight = { dx: 1, dy: 0 };
const compressed = compressPath([
{ position: { x: 0, y: 0 }, facing: facingUp, t: 0 },
{ position: { x: 0, y: 1 }, facing: facingUp, t: 1 },
{ position: { x: 0, y: 2 }, facing: facingRight, t: 2 },
{ position: { x: 1, y: 2 }, facing: facingRight, t: 3 },
{ position: { x: 2, y: 2 }, facing: facingRight, t: 4 },
]);
expect(compressed).toEqual([
[0, 0, 0, 1, 0],
[0, 2, 1, 0, 2],
[2, 2, 1, 0, 4],
]);
});
});