diff --git a/src/components/three/ModelViewer.tsx b/src/components/three/ModelViewer.tsx index 7cae3e7..bf1a160 100644 --- a/src/components/three/ModelViewer.tsx +++ b/src/components/three/ModelViewer.tsx @@ -54,11 +54,43 @@ export function elementKey(modelId: string, el: THREE.Object3D): string { export const sceneRef: { current: THREE.Scene | null } = { current: null }; function SceneRefCapture() { - const { scene } = useThree(); + const { scene, camera } = useThree(); useEffect(() => { sceneRef.current = scene; return () => { if (sceneRef.current === scene) sceneRef.current = null; }; }, [scene]); + useEffect(() => { + // Expose the main camera to the ViewCube bus + // (lazy-imported to avoid circular deps) + import('./viewCubeBus').then(({ mainCameraRef }) => { + mainCameraRef.current = camera; + }); + return () => { + import('./viewCubeBus').then(({ mainCameraRef }) => { + if (mainCameraRef.current === camera) mainCameraRef.current = null; + }); + }; + }, [camera]); + return null; +} + +/** Drives the camera animation requested by the ViewCube. */ +function ViewCubeAnimator() { + const { camera } = useThree(); + useFrame((_state, delta) => { + // Eager require to avoid async stalling per-frame + const bus = require('./viewCubeBus'); + const anim = bus.viewAnim; + if (!anim.active) return; + anim.t = Math.min(1, anim.t + delta / anim.duration); + const k = anim.t < 0.5 ? 2 * anim.t * anim.t : 1 - Math.pow(-2 * anim.t + 2, 2) / 2; + camera.position.lerpVectors(anim.startPos, anim.endPos, k); + camera.up.lerpVectors(anim.startUp, anim.endUp, k).normalize(); + const controls = bus.mainControlsRef.current; + if (controls?.target) camera.lookAt(controls.target); + controls?.update?.(); + if (anim.t >= 1) anim.active = false; + }); return null; }