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