added DreamVR component for neural network-based VR visualization of chip input dreams, integrated @react-three/fiber and @react-three/drei dependencies, and updated package-lock.json

Signed-off-by: Matthias Puchstein <matthias@puchstein.bayern>
This commit is contained in:
2025-07-17 00:19:41 +02:00
parent c4bf7c8b20
commit afec14c390
5 changed files with 949 additions and 12 deletions

265
src/components/DreamVR.tsx Normal file
View File

@@ -0,0 +1,265 @@
import React, {useMemo, useRef} from 'react';
import {Canvas, useFrame} from '@react-three/fiber';
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
}) => {
const nodeRef = useRef<THREE.Mesh>(null);
const initialScale = scale;
useFrame(({clock}) => {
if (nodeRef.current) {
// Create a pulsing effect
const pulse = Math.sin(clock.getElapsedTime() * pulseSpeed) * pulseIntensity + 1;
nodeRef.current.scale.set(initialScale * pulse, initialScale * pulse, initialScale * pulse);
}
});
return (
<mesh ref={nodeRef} position={position}>
<sphereGeometry args={[1, 32, 32]}/>
<meshStandardMaterial
color={color}
emissive={color}
emissiveIntensity={0.5}
transparent
opacity={0.8}
/>
</mesh>
);
};
// Neural Connection component representing connections between synapses
const NeuralConnection = ({start, end, color, thickness, pulseSpeed, pulseIntensity}: {
start: [number, number, number],
end: [number, number, number],
color: string,
thickness: number,
pulseSpeed: number,
pulseIntensity: number
}) => {
const connectionRef = useRef<THREE.Mesh>(null);
// 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]
);
}, [start, end]);
const length = useMemo(() => direction.length(), [direction]);
// Calculate rotation to align cylinder with direction
const rotationMatrix = useMemo(() => {
const normalizedDirection = direction.clone().normalize();
const quaternion = new THREE.Quaternion();
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];
}, [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;
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>
);
};
// Neural Network component that generates nodes and connections
const NeuralNetwork = ({dream}: { dream: Dream }) => {
// Generate nodes and connections based on dream data
const nodes = useMemo(() => {
if (dream.input.inputType !== 'chip') return [];
const chipInput = dream.input;
const nodeCount = 50; // Number of nodes to generate
const nodes = [];
// Use EEG data to influence node positions and properties
for (let i = 0; i < nodeCount; i++) {
const alphaValue = chipInput.eeg.alpha[i % chipInput.eeg.alpha.length] / 50;
const betaValue = chipInput.eeg.beta[i % chipInput.eeg.beta.length] / 50;
const thetaValue = chipInput.eeg.theta[i % chipInput.eeg.theta.length] / 50;
// Calculate position based on EEG values
const x = (Math.random() - 0.5) * 20 + alphaValue * 10;
const y = (Math.random() - 0.5) * 20 + betaValue * 10;
const z = (Math.random() - 0.5) * 20 + thetaValue * 10;
// Determine color based on dominant wave
let color;
const dominantWave = Math.max(alphaValue, betaValue, thetaValue);
if (dominantWave === alphaValue) {
color = '#8884d8'; // Alpha - purple
} else if (dominantWave === betaValue) {
color = '#82ca9d'; // Beta - green
} else {
color = '#ffc658'; // Theta - yellow
}
// Scale based on pulse data
const scale = 0.2 + (chipInput.puls[i % chipInput.puls.length] / 200);
// Pulse speed and intensity based on HRV and movement
const pulseSpeed = 1 + (chipInput.hrv[i % chipInput.hrv.length] / 35);
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
});
}
return nodes;
}, [dream]);
// Generate connections between nodes
const connections = useMemo(() => {
if (dream.input.inputType !== 'chip') return [];
const connections = [];
const connectionCount = nodes.length * 2; // Each node connects to ~2 others
for (let i = 0; i < connectionCount; i++) {
const startNodeIndex = i % nodes.length;
// Connect to a nearby node (not completely random)
const endNodeIndex = (startNodeIndex + 1 + Math.floor(Math.random() * 5)) % nodes.length;
if (startNodeIndex !== endNodeIndex) {
connections.push({
start: nodes[startNodeIndex].position,
end: nodes[endNodeIndex].position,
color: nodes[startNodeIndex].color,
thickness: 0.05 + Math.random() * 0.05,
pulseSpeed: nodes[startNodeIndex].pulseSpeed,
pulseIntensity: nodes[startNodeIndex].pulseIntensity
});
}
}
return connections;
}, [nodes, dream]);
return (
<>
{/* Render all nodes */}
{nodes.map((node, index) => (
<NeuralNode
key={`node-${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}`}
start={connection.start}
end={connection.end}
color={connection.color}
thickness={connection.thickness}
pulseSpeed={connection.pulseSpeed}
pulseIntensity={connection.pulseIntensity}
/>
))}
</>
);
};
// Main DreamVR component
interface DreamVRProps {
dream: Dream;
height?: string;
}
const DreamVR: React.FC<DreamVRProps> = ({dream, height = '500px'}) => {
// Only render 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 style={{height, width: '100%'}}>
<Canvas shadows>
{/* Camera setup */}
<PerspectiveCamera makeDefault position={[0, 0, 30]}/>
{/* Controls */}
<OrbitControls
enableZoom={true}
enablePan={true}
enableRotate={true}
autoRotate={true}
autoRotateSpeed={0.5}
/>
{/* Lighting */}
<ambientLight intensity={0.5}/>
<pointLight position={[10, 10, 10]} intensity={1}/>
<pointLight position={[-10, -10, -10]} intensity={0.5}/>
{/* Neural network visualization */}
<NeuralNetwork dream={dream}/>
{/* Background */}
<color attach="background" args={['#000']}/>
</Canvas>
</div>
);
};
export default DreamVR;

