Newer
Older
rpg / v2 / apps / game / src / deer.ts
/**
 * Воксельный олень (безгласная фауна v1, воксельная версия): тихая
 * серо-бурая живность у прудов. Модель строится по мировому центру (cx, cz)
 * мордой вдоль +X; шаг (step) поднимает диагональные пары ног — грубый, но
 * живой walk-цикл. Направление (dir) — поворот на четверти оборота вокруг Y,
 * олень смотрит туда, куда идёт.
 */
import { GROUND_Y } from './areas';

/** Воксель позы: позиция в мире + цвет-строка (как у VoxelCloud). */
export interface DeerVoxel {
    pos: [number, number, number];
    color: string;
}

const BODY = '#6e6156'; // серо-бурый бок
const DARK = '#54493f'; // копыта, нос, тень брюха
const LIGHT = '#7d7264'; // спина, хвост
const ANTLER = '#8a7a66'; // рога (сухое дерево)

/**
 * Поворот смещения на dir×90° вокруг Y: dir 0 — морда +X,
 * 1 — +Z, 2 — −X, 3 — −Z (модель симметрична по бокам, зеркала не нужно).
 */
export function rotXZ(dx: number, dz: number, dir: number): [number, number] {
    for (let i = 0; i < (dir & 3); i++) {
        const t = dx;
        dx = -dz;
        dz = t;
    }
    return [dx, dz];
}

/**
 * Олень в мировых координатах (редизайн трека 29): ноги на земле
 * (GROUND_Y), высота ~14 с рогами (герой — 16). Силуэт олений: грудь
 * спереди выше, круп сзади, шея ступеньками вверх, голова с мордой и
 * ушами, ветвистые рога, светлые пятна на боках. step — фаза шага (0/1),
 * поднимает диагональные пары ног; dir — направление взгляда (см. rotXZ).
 */
export function deerVoxels(cx: number, cz: number, step: boolean, dir = 0): DeerVoxel[] {
    const out: DeerVoxel[] = [];
    const put = (dx: number, dy: number, dz: number, color: string) => {
        const [rx, rz] = rotXZ(dx, dz, dir);
        out.push({ pos: [cx + rx, dy, cz + rz], color });
    };
    // Ноги 1×1 с копытами: диагональные пары (перед-лево + зад-право)
    // поднимаются на шаге (бедро BODY, низ DARK).
    const lift = step ? 2 : 0;
    for (const [lx, lz] of [[-5, -1], [4, 1], [-5, 1], [4, -1]] as const) {
        const up = (lx === -5) === (lz === -1) ? lift : 0; // диагональ шагает
        put(lx, GROUND_Y + up, lz, DARK); // копыто
        put(lx, GROUND_Y + 1 + up, lz, DARK);
        put(lx, GROUND_Y + 2 + up, lz, BODY);
        put(lx, GROUND_Y + 3 + up, lz, BODY);
    }
    // Туловище 10×3×5: брюхо тёмное, бок пятнистый, спина светлая;
    // круп сзади (x<0) на воксель выше груди — олений профиль.
    for (let dx = -5; dx <= 4; dx++)
        for (let dz = -2; dz <= 2; dz++) {
            put(dx, GROUND_Y + 4, dz, Math.abs(dz) === 2 ? DARK : BODY); // брюхо/бока
            const side = (dx + dz) % 3 === 0 ? LIGHT : BODY; // пятна оленёнка
            put(dx, GROUND_Y + 5, dz, side);
            put(dx, GROUND_Y + 6, dz, LIGHT); // спина
        }
    for (let dz = -1; dz <= 1; dz++) put(-5, GROUND_Y + 7, dz, LIGHT); // круп выше
    // Шея ступеньками вверх от груди.
    for (let dz = -1; dz <= 1; dz++) {
        put(4, GROUND_Y + 7, dz, BODY);
        put(5, GROUND_Y + 8, dz, BODY);
        put(5, GROUND_Y + 9, dz, BODY);
    }
    // Голова 3×2×3: морда к +X, нос тёмный, уши в стороны.
    for (let dx = 5; dx <= 6; dx++)
        for (let dz = -1; dz <= 1; dz++) {
            put(dx, GROUND_Y + 10, dz, BODY);
            put(dx, GROUND_Y + 11, dz, LIGHT);
        }
    put(7, GROUND_Y + 10, 0, DARK); // нос
    put(5, GROUND_Y + 11, -2, DARK); // уши
    put(5, GROUND_Y + 11, 2, DARK);
    // Рога ветвистые: ствол вверх, отросток в сторону.
    for (const sz of [-1, 1] as const) {
        put(5, GROUND_Y + 12, sz, ANTLER);
        put(5, GROUND_Y + 13, sz, ANTLER);
        put(6, GROUND_Y + 14, sz, ANTLER);
        put(4, GROUND_Y + 13, sz * 2, ANTLER); // отросток
    }
    // Хвост.
    put(-6, GROUND_Y + 6, 0, LIGHT);
    return out;
}