import { useMemo, useRef, useEffect, useState } from "react"; import { useGLTF } from "@react-three/drei"; import { Grid, TransformControls } from "@react-three/drei"; import * as THREE from "three"; import type { SceneData, MapNode, TransformMode } from "./types"; interface MapViewerProps { sceneData: SceneData; selectedNodeIndex: number | null; onSelectNode: (index: number | null) => void; hoveredNodeIndex: number | null; onHoverNode: (index: number | null) => void; transformMode: TransformMode; onTransformStart: () => void; onTransformEnd: () => void; onNodeTransform: (nodeIndex: number, transform: MapNode) => void; } export default function MapViewer({ sceneData, selectedNodeIndex, onSelectNode, hoveredNodeIndex, onHoverNode, transformMode, onTransformStart, onTransformEnd, onNodeTransform, }: MapViewerProps): React.JSX.Element { const objectsMapRef = useRef>(new Map()); const handleTransformMouseDown = () => { onTransformStart?.(); }; const handleTransformMouseUp = () => { if (selectedNodeIndex !== null) { const obj = objectsMapRef.current.get(selectedNodeIndex); if (!obj) return; const node = sceneData.mapNodes[selectedNodeIndex]; if (node) { const updatedNode: MapNode = { ...node, position: [obj.position.x, obj.position.y, obj.position.z], rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z], scale: [obj.scale.x, obj.scale.y, obj.scale.z], }; onNodeTransform?.(selectedNodeIndex, updatedNode); } } onTransformEnd?.(); }; const [selectedObject, setSelectedObject] = useState( null, ); useEffect(() => { if (selectedNodeIndex !== null) { const obj = objectsMapRef.current.get(selectedNodeIndex); setSelectedObject(obj || null); } else { setSelectedObject(null); } }, [selectedNodeIndex]); return ( <> { (e as { stopPropagation?: () => void }).stopPropagation?.(); onSelectNode(null); }} > {sceneData.mapNodes.map((node, index) => { const modelUrl = sceneData.models.get(node.name); if (modelUrl) { return ( ); } else { return ( ); } })} {selectedObject && ( )} ); } function ModelNodeWithRef({ index, node, modelUrl, isSelected, isHovered, objectsMapRef, onSelectNode, onHoverNode, }: { index: number; node: MapNode; modelUrl: string; isSelected: boolean; isHovered: boolean; objectsMapRef: React.RefObject>; onSelectNode: (index: number | null) => void; onHoverNode: (index: number | null) => void; }) { const groupRef = useRef(null); const { scene } = useGLTF(modelUrl); const sceneInstance = useMemo(() => scene.clone(true), [scene]); useEffect(() => { if (groupRef.current) { groupRef.current.position.set(...node.position); groupRef.current.rotation.set(...node.rotation); groupRef.current.scale.set(...node.scale); groupRef.current.userData = { nodeIndex: index, nodeName: node.name }; objectsMapRef.current.set(index, groupRef.current); } const currentMap = objectsMapRef.current; const currentIndex = index; return () => { currentMap.delete(currentIndex); }; }, [index]); useEffect(() => { if (groupRef.current) { groupRef.current.position.set(...node.position); groupRef.current.rotation.set(...node.rotation); groupRef.current.scale.set(...node.scale); } }, [node.position, node.rotation, node.scale]); useEffect(() => { if (!groupRef.current) return; groupRef.current.traverse((child) => { if ((child as THREE.Mesh).isMesh) { const mesh = child as THREE.Mesh; if ( mesh.material && mesh.material instanceof THREE.MeshStandardMaterial ) { if (isSelected) { mesh.material = mesh.material.clone(); (mesh.material as THREE.MeshStandardMaterial).color.set("#ff6600"); } else if (isHovered) { mesh.material = mesh.material.clone(); (mesh.material as THREE.MeshStandardMaterial).color.set("#ff9900"); } } } }); }, [isSelected, isHovered]); return ( { (e as { stopPropagation?: () => void }).stopPropagation?.(); onSelectNode(index); }} onPointerEnter={(e: unknown) => { (e as { stopPropagation?: () => void }).stopPropagation?.(); onHoverNode(index); }} onPointerLeave={(e: unknown) => { (e as { stopPropagation?: () => void }).stopPropagation?.(); onHoverNode(null); }} /> ); } function FallbackNodeWithRef({ index, node, isSelected, isHovered, objectsMapRef, onSelectNode, onHoverNode, }: { index: number; node: MapNode; isSelected: boolean; isHovered: boolean; objectsMapRef: React.RefObject>; onSelectNode: (index: number | null) => void; onHoverNode: (index: number | null) => void; }) { const meshRef = useRef(null); useEffect(() => { if (meshRef.current) { meshRef.current.position.set(...node.position); meshRef.current.rotation.set(...node.rotation); meshRef.current.scale.set(...node.scale); meshRef.current.userData = { nodeIndex: index, nodeName: node.name }; objectsMapRef.current.set(index, meshRef.current); } const currentMap = objectsMapRef.current; const currentIndex = index; return () => { currentMap.delete(currentIndex); }; }, [index]); useEffect(() => { if (meshRef.current) { meshRef.current.position.set(...node.position); meshRef.current.rotation.set(...node.rotation); meshRef.current.scale.set(...node.scale); } }, [node.position, node.rotation, node.scale]); const color = isSelected ? "#ff6600" : isHovered ? "#ff9900" : "#cccccc"; return ( { (e as { stopPropagation?: () => void }).stopPropagation?.(); onSelectNode(index); }} onPointerEnter={(e: unknown) => { (e as { stopPropagation?: () => void }).stopPropagation?.(); onHoverNode(index); }} onPointerLeave={(e: unknown) => { (e as { stopPropagation?: () => void }).stopPropagation?.(); onHoverNode(null); }} > ); }