// 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]}; }