Newer
Older
rpg / v2 / apps / demo / src / main.ts
/**
 * Демо-полигон v2: воксельная сценка (пепельный луг) под скриншот-гейтом.
 * Мир строится в воксельных единицах, проекция — только в VoxelRenderer.
 * Палитра — слоты арт-библии v1 (docs/art-style.md); тёплый цвет — только жизнь.
 */
import { VoxelGrid, VoxelRenderer, meshVoxels } from '@rpg/engine';

// Слоты палитры арт-библии: индексы сцены → hex
const PALETTE = {
    colors: {
        1: '#4a5340', // G1 — базовый луг
        2: '#343b2c', // G0 — тень травы (нижний слой)
        3: '#626c4c', // G2 — трава, свет (пятна)
        4: '#5c3b2e', // R1 — стволы/крыша тень
        5: '#7d8662', // G2H — крона, свет
        6: '#5b5b66', // P4 — камень, стены
        7: '#94949e', // P6 — свет камня
        8: '#82503a', // R2 — светлая ржавчина (крыша)
        9: '#d99a32', // B1 — цветок (жизнь!)
        10: '#44454e', // M1 — металл
    },
};

const S = 32; // сторона площадки

/** Сценка: земля, деревья, дом, валун, цветы. Возвращает меш для рендера. */
function buildScene(): ReturnType<typeof meshVoxels> {
    const g = new VoxelGrid(S, 10, S);
    // земля: два слоя, пятна светлой травы сверху
    for (let x = 0; x < S; x++)
        for (let z = 0; z < S; z++) {
            g.set(x, 0, z, 2);
            const patch = (x * 7 + z * 13) % 11 < 3; // детерминированные пятна
            g.set(x, 1, z, patch ? 3 : 1);
        }
    for (const [tx, tz, h] of [[6, 8, 4], [24, 12, 5], [10, 25, 4], [26, 26, 4]] as const) {
        tree(g, tx, tz, h);
    }
    house(g, 15, 6);
    boulder(g, 22, 21);
    for (const [fx, fz] of [[9, 17], [13, 21], [20, 16], [27, 9]] as const) {
        g.set(fx, 2, fz, 9); // цветок — единственный тёплый цвет
    }
    return meshVoxels(g, PALETTE);
}

/** Дерево: ствол + двухслойная крона со светлым верхом. */
function tree(g: VoxelGrid, x: number, z: number, h: number): void {
    for (let y = 2; y < 2 + h; y++) g.set(x, y, z, 4);
    const top = 2 + h;
    for (let dy = 0; dy < 2; dy++)
        for (let dx = -1; dx <= 1; dx++)
            for (let dz = -1; dz <= 1; dz++) {
                g.set(x + dx, top + dy, z + dz, dy === 0 ? 3 : 5); // низ кроны темнее
            }
    g.set(x, top + 2, z, 5);
}

/** Дом: стены P4 со светлыми пилястрами, двускатная крыша R2. */
function house(g: VoxelGrid, x: number, z: number): void {
    const w = 6, d = 5, hWall = 3;
    for (let i = 0; i < w; i++)
        for (let j = 0; j < d; j++) {
            const wall = i === 0 || j === 0 || i === w - 1 || j === d - 1;
            for (let y = 2; y < 2 + hWall; y++) {
                if (wall) g.set(x + i, y, z + j, (i + j) % 3 === 0 ? 7 : 6);
                else g.set(x + i, y, z + j, 10); // интерьер темнее (виден сквозь проёмы)
            }
        }
    // дверной проём с юга
    for (let y = 2; y < 4; y++) g.set(x + 3, y, z, 0);
    // крыша: три сужающиеся ступени (скат вверх к коньку)
    for (let t = 0; t < 3; t++) {
        const j0 = z + t, j1 = z + d - 1 - t;
        for (let i = 0; i < w; i++)
            for (let j = j0; j <= j1; j++) g.set(x + i, 2 + hWall + t, j, t === 2 ? 4 : 8);
    }
}

/** Валун: неровная глыба, свет сверху. */
function boulder(g: VoxelGrid, x: number, z: number): void {
    const blobs = [[0, 0, 0], [1, 0, 0], [0, 0, 1], [1, 0, 1], [0, 1, 0], [1, 1, 0]];
    for (const [dx, dy, dz] of blobs) g.set(x + dx, 2 + dy, z + dz, dy ? 7 : 6);
}

const mesh = buildScene();
const canvas = document.getElementById('scene') as HTMLCanvasElement;
const renderer = new VoxelRenderer(canvas, { target: [S / 2, 2, S / 2], viewSize: 15 });
renderer.addMesh(mesh);

// Агентный мост (только DEV-поведение): солнце + статистика меша
let sunK = 0.5;
(window as unknown as Record<string, unknown>).__agent = {
    setSun(k: number): void { sunK = k; renderer.setSun(k); },
    stats(): { vertices: number; triangles: number; sun: number } {
        return { vertices: mesh.vertexCount, triangles: mesh.triangleCount, sun: sunK };
    },
};

function frame(): void {
    renderer.render();
    requestAnimationFrame(frame);
}
frame();
(window as unknown as Record<string, unknown>).__beacon = true;