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:
@@ -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;
|
Reference in New Issue
Block a user