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 {Canvas, useFrame} from '@react-three/fiber';
import {createXRStore, XR} from '@react-three/xr';
import {PerspectiveCamera} from '@react-three/drei';
import {createXRStore, useXR, XR} from '@react-three/xr';
import {OrbitControls, PerspectiveCamera} from '@react-three/drei';
import * as THREE from 'three';
import type Dream from '../types/Dream';
// Neural Node component representing a synapse in the neural network
const NeuralNode = ({position, color, scale, pulseSpeed, pulseIntensity}: {
position: [number, number, number],
color: string,
scale: number,
pulseSpeed: number,
pulseIntensity: number
position: [number, number, number], color: string, scale: number, pulseSpeed: number, pulseIntensity: number
}) => {
const nodeRef = useRef<THREE.Mesh>(null);
const initialScale = scale;
@@ -24,18 +20,10 @@ const NeuralNode = ({position, color, scale, pulseSpeed, pulseIntensity}: {
}
});
return (
<mesh ref={nodeRef} position={position}>
<sphereGeometry args={[1, 32, 32]}/>
<meshStandardMaterial
color={color}
emissive={color}
emissiveIntensity={0.5}
transparent
opacity={0.8}
/>
</mesh>
);
return (<mesh ref={nodeRef} position={position}>
<sphereGeometry args={[scale, 8, 8]}/>
<meshStandardMaterial color={color} emissive={color} emissiveIntensity={0.2}/>
</mesh>);
};
// 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
const direction = useMemo(() => {
return new THREE.Vector3(
end[0] - start[0],
end[1] - start[1],
end[2] - start[2]
);
return new THREE.Vector3(end[0] - start[0], end[1] - start[1], end[2] - start[2]);
}, [start, end]);
const length = useMemo(() => direction.length(), [direction]);
// Calculate rotation to align cylinder with direction
const rotationMatrix = useMemo(() => {
useMemo(() => {
const normalizedDirection = direction.clone().normalize();
const quaternion = new THREE.Quaternion();
quaternion.setFromUnitVectors(
new THREE.Vector3(0, 1, 0), // Default cylinder orientation
normalizedDirection
);
quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), // Default cylinder orientation
normalizedDirection);
return new THREE.Matrix4().makeRotationFromQuaternion(quaternion);
}, [direction]);
// Calculate position (midpoint between start and end)
const position = useMemo(() => {
return [
(start[0] + end[0]) / 2,
(start[1] + end[1]) / 2,
(start[2] + end[2]) / 2
] as [number, number, number];
return [(start[0] + end[0]) / 2, (start[1] + end[1]) / 2, (start[2] + end[2]) / 2] as [number, number, number];
}, [start, end]);
useFrame(({clock}) => {
if (connectionRef.current) {
// Create a pulsing effect for the connection
const pulse = Math.sin(clock.getElapsedTime() * pulseSpeed +
(start[0] + start[1] + start[2])) * pulseIntensity + 1;
const pulse = Math.sin(clock.getElapsedTime() * pulseSpeed + (start[0] + start[1] + start[2])) * pulseIntensity + 1;
connectionRef.current.scale.set(thickness * pulse, length / 2, thickness * pulse);
}
});
return (
<mesh
ref={connectionRef}
position={position}
matrix={rotationMatrix}
>
<cylinderGeometry args={[thickness, thickness, length / 2, 8]}/>
<meshStandardMaterial
color={color}
emissive={color}
emissiveIntensity={0.5}
transparent
opacity={0.6}
/>
</mesh>
);
return (<mesh ref={connectionRef} position={position}>
<cylinderGeometry args={[thickness, thickness, length]}/>
<meshStandardMaterial color={color} emissive={color} emissiveIntensity={0.1}/>
</mesh>);
};
// 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);
nodes.push({
position: [x, y, z] as [number, number, number],
color,
scale,
pulseSpeed,
pulseIntensity
position: [x, y, z] as [number, number, number], color, scale, pulseSpeed, pulseIntensity
});
}
@@ -185,34 +145,50 @@ const NeuralNetwork = ({dream}: { dream: Dream }) => {
return connections;
}, [nodes, dream]);
return (
<>
return (<>
{/* Render all nodes */}
{nodes.map((node, index) => (
<NeuralNode
key={`node-${index}`}
{nodes.map((node, index) => (<NeuralNode
key={index}
position={node.position}
color={node.color}
scale={node.scale}
pulseSpeed={node.pulseSpeed}
pulseIntensity={node.pulseIntensity}
/>
))}
/>))}
{/* Render all connections */}
{connections.map((connection, index) => (
<NeuralConnection
key={`connection-${index}`}
{connections.map((connection, index) => (<NeuralConnection
key={index}
start={connection.start}
end={connection.end}
color={connection.color}
thickness={connection.thickness}
pulseSpeed={connection.pulseSpeed}
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
@@ -229,61 +205,45 @@ const DreamVR: React.FC<DreamVRProps> = ({dream, height = '500px'}) => {
// Only render VR for dream with chip input type
if (dream.input.inputType !== 'chip') {
return (
<div className="flex items-center justify-center" style={{height}}>
<p className="text-center text-gray-500">
VR-Visualisierung ist nur für Träume mit Chip-Eingabe verfügbar.
</p>
</div>
);
return (<div className="flex items-center justify-center h-full text-gray-500">
VR-Visualisierung ist nur für Träume mit Chip-Eingabe verfügbar.
</div>);
}
return (
<div ref={containerRef} style={{height, width: '100%', position: 'relative'}}>
return (<div ref={containerRef} className="relative w-full" style={{height}}>
{/* VR Entry Button */}
<div className="absolute top-2 right-2 z-10 flex space-x-2">
<button
onClick={() => store.enterVR()}
className="p-2 bg-white/20 backdrop-blur-sm rounded-full text-white hover:bg-white/30 transition-colors"
aria-label="Enter VR"
>
<svg
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>
<button
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"
aria-label="Enter VR"
>
🥽 VR
</button>
<Canvas shadows>
<Canvas camera={{position: [0, 0, 10], fov: 60}}>
<XR store={store}>
{/* Camera setup */}
<PerspectiveCamera makeDefault position={[0, 0, 30]}/>
<PerspectiveCamera makeDefault position={[0, 0, 10]}/>
{/* Camera Controls - only active outside VR */}
<CameraControls/>
{/* Lighting */}
<ambientLight intensity={0.5}/>
<directionalLight position={[10, 10, 10]} intensity={1}/>
<directionalLight position={[-10, -10, -10]} intensity={0.5}/>
<ambientLight intensity={0.2}/>
<pointLight position={[10, 10, 10]} intensity={0.8}/>
<pointLight position={[-10, -10, -10]} intensity={0.4}/>
{/* Neural network visualization */}
<NeuralNetwork dream={dream}/>
{/* Background */}
<color attach="background" args={['#000']}/>
<mesh scale={[100, 100, 100]}>
<sphereGeometry args={[1, 32, 32]}/>
<meshBasicMaterial color="#000012" side={THREE.BackSide}/>
</mesh>
</XR>
</Canvas>
</div>
);
</div>);
};
export default DreamVR;