Newer
Older
rpg / v2 / packages / engine / src / models / stamp.ts
/**
 * Штамповка модели в мир: (0,0,0) модели → заданная мировая точка.
 * Клиппинг: воксели вне вертикали мира и вне опционального прямоугольника
 * (half-open [min, max)) пропускаются. Возвращает число поставленных
 * вокселей; dirty-трекинг мира обычный (remeshDirty демки подхватит).
 */
import { VoxelWorld } from '../world/world';
import { decodeModel } from './format';
import type { VoxelModel } from './format';

export interface StampClip {
    /** half-open границы клиппинга в мировых координатах. */
    minX?: number; minY?: number; minZ?: number;
    maxX?: number; maxY?: number; maxZ?: number;
}

export function stampModel(
    world: VoxelWorld, model: VoxelModel, x: number, y: number, z: number,
    clip: StampClip = {},
): number {
    const grid = decodeModel(model);
    const inClip = (wx: number, wy: number, wz: number): boolean =>
        (clip.minX === undefined || wx >= clip.minX) && (clip.maxX === undefined || wx < clip.maxX) &&
        (clip.minY === undefined || wy >= clip.minY) && (clip.maxY === undefined || wy < clip.maxY) &&
        (clip.minZ === undefined || wz >= clip.minZ) && (clip.maxZ === undefined || wz < clip.maxZ);
    let placed = 0;
    for (let ly = 0; ly < grid.sy; ly++)
        for (let lz = 0; lz < grid.sz; lz++)
            for (let lx = 0; lx < grid.sx; lx++) {
                const slot = grid.get(lx, ly, lz);
                if (slot === 0) continue;
                const wx = x + lx, wy = y + ly, wz = z + lz;
                if (!inClip(wx, wy, wz)) continue;
                if (world.set(wx, wy, wz, slot)) placed++;
            }
    return placed;
}