// ==================== ОККЛЮЗИЯ 3D — плотная сетка ==================== // Границы мира фиксированы генератором: WORLD_DEPTH = 48 по X и Z (то есть // gx,gz из -48..48), а по Y генерация идёт от BEDROCK_GY-1 = 44 вниз до 0. // 97 * 45 * 97 = 423 855 клеток — 414 КБ в Uint8Array. // // Зачем: isHiddenAt проверяла 6 соседей через getBlock, а тот строил строку // "gx,gy,gz" и искал в Map. На 68k кандидатов это ~409k строковых // конкатенаций — профиль показал 129 мс из 153 мс всего перестроения мешей. // Здесь 6 чтений массива вместо 6 строковых lookup'ов. // // ВАЖНО: сетку обязаны обновлять все, кто меняет мир — setBlock/removeBlock // в world-storage3d.js. Иначе окклюзия разойдётся с реальными блоками и // внутренние грани начнут просвечивать (или исчезнут видимые блоки). import { BLOCKS } from '../data/blocks.js'; export const OCC_X = 2 * 48 + 1; // 97 export const OCC_Z = 2 * 48 + 1; export const OCC_Y = 45; // gy 0..44 export const OCC_MIN = -48, OCC_MAX = 48, OCC_MAX_GY = 44; // Значения ячеек export const OCC_EMPTY = 0; // блока нет export const OCC_PRESENT = 1; // блок есть, но он прозрачный/жидкий/декор export const OCC_OPAQUE = 2; // непрозрачный блок — закрывает соседей const occGrid = new Uint8Array(OCC_X * OCC_Y * OCC_Z); // Раскладка: (gy*OCC_Z + (gz-OCC_MIN))*OCC_X + (gx-OCC_MIN) // => шаг по gx = 1, по gz = OCC_X, по gy = OCC_X*OCC_Z export const OCC_STEP_X = 1; export const OCC_STEP_Z = OCC_X; export const OCC_STEP_Y = OCC_X * OCC_Z; export function occIndex(gx, gy, gz) { if (gx < OCC_MIN || gx > OCC_MAX || gz < OCC_MIN || gz > OCC_MAX || gy < 0 || gy > OCC_MAX_GY) return -1; return (gy * OCC_Z + (gz - OCC_MIN)) * OCC_X + (gx - OCC_MIN); } // Плотность блока для оклюзии export function occDensity(t) { const def = BLOCKS[t]; if (!def) return OCC_EMPTY; const opaque = !!def.solid && !def.fluid && !def.decor && !def.climbable && def.alpha === undefined; return opaque ? OCC_OPAQUE : OCC_PRESENT; } export function occSet(gx, gy, gz, density) { const i = occIndex(gx, gy, gz); if (i >= 0) occGrid[i] = density; } // Пересобрать сетку целиком из массива блоков. Нужна при загрузке сейва и // после полной генерации — там setBlock вызывается с bumpWorld=false, но // запись в сетку всё равно идёт, так что это страховка на случай, когда // массив блоков был подменён снаружи. export function occRebuildFrom(blocksArr) { occGrid.fill(OCC_EMPTY); for (let i = 0; i < blocksArr.length; i++) { const b = blocksArr[i]; if (b.dead) continue; const idx = occIndex(b.gx, b.gy, b.gz || 0); if (idx >= 0) occGrid[idx] = occDensity(b.t); } } export function occClear() { occGrid.fill(OCC_EMPTY); }