Skip to content

Instantly share code, notes, and snippets.

@netgfx
Created November 21, 2024 23:19
Show Gist options
  • Save netgfx/983c305ef540c8d396a3c3c90686ac41 to your computer and use it in GitHub Desktop.
Save netgfx/983c305ef540c8d396a3c3c90686ac41 to your computer and use it in GitHub Desktop.
SeasonalParticles
import React, { useRef, useMemo } from "react";
import { Canvas, useFrame, useLoader } from "@react-three/fiber";
import { TextureLoader, Mesh, Group, Color, Texture } from "three";
import { OrbitControls } from "@react-three/drei";
interface LeafProps {
position: [number, number, number];
rotation: [number, number, number];
scale: number;
speed: number;
texture: Texture;
}
const leafTexture =
"https://gwcjylrsyylsuacdrnov.supabase.co/storage/v1/object/public/assets/leaf.png";
const flowerTexture =
"https://gwcjylrsyylsuacdrnov.supabase.co/storage/v1/object/public/assets/flower.png";
const snowTexture =
"https://gwcjylrsyylsuacdrnov.supabase.co/storage/v1/object/public/assets/snow.png";
const icecreamTexture =
"https://gwcjylrsyylsuacdrnov.supabase.co/storage/v1/object/public/assets/icecream.png";
// Function to determine current season
const getCurrentSeason = (): "spring" | "summer" | "autumn" | "winter" => {
const now = new Date();
const month = now.getMonth();
if (month >= 2 && month <= 4) return "spring";
if (month >= 5 && month <= 7) return "summer";
if (month >= 8 && month <= 10) return "autumn";
return "winter";
};
// Season-specific configurations
const seasonConfig = {
spring: {
texture: flowerTexture,
particleCount: 40,
rotationSpeed: 0.03,
fallSpeed: 0.01,
scale: 0.15,
},
summer: {
texture: icecreamTexture,
particleCount: 30,
rotationSpeed: 0.02,
fallSpeed: 0.015,
scale: 0.2,
},
autumn: {
texture: leafTexture,
particleCount: 50,
rotationSpeed: 0.05,
fallSpeed: 0.02,
scale: 0.2,
},
winter: {
texture: snowTexture,
particleCount: 60,
rotationSpeed: 0.01,
fallSpeed: 0.025,
scale: 0.15,
},
};
function Particle({ position, rotation, scale, speed, texture }: LeafProps) {
const meshRef = useRef<Mesh>(null);
const season = getCurrentSeason();
const config = seasonConfig[season];
useFrame(() => {
if (meshRef.current) {
// Adjust movement based on season
meshRef.current.rotation.y += config.rotationSpeed;
meshRef.current.rotation.z += config.rotationSpeed;
meshRef.current.position.y -= speed * config.fallSpeed;
// Reset position when particle falls below view
if (meshRef.current.position.y < -3) {
meshRef.current.position.y += 6;
}
}
});
// Apply season-specific scale
const finalScale = scale * config.scale;
return (
<mesh
ref={meshRef}
position={position}
rotation={rotation}
scale={finalScale}
>
<planeGeometry args={[1, 1, 10, 10]} />
<meshBasicMaterial map={texture} transparent={true} side={2} />
</mesh>
);
}
function ParticleScene() {
const groupRef = useRef<Group>(null);
const season = getCurrentSeason();
const config = seasonConfig[season];
// Load the appropriate texture based on season
const texture = useLoader(TextureLoader, config.texture);
useFrame(() => {
if (groupRef.current) {
groupRef.current.rotation.y += 0.01;
}
});
const particles = useMemo(() => {
return Array.from({ length: config.particleCount }, (_, i) => ({
position: [
(Math.random() - 0.5) * 3,
(Math.random() - 0.5) * 6,
(Math.random() - 0.5) * 3,
] as [number, number, number],
rotation: [
0,
(Math.random() - 0.5) * 6.28,
(Math.random() - 0.5) * 6.28,
] as [number, number, number],
scale: Math.random() * 0.5 + 0.2,
speed: Math.random() * 0.25 + 0.05,
key: i,
}));
}, [config.particleCount]);
return (
<>
{texture && (
<group ref={groupRef}>
{particles.map(({ key, ...props }) => (
<Particle key={key} {...props} texture={texture} />
))}
</group>
)}
</>
);
}
export function SeasonalParticles() {
const [hasWebGLError, setHasWebGLError] = React.useState(false);
if (hasWebGLError) {
return null;
}
return (
<>
<div className="absolute right-0 top-0 z-[-1] h-screen w-[100vw] bg-gradient-to-r from-white to-transparent"></div>
<div className="absolute right-0 top-0 z-[99]">
<Canvas
camera={{ position: [0, 0, 7], fov: 35 }}
gl={{ antialias: true }}
onCreated={({ gl }) => {
gl.setClearColor(new Color(1, 1, 1), 0);
}}
style={{ width: "100vw", height: "100vh" }}
onError={() => setHasWebGLError(true)}
fallback={null}
>
<OrbitControls />
<ParticleScene />
</Canvas>
</div>
</>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment