import { useRef, useEffect } from 'react'; import { useThree, useFrame } from '@react-three/fiber'; import { OrbitControls as DreiOrbitControls } from '@react-three/drei'; import * as THREE from 'three'; import { useVisualizerStore } from '@/core/store'; /** * Camera controls component with orbit, zoom, and pan */ export function CameraControls() { const controlsRef = useRef(null); const model = useVisualizerStore(state => state.model); const setCameraPosition = useVisualizerStore(state => state.setCameraPosition); const setCameraTarget = useVisualizerStore(state => state.setCameraTarget); const { camera: threeCamera } = useThree(); // Update store when camera moves useFrame(() => { if (controlsRef.current) { const pos = threeCamera.position; const target = controlsRef.current.target; // Debounce updates setCameraPosition({ x: pos.x, y: pos.y, z: pos.z }); setCameraTarget({ x: target.x, y: target.y, z: target.z }); } }); // Reset camera when model changes useEffect(() => { if (model && controlsRef.current) { // Compute camera position to frame the model const nodeCount = model.graph.nodes.length; const distance = Math.max(nodeCount * 1.5, 15); threeCamera.position.set(0, distance * 0.3, distance); controlsRef.current.target.set(0, -nodeCount * 0.5, 0); controlsRef.current.update(); } }, [model, threeCamera]); return ( ); } /** * Camera animation for transitions */ export function useCameraAnimation() { const { camera } = useThree(); const targetRef = useRef(null); const lookAtRef = useRef(null); const progressRef = useRef(0); useFrame((_, delta) => { if (targetRef.current && progressRef.current < 1) { progressRef.current += delta * 2; const t = Math.min(progressRef.current, 1); const eased = 1 - Math.pow(1 - t, 3); // Ease out cubic camera.position.lerp(targetRef.current, eased); if (lookAtRef.current) { camera.lookAt(lookAtRef.current); } if (t >= 1) { targetRef.current = null; lookAtRef.current = null; } } }); const animateTo = (position: THREE.Vector3, lookAt?: THREE.Vector3) => { targetRef.current = position; lookAtRef.current = lookAt || null; progressRef.current = 0; }; return { animateTo }; } /** * Focus camera on a specific node */ export function useFocusNode() { const computedNodes = useVisualizerStore(state => state.computedNodes); const { animateTo } = useCameraAnimation(); const focusNode = (nodeId: string) => { const node = computedNodes.get(nodeId); if (!node) return; const { x, y, z } = node.computedPosition; const targetPos = new THREE.Vector3(x, y + 5, z + 10); const lookAtPos = new THREE.Vector3(x, y, z); animateTo(targetPos, lookAtPos); }; return { focusNode }; }