Newer
Older
rpg / packages / engine / src / math / __tests__ / shapes.test.ts
import { describe, it, expect } from 'vitest';
import { inCircle, inCone, angleBetween, nearest } from '../shapes';

describe('inCircle', () => {
    it('точка внутри и на границе', () => {
        const c = { x: 0, y: 0 };
        expect(inCircle(c, 10, { x: 3, y: 4 })).toBe(true);
        expect(inCircle(c, 5, { x: 3, y: 4 })).toBe(true); // ровно 5
        expect(inCircle(c, 5, { x: 3, y: 5 })).toBe(false);
        expect(inCircle(c, 0, { x: 0, y: 0 })).toBe(true);
    });

    it('точка вне центра', () => {
        expect(inCircle({ x: 100, y: 50 }, 10, { x: 0, y: 0 })).toBe(false);
        // расстояние sqrt(50² + 30²) ≈ 58.3
        expect(inCircle({ x: 100, y: 50 }, 60, { x: 50, y: 20 })).toBe(true);
    });
});

describe('inCone', () => {
    const from = { x: 0, y: 0 };
    const dir = { x: 1, y: 0 }; // смотрит вправо
    const halfAngle = Math.PI / 4; // 45°

    it('внутри конуса', () => {
        expect(inCone(from, dir, 50, halfAngle, { x: 30, y: 10 })).toBe(true);
        expect(inCone(from, dir, 50, halfAngle, { x: 30, y: -10 })).toBe(true);
    });

    it('за пределами дальности', () => {
        expect(inCone(from, dir, 50, halfAngle, { x: 60, y: 0 })).toBe(false);
    });

    it('за границей угла («слепая зона» за спиной)', () => {
        expect(inCone(from, dir, 50, halfAngle, { x: 30, y: 40 })).toBe(false);
        expect(inCone(from, dir, 50, halfAngle, { x: -30, y: 0 })).toBe(false);
    });

    it('вблизи границы угла и радиуса', () => {
        // чуть внутри границы угла 45° (tan 45°=1)
        expect(inCone(from, dir, 50, halfAngle, { x: 10, y: 9.9 })).toBe(true);
        // чуть снаружи
        expect(inCone(from, dir, 50, halfAngle, { x: 10, y: 10.1 })).toBe(false);
        // расстояние ровно 50 — граница радиуса включена
        expect(inCone(from, dir, 50, halfAngle, { x: 50, y: 0 })).toBe(true);
        expect(inCone(from, dir, 50, halfAngle, { x: 50.1, y: 0 })).toBe(false);
    });

    it('нулевое направление — только дальность', () => {
        expect(inCone(from, { x: 0, y: 0 }, 50, halfAngle, { x: 0, y: -40 })).toBe(true);
        expect(inCone(from, { x: 0, y: 0 }, 50, halfAngle, { x: 60, y: 0 })).toBe(false);
    });
});

describe('angleBetween', () => {
    it('известные углы', () => {
        expect(angleBetween({ x: 1, y: 0 }, { x: 0, y: 1 })).toBeCloseTo(Math.PI / 2);
        expect(angleBetween({ x: 1, y: 0 }, { x: -1, y: 0 })).toBeCloseTo(Math.PI);
        expect(angleBetween({ x: 2, y: 0 }, { x: 5, y: 0 })).toBeCloseTo(0);
    });

    it('нулевой вектор даёт 0 (без NaN)', () => {
        expect(angleBetween({ x: 0, y: 0 }, { x: 1, y: 0 })).toBe(0);
        expect(angleBetween({ x: 1, y: 0 }, { x: 0, y: 0 })).toBe(0);
    });
});

describe('nearest', () => {
    it('выбирает ближайшую', () => {
        const list = [
            { x: 50, y: 0 },
            { x: 10, y: 0 },
            { x: 30, y: 0 }
        ];
        expect(nearest(list, { x: 0, y: 0 })).toEqual({ x: 10, y: 0 });
    });

    it('учитывает maxRange', () => {
        const list = [{ x: 100, y: 0 }];
        expect(nearest(list, { x: 0, y: 0 }, 50)).toBeNull();
        expect(nearest(list, { x: 0, y: 0 }, 100)).toEqual({ x: 100, y: 0 });
    });

    it('пустой список — null', () => {
        expect(nearest([], { x: 0, y: 0 })).toBeNull();
    });
});