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;
}