import { describe, expect, it } from 'vitest';
import { walkClip } from '../locomotion';
import { samplePose, validateClip, footMinY } from '../clip';
import { makeRig } from '../rig';
import type { VoxelModel } from '../../models/format';
const PARAMS = { duration: 0.8, legSwing: 0.5, armSwing: 0.3, bob: 0.2, sway: 0.1 };
describe('walkClip (процедурная локомоция)', () => {
it('детерминирован', () => {
expect(JSON.stringify(walkClip(PARAMS))).toBe(JSON.stringify(walkClip(PARAMS)));
});
it('проходит числовую валидацию (цикл замкнут, ключи монотонны)', () => {
expect(validateClip(walkClip(PARAMS))).toEqual([]);
});
it('ноги в противофазе, рука против одноимённой ноги', () => {
const clip = walkClip(PARAMS);
const q = samplePose(clip, PARAMS.duration * 0.25); // левая нога в махе
expect(q.legL?.rot?.[0]).toBeCloseTo(PARAMS.legSwing);
expect(q.legR?.rot?.[0]).toBeCloseTo(-PARAMS.legSwing);
expect(q.armL?.rot?.[0]).toBeCloseTo(-PARAMS.armSwing);
expect(q.armR?.rot?.[0]).toBeCloseTo(PARAMS.armSwing);
});
it('корень: в контакте y=0, в полном шаге приседает, качается вбок', () => {
const clip = walkClip(PARAMS);
const contact = samplePose(clip, 0);
expect(contact.root?.pos?.[1]).toBeCloseTo(0);
const stride = samplePose(clip, PARAMS.duration * 0.25);
expect(stride.root?.pos?.[1]).toBeCloseTo(-PARAMS.bob);
expect(stride.root?.pos?.[0]).toBeCloseTo(PARAMS.sway);
const strideR = samplePose(clip, PARAMS.duration * 0.75);
expect(strideR.root?.pos?.[0]).toBeCloseTo(-PARAMS.sway);
});
it('контакт стопы: в опоре стопа на земле, в махе поднята', () => {
// манекен-заглушка: нога 5 вокселей вниз от pivot на y=5
const model: VoxelModel = {
size: [1, 6, 1],
palette: { 6: '#5b5b66' },
data: btoa(String.fromCharCode(6, 6, 6, 6, 6, 6)),
};
const rig = makeRig(
[
{ name: 'root', parent: -1, pivot: [0.5, 0, 0.5] },
{ name: 'legL', parent: 0, pivot: [0.5, 5, 0.5] },
],
model,
[1, 1, 1, 1, 1, 0],
);
const clip = walkClip({ ...PARAMS, legSwing: 0.5 });
const ground = footMinY(rig, model, clip, 'legL', 0);
const swing = footMinY(rig, model, clip, 'legL', PARAMS.duration * 0.25);
expect(ground).toBeCloseTo(0.5, 2); // стопа на земле (центр нижнего вокселя 0.5)
// мах поднимает стопу (с поправкой на приседание корня −bob в этой же фазе)
expect(swing).toBeGreaterThan(ground + PARAMS.legSwing * 0.5);
});
});