202 lines
5.3 KiB
TypeScript
Executable File
202 lines
5.3 KiB
TypeScript
Executable File
|
|
import React, { useRef, useMemo, useEffect } from 'react';
|
|
import { Canvas, useFrame } from '@react-three/fiber';
|
|
import * as THREE from 'three';
|
|
|
|
// Add global declaration to fix TypeScript errors with React Three Fiber elements
|
|
declare module 'react' {
|
|
namespace JSX {
|
|
interface IntrinsicElements {
|
|
points: any;
|
|
bufferGeometry: any;
|
|
bufferAttribute: any;
|
|
pointsMaterial: any;
|
|
ambientLight: any;
|
|
}
|
|
}
|
|
}
|
|
|
|
declare global {
|
|
namespace JSX {
|
|
interface IntrinsicElements {
|
|
points: any;
|
|
bufferGeometry: any;
|
|
bufferAttribute: any;
|
|
pointsMaterial: any;
|
|
ambientLight: any;
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- WARP EFFECT ---
|
|
const WarpParticles = () => {
|
|
const count = 2000;
|
|
const mesh = useRef<THREE.Points>(null);
|
|
|
|
// Particles setup
|
|
const particles = useMemo(() => {
|
|
const temp = [];
|
|
for (let i = 0; i < count; i++) {
|
|
const x = (Math.random() - 0.5) * 30;
|
|
const y = (Math.random() - 0.5) * 60; // Taller spread
|
|
const z = (Math.random() - 0.5) * 20;
|
|
temp.push(x, y, z);
|
|
}
|
|
return new Float32Array(temp);
|
|
}, [count]);
|
|
|
|
useFrame((state, delta) => {
|
|
if (!mesh.current) return;
|
|
|
|
const time = state.clock.getElapsedTime();
|
|
|
|
// Logic:
|
|
// 1. Initial burst (0-1s): Speed = 3.0
|
|
// 2. Slow down (1s+): Speed = 0.5 (2x slower than a standard '1.0' baseline)
|
|
|
|
let targetSpeed = 0.5; // Slow cruising speed
|
|
|
|
if (time < 1.0) {
|
|
targetSpeed = 4.0; // Fast entry
|
|
} else if (time < 1.8) {
|
|
// Smooth deceleration phase
|
|
const t = (time - 1.0) / 0.8;
|
|
targetSpeed = THREE.MathUtils.lerp(4.0, 0.5, t);
|
|
}
|
|
|
|
const positions = mesh.current.geometry.attributes.position.array as Float32Array;
|
|
const moveY = targetSpeed * delta * 10; // Scale speed to movement
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
// Move particles up
|
|
positions[i * 3 + 1] += moveY;
|
|
|
|
// Seamless wrap around
|
|
if (positions[i * 3 + 1] > 30) {
|
|
positions[i * 3 + 1] -= 60; // Subtract height to wrap seamlessly
|
|
// Randomize X/Z on respawn to create new star patterns
|
|
positions[i * 3] = (Math.random() - 0.5) * 30;
|
|
positions[i * 3 + 2] = (Math.random() - 0.5) * 20;
|
|
}
|
|
}
|
|
|
|
mesh.current.geometry.attributes.position.needsUpdate = true;
|
|
|
|
// Stretch effect based on speed
|
|
// Higher speed = more vertical stretch
|
|
mesh.current.scale.y = 1 + targetSpeed * 0.5;
|
|
});
|
|
|
|
return (
|
|
<points ref={mesh}>
|
|
<bufferGeometry>
|
|
<bufferAttribute
|
|
attach="attributes-position"
|
|
count={particles.length / 3}
|
|
array={particles}
|
|
itemSize={3}
|
|
/>
|
|
</bufferGeometry>
|
|
<pointsMaterial
|
|
size={0.08}
|
|
color="#ffffff"
|
|
transparent
|
|
opacity={0.6}
|
|
sizeAttenuation
|
|
blending={THREE.AdditiveBlending}
|
|
depthWrite={false}
|
|
/>
|
|
</points>
|
|
);
|
|
};
|
|
|
|
export const WarpOverlay = () => {
|
|
return (
|
|
<div className="absolute inset-0 z-40 pointer-events-none mix-blend-screen">
|
|
<Canvas
|
|
camera={{ position: [0, 0, 15], fov: 60 }}
|
|
gl={{ alpha: true }}
|
|
style={{ pointerEvents: 'none' }} // Explicitly disable pointer events on canvas element
|
|
>
|
|
<WarpParticles />
|
|
</Canvas>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
// --- SAND EFFECT ---
|
|
const SandParticles = () => {
|
|
const count = 4000;
|
|
const mesh = useRef<THREE.Points>(null);
|
|
|
|
const particles = useMemo(() => {
|
|
const temp = [];
|
|
for (let i = 0; i < count; i++) {
|
|
const x = (Math.random() - 0.5) * 25;
|
|
const y = (Math.random() - 0.5) * 25;
|
|
const z = (Math.random() - 0.5) * 10;
|
|
temp.push(x, y, z);
|
|
}
|
|
return new Float32Array(temp);
|
|
}, [count]);
|
|
|
|
useFrame((state) => {
|
|
if (!mesh.current) return;
|
|
|
|
const positions = mesh.current.geometry.attributes.position.array as Float32Array;
|
|
const time = state.clock.getElapsedTime();
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
const ix = i * 3;
|
|
const iy = i * 3 + 1;
|
|
const iz = i * 3 + 2;
|
|
|
|
// Chaotic wind movement
|
|
positions[ix] += Math.sin(time * 0.5 + positions[iy] * 0.5) * 0.02;
|
|
positions[iy] += Math.cos(time * 0.3 + positions[ix] * 0.5) * 0.01;
|
|
|
|
// Wrap around
|
|
if (Math.abs(positions[ix]) > 12) positions[ix] *= -0.9;
|
|
if (Math.abs(positions[iy]) > 12) positions[iy] *= -0.9;
|
|
}
|
|
mesh.current.geometry.attributes.position.needsUpdate = true;
|
|
mesh.current.rotation.y = time * 0.05;
|
|
});
|
|
|
|
return (
|
|
<points ref={mesh}>
|
|
<bufferGeometry>
|
|
<bufferAttribute
|
|
attach="attributes-position"
|
|
count={particles.length / 3}
|
|
array={particles}
|
|
itemSize={3}
|
|
/>
|
|
</bufferGeometry>
|
|
<pointsMaterial
|
|
size={0.05}
|
|
color="#c4c3be"
|
|
transparent
|
|
opacity={0.8}
|
|
sizeAttenuation
|
|
depthWrite={false}
|
|
/>
|
|
</points>
|
|
);
|
|
};
|
|
|
|
export const SandOverlay = () => {
|
|
return (
|
|
<div className="absolute inset-0 z-40 pointer-events-none">
|
|
<Canvas
|
|
camera={{ position: [0, 0, 10], fov: 60 }}
|
|
gl={{ alpha: true }}
|
|
style={{ pointerEvents: 'none' }} // Explicitly disable pointer events on canvas element
|
|
>
|
|
<ambientLight intensity={1} />
|
|
<SandParticles />
|
|
</Canvas>
|
|
</div>
|
|
);
|
|
};
|