studio/src/editor/engine/voxel/VoxelChunk.js
МИН 31adbf151b Initial public release: Студия Рублокса v1.0
Open-source веб-студия для создания игр Рублокса, двойная лицензия
AGPL-3.0 + Коммерческая.

Главное:
- Vite 5 + React 18 + Babylon 7.54.3 + Monaco Editor + Colyseus 0.16
- Самодостаточный движок ~28к строк (66 файлов): BlockManager,
  TerrainVoxelBuilder, ModelManager, DecoManager, PlayerController,
  ScriptSandboxWorker, MultiplayerSync, 30+ GD-гейммодов
- Главный редактор KubikonEditor (~37к строк) + панели, ScriptEditor (Monaco)
- Витрина игр (KubikonFeed, KubikonStudio, KubikonDocs, KubikonLearn)
- Geometry Dash sub-app (GdMenu, GdShop, GdRules, GdCoverArt)
- 10 admin-preview каталогов для дизайнеров (скины, музыка, порталы и т.д.)
- Конфигурируемый бэкенд через VITE_API_BASE — работает со staging
  (dev-api.rublox.pro) без настройки
- Standalone-режим (VITE_STANDALONE=true) — открыть пустой редактор без бэка
- Полная документация (на русском): README, ARCHITECTURE, CONTRIBUTING,
  SECURITY, CHANGELOG
- ESLint + Prettier + EditorConfig
- Legal: LICENSE (AGPL-3.0), LICENSE-COMMERCIAL.md, CLA.md, COPYRIGHT.md
- Issue templates: bug_report, feature_request, security_disclosure

Перед публикацией:
- Все импорты из minecraftia заменены на локальные
- Все хардкоды URL (minecraftia-school.ru) и внутренних IP убраны → env
- Admin-эндпоинты Kubikon3DService вырезаны (остаются в приватном репо)
- AdminKubikonModeration не публикуется (модерация — в team.rublox.pro)
- 93 МБ ассетов public/kubikon-assets вынесены в .gitignore
  (раздаются через release artifact)
2026-05-27 23:41:10 +03:00

148 lines
6.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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<matIdx, Mesh>). Устанавливает
* 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}`;
}