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);
});
});