'use client' import { Suspense, useEffect } from 'react' import { Canvas } from '@react-three/fiber' import { Stage, OrbitControls } from '@react-three/drei' import { useLoader } from '@react-three/fiber' import type { Material, Mesh, Texture } from 'three' import { TextureLoader } from 'three' import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js' interface OpacityMapEntry { target: string url: string } function resolveAssetUrl(requestedUrl: string, assetUrls: Record) { if (requestedUrl.startsWith('blob:') || requestedUrl.startsWith('data:')) { return requestedUrl } const cleanUrl = decodeURIComponent(requestedUrl.split(/[?#]/)[0] || '') const filename = cleanUrl.split(/[\\/]/).pop()?.toLowerCase() return filename ? assetUrls[filename] || requestedUrl : requestedUrl } function getOpacityMapEntries(assetUrls: Record) { return Object.entries(assetUrls).reduce((entries, [filename, url]) => { const match = filename.toLowerCase().match(/^opacity(?:[_-](.+))?\.(png|jpe?g|webp)$/) if (!match) return entries entries.push({ target: match[1] || '', url }) return entries }, []) } function getMaterialName(material: Material) { return material.name.toLowerCase() } function getObjectName(mesh: Mesh) { return mesh.name.toLowerCase() } function applyAlphaMap(material: Material, texture: Texture) { if (!('alphaMap' in material)) return texture.flipY = false material.alphaMap = texture material.transparent = true material.alphaTest = 0.01 material.needsUpdate = true } function pickOpacityMap( mesh: Mesh, material: Material, entries: OpacityMapEntry[], textures: Texture[], ) { const objectName = getObjectName(mesh) const materialName = getMaterialName(material) const targetedIndex = entries.findIndex((entry) => ( entry.target && (objectName.includes(entry.target) || materialName.includes(entry.target)) )) if (targetedIndex >= 0) return textures[targetedIndex] const genericIndex = entries.findIndex((entry) => entry.target === '') if (genericIndex >= 0) return textures[genericIndex] return entries.length === 1 ? textures[0] : undefined } function Model({ url, assetUrls }: { url: string; assetUrls: Record }) { const { scene } = useLoader(GLTFLoader, url, (loader) => { loader.manager.setURLModifier((requestedUrl) => resolveAssetUrl(requestedUrl, assetUrls)) }) const opacityMapEntries = getOpacityMapEntries(assetUrls) const opacityMaps = useLoader(TextureLoader, opacityMapEntries.map((entry) => entry.url)) as Texture[] useEffect(() => { if (opacityMapEntries.length === 0) return scene.traverse((object) => { const mesh = object as Mesh if (!mesh.isMesh || !mesh.material) return const materials = Array.isArray(mesh.material) ? mesh.material : [mesh.material] materials.forEach((material) => { const opacityMap = pickOpacityMap(mesh, material, opacityMapEntries, opacityMaps) if (opacityMap) applyAlphaMap(material, opacityMap) }) }) }, [scene, opacityMapEntries, opacityMaps]) return } export default function SceneViewer({ url, assetUrls }: { url: string; assetUrls: Record }) { return ( ) }