View File

@@ -85,6 +85,7 @@ export const mockDreams: Dream[] = [
interpretation: 'Diese Szene steht für Geborgenheit und Gemeinschaft auf einer höheren Ebene. Der Sternenstaub in den Tassen symbolisiert geteilte Träume, die pastelligen Regenbögen zeigen eine optimistische Grundstimmung und Leichtigkeit.',
image: '04.png',
audio: '04.mp3',
video: '04.mp4'
}
}),
@@ -102,7 +103,8 @@ export const mockDreams: Dream[] = [
ai: {
interpretation: 'Der Spiegelgarten repräsentiert Selbsterkenntnis und Rückblick auf die eigene Vergangenheit. Jeder Spiegel steht für ein Fragment der Erinnerung, das entfernte Lachen deutet auf positive Prägungen und emotionale Verankerung hin.',
image: '05.png',
audio: '05.mp3'
audio: '05.mp3',
video: '05.mp4'
}
}),
@@ -208,7 +210,7 @@ export const mockDreams: Dream[] = [
tags: ['Natur', 'Technologie', 'Innovation'],
input: {
inputType: 'image',
img: 'digital_garden.jpg',
img: 'in_11.png',
imgAlt: 'Ein Garten aus leuchtenden Codezeilen und holografischen Pflanzen',
description: 'Ich pflegte einen Garten, in dem Pflanzen aus Codezeilen und Algorithmen wuchsen. Jede Blüte entfaltete sich zu einer neuen Technologie.'
} as ImageInput,

View File

@@ -11,6 +11,7 @@ import 'slick-carousel/slick/slick.css';
import 'slick-carousel/slick/slick-theme.css';
import {useEffect, useRef} from 'react';
import * as d3 from 'd3';
import DreamVR from '../components/DreamVR';
export default function DreamPage() {
const {id} = useParams<{ id: string }>();
@@ -530,6 +531,16 @@ export default function DreamPage() {
</div>
</div>)}
{/* VR Visualization */}
{(dream.id === 10 || dream.input.inputType === 'chip') && (
<div className="dreamy-card rounded-xl sm:rounded-2xl p-4 sm:p-6">
<div className="flex items-center mb-4">
<span className="font-medium dreamy-text">VR-Visualisierung</span>
</div>
<DreamVR dream={dream} height="600px"/>
</div>
)}
<div className="dreamy-card rounded-xl sm:rounded-2xl p-4 sm:p-6">
<h2 className="text-lg font-semibold mb-3 dreamy-text">Details</h2>
<div className="space-y-3" style={{color: 'var(--text-muted)'}}>