/**
* Воксельный олень (безгласная фауна 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;
}