Files
Website/src/lib/components/ParticleField.svelte
2025-11-28 02:40:12 +00:00

102 lines
2.9 KiB
Svelte

<script lang="ts">
import { T, useTask } from '@threlte/core';
import { onMount } from 'svelte';
import * as THREE from 'three';
import { themeColors, mode } from '$lib/stores/theme';
interface Props {
particleCount?: number;
}
let { particleCount = 500 }: Props = $props();
let particlesGeometry: THREE.BufferGeometry;
let particlesMaterial: THREE.PointsMaterial;
let particles = $state<THREE.Points | null>(null);
let positions: Float32Array;
let velocities: Float32Array;
let time = 0;
// Create particles
function createParticles() {
positions = new Float32Array(particleCount * 3);
velocities = new Float32Array(particleCount * 3);
for (let i = 0; i < particleCount; i++) {
const i3 = i * 3;
// Spread particles in a larger area
positions[i3] = (Math.random() - 0.5) * 20;
positions[i3 + 1] = (Math.random() - 0.5) * 20;
positions[i3 + 2] = (Math.random() - 0.5) * 20;
// Random velocities
velocities[i3] = (Math.random() - 0.5) * 0.01;
velocities[i3 + 1] = (Math.random() - 0.5) * 0.01;
velocities[i3 + 2] = (Math.random() - 0.5) * 0.01;
}
particlesGeometry = new THREE.BufferGeometry();
particlesGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
particlesMaterial = new THREE.PointsMaterial({
size: 0.05,
sizeAttenuation: true,
transparent: true,
opacity: 0.6,
blending: THREE.AdditiveBlending,
depthWrite: false
});
particles = new THREE.Points(particlesGeometry, particlesMaterial);
}
onMount(() => {
createParticles();
});
// Update particle color based on theme
$effect(() => {
if (particlesMaterial) {
const color = new THREE.Color($themeColors.primary);
particlesMaterial.color = color;
particlesMaterial.opacity = $mode === 'dark' ? 0.6 : 0.4;
}
});
// Animation loop
useTask((delta) => {
if (!particles || !particlesGeometry) return;
time += delta;
const positionAttribute = particlesGeometry.getAttribute('position') as THREE.BufferAttribute;
const positionArray = positionAttribute.array as Float32Array;
for (let i = 0; i < particleCount; i++) {
const i3 = i * 3;
// Add some wave motion
positionArray[i3] += velocities[i3] + Math.sin(time + i * 0.1) * 0.001;
positionArray[i3 + 1] += velocities[i3 + 1] + Math.cos(time + i * 0.1) * 0.001;
positionArray[i3 + 2] += velocities[i3 + 2];
// Wrap around boundaries
if (positionArray[i3] > 10) positionArray[i3] = -10;
if (positionArray[i3] < -10) positionArray[i3] = 10;
if (positionArray[i3 + 1] > 10) positionArray[i3 + 1] = -10;
if (positionArray[i3 + 1] < -10) positionArray[i3 + 1] = 10;
if (positionArray[i3 + 2] > 10) positionArray[i3 + 2] = -10;
if (positionArray[i3 + 2] < -10) positionArray[i3 + 2] = 10;
}
positionAttribute.needsUpdate = true;
// Rotate the entire particle system slowly
particles.rotation.y += delta * 0.05;
particles.rotation.x += delta * 0.02;
});
</script>
{#if particles}
<T is={particles} />
{/if}