'use client'; import { useRef, useState, useCallback } from 'react'; import { Html } from '@react-three/drei'; import { useFrame, ThreeEvent } from '@react-three/fiber'; import * as THREE from 'three'; import { SocketName, getSocketTransform } from './SocketPoints'; interface InteractiveHotspotProps { socketName: SocketName; label: string; icon?: string; onClick?: (socketName: SocketName) => void; visible?: boolean; } export function InteractiveHotspot({ socketName, label, icon = '+', onClick, visible = true, }: InteractiveHotspotProps) { const meshRef = useRef(null); const ringRef = useRef(null); const [hovered, setHovered] = useState(false); const [showTooltip, setShowTooltip] = useState(false); const transform = getSocketTransform(socketName); // Pulsing animation for the hotspot useFrame((state) => { if (meshRef.current) { const scale = 1 + Math.sin(state.clock.elapsedTime * 3) * 0.1; meshRef.current.scale.setScalar(hovered ? 1.3 : scale); } if (ringRef.current) { const ringScale = 1 + Math.sin(state.clock.elapsedTime * 2) * 0.15; ringRef.current.scale.setScalar(ringScale); const opacity = 0.3 + Math.sin(state.clock.elapsedTime * 2) * 0.2; if (ringRef.current.material instanceof THREE.MeshBasicMaterial) { ringRef.current.material.opacity = opacity; } } }); const handleClick = useCallback( (e: ThreeEvent) => { e.stopPropagation(); onClick?.(socketName); }, [onClick, socketName] ); const handlePointerEnter = useCallback(() => { setHovered(true); setShowTooltip(true); document.body.style.cursor = 'pointer'; }, []); const handlePointerLeave = useCallback(() => { setHovered(false); setShowTooltip(false); document.body.style.cursor = 'auto'; }, []); return ( {/* 3D pulsing sphere */} {/* Outer ring */} {/* HTML label */} {visible && showTooltip && (
{label}
{icon} Click to customize
)}
); } // Hotspot panel component to manage visibility of multiple hotspots interface HotspotPanelProps { onSocketClick?: (socketName: SocketName) => void; visibleSockets?: SocketName[]; } export function HotspotPanel({ onSocketClick, visibleSockets }: HotspotPanelProps) { const defaultSockets: SocketName[] = ['head', 'chest', 'left_shoulder', 'right_shoulder', 'back', 'base']; const socketsToShow = visibleSockets || defaultSockets; const socketLabels: Record = { head: 'Head Mount', chest: 'Chest Plate', left_shoulder: 'Left Shoulder', right_shoulder: 'Right Shoulder', back: 'Back Pack', base: 'Base Unit', }; const socketIcons: Record = { head: 'H', chest: 'C', left_shoulder: 'L', right_shoulder: 'R', back: 'B', base: 'U', }; return ( <> {socketsToShow.map((socket) => ( ))} ); }