/** * VoxelChunk — единица хранения и рендеринга воксельного мира. * * Один чанк — куб 32×32×32 ячеек (стандарт Minecraft). * Внутри — Uint8Array(32768): индекс материала каждой ячейки в палитре слоя. * 0 = пусто (воздух) * 1..255 = индекс в palette[] слоя * * Чанк идентифицируется (cx, cy, cz) в chunk-space — целые числа. * Мировые координаты ячейки внутри чанка: * worldX = (cx * 32 + localX) * voxelSize * где voxelSize — параметр слоя (0.25 для terrain, 0.05 для deco). * * Layout: linear-ordered по Y → Z → X для cache-friendly меширования * (когда мы идём по слоям сверху вниз во время surface culling). * index = localY * 32*32 + localZ * 32 + localX * * Production assumptions: * - Чанк создаётся пустым (вся ячейка = 0) * - Прямая модификация data — допустимо, но dirty-флаг надо ставить вручную * - Когда чанк пустой (nonEmptyCount=0) — слой может удалить его из Map */ export const CHUNK_SIZE = 32; export const CHUNK_VOLUME = CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE; // 32768 export class VoxelChunk { /** * @param {number} cx - chunk X (целое) * @param {number} cy - chunk Y * @param {number} cz - chunk Z */ constructor(cx, cy, cz) { this.cx = cx; this.cy = cy; this.cz = cz; /** Uint8Array(32768) — material index каждой ячейки. */ this.data = new Uint8Array(CHUNK_VOLUME); /** Сколько занятых (non-zero) ячеек. Поддерживается setLocal. */ this.nonEmptyCount = 0; /** Нужно ли перестроить mesh при следующем frame'е. */ this.dirty = true; /** Ссылка на собранный mesh (или Map). Устанавливает * ChunkMesher/Renderer; чанк сам не знает что это. */ this.meshRef = null; /** Версия — инкрементится при каждой записи. Используется * для дельта-синка в мультиплеере (Этап 8). */ this.version = 0; } /** * Преобразовать локальные (x,y,z) в индекс data[]. * Координаты — 0..31. Без bounds check (для скорости — caller должен * сам проверять, что в диапазоне). */ static localIndex(lx, ly, lz) { return ly * CHUNK_SIZE * CHUNK_SIZE + lz * CHUNK_SIZE + lx; } /** * Прочитать material index в локальной (lx,ly,lz). Возвращает 0 если * вне границ чанка — caller обычно использует это для проверки соседей * (если мы за границей чанка → воздух, или вызывающий должен сам * проверять соседний чанк). */ getLocal(lx, ly, lz) { if (lx < 0 || lx >= CHUNK_SIZE || ly < 0 || ly >= CHUNK_SIZE || lz < 0 || lz >= CHUNK_SIZE) { return 0; } return this.data[VoxelChunk.localIndex(lx, ly, lz)]; } /** * Записать material index в локальную ячейку. * matIdx = 0 — очистка (удаление voxel'а). * Возвращает true если значение изменилось (нужна перестройка mesh'а). */ setLocal(lx, ly, lz, matIdx) { if (lx < 0 || lx >= CHUNK_SIZE || ly < 0 || ly >= CHUNK_SIZE || lz < 0 || lz >= CHUNK_SIZE) { return false; } const idx = VoxelChunk.localIndex(lx, ly, lz); const old = this.data[idx]; if (old === matIdx) return false; // Обновляем nonEmptyCount if (old === 0 && matIdx !== 0) { this.nonEmptyCount++; } else if (old !== 0 && matIdx === 0) { this.nonEmptyCount--; } this.data[idx] = matIdx; this.dirty = true; this.version++; return true; } /** Полная очистка чанка. */ clear() { if (this.nonEmptyCount === 0) return; this.data.fill(0); this.nonEmptyCount = 0; this.dirty = true; this.version++; } isEmpty() { return this.nonEmptyCount === 0; } /** * Координаты левого-нижнего-переднего угла чанка в мире, в воксельных * (не мировых!) индексах. Используется при surface culling для * вычисления глобальных координат воксел'а. */ voxelOriginX() { return this.cx * CHUNK_SIZE; } voxelOriginY() { return this.cy * CHUNK_SIZE; } voxelOriginZ() { return this.cz * CHUNK_SIZE; } } /** * Хелпер: преобразовать мировые целочисленные voxel-координаты (gx,gy,gz) * в (chunkKey, localX, localY, localZ). * gx — это grid-индекс voxel'а (как сейчас в TerrainManager.voxels Map), * не координата в метрах. Перевод в метры — отдельно: world = gx * voxelSize. */ export function voxelToChunk(gx, gy, gz) { // Math.floor вместо >>0 — потому что отрицательные voxel-координаты // должны идти в чанки -1, -2, ... а не в 0 (как делает побитовый shift). const cx = Math.floor(gx / CHUNK_SIZE); const cy = Math.floor(gy / CHUNK_SIZE); const cz = Math.floor(gz / CHUNK_SIZE); // Локальные координаты — всегда 0..31 (используем mod с защитой от // отрицательных). const lx = gx - cx * CHUNK_SIZE; const ly = gy - cy * CHUNK_SIZE; const lz = gz - cz * CHUNK_SIZE; return { cx, cy, cz, lx, ly, lz }; } /** Ключ чанка в Map (строка "cx,cy,cz"). */ export function chunkKey(cx, cy, cz) { return `${cx},${cy},${cz}`; }