({
rotationSpeed = 0.1,
wireframe = false,
opacity = 1,
tint,
}: BVR1ModelProps)
| 13 | } |
| 14 | |
| 15 | export function BVR1Model({ |
| 16 | rotationSpeed = 0.1, |
| 17 | wireframe = false, |
| 18 | opacity = 1, |
| 19 | tint, |
| 20 | }: BVR1ModelProps) { |
| 21 | const { scene } = useGLTF("/models/bvr1_assembly_realistic.glb"); |
| 22 | const groupRef = useRef<THREE.Group>(null); |
| 23 | const rotationRef = useRef<THREE.Group>(null); |
| 24 | |
| 25 | useEffect(() => { |
| 26 | if (!groupRef.current) return; |
| 27 | // CAD Z-up to Three.js Y-up |
| 28 | groupRef.current.rotation.x = -Math.PI / 2; |
| 29 | groupRef.current.updateMatrixWorld(true); |
| 30 | |
| 31 | // Ground the model |
| 32 | const box = new THREE.Box3().setFromObject(groupRef.current); |
| 33 | groupRef.current.position.y = -box.min.y; |
| 34 | }, [scene]); |
| 35 | |
| 36 | useEffect(() => { |
| 37 | const clone = scene.clone(true); |
| 38 | clone.traverse((child) => { |
| 39 | if (child instanceof THREE.Mesh && child.material) { |
| 40 | const mat = (child.material as THREE.MeshStandardMaterial).clone(); |
| 41 | if (wireframe) mat.wireframe = true; |
| 42 | if (opacity < 1) { |
| 43 | mat.transparent = true; |
| 44 | mat.opacity = opacity; |
| 45 | } |
| 46 | if (tint) { |
| 47 | mat.color = new THREE.Color(tint); |
| 48 | } |
| 49 | child.material = mat; |
| 50 | } |
| 51 | }); |
| 52 | }, [scene, wireframe, opacity, tint]); |
| 53 | |
| 54 | useFrame((_, delta) => { |
| 55 | if (rotationRef.current) { |
| 56 | rotationRef.current.rotation.y += delta * rotationSpeed; |
| 57 | } |
| 58 | }); |
| 59 | |
| 60 | return ( |
| 61 | <group ref={rotationRef}> |
| 62 | <group ref={groupRef}> |
| 63 | <primitive object={scene.clone()} /> |
| 64 | </group> |
| 65 | </group> |
| 66 | ); |
| 67 | } |
| 68 | |
| 69 | useGLTF.preload("/models/bvr1_assembly_realistic.glb"); |
nothing calls this directly
no outgoing calls
no test coverage detected