Skip to content

src/explosions.ts

79 lines · 2.3 KB · TypeScript
Raw
import * as THREE from 'three'

type Burst = {
  points: THREE.Points<THREE.BufferGeometry, THREE.PointsMaterial>
  velocities: Float32Array
  life: number
  maxLife: number
}

export class Explosions {
  private bursts: Burst[] = []

  constructor(private readonly scene: THREE.Scene) {}

  spawn(position: THREE.Vector3, color: number, count = 24, speed = 8) {
    const positions = new Float32Array(count * 3)
    const velocities = new Float32Array(count * 3)
    for (let i = 0; i < count; i++) {
      positions[i * 3] = position.x
      positions[i * 3 + 1] = position.y
      positions[i * 3 + 2] = position.z

      const angle = Math.random() * Math.PI * 2
      const magnitude = THREE.MathUtils.randFloat(0.3, 1) * speed
      velocities[i * 3] = Math.cos(angle) * magnitude
      velocities[i * 3 + 1] = Math.sin(angle) * magnitude
      velocities[i * 3 + 2] = THREE.MathUtils.randFloatSpread(magnitude * 0.5)
    }

    const geometry = new THREE.BufferGeometry()
    geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3))
    const points = new THREE.Points(
      geometry,
      new THREE.PointsMaterial({
        color,
        size: 0.45,
        transparent: true,
        depthWrite: false,
        blending: THREE.AdditiveBlending,
      }),
    )
    this.scene.add(points)

    const maxLife = THREE.MathUtils.randFloat(0.5, 0.8)
    this.bursts.push({ points, velocities, life: maxLife, maxLife })
  }

  update(dt: number) {
    const damping = Math.exp(-3 * dt)
    for (const burst of this.bursts) {
      burst.life -= dt
      const attribute = burst.points.geometry.attributes.position!
      const positions = attribute.array as Float32Array
      for (let i = 0; i < positions.length; i++) {
        positions[i]! += burst.velocities[i]! * dt
        burst.velocities[i]! *= damping
      }
      attribute.needsUpdate = true
      burst.points.material.opacity = Math.max(0, burst.life / burst.maxLife)
    }

    this.bursts = this.bursts.filter((burst) => {
      if (burst.life > 0) return true
      this.dispose(burst)
      return false
    })
  }

  clear() {
    for (const burst of this.bursts) this.dispose(burst)
    this.bursts = []
  }

  private dispose(burst: Burst) {
    this.scene.remove(burst.points)
    burst.points.geometry.dispose()
    burst.points.material.dispose()
  }
}