import { useGLTF } from '@react-three/drei'; import { useThree } from '@react-three/fiber'; import { useEffect, useMemo, useCallback } from 'react'; import { Group } from 'three'; import type { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; import { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader.js'; import { useViewerStore } from '../../store/viewerStore'; import type { LoadedMachineAsset, MachineAssetDefinition } from '../../types/viewer'; import { getMachineAssetDefinition, isMachineAssetSupported } from '../assets/machineAssets'; import { prepareGltfPartMetadata } from '../interaction/objectPartLookup'; import { PreparedMachineRoot } from './PreparedMachineRoot'; export interface MachineAssetInstanceProps { machineId: string; } export function MachineAssetInstance({ machineId }: MachineAssetInstanceProps) { const definition = getMachineAssetDefinition(machineId); if (!definition) { return ; } if (definition.kind === 'procedural') { return ; } if (definition.kind === 'gltf' || definition.kind === 'hybrid') { return ; } return ; } function ProceduralMachineAsset({ definition }: { definition: MachineAssetDefinition }) { const setActiveMachine = useViewerStore((state) => state.setActiveMachine); const setSceneBounds = useViewerStore((state) => state.setSceneBounds); const setLoadingState = useViewerStore((state) => state.setLoadingState); const asset = useMemo(() => { if (!definition.create) { throw new Error(`Procedural machine "${definition.id}" does not provide a create() factory.`); } return definition.create(); }, [definition]); useEffect(() => { setLoadingState({ phase: 'preparing', progress: 70, label: definition.metadata.title }); setActiveMachine(definition.id, asset.parts, asset.metadata); if (asset.metadata.approximateBounds) { setSceneBounds(asset.metadata.approximateBounds); } setLoadingState({ phase: 'ready', progress: 100, label: definition.metadata.title }); return () => { asset.dispose?.(); }; }, [asset, definition.id, definition.metadata.title, setActiveMachine, setLoadingState, setSceneBounds]); return ; } function GltfMachineAsset({ definition }: { definition: MachineAssetDefinition & { url?: string } }) { const gl = useThree((state) => state.gl); const setActiveMachine = useViewerStore((state) => state.setActiveMachine); const setSceneBounds = useViewerStore((state) => state.setSceneBounds); const setLoadingState = useViewerStore((state) => state.setLoadingState); if (!definition.url) { throw new Error(`GLTF machine "${definition.id}" is missing a url.`); } const extendLoader = useCallback( (loader: GLTFLoader) => { if (!definition.ktx2TranscoderPath) { return; } const ktx2Loader = new KTX2Loader() .setTranscoderPath(definition.ktx2TranscoderPath) .detectSupport(gl); loader.setKTX2Loader(ktx2Loader); }, [definition.ktx2TranscoderPath, gl] ); const gltf = useGLTF( definition.url, definition.dracoPath ?? false, definition.meshopt ?? false, extendLoader ) as GLTF; const asset: LoadedMachineAsset = useMemo(() => { const root = new Group(); root.name = `${definition.id}_GLTFRoot`; root.userData.assetKind = definition.kind; root.userData.machineId = definition.id; const clonedScene = gltf.scene.clone(true); prepareGltfPartMetadata(clonedScene, definition.metadata.parts, definition.partNameMap); root.add(clonedScene); return { root, metadata: definition.metadata, parts: definition.metadata.parts, source: { kind: definition.kind, url: definition.url } }; }, [definition, gltf.scene]); useEffect(() => { setLoadingState({ phase: 'ready', progress: 100, label: definition.metadata.title }); setActiveMachine(definition.id, asset.parts, asset.metadata); if (definition.metadata.approximateBounds) { setSceneBounds(definition.metadata.approximateBounds); } }, [asset, definition, setActiveMachine, setLoadingState, setSceneBounds]); return ; } function UnsupportedAssetRegistration({ machineId }: { machineId: string }) { const setActiveMachine = useViewerStore((state) => state.setActiveMachine); const setLoadingState = useViewerStore((state) => state.setLoadingState); useEffect(() => { if (!isMachineAssetSupported(machineId)) { setActiveMachine(machineId, [], null); setLoadingState({ phase: 'error', progress: 0, label: machineId, error: `No viewer asset has been registered for "${machineId}".` }); } }, [machineId, setActiveMachine, setLoadingState]); return null; }