import * as THREE from "three"; import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"; import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js"; type Rect = [number, number, number, number]; let atlasImage: HTMLImageElement | null = null; const modelCache = new Map(); const createAudio = (path: string) => { const audio = import.meta.client ? new Audio(path) : null; return { play: () => { if (!audio) return; audio.currentTime = 0; audio.play().catch(() => {}); }, }; }; const loaded = ref(0); const total = ref({{TOTAL}}); const isReady = computed(() => loaded.value === total.value); if (import.meta.client) { atlasImage = document.createElement('img'); atlasImage.src = '/nds/atlas.webp?v={{ATLAS_HASH}}'; if (atlasImage.complete) { loaded.value += 1; } else { atlasImage.onload = () => { loaded.value += 1; }; } } const drawAtlasImage = ( ctx: CanvasRenderingContext2D, [sx, sy, sw, sh]: Rect, [dx, dy, dw, dh]: Rect, opts?: Partial<{ colored: boolean }>, ): void => { if (!atlasImage) return; if (opts?.colored) { const app = useAppStore(); sh /= 16; sy += (app.color.row * 4 + app.color.col) * sh; dh = sh; } ctx.drawImage(atlasImage, sx, sy, sw, sh, dx, dy, dw, dh); }; const createModel = (path: string) => { const cached = modelCache.get(path); if (cached) { return cached; } const model = new THREE.Group(); modelCache.set(path, model); const dracoLoader = new DRACOLoader(); dracoLoader.setDecoderPath("/draco/"); const loader = new GLTFLoader(); loader.setDRACOLoader(dracoLoader); loader.load( path, (gltf) => { for (const child of [...gltf.scene.children]) { model.add(child); } loaded.value += 1; }, undefined, (error) => { console.error(`Error loading model ${path}:`, error); loaded.value += 1; } ); return model; }; export type AtlasImage = { draw: ( ctx: CanvasRenderingContext2D, x: number, y: number, opts?: Partial<{ colored: boolean }>, ) => void; rect: { x: number; y: number; width: number; height: number }; }; type ImageTree = { [key: string]: AtlasImage | ImageTree; }; type ModelTree = { [key: string]: THREE.Group | ModelTree; }; export type AudioEntry = ReturnType; type AudioTree = { [key: string]: AudioEntry | AudioTree; }; const assets = { images: {{IMAGES}} as const satisfies ImageTree, models: {{MODELS}} as const satisfies ModelTree, audio: {{AUDIO}} as const satisfies AudioTree, }; type Assets = typeof assets; export const useAssets = (selector: (assets: Assets) => T = (a) => a as T) => { return { assets: selector(assets), loaded, total, isReady, }; };