refactored DreamVR to simplify component structure, integrate OrbitControls for non-VR mode, enhance lighting, and optimize render logic for improved performance and maintainability

Signed-off-by: Matthias Puchstein <matthias@puchstein.bayern>
This commit is contained in:
2025-07-17 03:36:20 +02:00
parent 7754370a70
commit 2d294de69a

View File

@@ -1,17 +1,13 @@
import React, {useMemo, useRef} from 'react'; import React, {useMemo, useRef} from 'react';
import {Canvas, useFrame} from '@react-three/fiber'; import {Canvas, useFrame} from '@react-three/fiber';
import {createXRStore, XR} from '@react-three/xr'; import {createXRStore, useXR, XR} from '@react-three/xr';
import {PerspectiveCamera} from '@react-three/drei'; import {OrbitControls, PerspectiveCamera} from '@react-three/drei';
import * as THREE from 'three'; import * as THREE from 'three';
import type Dream from '../types/Dream'; import type Dream from '../types/Dream';
// Neural Node component representing a synapse in the neural network // Neural Node component representing a synapse in the neural network
const NeuralNode = ({position, color, scale, pulseSpeed, pulseIntensity}: { const NeuralNode = ({position, color, scale, pulseSpeed, pulseIntensity}: {
position: [number, number, number], position: [number, number, number], color: string, scale: number, pulseSpeed: number, pulseIntensity: number
color: string,
scale: number,
pulseSpeed: number,
pulseIntensity: number
}) => { }) => {
const nodeRef = useRef<THREE.Mesh>(null); const nodeRef = useRef<THREE.Mesh>(null);
const initialScale = scale; const initialScale = scale;
@@ -24,18 +20,10 @@ const NeuralNode = ({position, color, scale, pulseSpeed, pulseIntensity}: {
} }
}); });
return ( return (<mesh ref={nodeRef} position={position}>
<mesh ref={nodeRef} position={position}> <sphereGeometry args={[scale, 8, 8]}/>
<sphereGeometry args={[1, 32, 32]}/> <meshStandardMaterial color={color} emissive={color} emissiveIntensity={0.2}/>
<meshStandardMaterial </mesh>);
color={color}
emissive={color}
emissiveIntensity={0.5}
transparent
opacity={0.8}
/>
</mesh>
);
}; };
// Neural Connection component representing connections between synapses // Neural Connection component representing connections between synapses
@@ -51,60 +39,36 @@ const NeuralConnection = ({start, end, color, thickness, pulseSpeed, pulseIntens
// Create a cylinder between two points // Create a cylinder between two points
const direction = useMemo(() => { const direction = useMemo(() => {
return new THREE.Vector3( return new THREE.Vector3(end[0] - start[0], end[1] - start[1], end[2] - start[2]);
end[0] - start[0],
end[1] - start[1],
end[2] - start[2]
);
}, [start, end]); }, [start, end]);
const length = useMemo(() => direction.length(), [direction]); const length = useMemo(() => direction.length(), [direction]);
// Calculate rotation to align cylinder with direction // Calculate rotation to align cylinder with direction
const rotationMatrix = useMemo(() => { useMemo(() => {
const normalizedDirection = direction.clone().normalize(); const normalizedDirection = direction.clone().normalize();
const quaternion = new THREE.Quaternion(); const quaternion = new THREE.Quaternion();
quaternion.setFromUnitVectors( quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), // Default cylinder orientation
new THREE.Vector3(0, 1, 0), // Default cylinder orientation normalizedDirection);
normalizedDirection
);
return new THREE.Matrix4().makeRotationFromQuaternion(quaternion); return new THREE.Matrix4().makeRotationFromQuaternion(quaternion);
}, [direction]); }, [direction]);
// Calculate position (midpoint between start and end) // Calculate position (midpoint between start and end)
const position = useMemo(() => { const position = useMemo(() => {
return [ return [(start[0] + end[0]) / 2, (start[1] + end[1]) / 2, (start[2] + end[2]) / 2] as [number, number, number];
(start[0] + end[0]) / 2,
(start[1] + end[1]) / 2,
(start[2] + end[2]) / 2
] as [number, number, number];
}, [start, end]); }, [start, end]);
useFrame(({clock}) => { useFrame(({clock}) => {
if (connectionRef.current) { if (connectionRef.current) {
// Create a pulsing effect for the connection // Create a pulsing effect for the connection
const pulse = Math.sin(clock.getElapsedTime() * pulseSpeed + const pulse = Math.sin(clock.getElapsedTime() * pulseSpeed + (start[0] + start[1] + start[2])) * pulseIntensity + 1;
(start[0] + start[1] + start[2])) * pulseIntensity + 1;
connectionRef.current.scale.set(thickness * pulse, length / 2, thickness * pulse); connectionRef.current.scale.set(thickness * pulse, length / 2, thickness * pulse);
} }
}); });
return ( return (<mesh ref={connectionRef} position={position}>
<mesh <cylinderGeometry args={[thickness, thickness, length]}/>
ref={connectionRef} <meshStandardMaterial color={color} emissive={color} emissiveIntensity={0.1}/>
position={position} </mesh>);
matrix={rotationMatrix}
>
<cylinderGeometry args={[thickness, thickness, length / 2, 8]}/>
<meshStandardMaterial
color={color}
emissive={color}
emissiveIntensity={0.5}
transparent
opacity={0.6}
/>
</mesh>
);
}; };
// Neural Network component that generates nodes and connections // Neural Network component that generates nodes and connections
@@ -147,11 +111,7 @@ const NeuralNetwork = ({dream}: { dream: Dream }) => {
const pulseIntensity = 0.1 + (chipInput.bewegung[i % chipInput.bewegung.length] * 2); const pulseIntensity = 0.1 + (chipInput.bewegung[i % chipInput.bewegung.length] * 2);
nodes.push({ nodes.push({
position: [x, y, z] as [number, number, number], position: [x, y, z] as [number, number, number], color, scale, pulseSpeed, pulseIntensity
color,
scale,
pulseSpeed,
pulseIntensity
}); });
} }
@@ -185,34 +145,50 @@ const NeuralNetwork = ({dream}: { dream: Dream }) => {
return connections; return connections;
}, [nodes, dream]); }, [nodes, dream]);
return ( return (<>
<>
{/* Render all nodes */} {/* Render all nodes */}
{nodes.map((node, index) => ( {nodes.map((node, index) => (<NeuralNode
<NeuralNode key={index}
key={`node-${index}`}
position={node.position} position={node.position}
color={node.color} color={node.color}
scale={node.scale} scale={node.scale}
pulseSpeed={node.pulseSpeed} pulseSpeed={node.pulseSpeed}
pulseIntensity={node.pulseIntensity} pulseIntensity={node.pulseIntensity}
/> />))}
))}
{/* Render all connections */} {/* Render all connections */}
{connections.map((connection, index) => ( {connections.map((connection, index) => (<NeuralConnection
<NeuralConnection key={index}
key={`connection-${index}`}
start={connection.start} start={connection.start}
end={connection.end} end={connection.end}
color={connection.color} color={connection.color}
thickness={connection.thickness} thickness={connection.thickness}
pulseSpeed={connection.pulseSpeed} pulseSpeed={connection.pulseSpeed}
pulseIntensity={connection.pulseIntensity} pulseIntensity={connection.pulseIntensity}
/> />))}
))} </>);
</> };
);
// Camera Controls component that handles XR and non-XR states
const CameraControls = () => {
const {session} = useXR();
// Only enable OrbitControls when NOT in VR session
if (session) {
return null; // Let XR handle camera in VR mode
}
return (<OrbitControls
enablePan={true}
enableZoom={true}
enableRotate={true}
enableDamping={true}
dampingFactor={0.05}
minDistance={5}
maxDistance={50}
maxPolarAngle={Math.PI}
minPolarAngle={0}
/>);
}; };
// Main DreamVR component // Main DreamVR component
@@ -229,61 +205,45 @@ const DreamVR: React.FC<DreamVRProps> = ({dream, height = '500px'}) => {
// Only render VR for dream with chip input type // Only render VR for dream with chip input type
if (dream.input.inputType !== 'chip') { if (dream.input.inputType !== 'chip') {
return ( return (<div className="flex items-center justify-center h-full text-gray-500">
<div className="flex items-center justify-center" style={{height}}> VR-Visualisierung ist nur für Träume mit Chip-Eingabe verfügbar.
<p className="text-center text-gray-500"> </div>);
VR-Visualisierung ist nur für Träume mit Chip-Eingabe verfügbar.
</p>
</div>
);
} }
return ( return (<div ref={containerRef} className="relative w-full" style={{height}}>
<div ref={containerRef} style={{height, width: '100%', position: 'relative'}}>
{/* VR Entry Button */} {/* VR Entry Button */}
<div className="absolute top-2 right-2 z-10 flex space-x-2"> <button
<button onClick={() => store.enterVR()}
onClick={() => store.enterVR()} className="absolute top-4 right-4 z-10 p-2 bg-white/20 backdrop-blur-sm rounded-full text-white hover:bg-white/30 transition-colors"
className="p-2 bg-white/20 backdrop-blur-sm rounded-full text-white hover:bg-white/30 transition-colors" aria-label="Enter VR"
aria-label="Enter VR" >
> 🥽 VR
<svg </button>
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"/>
<line x1="8" y1="21" x2="16" y2="21"/>
<line x1="12" y1="17" x2="12" y2="21"/>
</svg>
</button>
</div>
<Canvas shadows> <Canvas camera={{position: [0, 0, 10], fov: 60}}>
<XR store={store}> <XR store={store}>
{/* Camera setup */} {/* Camera setup */}
<PerspectiveCamera makeDefault position={[0, 0, 30]}/> <PerspectiveCamera makeDefault position={[0, 0, 10]}/>
{/* Camera Controls - only active outside VR */}
<CameraControls/>
{/* Lighting */} {/* Lighting */}
<ambientLight intensity={0.5}/> <ambientLight intensity={0.2}/>
<directionalLight position={[10, 10, 10]} intensity={1}/> <pointLight position={[10, 10, 10]} intensity={0.8}/>
<directionalLight position={[-10, -10, -10]} intensity={0.5}/> <pointLight position={[-10, -10, -10]} intensity={0.4}/>
{/* Neural network visualization */} {/* Neural network visualization */}
<NeuralNetwork dream={dream}/> <NeuralNetwork dream={dream}/>
{/* Background */} {/* Background */}
<color attach="background" args={['#000']}/> <mesh scale={[100, 100, 100]}>
<sphereGeometry args={[1, 32, 32]}/>
<meshBasicMaterial color="#000012" side={THREE.BackSide}/>
</mesh>
</XR> </XR>
</Canvas> </Canvas>
</div> </div>);
);
}; };
export default DreamVR; export default DreamVR;