import { describe, expect, it } from 'vitest';
import { orbitPosition, modelStats, modelCenter } from '../modelViewer';
import { mannequin } from '../../models/mannequin';
import { VoxelGrid } from '../../voxel/grid';
import { encodeModel } from '../../models/format';
describe('ModelViewer — чистые части (урок №3)', () => {
it('орбита: yaw 0 — камера по +x, pitch 0 — на горизонтали', () => {
expect(orbitPosition(0, 0, 10, [0, 4, 0])).toEqual([10, 4, 0]);
// pitch π/2 — строго над целью
const top = orbitPosition(0.7, Math.PI / 2, 12, [1, 2, 3]);
expect(top[0]).toBeCloseTo(1, 6);
expect(top[1]).toBeCloseTo(14, 6);
expect(top[2]).toBeCloseTo(3, 6);
// yaw π/2 — по +z; дистанция масштабирует горизонтальные составляющие
const p = orbitPosition(Math.PI / 2, 0, 5, [0, 0, 0]);
expect(p[0]).toBeCloseTo(0, 6);
expect(p[2]).toBeCloseTo(5, 6);
});
it('статы манекена: воксели/кости/тайли считаются по сетке', () => {
const { model, rig } = mannequin();
const s = modelStats(model, rig);
expect(s.size).toEqual([10, 16, 4]);
expect(s.voxels).toBe(164);
expect(s.bones).toBe(5);
// 16 тайлей лица + 3 скина (трек 29)
expect(s.tiles).toBe(19);
// без риги — 0 костей
expect(modelStats(model).bones).toBe(0);
});
it('modelCenter — центр bbox занятых вокселей, не угол сетки', () => {
// куб 2×2×2, сдвинутый в угол сетки 10×10×10 — центр (1,1,1)
const g = new VoxelGrid(10, 10, 10);
for (let x = 0; x <= 1; x++) for (let y = 0; y <= 1; y++) for (let z = 0; z <= 1; z++) g.set(x, y, z, 1);
expect(modelCenter(encodeModel(g, { 1: '#fff' }))).toEqual([1, 1, 1]);
// асимметричная модель: занят x∈[4..5] — центр 2.5 по оси со сдвигом
const g2 = new VoxelGrid(6, 4, 2);
g2.set(1, 0, 0, 1);
g2.set(2, 2, 1, 1);
expect(modelCenter(encodeModel(g2, { 1: '#fff' }))).toEqual([2, 1.5, 1]);
// пустая модель — центр размера сетки
expect(modelCenter(encodeModel(new VoxelGrid(4, 6, 2), {}))).toEqual([2, 3, 1]);
});
});