import * as THREE from "three"; import type { MediaItem } from "./types"; const textureCache = new Map(); const loadCallbacks = new Map void>>(); const loader = new THREE.TextureLoader(); const isTextureLoaded = (tex: THREE.Texture): boolean => { const img = tex.image as HTMLImageElement | undefined; return img instanceof HTMLImageElement && img.complete && img.naturalWidth > 0; }; export const getTexture = (item: MediaItem, onLoad?: (texture: THREE.Texture) => void): THREE.Texture => { const key = item.url; const existing = textureCache.get(key); if (existing) { if (onLoad) { if (isTextureLoaded(existing)) { onLoad(existing); } else { loadCallbacks.get(key)?.add(onLoad); } } return existing; } const callbacks = new Set<(tex: THREE.Texture) => void>(); if (onLoad) callbacks.add(onLoad); loadCallbacks.set(key, callbacks); const texture = loader.load( key, (tex) => { tex.minFilter = THREE.LinearMipmapLinearFilter; tex.magFilter = THREE.LinearFilter; tex.generateMipmaps = true; tex.anisotropy = 4; tex.colorSpace = THREE.SRGBColorSpace; tex.needsUpdate = true; loadCallbacks.get(key)?.forEach((cb) => { try { cb(tex); } catch (err) { console.error(`Callback failed: ${JSON.stringify(err)}`); } }); loadCallbacks.delete(key); }, undefined, (err) => console.error("Texture load failed:", key, err) ); textureCache.set(key, texture); return texture; };