/**
* Манекен — эталонная модель для рига и анимации (docs/plan.md, трек 6).
* Деревянный болванчик 11×21×4 (масштаб мира UNIT=10: герой ~2 тайла
* ростом — на кубической голове/торсе/конечностях силуэт читается):
* ноги (P4), торс (R1), голова (P6), руки (R2 — ржавые, видны в махе).
* Части известны при генерации, поэтому привязка к костям передаётся
* явно (bindNearest здесь не нужен: торс рядом с плечами и Voronoi
* по pivot отнёс бы его к рукам).
*/
import { VoxelGrid } from '../voxel/grid';
import { encodeModel } from './format';
import type { VoxelModel } from './format';
import { makeRig } from '../animation/rig';
import type { VoxelRig } from '../animation/rig';
// Слоты арт-библии: как в генераторах деревьев/валунов
const TORSO = 4; // R1 — тёмное дерево
const LEG = 6; // P4 — камень (ноги-колонны)
const HEAD = 7; // P6 — свет
const ARM = 8; // R2 — ржавчина (махи рук заметны)
export const MANNEQUIN_BONES = ['root', 'legL', 'legR', 'armL', 'armR'] as const;
export type MannequinBone = (typeof MANNEQUIN_BONES)[number];
export interface Mannequin {
model: VoxelModel;
rig: VoxelRig;
}
/** Детерминированный манекен с ригой: root → legL/legR/armL/armR. */
export function mannequin(): Mannequin {
const g = new VoxelGrid(11, 21, 4);
// привязка: индекс вокселя → индекс кости (root=0, legL=1, legR=2, armL=3, armR=4)
const binding = new Array<number>(g.data.length).fill(-1);
const put = (x: number, y: number, z: number, slot: number, bone: number): void => {
g.set(x, y, z, slot);
binding[x + g.sx * (y + g.sy * z)] = bone;
};
// ноги: колонны 2×2 (x3..4 и x6..7), y=0..8 — между ними просвет
for (let y = 0; y <= 8; y++)
for (let z = 1; z <= 2; z++)
for (let dx = 0; dx <= 1; dx++) {
put(3 + dx, y, z, LEG, 1);
put(6 + dx, y, z, LEG, 2);
}
// торс: x=3..7, y=9..15; голова: x=4..6, y=16..20 (куб на плечах)
for (let y = 9; y <= 15; y++)
for (let z = 1; z <= 2; z++)
for (let x = 3; x <= 7; x++) put(x, y, z, TORSO, 0);
for (let y = 16; y <= 20; y++)
for (let z = 1; z <= 2; z++)
for (let x = 4; x <= 6; x++) put(x, y, z, HEAD, 0);
// руки: колонны 2×2 по бокам торса, y=9..14 (свободно висят)
for (let y = 9; y <= 14; y++)
for (let z = 1; z <= 2; z++)
for (let dx = 0; dx <= 1; dx++) {
put(1 + dx, y, z, ARM, 3);
put(8 + dx, y, z, ARM, 4);
}
const model = encodeModel(g, { 4: '#5c3b2e', 6: '#5b5b66', 7: '#94949e', 8: '#82503a' });
const rig = makeRig(
[
{ name: 'root', parent: -1, pivot: [5.5, 0, 2] }, // земля, центр пят
{ name: 'legL', parent: 0, pivot: [4, 9, 2] }, // бедро левой
{ name: 'legR', parent: 0, pivot: [7, 9, 2] }, // бедро правой
{ name: 'armL', parent: 0, pivot: [2, 15, 2] }, // плечо левой
{ name: 'armR', parent: 0, pivot: [9, 15, 2] }, // плечо правой
],
model,
binding,
);
return { model, rig };
}