Skip to content

Instantly share code, notes, and snippets.

@pailhead
Created May 19, 2020 23:13
Show Gist options
  • Save pailhead/b6eae5d347d9817391bf7bef8b8a9e61 to your computer and use it in GitHub Desktop.
Save pailhead/b6eae5d347d9817391bf7bef8b8a9e61 to your computer and use it in GitHub Desktop.
particle worker
import { Vector3 } from 'three'
const PI2 = Math.PI * 2
class Particle {
public random0 = Math.random()
public random1 = Math.random()
public random11 = Math.random() * 2 - 1
position = new Vector3()
velocity = new Vector3()
angle = 0
angleVelocity = 0
life = 0
maxLife = 0
startSize = 0
endSize = 1
constructor(public index: number) {}
}
const _v3 = new Vector3()
class ParticleSystem {
private pool: Particle[] = []
private deadParticles: number[] = []
private liveParticles: Map<number, number> = new Map()
public birthRate = 40
public birthRateJitter = 0.3
public maxLifeTime = 15
public lifeTimeVariation = 0.2
public spreadXZ = Math.PI * 2
public spreadY = 0.15
public speed = 10
public speedVariation = 0
public angle = 0
public angleVariation = 1
public angleVelocity = 0.7
public angleVelocityVariation = 0.5
public drag = 0.6
public gravity = 0.3
public startSize = 7
public endSize = 20
public startSizeVariation = 0.5
public endSizeVariation = 0.2
// private _maxSize = 200
public position = new Vector3()
private _gravityV3 = new Vector3(0, 0, 0)
private _lastBirthLeftover = 0
private _textureSize = 2048
public positionTextureBuffer = new Float32Array(this._textureSize * 1 * 4) //xyz angle?
public lifeTextureBuffer = new Float32Array(
new Array(this._textureSize * 1 * 4)
)
public randomTextureBuffer = new Float32Array(
new Array(this._textureSize * 1 * 4).fill(-1)
)
public liveIndexTextureBuffer = new Uint16Array(this._textureSize)
_getParticle() {
let particle: Particle
if (this.deadParticles.length) {
particle = this.pool[this.deadParticles.pop()!]
} else {
particle = new Particle(this.pool.length)
this.pool.push(particle)
}
particle.life = 0
particle.random0 = Math.random()
particle.random1 = Math.random()
particle.random11 = Math.random() * 2 - 1
this.liveParticles.set(particle.index, particle.index)
return particle
}
update(delta: number): void {
// console.log('delta:', delta.toFixed())
const {
birthRateJitter,
positionTextureBuffer,
lifeTextureBuffer,
liveIndexTextureBuffer
} = this
const birthRateInverse = 1 / this.birthRate
const deltaSecond = delta * 0.001
const birthsThisFrame = deltaSecond * this.birthRate
const btfCeil = Math.ceil(birthsThisFrame - this._lastBirthLeftover)
const dragModifier = 1 - this.drag * deltaSecond
this._gravityV3.y = this.gravity * deltaSecond
// console.group()
const iterationCount = { value: 0 }
this.liveParticles.forEach(
this._processParticle(deltaSecond, dragModifier, iterationCount)
)
if (btfCeil > 0) {
this._lastBirthLeftover = btfCeil - this._lastBirthLeftover
for (let i = 0; i < btfCeil; i++) {
const { index } = this._createParticle()
const adjustedBirth = (i + birthRateJitter) * birthRateInverse
this._processParticle(
adjustedBirth,
dragModifier,
iterationCount
)(index)
}
} else {
this._lastBirthLeftover -= birthsThisFrame
}
// console.groupEnd()
// console.log('live ', iterationCount.value)
// console.log('worker: sending...')
postMessage(
{
message: 'particles_computed',
lifeTextureBuffer,
positionTextureBuffer,
liveIndexTextureBuffer,
liveParticleCount: iterationCount.value
},
[
positionTextureBuffer.buffer,
lifeTextureBuffer.buffer,
liveIndexTextureBuffer.buffer
]
)
}
_createParticle(): Particle {
const {
speed,
speedVariation,
angle,
angleVariation,
angleVelocityVariation,
angleVelocity,
maxLifeTime,
lifeTimeVariation,
startSize,
startSizeVariation,
endSize,
endSizeVariation
} = this
const particle = this._getParticle()
particle.life = 0
particle.maxLife = maxLifeTime - particle.random0 * lifeTimeVariation
particle.position.copy(this.position)
particle.startSize = Math.max(
startSize - particle.random0 * startSizeVariation,
0
)
particle.endSize = Math.max(
endSize - particle.random1 * endSizeVariation,
0
)
const sy = particle.random0 * this.spreadY
const sxz = particle.random1 * this.spreadXZ
const s = Math.sin(sy)
const y = Math.cos(sy)
const x = Math.cos(sxz) * s
const z = Math.sin(sxz) * s
particle.velocity
.set(x, y, z)
.multiplyScalar(speed * (1 - speedVariation * particle.random0))
particle.angle = angle + angleVariation * PI2 * particle.random1
particle.angleVelocity =
angleVelocity *
(1 - angleVelocityVariation * Math.abs(particle.random11)) *
Math.sign(particle.random11)
// console.log('birth', particle.index)
//deltas here need to be adjusted
return particle
}
_killParticle(particleIndex: number) {
this.deadParticles.push(particleIndex)
this.liveParticles.delete(particleIndex)
}
_processParticle = (
delta: number,
dragModifier: number,
iterationCount: { value: number }
) => (index: number): void => {
const particle = this.pool[index]
particle.life += delta
if (particle.life >= particle.maxLife) {
this._killParticle(particle.index)
// console.log('kill', index)
// this.lifeTextureBuffer[i4] = -1
// this.lifeTextureBuffer[i4 + 1] = -1
// iterationCount.value++
// killedCount.value++
// console.log('kill')
return
}
// console.log('p', iterationCount.value, index)
const i4 = iterationCount.value * 4
// const i4 = index * 4
this.liveIndexTextureBuffer[iterationCount.value] = iterationCount.value
// console.log(index, iterationCount.value)
_v3.copy(particle.velocity)
particle.position.add(_v3.multiplyScalar(delta))
particle.position.multiplyScalar(dragModifier)
particle.velocity.add(this._gravityV3)
particle.angle += particle.angleVelocity * delta
particle.angleVelocity *= dragModifier
this.lifeTextureBuffer[i4] = particle.life
this.lifeTextureBuffer[i4 + 1] = particle.maxLife
this.lifeTextureBuffer[i4 + 2] = particle.startSize
this.lifeTextureBuffer[i4 + 3] = particle.endSize
this.positionTextureBuffer[i4] = particle.position.x
this.positionTextureBuffer[i4 + 1] = particle.position.y
this.positionTextureBuffer[i4 + 2] = particle.position.z
this.positionTextureBuffer[i4 + 3] = particle.angle
iterationCount.value++
}
}
const worker = new ParticleSystem()
self.onmessage = event => {
if (event.data.message === 'consumed') {
worker.positionTextureBuffer = event.data.positionTextureBuffer
worker.lifeTextureBuffer = event.data.lifeTextureBuffer
worker.liveIndexTextureBuffer = event.data.liveIndexTextureBuffer
}
}
const FREQUENCY = 30
let lastTime = performance.now()
setInterval(() => {
const now = performance.now()
const delta = now - lastTime
lastTime = now
worker.update(delta)
}, FREQUENCY)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment