Spaces:
Sleeping
Sleeping
File size: 4,405 Bytes
1829efb | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | import { useMemo, useEffect } from 'react'
import { RigidBody } from '@react-three/rapier'
import { useGLTF } from '@react-three/drei'
import { clone as cloneSkeleton } from 'three/examples/jsm/utils/SkeletonUtils.js'
import * as THREE from 'three'
import { useDebugStore } from '../../store/debug'
import { ObjectRenderMode, WorldRenderMode } from '../../types/world'
import { useAssetMaterials } from '../scene/useAssetMaterials'
import { DROP_TARGET_LAYER } from '../scene/dropTargets'
import { shadowCatcherColor, shadowCatcherOpacity } from '../scene/shadows'
interface Props {
url: string
flipY?: boolean
groundPlaneOffset?: number
metricScaleFactor?: number
shadowOpacity?: number
shadowColor?: string
}
const ignoreRaycast: THREE.Object3D['raycast'] = () => {}
export function WorldCollider({ url, flipY, groundPlaneOffset, metricScaleFactor, shadowOpacity, shadowColor }: Props) {
const { scene: rawScene } = useGLTF(url)
const objectRenderMode = useDebugStore((s) => s.objectRenderMode)
const worldRenderMode = useDebugStore((s) => s.worldRenderMode)
const { wireframeMaterial, shadedMaterial, wireframeOverlayMaterial } = useAssetMaterials()
const normalizedGroundPlaneOffset = groundPlaneOffset ?? 0
const normalizedMetricScaleFactor = metricScaleFactor ?? 1
const normalizedRotation = flipY ? Math.PI : 0
const colliderTransformKey = `${url}:${normalizedRotation}:${normalizedGroundPlaneOffset}:${normalizedMetricScaleFactor}`
// Own shadow material instance — not shared, so shader compiles correctly per-mesh
const shadowMat = useMemo(() => new THREE.ShadowMaterial({
color: shadowCatcherColor(shadowColor),
opacity: shadowCatcherOpacity(shadowOpacity),
transparent: true,
depthWrite: false,
}), [shadowColor, shadowOpacity])
useEffect(() => () => shadowMat.dispose(), [shadowMat])
useEffect(() => {
shadowMat.color.set(shadowCatcherColor(shadowColor))
shadowMat.opacity = shadowCatcherOpacity(shadowOpacity)
shadowMat.needsUpdate = true
}, [shadowColor, shadowMat, shadowOpacity])
const { scene, overlayScene, dropTargetScene } = useMemo(() => {
const dropTargetScene = cloneSkeleton(rawScene)
dropTargetScene.traverse((child) => {
child.layers.set(DROP_TARGET_LAYER)
})
return {
scene: cloneSkeleton(rawScene),
overlayScene: cloneSkeleton(rawScene),
dropTargetScene,
}
}, [rawScene])
const showMesh = worldRenderMode !== WorldRenderMode.ObjectOnly
useEffect(() => {
const isShadowCatcher = objectRenderMode === ObjectRenderMode.Lit
scene.traverse((child) => {
if (!(child instanceof THREE.Mesh)) return
child.visible = showMesh
child.raycast = ignoreRaycast
child.receiveShadow = isShadowCatcher
if (child.material !== wireframeMaterial && child.material !== shadedMaterial && child.material !== shadowMat) {
const old = Array.isArray(child.material) ? child.material : [child.material]
old.forEach((m) => m?.dispose?.())
}
child.material = isShadowCatcher ? shadowMat
: objectRenderMode === ObjectRenderMode.ShadedWireframe ? shadedMaterial
: wireframeMaterial
child.material.needsUpdate = true
})
}, [scene, showMesh, objectRenderMode, wireframeMaterial, shadedMaterial, shadowMat])
useEffect(() => {
overlayScene.traverse((child) => {
if (!(child instanceof THREE.Mesh)) return
child.material = wireframeOverlayMaterial
child.renderOrder = 1
child.raycast = ignoreRaycast
})
}, [overlayScene, wireframeOverlayMaterial])
return (
<>
<RigidBody
key={colliderTransformKey}
type="fixed"
colliders="trimesh"
rotation={[normalizedRotation, 0, 0]}
position={[0, normalizedGroundPlaneOffset, 0]}
scale={[normalizedMetricScaleFactor, normalizedMetricScaleFactor, normalizedMetricScaleFactor]}
>
<primitive object={scene} />
{objectRenderMode === ObjectRenderMode.ShadedWireframe && showMesh && (
<primitive object={overlayScene} />
)}
</RigidBody>
<primitive
object={dropTargetScene}
rotation={[normalizedRotation, 0, 0]}
position={[0, normalizedGroundPlaneOffset, 0]}
scale={[normalizedMetricScaleFactor, normalizedMetricScaleFactor, normalizedMetricScaleFactor]}
/>
</>
)
}
|