Spaces:
Sleeping
Sleeping
| import { useEffect, useMemo } from 'react' | |
| import { useLoader } from '@react-three/fiber' | |
| import * as THREE from 'three' | |
| import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js' | |
| import { clone as cloneSkeleton } from 'three/examples/jsm/utils/SkeletonUtils.js' | |
| import { ObjectRenderMode, type WorldObjectAsset } from '../../types/world' | |
| import { SHADED_COLOR, useAssetMaterials } from './useAssetMaterials' | |
| interface MeshMaterialState { | |
| mesh: THREE.Mesh | |
| litMaterials: THREE.Material | THREE.Material[] | |
| } | |
| interface SceneObjectVisualArgs { | |
| asset: WorldObjectAsset | |
| renderMode: ObjectRenderMode | |
| } | |
| const ignoreRaycast: THREE.Object3D['raycast'] = () => {} | |
| function cloneMaterial(material: THREE.Material | THREE.Material[]): THREE.Material | THREE.Material[] { | |
| if (Array.isArray(material)) return material.map((m) => m.clone()) | |
| return material.clone() | |
| } | |
| function asMaterialArray(material: THREE.Material | THREE.Material[]) { | |
| return Array.isArray(material) ? material : [material] | |
| } | |
| export function useSceneObjectVisual({ asset, renderMode }: SceneObjectVisualArgs) { | |
| const gltf = useLoader(GLTFLoader, asset.url) | |
| const { wireframeMaterial, shadedMaterial, wireframeOverlayMaterial } = useAssetMaterials() | |
| const { scene, wireframeOverlayScene, offset, size, bounds, materialStates } = useMemo(() => { | |
| const clonedScene = cloneSkeleton(gltf.scene) | |
| const overlayScene = cloneSkeleton(gltf.scene) | |
| const states: MeshMaterialState[] = [] | |
| clonedScene.traverse((child) => { | |
| if (!(child instanceof THREE.Mesh)) return | |
| child.castShadow = true | |
| child.receiveShadow = true | |
| child.raycast = ignoreRaycast | |
| const litMaterials = cloneMaterial(child.material) | |
| child.material = litMaterials | |
| states.push({ mesh: child, litMaterials }) | |
| }) | |
| const box = new THREE.Box3().setFromObject(clonedScene) | |
| const center = new THREE.Vector3() | |
| const size = new THREE.Vector3() | |
| box.getCenter(center) | |
| box.getSize(size) | |
| overlayScene.traverse((child) => { | |
| if (!(child instanceof THREE.Mesh)) return | |
| child.raycast = ignoreRaycast | |
| }) | |
| return { | |
| scene: clonedScene, | |
| wireframeOverlayScene: overlayScene, | |
| offset: new THREE.Vector3(-center.x, -box.min.y, -center.z), | |
| size, | |
| bounds: box.clone(), | |
| materialStates: states, | |
| } | |
| }, [gltf.scene]) | |
| useEffect(() => { | |
| wireframeOverlayScene.traverse((child) => { | |
| if (!(child instanceof THREE.Mesh)) return | |
| child.material = wireframeOverlayMaterial | |
| child.renderOrder = 1 | |
| child.raycast = ignoreRaycast | |
| }) | |
| }, [wireframeOverlayMaterial, wireframeOverlayScene]) | |
| useEffect(() => { | |
| if (renderMode === ObjectRenderMode.ShadedWireframe) { | |
| shadedMaterial.color.copy(SHADED_COLOR) | |
| } | |
| for (const state of materialStates) { | |
| if (renderMode === ObjectRenderMode.Wireframe) { | |
| state.mesh.material = wireframeMaterial | |
| continue | |
| } | |
| if (renderMode === ObjectRenderMode.ShadedWireframe) { | |
| state.mesh.material = shadedMaterial | |
| continue | |
| } | |
| state.mesh.material = state.litMaterials | |
| } | |
| }, [materialStates, renderMode, shadedMaterial, wireframeMaterial]) | |
| useEffect(() => { | |
| return () => { | |
| for (const state of materialStates) { | |
| for (const material of asMaterialArray(state.litMaterials)) { | |
| material.dispose() | |
| } | |
| } | |
| } | |
| }, [materialStates]) | |
| return { scene, wireframeOverlayScene, offset, size, bounds } | |
| } | |