import { Environment, Grid, OrbitControls, ContactShadows } from '@react-three/drei'; import { Canvas, useThree } from '@react-three/fiber'; import { Suspense, useCallback, useEffect, useRef } from 'react'; import { LOCAL_ENVIRONMENT_HDR } from './lighting/localEnvironment'; import { ACESFilmicToneMapping, Vector3 } from 'three'; import type { Camera } from 'three'; import { CAMERA_PRESETS } from './cameraPresets'; import { MachinePlaceholderModel } from './MachinePlaceholderModel'; import type { MachineRegistryEntry } from '../modules/machines/schema'; import { useViewerStore } from '../store/viewerStore'; import type { VectorTuple } from '../types/viewer'; interface SceneRootProps { machine: MachineRegistryEntry; } interface OrbitControlsHandle { target: Vector3; update: () => void; } function roundVector(vector: VectorTuple): VectorTuple { return vector.map((value) => Number(value.toFixed(3))) as VectorTuple; } function applyCamera(camera: Camera, position: VectorTuple, target: VectorTuple) { camera.position.set(position[0], position[1], position[2]); camera.lookAt(target[0], target[1], target[2]); } function RendererFeatureFlags({ clippingEnabled }: { clippingEnabled: boolean }) { const gl = useThree((state) => state.gl); useEffect(() => { gl.localClippingEnabled = clippingEnabled; }, [clippingEnabled, gl]); return null; } function ViewerCameraController() { const controlsRef = useRef(null); const { camera } = useThree(); const activeCameraPreset = useViewerStore((state) => state.activeCameraPreset); const cameraOverride = useViewerStore((state) => state.cameraOverride); const setCameraOverride = useViewerStore((state) => state.setCameraOverride); useEffect(() => { const targetCamera = cameraOverride ?? CAMERA_PRESETS[activeCameraPreset]; applyCamera(camera, targetCamera.position, targetCamera.target); controlsRef.current?.target.set( targetCamera.target[0], targetCamera.target[1], targetCamera.target[2] ); controlsRef.current?.update(); }, [activeCameraPreset, camera, cameraOverride]); const handleControlsEnd = useCallback(() => { const controls = controlsRef.current; if (!controls) { return; } setCameraOverride( roundVector([camera.position.x, camera.position.y, camera.position.z]), roundVector([controls.target.x, controls.target.y, controls.target.z]) ); }, [camera, setCameraOverride]); return ( { controlsRef.current = controls as OrbitControlsHandle | null; }} /> ); } export function SceneRoot({ machine }: SceneRootProps) { const crossSectionAxis = useViewerStore((state) => state.crossSectionAxis); const setSelectedPartId = useViewerStore((state) => state.setSelectedPartId); return ( { gl.setClearColor('#0B0E14', 1); gl.toneMapping = ACESFilmicToneMapping; gl.toneMappingExposure = 1; }} onPointerMissed={() => setSelectedPartId(undefined)} shadows > ); }