[READ-ONLY] Mirror of https://github.com/flo-bit/bluesky-particles. flo-bit.dev/bluesky-visualizers/particles
0

Configure Feed

Select the types of activity you want to include in your feed.

bluesky-particles / src / particles.ts
3.0 kB 109 lines
1import * as PIXI from "pixi.js"; 2 3interface ParticleSprite extends PIXI.Sprite { 4 speedX: number; 5 speedY: number; 6 age: number; 7 maxAge: number; 8 initialSize: number; 9} 10 11export type ParticleOptions = { 12 x: number; 13 y: number; 14 size: number; 15 color: PIXI.ColorSource; 16 alpha: number; 17 speedX: number; 18 speedY: number; 19 maxAge: number; 20}; 21 22export default class ParticleSystem { 23 private app: PIXI.Application; 24 private maxParticles: number; 25 public container: PIXI.Container; 26 private particles: ParticleSprite[]; 27 private particlePool: ParticleSprite[]; 28 private baseTexture: PIXI.Texture; 29 30 private loaded = false; 31 32 constructor(texture: string, maxParticles: number = 10000) { 33 this.maxParticles = maxParticles; 34 this.container = new PIXI.Container(); 35 this.container.zIndex = 10; 36 this.particles = []; 37 this.particlePool = []; 38 this.loadTexture(texture); 39 } 40 41 async loadTexture(url: string) { 42 const texture = await PIXI.Assets.load(url); 43 this.baseTexture = texture; 44 this.loaded = true; 45 } 46 47 spawnParticle(opts: Partial<ParticleOptions>): ParticleSprite | null { 48 if (!this.loaded) return null; 49 let particle: ParticleSprite; 50 if (this.particlePool.length > 0) { 51 particle = this.particlePool.pop()!; 52 } else if (this.particles.length < this.maxParticles) { 53 particle = new PIXI.Sprite(this.baseTexture) as ParticleSprite; 54 particle.anchor.set(0.5, 0.5); 55 this.container.addChild(particle); 56 } else { 57 return null; 58 } 59 60 particle.rotation = (Math.random() - 0.5) * 0.5; 61 particle.visible = true; 62 particle.x = opts.x ?? 0; 63 particle.y = opts.y ?? 0; 64 particle.width = particle.height = opts.size ?? 1; 65 particle.tint = opts.color ?? 0xffffff; 66 particle.alpha = opts.alpha ?? 1; 67 particle.speedX = opts.speedX ?? 0; 68 particle.speedY = opts.speedY ?? 0; 69 particle.age = 0; 70 particle.maxAge = opts.maxAge ?? 1; 71 particle.initialSize = opts.size ?? 1; 72 73 this.particles.push(particle); 74 75 return particle; 76 } 77 78 removeParticle(particle: ParticleSprite): void { 79 const index = this.particles.indexOf(particle); 80 if (index !== -1) { 81 particle.visible = false; 82 83 this.particles.splice(index, 1); 84 this.particlePool.push(particle); 85 } 86 } 87 88 update(deltaTime: number): void { 89 for (let i = this.particles.length - 1; i >= 0; i--) { 90 const particle = this.particles[i]; 91 particle.x += particle.speedX * deltaTime; 92 particle.y += particle.speedY * deltaTime; 93 particle.age += deltaTime; 94 95 // Shrink the particle 96 let lifeRatio = 1 - particle.age / particle.maxAge; 97 lifeRatio = Math.min(Math.max(lifeRatio, 0), 1); 98 particle.width = particle.height = particle.initialSize * lifeRatio; 99 100 // Remove particle if it's too old or off-screen 101 if ( 102 particle.age >= particle.maxAge || 103 particle.initialSize * lifeRatio < 4 104 ) { 105 this.removeParticle(particle); 106 } 107 } 108 } 109}