Files
Emil c7e86663d8
MVP checks / mvp (push) Waiting to run
Expand voxel gameplay, lighting, full-height streaming and world imports
Add shared Rust/WASM physics, worker meshing and diagnostics, 64-chunk full-height streaming, atlas texture support, and baseline world import. Document the current implementation and include the supplied in-game lobby screenshot.
2026-09-17 02:10:53 +03:00

71 lines
4.0 KiB
JavaScript

// Declarative data only: the same resolved map is structured-cloned to mesh workers.
export const BLOCK_FACES = "@block_faces";
/** An edit job only needs face definitions for the materials in its neighborhood. */
export function textureSubset(layers, materials) {
const mapping = layers.get(BLOCK_FACES);
if (!mapping) return layers;
const result = new Map(layers), sets = [], states = {}, defaults = {}, indices = new Map();
const include = old => {
if (!Number.isInteger(old)) return undefined;
if (!indices.has(old)) { indices.set(old, sets.length); sets.push(mapping.sets[old]); }
return indices.get(old);
};
for (const {state} of materials.values()) {
if (typeof state !== "string") continue;
const base = state.split("[")[0];
if (Object.hasOwn(mapping.states, state)) states[state] = include(mapping.states[state]);
if (Object.hasOwn(mapping.defaults, base)) defaults[base] = include(mapping.defaults[base]);
}
result.set(BLOCK_FACES, {sets, states, defaults});
return result;
}
export function prepareTexturePack(descriptor, files, limits) {
const size = descriptor?.pixel_size, entries = descriptor?.textures, atlas = descriptor?.atlas;
if (!Number.isInteger(size) || size < 1 || size > 128 || !Array.isArray(entries) || !entries.length ||
entries.length > (atlas ? 16384 : Math.min(256, limits.layers)))
throw Error("Некорректный размер текстурпака.");
let width = size, height = size;
if (atlas) {
if (![atlas.columns, atlas.rows].every(v => Number.isInteger(v) && v > 0) ||
atlas.columns * atlas.rows < entries.length || !files.has(atlas.path))
throw Error("Некорректный атлас текстурпака.");
width *= atlas.columns; height *= atlas.rows;
if (width > limits.size || height > limits.size) throw Error("Атлас превышает лимит видеокарты.");
}
const layers = new Map();
for (const entry of entries) {
if (!entry || typeof entry.name !== "string" || !/^[a-z0-9_]+$/.test(entry.name) || layers.has(entry.name) || !atlas && !files.has(entry.path))
throw Error("Некорректный ресурс текстурпака.");
layers.set(entry.name, layers.size);
}
const mapping = descriptor.block_faces;
if (mapping) {
if (!Array.isArray(mapping.sets) || mapping.sets.length > 65536 || !mapping.states || !mapping.defaults ||
Object.keys(mapping.states).length > 100000) throw Error("Некорректная таблица граней.");
const sets = mapping.sets.map(faces => {
if (!Array.isArray(faces) || faces.length !== 6) throw Error("Ожидалось шесть граней.");
return faces.map(face => {
if (face === null) return null;
const tint = face.tint ?? [1, 1, 1], uv = face.uv;
if (!layers.has(face.texture) || !Array.isArray(tint) || tint.length !== 3 ||
!tint.every(v => Number.isFinite(v) && v >= 0 && v <= 1) ||
uv !== undefined && (!Array.isArray(uv) || uv.length !== 8 || !uv.every(v => Number.isFinite(v) && Math.abs(v) <= 256)))
throw Error("Некорректная текстура грани.");
return {layer: layers.get(face.texture), tint, cutout: face.cutout === true, ...(uv ? {uv} : {})};
});
});
for (const table of [mapping.states, mapping.defaults])
for (const [state, index] of Object.entries(table))
if (state.length > 1024 || !Number.isInteger(index) || index < 0 || index >= sets.length)
throw Error("Некорректное состояние в таблице граней.");
layers.set(BLOCK_FACES, {sets, states: mapping.states, defaults: mapping.defaults});
}
const overlay = descriptor.grass_overlay;
if (overlay && (!atlas || !layers.has(overlay.base) || !layers.has(overlay.overlay)))
throw Error("Некорректный слой травы.");
return {layers, width, height, atlas, size, entries,
grassOverlay: overlay ? [layers.get(overlay.base), layers.get(overlay.overlay)] : [-1, -1]};
}