player/src/engine/voxelModelCodec.js
МИН 87444ee2c8 Initial public release: Rublox Player v1.0
Open-source web player for Rublox games, dual-licensed under
AGPL-3.0 + Commercial.

Highlights:
- Babylon.js 7 + React 18 + Vite 5 stack
- Self-contained engine (~46k lines): BlockManager, ModelManager,
  PlayerController, ScriptSandboxWorker, MultiplayerSync, 30+ GD
  gamemodes
- Configurable backend via VITE_API_BASE and friends — works against
  staging (dev-api.rublox.pro) out of the box
- Standalone mode (VITE_STANDALONE=true) loads a bundled sample game
  for first-run without any backend
- Full docs: README, ARCHITECTURE, CONTRIBUTING, SECURITY, CHANGELOG
- Lint + format scaffolding (ESLint + Prettier + EditorConfig)
- Legal: LICENSE (AGPL-3.0), LICENSE-COMMERCIAL.md, CLA.md, COPYRIGHT.md
- Issue templates: bug_report, feature_request, security_disclosure

Removed before public release:
- frontend_deploy.py (contained production SSH credentials)
- ~27 admin endpoints (kept in private repo)
- Hard-coded internal URLs and IPs
- All previous git history (clean repo init)
2026-05-27 23:04:04 +03:00

137 lines
5.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.

/**
* voxelModelCodec — кодек воксельных моделей пользователя.
*
* ПРОБЛЕМА которую решает:
* Старый формат v2 хранил каждый воксель как
* {"x":2,"y":0,"z":24,"c":"#7c5430"} ≈ 36 байт/воксель.
* Модель в 7000+ вокселей = 250+ КБ JSON. Парсинг, передача по сети
* и постановка в сцену — медленные. Редактор грузил модель >10 сек.
*
* ФОРМАТ v3 (компактный):
* {
* "version": 3,
* "size": 48,
* "palette": ["#7c5430", "a06a3a", "t:grass", ...], // уникальные материалы
* "data": [coordIdx, palIdx, coordIdx, palIdx, ...] // плоский массив чисел
* }
* - coordIdx = x + y*size + z*size*size — одно число вместо трёх полей.
* - palIdx — индекс в palette. Цвета/текстуры дедуплицированы.
* - Цвет в палитре: строка "#rrggbb" или "rrggbb" (# опционально).
* Текстура: строка с префиксом "t:" — например "t:grass".
* ≈ 7-9 байт/воксель — в 4-5 раз компактнее v2.
*
* СОВМЕСТИМОСТЬ:
* decodeVoxelModel() читает И v3, И v2 (старые модели), И v1 (только цвет).
* encodeVoxelModel() всегда пишет v3.
* Внутреннее представление — массив { x, y, z, c?, t? } (как было в v2),
* чтобы остальной код (рендер, физика) не переписывать.
*/
/** Нормализовать hex-цвет к виду "#rrggbb" (lowercase, с #). */
function normHex(hex) {
if (typeof hex !== 'string') return '#ffffff';
let h = hex.trim().toLowerCase();
if (h[0] === '#') h = h.slice(1);
if (!/^[0-9a-f]{6}$/.test(h)) return '#ffffff';
return '#' + h;
}
/**
* Декодировать model_data (строка JSON или объект) в нормализованную форму.
* @returns {{ size:number, voxels:Array<{x,y,z,c?,t?}> } | null}
*/
export function decodeVoxelModel(modelData) {
let data;
try {
data = typeof modelData === 'string' ? JSON.parse(modelData) : modelData;
} catch (e) {
return null;
}
if (!data || typeof data !== 'object') return null;
const size = (typeof data.size === 'number' && data.size > 0) ? (data.size | 0) : 32;
// --- Формат v3: палитра + плоский массив ---
if (data.version === 3 && Array.isArray(data.palette) && Array.isArray(data.data)) {
const palette = data.palette;
const flat = data.data;
const s2 = size * size;
const voxels = [];
for (let i = 0; i + 1 < flat.length; i += 2) {
const coord = flat[i] | 0;
const palIdx = flat[i + 1] | 0;
const mat = palette[palIdx];
if (mat == null) continue;
const x = coord % size;
const y = ((coord / size) | 0) % size;
const z = (coord / s2) | 0;
if (typeof mat === 'string' && mat.startsWith('t:')) {
voxels.push({ x, y, z, t: mat.slice(2) });
} else {
voxels.push({ x, y, z, c: normHex(mat) });
}
}
return { size, voxels };
}
// --- Формат v2 / v1: список объектов {x,y,z,c|t} ---
if (Array.isArray(data.voxels)) {
const voxels = [];
for (const v of data.voxels) {
if (!v || typeof v.x !== 'number') continue;
const x = v.x | 0, y = v.y | 0, z = v.z | 0;
if (v.t) voxels.push({ x, y, z, t: v.t });
else voxels.push({ x, y, z, c: normHex(v.c || '#ffffff') });
}
return { size, voxels };
}
return null;
}
/**
* Закодировать модель в компактный формат v3.
* @param {number} size — размер сетки (8/16/32/48/64/128).
* @param {Array<{x,y,z,c?,t?}>} voxels — список вокселей.
* @returns {string} JSON-строка формата v3.
*/
export function encodeVoxelModel(size, voxels) {
const sz = (typeof size === 'number' && size > 0) ? (size | 0) : 32;
const s2 = sz * sz;
const palette = [];
const palMap = new Map(); // материал-строка → индекс палитры
const flat = [];
const palIndexOf = (matStr) => {
let idx = palMap.get(matStr);
if (idx === undefined) {
idx = palette.length;
palette.push(matStr);
palMap.set(matStr, idx);
}
return idx;
};
for (const v of voxels) {
if (!v || typeof v.x !== 'number') continue;
const x = v.x | 0, y = v.y | 0, z = v.z | 0;
if (x < 0 || y < 0 || z < 0 || x >= sz || y >= sz || z >= sz) continue;
const coord = x + y * sz + z * s2;
// Материал: текстура "t:<id>" или цвет "#rrggbb" (# убираем — экономия).
let matStr;
if (v.t) {
matStr = 't:' + v.t;
} else {
matStr = normHex(v.c || '#ffffff').slice(1); // без # — короче
}
flat.push(coord, palIndexOf(matStr));
}
return JSON.stringify({
version: 3,
size: sz,
palette,
data: flat,
});
}