nc-planner/scripts/atlas.js

170 lines
5.0 KiB
JavaScript

import { emitEvent } from "./events.js";
import { checkEquivalence } from "./ncpf-utils.js";
export let atlases = [];
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) {
// TODO: atlas caching
emitEvent("config:process:atlas:start");
const textures = [
...Object.entries(data.configuration),
...data.addons.flatMap(a => Object.entries(a.configuration)),
]
.map(([t, d]) => [t, d.modules["plannerator:configuration_metadata"], d])
// .map(v => {console.log(v); return v})
.flatMap(([config, addon, data]) => Object.entries(data)
.map(([type, entries]) => [config, type, addon, entries]))
.flatMap(([c, t, a, e]) => Array.isArray(e)
? [[c, t, a, e]]
: t === "modules" && e["plannerator:global_elements"]
? [[c, "plannerator:global_elements", a, e["plannerator:global_elements"].elements]]
: [])
.flatMap(([c, t, a, l]) => l.map((v) => [c, t, a, v]))
.filter(([,,, b]) => b && b.modules && b.modules["plannerator:texture"])
.map(([c, t, a, b]) => ({
configuration: c,
configurationList: t,
configurationAddon: a,
texture: b.modules["plannerator:texture"].texture,
...(Object.fromEntries(Object.entries(b).filter(([k]) => k !== "modules")))
}));
if (textures.length === 0) {
throw new Error("Configuration processing failed - no textured blocks found");
}
await Promise.all(textures.map(getTextureSize));
atlases = [];
let offset = 0;
for (const t of Object.values(Object.groupBy(textures, t => t.size.toString()))) {
if (t[0].size[0] !== t[0].size[1]) {
throw new Error("Configuration processing failed - textures must be square");
}
const atlasEdgeSizeBlocks = 2 ** Math.ceil(Math.log2(Math.sqrt(t.length)));
const atlasEdgeSize = atlasEdgeSizeBlocks * t[0].size[0];
const canvas = new OffscreenCanvas(atlasEdgeSize, atlasEdgeSize);
const ctx = canvas.getContext("2d");
await Promise.all(t.map((b, i) => putTextureOnCanvas(ctx, atlasEdgeSizeBlocks, i, b)));
t.forEach((m, i) => {
m.index = offset + i;
delete m.texture;
});
const blob = await canvas.convertToBlob({ type: "image/png" });
atlases.push({
offset,
url: URL.createObjectURL(blob),
metadata: t,
size: atlasEdgeSizeBlocks,
resolution: atlasEdgeSize,
length: t.length,
});
offset += t.length;
}
console.log(atlases);
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 originalIndex = index;
for (const atlas of atlases) {
if (index < atlas.length) {
const atlasIndexX = index % atlas.size;
const atlasIndexY = (index - atlasIndexX) / atlas.size;
const size = atlas.metadata[index].size;
const x = atlasIndexX * size[0];
const y = atlasIndexY * size[1];
const div = document.createElement("div");
div.draggable = false;
div.dataset.originalIndex = originalIndex;
div.classList.add("atlas-texture");
div.style.backgroundImage = `url(${atlas.url})`;
div.style.setProperty("--atlas-resolution", `${atlas.resolution}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;
}
index -= atlas.length;
}
return null;
}
export function getAtlasMetadata(index) {
if (index < -1) return null;
if (index === -1) return {};
for (const atlas of atlases) {
if (index < atlas.length - 1) {
return atlas.metadata[index];
}
index -= atlas.length;
}
return null;
}
export function getAtlasIndexFromMetadata(metadata, configuration, configurationList = null) {
let index = 0;
for (const atlas of atlases) {
for (const atlasItemMeta of atlas.metadata) {
if (checkEquivalence(metadata, atlasItemMeta)
&& atlasItemMeta.configuration === configuration
&& (!configurationList || atlasItemMeta.configurationList === configurationList)) {
return index;
}
index++;
}
}
return -1;
}