From d919a34e54d5e95cfc2bad9991a40d81e8ef7d88 Mon Sep 17 00:00:00 2001 From: Erik Onarheim Date: Thu, 5 Dec 2024 21:39:01 -0600 Subject: [PATCH] fix: [#3310] Add clearParticles() back with fixed logic closes: #3310 --- CHANGELOG.md | 1 + src/engine/Particles/GpuParticleEmitter.ts | 4 ++ src/engine/Particles/GpuParticleRenderer.ts | 15 ++++++- src/engine/Particles/ParticleEmitter.ts | 13 ++++++ src/spec/GpuParticleSpec.ts | 43 ++++++++++++++++++++ src/spec/ParticleSpec.ts | 43 ++++++++++++++++++++ src/spec/images/GpuParticlesSpec/clear.png | Bin 0 -> 3302 bytes src/spec/images/ParticleSpec/clear.png | Bin 0 -> 5036 bytes 8 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 src/spec/images/GpuParticlesSpec/clear.png create mode 100644 src/spec/images/ParticleSpec/clear.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bc6b3226..85569b6e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -233,6 +233,7 @@ are doing mtv adjustments during precollision. ### Fixed +- Fixed issue where `ex.ParticleEmitter.clearParticles()` did not work - Fixed issue where the pointer `lastWorldPos` was not updated when the current `Camera` moved - Fixed issue where `cancel()`'d events still bubbled to the top level input handlers - Fixed issue where unexpected html HTML content from an image would silently hang the loader diff --git a/src/engine/Particles/GpuParticleEmitter.ts b/src/engine/Particles/GpuParticleEmitter.ts index 0c0274898..407bb1e21 100644 --- a/src/engine/Particles/GpuParticleEmitter.ts +++ b/src/engine/Particles/GpuParticleEmitter.ts @@ -95,6 +95,10 @@ export class GpuParticleEmitter extends Actor { this.renderer.emitParticles(particleCount); } + public clearParticles() { + this.renderer.clearParticles(); + } + draw(ctx: ExcaliburGraphicsContextWebGL, elapsed: number) { ctx.draw('ex.particle', this.renderer, elapsed); } diff --git a/src/engine/Particles/GpuParticleRenderer.ts b/src/engine/Particles/GpuParticleRenderer.ts index 79212bca6..dc3a47b58 100644 --- a/src/engine/Particles/GpuParticleRenderer.ts +++ b/src/engine/Particles/GpuParticleRenderer.ts @@ -150,6 +150,12 @@ export class GpuParticleRenderer { this._initialized = true; } + private _clearRequested = false; + clearParticles() { + this._particleData.fill(0); + this._clearRequested = true; + } + private _emitted: [life: number, index: number][] = []; emitParticles(particleCount: number) { const startIndex = this._particleIndex; @@ -264,7 +270,14 @@ export class GpuParticleRenderer { draw(gl: WebGL2RenderingContext) { if (this._initialized) { // Emit - this._uploadEmitted(gl); + if (this._clearRequested) { + gl.bindBuffer(gl.ARRAY_BUFFER, this._buffers[(this._drawIndex + 1) % 2]); + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this._particleData); + gl.bindBuffer(gl.ARRAY_BUFFER, null); + this._clearRequested = false; + } else { + this._uploadEmitted(gl); + } // Bind one buffer to ARRAY_BUFFER and the other to transform feedback buffer gl.bindVertexArray(this._currentVao); diff --git a/src/engine/Particles/ParticleEmitter.ts b/src/engine/Particles/ParticleEmitter.ts index 703045384..71c991425 100644 --- a/src/engine/Particles/ParticleEmitter.ts +++ b/src/engine/Particles/ParticleEmitter.ts @@ -96,6 +96,8 @@ export class ParticleEmitter extends Actor { this.deadParticles.push(particle); } + private _activeParticles: Particle[] = []; + /** * Causes the emitter to emit particles * @param particleCount Number of particles to emit right now @@ -110,6 +112,13 @@ export class ParticleEmitter extends Actor { this.addChild(p); } } + this._activeParticles.push(p); + } + } + + public clearParticles() { + for (let i = 0; i < this._activeParticles.length; i++) { + this.removeParticle(this._activeParticles[i]); } } @@ -177,6 +186,10 @@ export class ParticleEmitter extends Actor { this.scene.world.remove(this.deadParticles[i], false); this._particlePool.return(this.deadParticles[i]); } + const index = this._activeParticles.indexOf(this.deadParticles[i]); + if (index > -1) { + this._activeParticles.splice(index, 1); + } } this.deadParticles.length = 0; } diff --git a/src/spec/GpuParticleSpec.ts b/src/spec/GpuParticleSpec.ts index f9255dfad..cc514e1a5 100644 --- a/src/spec/GpuParticleSpec.ts +++ b/src/spec/GpuParticleSpec.ts @@ -131,6 +131,49 @@ describe('A GPU particle', () => { await expectAsync(engine.canvas).toEqualImage('src/spec/images/GpuParticlesSpec/particles.png'); }); + it('should clear particles', async () => { + const emitter = new ex.GpuParticleEmitter({ + pos: new ex.Vector(400, 100), + width: 20, + height: 30, + isEmitting: true, + emitRate: 5, + particle: { + minSpeed: 100, + maxSpeed: 200, + acc: ex.Vector.Zero.clone(), + minAngle: 0, + maxAngle: Math.PI / 2, + life: 4000, + opacity: 0.5, + fade: false, + startSize: 30, + endSize: 40, + beginColor: ex.Color.Red.clone(), + endColor: ex.Color.Blue.clone(), + graphic: null, + angularVelocity: 3, + randomRotation: false + }, + focus: null, + focusAccel: null, + emitterType: ex.EmitterType.Circle, + radius: 20, + random: new ex.Random(1337) + }); + engine.backgroundColor = ex.Color.Transparent; + engine.add(emitter); + emitter.emitParticles(10); + emitter.clearParticles(); + + engine.currentScene.update(engine, 100); + engine.currentScene.update(engine, 100); + engine.currentScene.update(engine, 100); + engine.currentScene.draw(engine.graphicsContext, 100); + engine.graphicsContext.flush(); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/GpuParticlesSpec/clear.png'); + }); + it("should emit particles and wrap it's ring buffer", async () => { const emitter = new ex.GpuParticleEmitter({ pos: new ex.Vector(400, 100), diff --git a/src/spec/ParticleSpec.ts b/src/spec/ParticleSpec.ts index 327ca8814..8d594c96d 100644 --- a/src/spec/ParticleSpec.ts +++ b/src/spec/ParticleSpec.ts @@ -131,6 +131,49 @@ describe('A particle', () => { await expectAsync(engine.canvas).toEqualImage('src/spec/images/ParticleSpec/Particles.png'); }); + it('should clear particles', async () => { + const emitter = new ex.ParticleEmitter({ + pos: new ex.Vector(400, 100), + width: 20, + height: 30, + isEmitting: true, + emitRate: 5, + particle: { + minSpeed: 100, + maxSpeed: 200, + acc: ex.Vector.Zero.clone(), + minAngle: 0, + maxAngle: Math.PI / 2, + life: 4000, + opacity: 0.5, + fade: false, + startSize: 30, + endSize: 40, + beginColor: ex.Color.Red.clone(), + endColor: ex.Color.Blue.clone(), + graphic: null, + angularVelocity: 3, + randomRotation: false + }, + focus: null, + focusAccel: null, + emitterType: ex.EmitterType.Circle, + radius: 20, + random: new ex.Random(1337) + }); + engine.backgroundColor = ex.Color.Transparent; + engine.add(emitter); + emitter.emitParticles(10); + emitter.clearParticles(); + + engine.currentScene.update(engine, 100); + engine.currentScene.update(engine, 100); + engine.currentScene.update(engine, 100); + engine.currentScene.draw(engine.graphicsContext, 100); + engine.graphicsContext.flush(); + await expectAsync(engine.canvas).toEqualImage('src/spec/images/ParticleSpec/clear.png'); + }); + it('can be parented', async () => { const emitter = new ex.ParticleEmitter({ pos: new ex.Vector(0, 0), diff --git a/src/spec/images/GpuParticlesSpec/clear.png b/src/spec/images/GpuParticlesSpec/clear.png new file mode 100644 index 0000000000000000000000000000000000000000..d46801b61af8e21d9f7b680785df37eb4242a085 GIT binary patch literal 3302 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 Kay{n~lJalhW-jjl literal 0 HcmV?d00001