130 lines
2.8 KiB
TypeScript
130 lines
2.8 KiB
TypeScript
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<string, THREE.Group>();
|
|
|
|
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<typeof createAudio>;
|
|
|
|
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 = <T = Assets>(selector: (assets: Assets) => T = (a) => a as T) => {
|
|
return {
|
|
assets: selector(assets),
|
|
loaded,
|
|
total,
|
|
isReady,
|
|
};
|
|
};
|