import { Canvas } from '@react-three/fiber'; import { useMemo } from 'react'; import { ACESFilmicToneMapping, PCFSoftShadowMap, SRGBColorSpace } from 'three'; import { useViewerStore } from '../../store/viewerStore'; import { ViewerScene } from '../../three/ViewerScene'; export interface ViewerCanvasProps { machineId: string; className?: string; } export function ViewerCanvas({ machineId, className = '' }: ViewerCanvasProps) { const clearSelection = useViewerStore((state) => state.clearSelection); const setHoveredPart = useViewerStore((state) => state.setHoveredPart); const display = useViewerStore((state) => state.display); const rendererProfile = useRendererProfile(); const dpr = useMemo<[number, number]>( () => [1, rendererProfile.maxDpr], [rendererProfile.maxDpr] ); return (
{ gl.toneMapping = ACESFilmicToneMapping; gl.outputColorSpace = SRGBColorSpace; gl.toneMappingExposure = display.exposure; gl.shadowMap.enabled = display.shadows; gl.shadowMap.type = PCFSoftShadowMap; gl.localClippingEnabled = true; }} onPointerMissed={() => { setHoveredPart(null); clearSelection(); }} >
); } function useRendererProfile(): { maxDpr: number; antialias: boolean; mobile: boolean; powerPreference: WebGLPowerPreference; } { if (typeof window === 'undefined') { return { maxDpr: 1.5, antialias: true, mobile: false, powerPreference: 'high-performance' }; } const mobile = window.matchMedia('(max-width: 768px)').matches || window.matchMedia('(pointer: coarse)').matches; const lowCoreCount = navigator.hardwareConcurrency ? navigator.hardwareConcurrency <= 4 : false; const maxDpr = mobile ? Math.min(window.devicePixelRatio || 1, 1.5) : Math.min(window.devicePixelRatio || 1, 2); return { maxDpr, antialias: !(mobile && lowCoreCount), mobile, powerPreference: mobile ? 'default' : 'high-performance' }; }