introduced WebXR support by integrating webxr-polyfill
and refactoring DreamVR
component for enhanced VR functionality and simplified code structure
Signed-off-by: Matthias Puchstein <matthias@puchstein.bayern>
This commit is contained in:
BIN
docs/poster.pdf
Normal file
BIN
docs/poster.pdf
Normal file
Binary file not shown.
49
package-lock.json
generated
49
package-lock.json
generated
@@ -20,7 +20,8 @@
|
|||||||
"react-slick": "^0.30.3",
|
"react-slick": "^0.30.3",
|
||||||
"slick-carousel": "^1.8.1",
|
"slick-carousel": "^1.8.1",
|
||||||
"tailwindcss": "^4.1.11",
|
"tailwindcss": "^4.1.11",
|
||||||
"three": "^0.178.0"
|
"three": "^0.178.0",
|
||||||
|
"webxr-polyfill": "^2.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.29.0",
|
"@eslint/js": "^9.29.0",
|
||||||
@@ -29,6 +30,7 @@
|
|||||||
"@types/react-dom": "^19.1.6",
|
"@types/react-dom": "^19.1.6",
|
||||||
"@types/react-slick": "^0.23.13",
|
"@types/react-slick": "^0.23.13",
|
||||||
"@types/three": "^0.178.1",
|
"@types/three": "^0.178.1",
|
||||||
|
"@types/webxr": "^0.5.22",
|
||||||
"@vitejs/plugin-react": "^4.5.2",
|
"@vitejs/plugin-react": "^4.5.2",
|
||||||
"eslint": "^9.29.0",
|
"eslint": "^9.29.0",
|
||||||
"eslint-plugin-react-hooks": "^5.2.0",
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
@@ -2963,6 +2965,17 @@
|
|||||||
],
|
],
|
||||||
"license": "CC-BY-4.0"
|
"license": "CC-BY-4.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/cardboard-vr-display": {
|
||||||
|
"version": "1.0.19",
|
||||||
|
"resolved": "https://registry.npmjs.org/cardboard-vr-display/-/cardboard-vr-display-1.0.19.tgz",
|
||||||
|
"integrity": "sha512-+MjcnWKAkb95p68elqZLDPzoiF/dGncQilLGvPBM5ZorABp/ao3lCs7nnRcYBckmuNkg1V/5rdGDKoUaCVsHzQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"gl-preserve-state": "^1.0.0",
|
||||||
|
"nosleep.js": "^0.7.0",
|
||||||
|
"webvr-polyfill-dpdb": "^1.0.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/case-anything": {
|
"node_modules/case-anything": {
|
||||||
"version": "2.1.13",
|
"version": "2.1.13",
|
||||||
"resolved": "https://registry.npmjs.org/case-anything/-/case-anything-2.1.13.tgz",
|
"resolved": "https://registry.npmjs.org/case-anything/-/case-anything-2.1.13.tgz",
|
||||||
@@ -4025,6 +4038,12 @@
|
|||||||
"integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==",
|
"integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/gl-preserve-state": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/gl-preserve-state/-/gl-preserve-state-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-zQZ25l3haD4hvgJZ6C9+s0ebdkW9y+7U2qxvGu1uWOJh8a4RU+jURIKEQhf8elIlFpMH6CrAY2tH0mYrRjet3Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/glob-parent": {
|
"node_modules/glob-parent": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||||
@@ -4785,6 +4804,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/nosleep.js": {
|
||||||
|
"version": "0.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/nosleep.js/-/nosleep.js-0.7.0.tgz",
|
||||||
|
"integrity": "sha512-Z4B1HgvzR+en62ghwZf6BwAR6x4/pjezsiMcbF9KMLh7xoscpoYhaSXfY3lLkqC68AtW+/qLJ1lzvBIj0FGaTA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/object-assign": {
|
"node_modules/object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
@@ -5927,6 +5952,28 @@
|
|||||||
"integrity": "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==",
|
"integrity": "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/webvr-polyfill-dpdb": {
|
||||||
|
"version": "1.0.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/webvr-polyfill-dpdb/-/webvr-polyfill-dpdb-1.0.18.tgz",
|
||||||
|
"integrity": "sha512-O0S1ZGEWyPvyZEkS2VbyV7mtir/NM9MNK3EuhbHPoJ8EHTky2pTXehjIl+IiDPr+Lldgx129QGt3NGly7rwRPw==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
|
"node_modules/webxr-polyfill": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/webxr-polyfill/-/webxr-polyfill-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-lgTKYVeD4HeTwWdJN+SeS6iflGx3epz/3dww9X4GyuuXmYGAV5p8l34jUM/HRGHn1jKS3oZNZRC/J9MlSk/Zhg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"cardboard-vr-display": "^1.0.19",
|
||||||
|
"gl-matrix": "^2.8.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/webxr-polyfill/node_modules/gl-matrix": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-0YCjVpE3pS5XWlN3J4X7AiAx65+nqAI54LndtVFnQZB6G/FVLkZH8y8V6R3cIoOQR4pUdfwQGd1iwyoXHJ4Qfw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/which": {
|
"node_modules/which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
|
@@ -22,7 +22,8 @@
|
|||||||
"react-slick": "^0.30.3",
|
"react-slick": "^0.30.3",
|
||||||
"slick-carousel": "^1.8.1",
|
"slick-carousel": "^1.8.1",
|
||||||
"tailwindcss": "^4.1.11",
|
"tailwindcss": "^4.1.11",
|
||||||
"three": "^0.178.0"
|
"three": "^0.178.0",
|
||||||
|
"webxr-polyfill": "^2.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.29.0",
|
"@eslint/js": "^9.29.0",
|
||||||
@@ -31,6 +32,7 @@
|
|||||||
"@types/react-dom": "^19.1.6",
|
"@types/react-dom": "^19.1.6",
|
||||||
"@types/react-slick": "^0.23.13",
|
"@types/react-slick": "^0.23.13",
|
||||||
"@types/three": "^0.178.1",
|
"@types/three": "^0.178.1",
|
||||||
|
"@types/webxr": "^0.5.22",
|
||||||
"@vitejs/plugin-react": "^4.5.2",
|
"@vitejs/plugin-react": "^4.5.2",
|
||||||
"eslint": "^9.29.0",
|
"eslint": "^9.29.0",
|
||||||
"eslint-plugin-react-hooks": "^5.2.0",
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
|
@@ -1,18 +1,9 @@
|
|||||||
import React, {useEffect, useMemo, useRef, useState} from 'react';
|
import React, {useMemo, useRef} from 'react';
|
||||||
import {Canvas, useFrame, useThree} from '@react-three/fiber';
|
import {Canvas, useFrame} from '@react-three/fiber';
|
||||||
import {OrbitControls, PerspectiveCamera} from '@react-three/drei';
|
import {createXRStore, XR} from '@react-three/xr';
|
||||||
|
import {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';
|
||||||
import {DeviceOrientationControls} from 'three-stdlib';
|
|
||||||
|
|
||||||
// Extended window interface with DeviceOrientationEvent
|
|
||||||
interface WindowWithDeviceOrientation extends Window {
|
|
||||||
DeviceOrientationEvent: {
|
|
||||||
prototype: DeviceOrientationEvent;
|
|
||||||
new(type: string, eventInitDict?: DeviceOrientationEventInit): DeviceOrientationEvent;
|
|
||||||
requestPermission?: () => Promise<string>;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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}: {
|
||||||
@@ -224,137 +215,18 @@ const NeuralNetwork = ({dream}: { dream: Dream }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Custom controls component that switches between OrbitControls and DeviceOrientationControls
|
|
||||||
const Controls = ({useDeviceOrientation}: { useDeviceOrientation: boolean }) => {
|
|
||||||
const {camera} = useThree();
|
|
||||||
const controlsRef = useRef<DeviceOrientationControls | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (useDeviceOrientation) {
|
|
||||||
// Create DeviceOrientationControls
|
|
||||||
controlsRef.current = new DeviceOrientationControls(camera);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (controlsRef.current) {
|
|
||||||
controlsRef.current.disconnect();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}, [camera, useDeviceOrientation]);
|
|
||||||
|
|
||||||
useFrame(() => {
|
|
||||||
if (useDeviceOrientation && controlsRef.current) {
|
|
||||||
controlsRef.current.update();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// If not using device orientation, use OrbitControls
|
|
||||||
if (!useDeviceOrientation) {
|
|
||||||
return (
|
|
||||||
<OrbitControls
|
|
||||||
enableZoom={true}
|
|
||||||
enablePan={true}
|
|
||||||
enableRotate={true}
|
|
||||||
autoRotate={true}
|
|
||||||
autoRotateSpeed={0.5}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Main DreamVR component
|
// Main DreamVR component
|
||||||
interface DreamVRProps {
|
interface DreamVRProps {
|
||||||
dream: Dream;
|
dream: Dream;
|
||||||
height?: string;
|
height?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create XR store outside the component to avoid recreation on each render
|
||||||
|
const store = createXRStore();
|
||||||
|
|
||||||
const DreamVR: React.FC<DreamVRProps> = ({dream, height = '500px'}) => {
|
const DreamVR: React.FC<DreamVRProps> = ({dream, height = '500px'}) => {
|
||||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
|
||||||
const [useDeviceOrientation, setUseDeviceOrientation] = useState(false);
|
|
||||||
const [deviceOrientationPermission, setDeviceOrientationPermission] = useState<'granted' | 'denied' | 'unknown'>('unknown');
|
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// Function to toggle fullscreen
|
|
||||||
const toggleFullscreen = () => {
|
|
||||||
if (!document.fullscreenElement) {
|
|
||||||
// Enter fullscreen
|
|
||||||
if (containerRef.current?.requestFullscreen) {
|
|
||||||
containerRef.current.requestFullscreen()
|
|
||||||
.then(() => setIsFullscreen(true))
|
|
||||||
.catch(err => console.error(`Error attempting to enable fullscreen: ${err.message}`));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Exit fullscreen
|
|
||||||
if (document.exitFullscreen) {
|
|
||||||
document.exitFullscreen()
|
|
||||||
.then(() => setIsFullscreen(false))
|
|
||||||
.catch(err => console.error(`Error attempting to exit fullscreen: ${err.message}`));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Function to request device orientation permission
|
|
||||||
const requestDeviceOrientationPermission = () => {
|
|
||||||
// Cast window to our extended interface
|
|
||||||
const windowWithOrientation = window as unknown as WindowWithDeviceOrientation;
|
|
||||||
|
|
||||||
// Check if DeviceOrientationEvent is available and if requestPermission is a function
|
|
||||||
if (typeof windowWithOrientation.DeviceOrientationEvent !== 'undefined' &&
|
|
||||||
typeof windowWithOrientation.DeviceOrientationEvent.requestPermission === 'function') {
|
|
||||||
// iOS 13+ requires permission
|
|
||||||
windowWithOrientation.DeviceOrientationEvent.requestPermission()
|
|
||||||
.then((permissionState: string) => {
|
|
||||||
if (permissionState === 'granted') {
|
|
||||||
setDeviceOrientationPermission('granted');
|
|
||||||
setUseDeviceOrientation(true);
|
|
||||||
} else {
|
|
||||||
setDeviceOrientationPermission('denied');
|
|
||||||
setUseDeviceOrientation(false);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error: Error) => {
|
|
||||||
console.error('Error requesting device orientation permission:', error);
|
|
||||||
setDeviceOrientationPermission('denied');
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// For non-iOS devices or older iOS versions
|
|
||||||
// Check if device orientation events are supported
|
|
||||||
if (window.DeviceOrientationEvent) {
|
|
||||||
setDeviceOrientationPermission('granted');
|
|
||||||
setUseDeviceOrientation(true);
|
|
||||||
} else {
|
|
||||||
setDeviceOrientationPermission('denied');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Effect to handle fullscreen change
|
|
||||||
useEffect(() => {
|
|
||||||
const handleFullscreenChange = () => {
|
|
||||||
setIsFullscreen(!!document.fullscreenElement);
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener('fullscreenchange', handleFullscreenChange);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener('fullscreenchange', handleFullscreenChange);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Check if device is mobile
|
|
||||||
useEffect(() => {
|
|
||||||
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
|
|
||||||
if (isMobile) {
|
|
||||||
// For mobile devices, we'll show the device orientation button
|
|
||||||
setDeviceOrientationPermission('unknown');
|
|
||||||
} else {
|
|
||||||
// For desktop, we'll use orbit controls
|
|
||||||
setDeviceOrientationPermission('denied');
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// 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 (
|
||||||
@@ -368,74 +240,47 @@ const DreamVR: React.FC<DreamVRProps> = ({dream, height = '500px'}) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={containerRef} style={{height, width: '100%', position: 'relative'}}>
|
<div ref={containerRef} style={{height, width: '100%', position: 'relative'}}>
|
||||||
{/* Controls */}
|
{/* VR Entry Button */}
|
||||||
<div className="absolute top-2 right-2 z-10 flex space-x-2">
|
<div className="absolute top-2 right-2 z-10 flex space-x-2">
|
||||||
<button
|
<button
|
||||||
onClick={toggleFullscreen}
|
onClick={() => store.enterVR()}
|
||||||
className="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={isFullscreen ? "Exit Fullscreen" : "Enter Fullscreen"}
|
aria-label="Enter VR"
|
||||||
>
|
>
|
||||||
{isFullscreen ? (
|
<svg
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
width="24"
|
||||||
<path
|
height="24"
|
||||||
d="M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3"/>
|
viewBox="0 0 24 24"
|
||||||
</svg>
|
fill="none"
|
||||||
) : (
|
stroke="currentColor"
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
strokeWidth="2"
|
||||||
stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
strokeLinecap="round"
|
||||||
<path
|
strokeLinejoin="round"
|
||||||
d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/>
|
>
|
||||||
</svg>
|
<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>
|
</button>
|
||||||
|
|
||||||
{deviceOrientationPermission === 'unknown' && (
|
|
||||||
<button
|
|
||||||
onClick={requestDeviceOrientationPermission}
|
|
||||||
className="p-2 bg-white/20 backdrop-blur-sm rounded-full text-white hover:bg-white/30 transition-colors"
|
|
||||||
aria-label="Enable Device Orientation"
|
|
||||||
>
|
|
||||||
<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="5" y="2" width="14" height="20" rx="2" ry="2"/>
|
|
||||||
<line x1="12" y1="18" x2="12" y2="18"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{deviceOrientationPermission === 'granted' && (
|
|
||||||
<button
|
|
||||||
onClick={() => setUseDeviceOrientation(!useDeviceOrientation)}
|
|
||||||
className={`p-2 backdrop-blur-sm rounded-full text-white transition-colors ${useDeviceOrientation ? 'bg-white/40' : 'bg-white/20 hover:bg-white/30'}`}
|
|
||||||
aria-label={useDeviceOrientation ? "Disable Device Orientation" : "Enable Device Orientation"}
|
|
||||||
>
|
|
||||||
<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="5" y="2" width="14" height="20" rx="2" ry="2"/>
|
|
||||||
<path d="M12 18h.01"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Canvas shadows>
|
<Canvas shadows>
|
||||||
{/* Camera setup */}
|
<XR store={store}>
|
||||||
<PerspectiveCamera makeDefault position={[0, 0, 30]}/>
|
{/* Camera setup */}
|
||||||
|
<PerspectiveCamera makeDefault position={[0, 0, 30]}/>
|
||||||
|
|
||||||
{/* Controls - either OrbitControls or DeviceOrientationControls */}
|
{/* Lighting */}
|
||||||
<Controls useDeviceOrientation={useDeviceOrientation}/>
|
<ambientLight intensity={0.5}/>
|
||||||
|
<directionalLight position={[10, 10, 10]} intensity={1}/>
|
||||||
|
<directionalLight position={[-10, -10, -10]} intensity={0.5}/>
|
||||||
|
|
||||||
{/* Lighting */}
|
{/* Neural network visualization */}
|
||||||
<ambientLight intensity={0.5}/>
|
<NeuralNetwork dream={dream}/>
|
||||||
<pointLight position={[10, 10, 10]} intensity={1}/>
|
|
||||||
<pointLight position={[-10, -10, -10]} intensity={0.5}/>
|
|
||||||
|
|
||||||
{/* Neural network visualization */}
|
{/* Background */}
|
||||||
<NeuralNetwork dream={dream}/>
|
<color attach="background" args={['#000']}/>
|
||||||
|
</XR>
|
||||||
{/* Background */}
|
|
||||||
<color attach="background" args={['#000']}/>
|
|
||||||
</Canvas>
|
</Canvas>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
Reference in New Issue
Block a user