151 lines
4.3 KiB
JavaScript
151 lines
4.3 KiB
JavaScript
import { emitEvent } from "./events.js";
|
|
|
|
let atlasSize = 0; // in items
|
|
let atlasResolution = 0; // in px
|
|
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) {
|
|
emitEvent("config:process:atlas:start");
|
|
const textures = [
|
|
...Object.entries(data.configuration),
|
|
...data.addons.flatMap(a => Object.entries(a.configuration)),
|
|
]
|
|
.flatMap(([config, data]) => Object.entries(data)
|
|
.map(([type, entries]) => [config, type, entries]))
|
|
.flatMap(([c, t, e]) => Array.isArray(e)
|
|
? [[c, t, e]]
|
|
: t === "modules" && e["plannerator:global_elements"]
|
|
? [[c, "plannerator:global_elements", e["plannerator:global_elements"].elements]]
|
|
: [])
|
|
.flatMap(([c, t, l]) => l.map((v) => [c, t, v]))
|
|
.filter(([,, b]) => b && b.modules && b.modules["plannerator:texture"])
|
|
.map(([c, t, b]) => ({
|
|
configuration: c,
|
|
configurationList: t,
|
|
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;
|
|
}
|