65 lines
2.6 KiB
TypeScript
65 lines
2.6 KiB
TypeScript
// the texture atlas: the engine's textures and every mod's in one image, built by the client when it joins a server
|
|
// from the textures in the server's .bmod files
|
|
import { TEXTURE_SIZE } from "$/common/constants.ts";
|
|
import type { SpriteRegion } from "$/common/constants.ts";
|
|
|
|
export interface AtlasLayout {
|
|
// in pixels, a power of two
|
|
size: number;
|
|
// in sprites, by texture id. engine:missing is always at 0, 0
|
|
regions: Record<string, SpriteRegion>;
|
|
}
|
|
|
|
// engine:missing first, then every other texture sorted by id, row by row
|
|
export function atlas_layout(ids: Iterable<string>): AtlasLayout {
|
|
const sorted = [...new Set(ids)].filter((id) => id !== "engine:missing").sort((a, b) => a.localeCompare(b));
|
|
const count = sorted.length + 1;
|
|
const size = 2 ** Math.ceil(Math.log2(Math.ceil(Math.sqrt(count)) * TEXTURE_SIZE));
|
|
const per_row = size / TEXTURE_SIZE;
|
|
const regions: Record<string, SpriteRegion> = { "engine:missing": { x: 0, y: 0 } };
|
|
sorted.forEach((id, i) => {
|
|
regions[id] = { x: (i + 1) % per_row, y: Math.floor((i + 1) / per_row) };
|
|
});
|
|
return { size, regions };
|
|
}
|
|
|
|
// textures are png bytes by id. ones that don't decode show as missing
|
|
export async function build_atlas(textures: Map<string, Uint8Array>) {
|
|
const { size, regions } = atlas_layout(textures.keys());
|
|
const canvas = new OffscreenCanvas(size, size);
|
|
const ctx = canvas.getContext("2d")!;
|
|
|
|
// magenta and black checker
|
|
ctx.fillStyle = "magenta";
|
|
ctx.fillRect(0, 0, 8, 8);
|
|
ctx.fillRect(8, 8, 8, 8);
|
|
ctx.fillStyle = "black";
|
|
ctx.fillRect(8, 0, 8, 8);
|
|
ctx.fillRect(0, 8, 8, 8);
|
|
|
|
await Promise.all([...textures].map(async ([id, png]) => {
|
|
const region = regions[id];
|
|
try {
|
|
const image = await createImageBitmap(new Blob([png as Uint8Array<ArrayBuffer>], { type: "image/png" }));
|
|
ctx.drawImage(image, region.x * TEXTURE_SIZE, region.y * TEXTURE_SIZE);
|
|
image.close();
|
|
} catch {
|
|
console.warn(`Texture ${id} isn't a png that can be shown, it will show as missing`);
|
|
regions[id] = regions["engine:missing"];
|
|
}
|
|
}));
|
|
|
|
return { image: canvas.transferToImageBitmap(), regions };
|
|
}
|
|
|
|
// the engine's own textures, which come with the client instead of a mod. see build_engine_textures in build.ts
|
|
export async function engine_textures(): Promise<Map<string, Uint8Array>> {
|
|
const names: string[] = await (await fetch("/assets/textures/index.json")).json();
|
|
const textures = new Map<string, Uint8Array>();
|
|
await Promise.all(names.map(async (name) => {
|
|
const response = await fetch(`/assets/textures/${name}.png`);
|
|
textures.set(`engine:${name}`, new Uint8Array(await response.arrayBuffer()));
|
|
}));
|
|
return textures;
|
|
}
|