nc-planner/scripts/atlas.js

115 lines
3.6 KiB
JavaScript

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