Created
May 19, 2020 23:13
-
-
Save pailhead/b6eae5d347d9817391bf7bef8b8a9e61 to your computer and use it in GitHub Desktop.
particle worker
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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