import { emitEvent } from "./events.js"; let atlasSize = 0; // in items let atlasResolution = 0; // in px export let atlasBlobURL = "about:blank"; export let atlasMetadata = []; async function getTextureSize(data) { const texture = data.texture; if (!texture) { data.size = [ 0, 0 ]; return; } await new Promise(resolve => { const image = new Image(); image.addEventListener("load", () => { data.size = [ image.width, image.height ]; data.image = image; resolve(); }); image.src = `data:image/png;base64,${texture}`; }); } function putTextureOnCanvas(ctx, atlasSize, index, data) { const atlasIndexX = index % atlasSize; const atlasIndexY = (index - atlasIndexX) / atlasSize; const x = atlasIndexX * data.size[0]; const y = atlasIndexY * data.size[1]; ctx.drawImage(data.image, x, y); // free up image data delete data.image; } export async function buildAtlas(data) { emitEvent("config:process:atlas:start"); const overhaulSFRBlocks = [ ...data.configuration["nuclearcraft:overhaul_sfr"]?.blocks, ...data.addons.flatMap(a => a.configuration["nuclearcraft:overhaul_sfr"]?.blocks) ] .filter(b => b && b.modules && b.modules["plannerator:texture"]) .map(b => ( { ...b, texture: b.modules["plannerator:texture"].texture, } )); if (overhaulSFRBlocks.length === 0) { throw new Error("Configuration processing failed - no textured blocks found"); } await Promise.all(overhaulSFRBlocks.map(getTextureSize)); // TODO: handle configurations with unique sizes if (new Set(overhaulSFRBlocks.map(v => v.size.toString())).size > 1) { throw new Error("Configuration processing failed - unable to handle multi-sized textures yet"); } if (overhaulSFRBlocks[0].size[0] !== overhaulSFRBlocks[0].size[1]) { throw new Error("Configuration processing failed - textures must be square"); } // TODO: handle sizes other than 16px if (overhaulSFRBlocks[0].size[0] !== 16) { throw new Error("Configuration processing failed - textures must be 16px"); } const atlasEdgeSizeBlocks = 2 ** Math.ceil(Math.log2(Math.sqrt(overhaulSFRBlocks.length))); const atlasEdgeSize = atlasEdgeSizeBlocks * overhaulSFRBlocks[0].size[0]; const canvas = new OffscreenCanvas(atlasEdgeSize, atlasEdgeSize); const ctx = canvas.getContext("2d"); await Promise.all(overhaulSFRBlocks.map((b, i) => putTextureOnCanvas(ctx, atlasEdgeSizeBlocks, i, b))); const blob = await canvas.convertToBlob({ type: "image/png" }); atlasSize = atlasEdgeSizeBlocks; atlasResolution = atlasEdgeSize; atlasBlobURL = URL.createObjectURL(blob); atlasMetadata = overhaulSFRBlocks; console.log("Atlas blob: %s", atlasBlobURL); emitEvent("config:process:atlas:end"); } export function makeAtlasImage(index) { if (index < -1) { return null; } if (index === -1) { const div = document.createElement("div"); div.classList.add("atlas-air"); return div; } const atlasIndexX = index % atlasSize; const atlasIndexY = (index - atlasIndexX) / atlasSize; const size = atlasMetadata[index].size; const x = atlasIndexX * size[0]; const y = atlasIndexY * size[1]; const div = document.createElement("div"); div.classList.add("atlas-texture"); div.style.backgroundImage = `url(${atlasBlobURL})`; div.style.setProperty("--atlas-resolution", `${atlasResolution}px`); div.style.setProperty("--raw-x", `${x}px`); div.style.setProperty("--raw-y", `${y}px`); div.style.setProperty("--size-x", `${size[0]}px`); div.style.setProperty("--size-y", `${size[1]}px`); return div; }