New modding system
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
// 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;
|
||||
}
|
||||
Reference in New Issue
Block a user