import React, { Suspense, useRef, useEffect } from 'react'; import { Canvas, useThree } from '@react-three/fiber'; import { OrbitControls, Grid, GizmoHelper, GizmoViewport, Sky, Stars, TransformControls, } from '@react-three/drei'; import * as THREE from 'three'; import { useStudioStore } from '../store/useStudioStore'; import { Model, ModelHandle } from './Model'; import './Viewport.css'; // ── One model + its optional TransformControls ──────────────── const ModelWithGizmo: React.FC<{ obj: any; isSelected: boolean; gizmoMode: 'translate' | 'rotate' | 'scale'; onOrbitEnable: (v: boolean) => void; }> = ({ obj, isSelected, gizmoMode, onOrbitEnable }) => { const modelRef = useRef(null); const { updateObject, setSelectedId } = useStudioStore(); const syncTransform = () => { const g = modelRef.current?.group; if (!g) return; updateObject(obj.id, { position: [+g.position.x.toFixed(3), +g.position.y.toFixed(3), +g.position.z.toFixed(3)], rotation: [ +THREE.MathUtils.radToDeg(g.rotation.x).toFixed(2), +THREE.MathUtils.radToDeg(g.rotation.y).toFixed(2), +THREE.MathUtils.radToDeg(g.rotation.z).toFixed(2), ], scale: [+g.scale.x.toFixed(3), +g.scale.y.toFixed(3), +g.scale.z.toFixed(3)], }); }; return ( <> setSelectedId(obj.id)} /> {isSelected && modelRef.current?.group && ( onOrbitEnable(false)} onMouseUp={() => { onOrbitEnable(true); syncTransform(); }} onChange={syncTransform} /> )} ); }; // ── HDR / equirectangular skybox ────────────────────────────── const SkyboxHDR: React.FC<{ url: string }> = ({ url }) => { const { scene } = useThree(); useEffect(() => { new THREE.TextureLoader().load(url, (tex) => { tex.mapping = THREE.EquirectangularReflectionMapping; scene.background = tex; scene.environment = tex; }); }, [url, scene]); return null; }; // ── Main scene content ──────────────────────────────────────── const SceneContent: React.FC = () => { const { objects, selectedId, ambientIntensity, directionalIntensity, showGrid, showAxes, skyboxType, skyboxUrl, bgColor, mode, gizmoMode, } = useStudioStore(); const { scene } = useThree(); const orbitRef = useRef(null); useEffect(() => { if (skyboxType !== 'uploaded') scene.background = new THREE.Color(bgColor); }, [bgColor, skyboxType, scene]); const setOrbit = (v: boolean) => { if (orbitRef.current) orbitRef.current.enabled = v; }; return ( <> {skyboxType === 'gradient' && ( )} {skyboxType === 'uploaded' && skyboxUrl && } {mode === 'render' && ( )} {objects.map((obj) => ( ))} {showGrid && ( )} {showAxes && } ); }; // ── Viewport root ───────────────────────────────────────────── const Viewport: React.FC = () => { const { gizmoMode, setGizmoMode, mode } = useStudioStore(); return (
{/* Floating gizmo mode toolbar — only in MODEL mode */} {mode === 'model' && (
{([ { id: 'translate', icon: '✛', label: 'Move', key: 'G' }, { id: 'rotate', icon: '↻', label: 'Rotate', key: 'R' }, { id: 'scale', icon: '⤢', label: 'Scale', key: 'S' }, ] as const).map((g) => ( ))}
)} useStudioStore.getState().setSelectedId(null)} >
); }; export default Viewport;