Spaces:
Build error
Build error
File size: 4,580 Bytes
1296ae5 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | import { useRef, useMemo, useState, useEffect } from 'react';
import { Canvas, useFrame } from '@react-three/fiber';
import { OrbitControls, Stars, useTexture } from '@react-three/drei';
import * as THREE from 'three';
function GaussianPoints({ worldData, isLoading }) {
const pointsRef = useRef();
const [hovered, setHovered] = useState(false);
const { positions, colors, sizes } = useMemo(() => {
if (isLoading || !worldData) {
return { positions: new Float32Array(0), colors: new Float32Array(0), sizes: new Float32Array(0) };
}
const count = worldData.pointCount || 50000;
const positions = new Float32Array(count * 3);
const colors = new Float32Array(count * 3);
const sizes = new Float32Array(count);
const colorPalette = worldData.colorPalette || [[0.4, 0.6, 0.9], [0.2, 0.8, 0.4], [0.9, 0.3, 0.5]];
for (let i = 0; i < count; i++) {
const i3 = i * 3;
// Spherical distribution for 360 capture feel
const theta = Math.random() * Math.PI * 2;
const phi = Math.acos(2 * Math.random() - 1);
const r = 2 + Math.random() * 3;
positions[i3] = r * Math.sin(phi) * Math.cos(theta);
positions[i3 + 1] = r * Math.sin(phi) * Math.sin(theta);
positions[i3 + 2] = r * Math.cos(phi);
const color = colorPalette[Math.floor(Math.random() * colorPalette.length)];
colors[i3] = color[0] + (Math.random() - 0.5) * 0.2;
colors[i3 + 1] = color[1] + (Math.random() - 0.5) * 0.2;
colors[i3 + 2] = color[2] + (Math.random() - 0.5) * 0.2;
sizes[i] = Math.random() * 0.05 + 0.01;
}
return { positions, colors, sizes };
}, [worldData, isLoading]);
useFrame((state) => {
if (pointsRef.current) {
pointsRef.current.rotation.y += 0.001;
pointsRef.current.rotation.x = Math.sin(state.clock.elapsedTime * 0.1) * 0.1;
}
});
if (isLoading) return null;
return (
<points
ref={pointsRef}
onPointerOver={() => setHovered(true)}
onPointerOut={() => setHovered(false)}
>
<bufferGeometry>
<bufferAttribute
attach="attributes-position"
count={positions.length / 3}
array={positions}
itemSize={3}
/>
<bufferAttribute
attach="attributes-color"
count={colors.length / 3}
array={colors}
itemSize={3}
/>
<bufferAttribute
attach="attributes-size"
count={sizes.length}
array={sizes}
itemSize={1}
/>
</bufferGeometry>
<pointsMaterial
size={0.05}
vertexColors
transparent
opacity={0.8}
sizeAttenuation
blending={THREE.AdditiveBlending}
depthWrite={false}
/>
</points>
);
}
function Scene({ worldData, isLoading }) {
return (
<>
<ambientLight intensity={0.5} />
<pointLight position={[10, 10, 10]} intensity={1} />
<Stars radius={100} depth={50} count={5000} factor={4} saturation={0} fade speed={1} />
<GaussianPoints worldData={worldData} isLoading={isLoading} />
<OrbitControls
enablePan={true}
enableZoom={true}
enableRotate={true}
zoomSpeed={0.5}
rotateSpeed={0.5}
minDistance={2}
maxDistance={20}
/>
</>
);
}
export default function GaussianSplatViewer({ worldData, isLoading }) {
if (isLoading) {
return (
<div className="w-full h-full flex items-center justify-center bg-slate-950">
<div className="text-center">
<div className="w-16 h-16 border-4 border-indigo-500 border-t-transparent rounded-full animate-spin mx-auto mb-4"></div>
<p className="text-slate-400 animate-pulse">Loading Gaussian Splats...</p>
<p className="text-slate-600 text-sm mt-2">Processing 360° capture data</p>
</div>
</div>
);
}
return (
<div className="w-full h-full relative">
<Canvas
camera={{ position: [0, 0, 8], fov: 60 }}
dpr={[1, 2]}
gl={{ antialias: true, alpha: true }}
style={{ background: 'radial-gradient(circle at center, #1e293b 0%, #020617 100%)' }}
>
<Scene worldData={worldData} isLoading={isLoading} />
</Canvas>
<div className="absolute bottom-4 left-4 glass-panel rounded-lg p-3 text-xs text-slate-400">
<p className="font-semibold text-slate-200 mb-1">Controls</p>
<p>Left Click: Rotate</p>
<p>Right Click: Pan</p>
<p>Scroll: Zoom</p>
</div>
</div>
);
} |