Newer
Older
rpg / v2 / packages / engine / src / models / mannequin.ts
/**
 * Манекен — эталонная модель для рига и анимации (docs/plan.md, трек 6).
 * Деревянный болванчик 5×11×3: ноги (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(5, 11, 3);
    // привязка: индекс вокселя → индекс кости (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;
    };
    // ноги: колонны x=1 и x=3, y=0..4
    for (let y = 0; y <= 4; y++) {
        put(1, y, 1, LEG, 1);
        put(3, y, 1, LEG, 2);
    }
    // торс: x=1..3, y=5..8; голова: x=2, y=9..10
    for (let y = 5; y <= 8; y++)
        for (let x = 1; x <= 3; x++) put(x, y, 1, TORSO, 0);
    put(2, 9, 1, HEAD, 0);
    put(2, 10, 1, HEAD, 0);
    // руки: колонны x=0 и x=4, y=5..8 (свободно висят)
    for (let y = 5; y <= 8; y++) {
        put(0, y, 1, ARM, 3);
        put(4, y, 1, ARM, 4);
    }
    const model = encodeModel(g, { 4: '#5c3b2e', 6: '#5b5b66', 7: '#94949e', 8: '#82503a' });
    const rig = makeRig(
        [
            { name: 'root', parent: -1, pivot: [2.5, 0, 1.5] },   // земля, центр пят
            { name: 'legL', parent: 0, pivot: [1.5, 5, 1.5] },    // бедро левой
            { name: 'legR', parent: 0, pivot: [3.5, 5, 1.5] },    // бедро правой
            { name: 'armL', parent: 0, pivot: [0.5, 9, 1.5] },    // плечо левой
            { name: 'armR', parent: 0, pivot: [4.5, 9, 1.5] },    // плечо правой
        ],
        model,
        binding,
    );
    return { model, rig };
}