MVP checks / mvp (push) Waiting to run
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.
71 lines
4.0 KiB
JavaScript
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]};
|
|
}
|