/** * ModelViewer3D - Interactive 3D model viewer using React Three Fiber. */ import { Suspense, useEffect } from 'react'; import { Canvas, useFrame, useThree } from '@react-three/fiber'; import { useGLTF, OrbitControls, Html, Loader, Environment, ContactShadows } from '@react-three/drei'; import * as THREE from 'three'; import { motion } from 'framer-motion'; import type { LightPreset } from '../types'; interface ModelViewer3DProps { url: string; wireframe: boolean; lightPreset: LightPreset; autoRotate: boolean; } function AutoScaledModel({ url, wireframe }: { url: string; wireframe: boolean }) { const { scene } = useGLTF(url); useEffect(() => { const box = new THREE.Box3().setFromObject(scene); const center = new THREE.Vector3(); box.getCenter(center); scene.position.sub(center); const size = new THREE.Vector3(); box.getSize(size); const maxDim = Math.max(size.x, size.y, size.z); const scale = maxDim > 0 ? 2 / maxDim : 1; scene.scale.setScalar(scale); }, [scene]); useEffect(() => { scene.traverse((child) => { if ((child as THREE.Mesh).isMesh) { const mesh = child as THREE.Mesh; if (Array.isArray(mesh.material)) { mesh.material.forEach((m) => { (m as THREE.Material & { wireframe: boolean }).wireframe = wireframe; }); } else { (mesh.material as THREE.Material & { wireframe: boolean }).wireframe = wireframe; } } }); }, [wireframe, scene]); return ; } function SceneLighting({ preset }: { preset: LightPreset }) { switch (preset) { case 'studio': return ( <> ); case 'outdoor': return ( <> ); case 'dramatic': return ( <> ); default: return null; } } function Turntable({ autoRotate }: { autoRotate: boolean }) { const { scene } = useThree(); useFrame((_, delta) => { if (autoRotate) { scene.rotation.y += delta * 0.5; } }); return null; } export default function ModelViewer3D({ url, wireframe, lightPreset, autoRotate, }: ModelViewer3DProps) { return ( }> ); }