| import React, { useRef, useEffect, useState } from 'react'; |
| import { Canvas, useFrame, useThree } from '@react-three/fiber'; |
| import { OrbitControls, Text, Sphere, Line } from '@react-three/drei'; |
| import * as THREE from 'three'; |
| import { motion } from 'framer-motion'; |
|
|
| |
| function EmotionSpace({ analysisData, isActive }) { |
| const meshRef = useRef(); |
| const pointsRef = useRef(); |
| const [emotionHistory, setEmotionHistory] = useState([]); |
|
|
| useEffect(() => { |
| if (analysisData && isActive) { |
| setEmotionHistory(prev => [...prev.slice(-50), analysisData]); |
| } |
| }, [analysisData, isActive]); |
|
|
| useFrame((state) => { |
| if (meshRef.current) { |
| meshRef.current.rotation.y += 0.005; |
| } |
| }); |
|
|
| |
| const getEmotionCoordinates = (emotions) => { |
| if (!emotions || emotions.length !== 7) return [0, 0, 0]; |
|
|
| |
| const valence = emotions[3] - emotions[4]; |
| const arousal = (emotions[0] + emotions[2] + emotions[5]) - (emotions[1] + emotions[6]); |
| const dominance = emotions[6] - (emotions[1] + emotions[2]); |
|
|
| return [valence * 2, arousal * 2, dominance * 2]; |
| }; |
|
|
| return ( |
| <group ref={meshRef}> |
| {/* Emotion axes */} |
| <Line points={[[-3, 0, 0], [3, 0, 0]]} color="red" lineWidth={2} /> |
| <Line points={[[0, -3, 0], [0, 3, 0]]} color="green" lineWidth={2} /> |
| <Line points={[[0, 0, -3], [0, 0, 3]]} color="blue" lineWidth={2} /> |
| |
| {/* Axis labels */} |
| <Text position={[3.2, 0, 0]} fontSize={0.3} color="red">Valence</Text> |
| <Text position={[0, 3.2, 0]} fontSize={0.3} color="green">Arousal</Text> |
| <Text position={[0, 0, 3.2]} fontSize={0.3} color="blue">Dominance</Text> |
| |
| {/* Current emotion point */} |
| {analysisData && ( |
| <Sphere |
| args={[0.1, 16, 16]} |
| position={getEmotionCoordinates(analysisData.emotion?.probabilities)} |
| > |
| <meshStandardMaterial |
| color={new THREE.Color().setHSL( |
| analysisData.emotion?.probabilities?.indexOf(Math.max(...analysisData.emotion.probabilities)) / 7, |
| 0.8, |
| 0.6 |
| )} |
| emissive={new THREE.Color(0.1, 0.1, 0.1)} |
| /> |
| </Sphere> |
| )} |
| |
| {/* Emotion trajectory */} |
| {emotionHistory.length > 1 && ( |
| <Line |
| points={emotionHistory.map(data => getEmotionCoordinates(data.emotion?.probabilities))} |
| color="cyan" |
| lineWidth={3} |
| /> |
| )} |
| |
| {/* Emotion labels at corners */} |
| <Text position={[2, 2, 2]} fontSize={0.2} color="yellow">Happy</Text> |
| <Text position={[-2, -2, -2]} fontSize={0.2} color="purple">Sad</Text> |
| <Text position={[2, -2, 0]} fontSize={0.2} color="orange">Angry</Text> |
| <Text position={[-2, 2, 0]} fontSize={0.2} color="pink">Surprised</Text> |
| </group> |
| ); |
| } |
|
|
| |
| function IntentVisualization({ analysisData, isActive }) { |
| const groupRef = useRef(); |
| const [intentHistory, setIntentHistory] = useState([]); |
|
|
| useEffect(() => { |
| if (analysisData && isActive) { |
| setIntentHistory(prev => [...prev.slice(-30), analysisData]); |
| } |
| }, [analysisData, isActive]); |
|
|
| useFrame((state) => { |
| if (groupRef.current) { |
| groupRef.current.rotation.z += 0.01; |
| } |
| }); |
|
|
| |
| const getIntentPosition = (intent, index) => { |
| const angle = (index / 5) * Math.PI * 2; |
| const radius = intent * 2; |
| return [Math.cos(angle) * radius, Math.sin(angle) * radius, 0]; |
| }; |
|
|
| return ( |
| <group ref={groupRef}> |
| {/* Intent radar chart */} |
| {analysisData?.intent?.probabilities?.map((prob, idx) => ( |
| <Sphere |
| key={idx} |
| args={[prob * 0.3, 8, 8]} |
| position={getIntentPosition(prob, idx)} |
| > |
| <meshStandardMaterial |
| color={new THREE.Color().setHSL(idx / 5, 0.7, 0.5)} |
| emissive={new THREE.Color(0.05, 0.05, 0.05)} |
| /> |
| </Sphere> |
| ))} |
| |
| {/* Intent labels */} |
| {['Agreement', 'Confusion', 'Hesitation', 'Confidence', 'Neutral'].map((intent, idx) => { |
| const angle = (idx / 5) * Math.PI * 2; |
| const x = Math.cos(angle) * 2.5; |
| const y = Math.sin(angle) * 2.5; |
| return ( |
| <Text |
| key={intent} |
| position={[x, y, 0]} |
| fontSize={0.15} |
| color="white" |
| anchorX="center" |
| anchorY="middle" |
| > |
| {intent} |
| </Text> |
| ); |
| })} |
| |
| {/* Connecting lines */} |
| {analysisData?.intent?.probabilities && ( |
| <Line |
| points={[ |
| ...analysisData.intent.probabilities.map((prob, idx) => getIntentPosition(prob, idx)), |
| getIntentPosition(analysisData.intent.probabilities[0], 0) // Close the shape |
| ]} |
| color="lime" |
| lineWidth={2} |
| /> |
| )} |
| </group> |
| ); |
| } |
|
|
| |
| function ModalityFusion({ analysisData, isActive }) { |
| const fusionRef = useRef(); |
|
|
| useFrame((state) => { |
| if (fusionRef.current) { |
| fusionRef.current.rotation.x += 0.005; |
| fusionRef.current.rotation.y += 0.003; |
| } |
| }); |
|
|
| return ( |
| <group ref={fusionRef}> |
| {/* Vision sphere */} |
| <Sphere args={[0.5, 16, 16]} position={[-2, 0, 0]}> |
| <meshStandardMaterial |
| color="blue" |
| emissive={new THREE.Color(0.1, 0.1, 0.3)} |
| transparent |
| opacity={analysisData?.modality_importance?.[0] || 0.3} |
| /> |
| </Sphere> |
| |
| {/* Audio sphere */} |
| <Sphere args={[0.5, 16, 16]} position={[0, 2, 0]}> |
| <meshStandardMaterial |
| color="green" |
| emissive={new THREE.Color(0.1, 0.3, 0.1)} |
| transparent |
| opacity={analysisData?.modality_importance?.[1] || 0.3} |
| /> |
| </Sphere> |
| |
| {/* Text sphere */} |
| <Sphere args={[0.5, 16, 16]} position={[2, 0, 0]}> |
| <meshStandardMaterial |
| color="red" |
| emissive={new THREE.Color(0.3, 0.1, 0.1)} |
| transparent |
| opacity={analysisData?.modality_importance?.[2] || 0.3} |
| /> |
| </Sphere> |
| |
| {/* Fusion center */} |
| <Sphere args={[0.3, 16, 16]} position={[0, 0, 0]}> |
| <meshStandardMaterial |
| color="white" |
| emissive={new THREE.Color(0.2, 0.2, 0.2)} |
| /> |
| </Sphere> |
| |
| {/* Connection lines */} |
| <Line points={[[-2, 0, 0], [0, 0, 0]]} color="cyan" lineWidth={3} /> |
| <Line points={[[0, 2, 0], [0, 0, 0]]} color="cyan" lineWidth={3} /> |
| <Line points={[[2, 0, 0], [0, 0, 0]]} color="cyan" lineWidth={3} /> |
| |
| {/* Labels */} |
| <Text position={[-2, -1, 0]} fontSize={0.2} color="blue">Vision</Text> |
| <Text position={[0, 3, 0]} fontSize={0.2} color="green">Audio</Text> |
| <Text position={[2, -1, 0]} fontSize={0.2} color="red">Text</Text> |
| <Text position={[0, -1.5, 0]} fontSize={0.25} color="white">Fusion</Text> |
| </group> |
| ); |
| } |
|
|
| |
| export default function Advanced3DVisualization({ analysisData, isActive }) { |
| const [activeView, setActiveView] = useState('emotion'); |
|
|
| return ( |
| <div className="w-full h-96 bg-black/50 rounded-2xl overflow-hidden border border-white/10"> |
| {/* View Controls */} |
| <div className="absolute top-4 left-4 z-10 flex space-x-2"> |
| {[ |
| { key: 'emotion', label: 'Emotion Space', icon: '🧠' }, |
| { key: 'intent', label: 'Intent Radar', icon: '🎯' }, |
| { key: 'fusion', label: 'Modality Fusion', icon: '🔗' } |
| ].map(({ key, label, icon }) => ( |
| <motion.button |
| key={key} |
| whileHover={{ scale: 1.05 }} |
| whileTap={{ scale: 0.95 }} |
| onClick={() => setActiveView(key)} |
| className={`px-3 py-2 rounded-lg text-sm font-medium transition-colors ${ |
| activeView === key |
| ? 'bg-cyan-600 text-white' |
| : 'bg-white/10 text-gray-300 hover:bg-white/20' |
| }`} |
| > |
| {icon} {label} |
| </motion.button> |
| ))} |
| </div> |
| |
| {/* 3D Canvas */} |
| <Canvas camera={{ position: [5, 5, 5], fov: 60 }}> |
| <ambientLight intensity={0.4} /> |
| <pointLight position={[10, 10, 10]} intensity={0.8} /> |
| <pointLight position={[-10, -10, -10]} intensity={0.3} /> |
| |
| <OrbitControls enablePan={true} enableZoom={true} enableRotate={true} /> |
| |
| {activeView === 'emotion' && ( |
| <EmotionSpace analysisData={analysisData} isActive={isActive} /> |
| )} |
| {activeView === 'intent' && ( |
| <IntentVisualization analysisData={analysisData} isActive={isActive} /> |
| )} |
| {activeView === 'fusion' && ( |
| <ModalityFusion analysisData={analysisData} isActive={isActive} /> |
| )} |
| </Canvas> |
| |
| {/* Info Panel */} |
| <div className="absolute bottom-4 right-4 bg-black/70 backdrop-blur-sm rounded-lg p-3 text-sm"> |
| <div className="text-cyan-400 font-semibold mb-2">3D Analysis</div> |
| <div className="text-gray-300"> |
| {activeView === 'emotion' && 'Visualizing emotion in 3D valence-arousal-dominance space'} |
| {activeView === 'intent' && 'Intent analysis as radar chart with temporal tracking'} |
| {activeView === 'fusion' && 'Multi-modal fusion showing contribution weights'} |
| </div> |
| <div className="text-xs text-gray-400 mt-1"> |
| Drag to rotate • Scroll to zoom • Right-click to pan |
| </div> |
| </div> |
| </div> |
| ); |
| } |