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 000000000..d46801b61 Binary files /dev/null and b/src/spec/images/GpuParticlesSpec/clear.png differ diff --git a/src/spec/images/ParticleSpec/clear.png b/src/spec/images/ParticleSpec/clear.png new file mode 100644 index 000000000..a607eb94f Binary files /dev/null and b/src/spec/images/ParticleSpec/clear.png differ