115 lines
3.6 KiB
JavaScript
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;
|
|
} |