import * as THREE from 'three' type Burst = { points: THREE.Points 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() } }