Files
wireless-docs/assets.js
2026-07-04 10:43:11 -04:00

110 lines
3.5 KiB
JavaScript

// assets.js
const jsonCache = new Map();
/**
* Resolves a namespace:path ID to a file path.
* Defaults to the 'minecraft' namespace if none is provided.
*/
export function resolveResourceLocation(id, type, ext = 'json') {
const parts = id.split(':');
const namespace = parts.length > 1 ? parts[0] : 'minecraft';
const path = parts.length > 1 ? parts[1] : parts[0];
return `assets/${namespace}/${type}/${path}.${ext}`;
}
async function fetchJSON(url) {
if (jsonCache.has(url)) return jsonCache.get(url);
const res = await fetch(url);
if (!res.ok) throw new Error(`Failed to load asset: ${url}`);
const data = await res.json();
jsonCache.set(url, data);
return data;
}
export async function loadBlockstate(id) {
const url = resolveResourceLocation(id, 'blockstates');
return fetchJSON(url);
}
export async function loadModel(id) {
const url = resolveResourceLocation(id, 'models');
const model = await fetchJSON(url);
// Recursively resolve and merge parent models
if (model.parent) {
// Parents sometimes use block/ or item/ prefixes directly
const parentId = model.parent.includes(':') ? model.parent : `minecraft:${model.parent}`;
const parent = await loadModel(parentId);
// Merge textures
model.textures = {...parent.textures, ...model.textures};
// Inherit elements if child doesn't override them
if (!model.elements) model.elements = parent.elements;
}
return model;
}
export class TextureAtlas {
constructor(size = 512) {
this.canvas = document.createElement('canvas');
this.canvas.width = size;
this.canvas.height = size;
this.ctx = this.canvas.getContext('2d', {willReadFrequently: true});
this.ctx.imageSmoothingEnabled = false;
this.map = new Map();
this.cellSize = 16;
this.padding = 2; // Add empty space between textures
this.x = 0;
this.y = 0;
this.rowHeight = 0;
}
async load(id) {
if (this.map.has(id)) return this.map.get(id);
const url = resolveResourceLocation(id, 'textures', 'png');
// Include padding in the wrap calculation
if (this.x + this.cellSize > this.canvas.width) {
this.x = 0;
this.y += this.rowHeight + this.padding;
this.rowHeight = 0;
}
const currentX = this.x;
const currentY = this.y;
try {
const img = await new Promise((resolve, reject) => {
const i = new Image();
i.crossOrigin = 'anonymous';
i.onload = () => resolve(i);
i.onerror = () => reject(new Error(url));
i.src = url;
});
this.ctx.drawImage(img, currentX, currentY, this.cellSize, this.cellSize);
} catch (e) {
console.warn(`Texture missing: ${id}`);
this.ctx.fillStyle = '#ff00ff';
this.ctx.fillRect(currentX, currentY, this.cellSize, this.cellSize);
}
// Clean UV mapping (padding protects the edges natively)
const uvData = {
u: currentX / this.canvas.width,
v: currentY / this.canvas.height,
du: this.cellSize / this.canvas.width,
dv: this.cellSize / this.canvas.height
};
this.map.set(id, uvData);
// Advance X by cell size AND padding
this.x += this.cellSize + this.padding;
this.rowHeight = Math.max(this.rowHeight, this.cellSize);
return uvData;
}
}