Newer
Older
rpg / v2 / packages / engine / src / animation / __tests__ / mat.test.ts
import { describe, expect, it } from 'vitest';
import { eulerMat, composeAffine, affinePoint, rotateAround, mat3Mul } from '../mat';

const close = (a: readonly number[], b: readonly number[], eps = 1e-9): void => {
    expect(a.length).toBe(b.length);
    a.forEach((v, i) => expect(Math.abs(v - b[i])).toBeLessThan(eps));
};

describe('mat (аффинная математика анимации)', () => {
    it('поворот на 90° вокруг X: Y → Z', () => {
        const p = affinePoint({ m: eulerMat([Math.PI / 2, 0, 0]), t: [0, 0, 0] }, [0, 1, 0]);
        close(p, [0, 0, 1]);
    });

    it('поворот на 90° вокруг Z: X → Y', () => {
        const p = affinePoint({ m: eulerMat([0, 0, Math.PI / 2]), t: [0, 0, 0] }, [1, 0, 0]);
        close(p, [0, 1, 0]);
    });

    it('rotateAround: поворот вокруг точки сохраняет саму точку', () => {
        const a = rotateAround([0, 0, Math.PI], [2, 3, 1], [0, 0, 0]);
        close(affinePoint(a, [2, 3, 1]), [2, 3, 1]);
        // противоположная точка pivota уходит на противоположную сторону
        close(affinePoint(a, [3, 3, 1]), [1, 3, 1]);
    });

    it('rotateAround: сдвиг pos двигает pivot', () => {
        const a = rotateAround([0, 0, 0], [2, 3, 1], [5, 0, -2]);
        close(affinePoint(a, [2, 3, 1]), [7, 3, -1]);
        close(affinePoint(a, [0, 0, 0]), [5, 0, -2]);
    });

    it('композиция родителя и локального трансформа = последовательное применение', () => {
        const parent = rotateAround([0, 0, Math.PI / 2], [0, 0, 0], [1, 0, 0]);
        const local = rotateAround([0, 0, 0], [0, 0, 0], [0, 2, 0]);
        const combined = composeAffine(parent, local);
        const direct = affinePoint(parent, affinePoint(local, [3, 1, 0]));
        close(affinePoint(combined, [3, 1, 0]), direct);
    });

    it('mat3Mul ассоциативен на примере двух поворотов', () => {
        const rx = eulerMat([0.3, 0, 0]);
        const ry = eulerMat([0, 0.7, 0]);
        const p: [number, number, number] = [0.4, -0.9, 1.3];
        const ab = affinePoint({ m: mat3Mul(rx, ry), t: [0, 0, 0] }, p);
        const aOfb = affinePoint({ m: rx, t: [0, 0, 0] }, affinePoint({ m: ry, t: [0, 0, 0] }, p));
        close(ab, aOfb);
    });
});