diff --git a/sandbox/tests/gpu-particles/index.ts b/sandbox/tests/gpu-particles/index.ts index 7ea00e7b4..62f4bd48f 100644 --- a/sandbox/tests/gpu-particles/index.ts +++ b/sandbox/tests/gpu-particles/index.ts @@ -7,11 +7,12 @@ var game = new ex.Engine({ var swordImg = new ex.ImageSource('https://cdn.rawgit.com/excaliburjs/Excalibur/7dd48128/assets/sword.png'); var particles = new ex.GpuParticleEmitter({ - pos: ex.vec(500, 500), + pos: ex.vec(100, 0), z: 1, emitterType: ex.EmitterType.Circle, maxParticles: 1000, particle: { + // transform: ex.ParticleTransform.Local, minSpeed: 1, maxSpeed: 10, minAngle: 3.4, @@ -26,17 +27,56 @@ var particles = new ex.GpuParticleEmitter({ endColor: ex.Color.Transparent }, radius: 1, - emitRate: 1, + emitRate: 100, isEmitting: true }); -game.input.pointers.primary.on('move', (evt) => { - particles.pos.x = evt.worldPos.x; - particles.pos.y = evt.worldPos.y; +var cpuParticles = new ex.ParticleEmitter({ + pos: ex.vec(-100, 0), + z: 1, + emitterType: ex.EmitterType.Circle, + particle: { + minSpeed: 1, + maxSpeed: 10, + minAngle: 3.4, + maxAngle: 6, + opacity: 0.7, + life: 2000, + maxSize: 5, + minSize: 5, + startSize: 5, + endSize: 1, + beginColor: ex.Color.fromRGB(23, 106, 170, 0.1), + endColor: ex.Color.Transparent + }, + radius: 1, + emitRate: 100, + isEmitting: true }); particles.isEmitting = true; -game.add(particles); +// game.add(particles); + +var particleParent = new ex.Actor({ + pos: ex.vec(400, 400), + width: 10, + height: 10, + color: ex.Color.Red +}); +game.add(particleParent); + +game.input.pointers.primary.on('move', (evt) => { + particleParent.pos.x = evt.worldPos.x; + particleParent.pos.y = evt.worldPos.y; +}); + +game.input.pointers.primary.on('wheel', (ev) => { + game.currentScene.camera.zoom += ev.deltaY / 1000; + game.currentScene.camera.zoom = ex.clamp(game.currentScene.camera.zoom, 0.05, 100); +}); + +particleParent.addChild(particles); +particleParent.addChild(cpuParticles); game.add( new ex.Actor({ diff --git a/src/engine/Graphics/Context/particle-renderer/particle-fragment.glsl b/src/engine/Graphics/Context/particle-renderer/particle-fragment.glsl index 62bcca1fb..c672166e8 100644 --- a/src/engine/Graphics/Context/particle-renderer/particle-fragment.glsl +++ b/src/engine/Graphics/Context/particle-renderer/particle-fragment.glsl @@ -35,7 +35,9 @@ void main(){ float dist = 1.0 - length(uv); float edge = fwidth(dot(uv, uv)); float circle = smoothstep(-edge/2.0, edge/2.0, dist); - vec4 color = mix(beginColor, endColor, 1.0 - lifePct) * startOpacity; - fragColor = color * (fade ? lifePct : 1.0) * circle; + vec3 color = mix(beginColor.rgb, endColor.rgb, 1.0 - lifePct); + fragColor.rgb = color; + fragColor.a = startOpacity * circle * (fade ? lifePct : 1.0);// * mix(beginColor.a, endColor.a, 1.0 - lifePct); + fragColor.rgb *= fragColor.a; } } \ No newline at end of file diff --git a/src/engine/Graphics/Context/particle-renderer/particle-renderer.ts b/src/engine/Graphics/Context/particle-renderer/particle-renderer.ts index a74718882..101cf6de0 100644 --- a/src/engine/Graphics/Context/particle-renderer/particle-renderer.ts +++ b/src/engine/Graphics/Context/particle-renderer/particle-renderer.ts @@ -10,7 +10,6 @@ import { HTMLImageSource } from '../ExcaliburGraphicsContext'; import { ImageSourceAttributeConstants } from '../../ImageSource'; import { parseImageWrapping } from '../../Wrapping'; import { parseImageFiltering } from '../../Filtering'; -import { AffineMatrix } from '../../../Math/affine-matrix'; import { ParticleTransform } from '../../../Particles/Particles'; export class ParticleRenderer implements RendererPlugin { @@ -65,7 +64,10 @@ export class ParticleRenderer implements RendererPlugin { this._shader.use(); this._shader.setUniformMatrix('u_matrix', this._context.ortho); - const transform = renderer.particle.transform === ParticleTransform.Local ? this._context.getTransform() : AffineMatrix.identity(); + const transform = + renderer.particle.transform === ParticleTransform.Local + ? this._context.getTransform() + : this._context.getTransform().multiply(renderer.emitter.transform.get().inverse); this._shader.setUniformAffineMatrix('u_transform', transform); this._shader.setUniformBoolean('fade', renderer.particle.fade ? true : false); this._shader.setUniformBoolean('useTexture', renderer.particle.graphic ? true : false); @@ -108,12 +110,6 @@ export class ParticleRenderer implements RendererPlugin { // gl.bindTexture(gl.TEXTURE_2D, obstacleTex); // gl.uniform1i(u_obstacle, 1); - // Blending wont work because ex doesn't have a depth attachment - // gl.enable(gl.DEPTH_TEST); - // gl.enable(gl.BLEND); - // gl.blendEquation(gl.FUNC_ADD); - // gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); - renderer.draw(gl); } hasPendingDraws(): boolean { diff --git a/src/engine/Particles/GpuParticleEmitter.ts b/src/engine/Particles/GpuParticleEmitter.ts index f37912813..1a1fa965a 100644 --- a/src/engine/Particles/GpuParticleEmitter.ts +++ b/src/engine/Particles/GpuParticleEmitter.ts @@ -1,7 +1,14 @@ -import { Actor, clamp, Engine, ExcaliburGraphicsContextWebGL, GraphicsComponent, ParticleRenderer, Random, vec, Vector } from '../'; +import { Engine } from '../Engine'; +import { Actor } from '../Actor'; import { EmitterType } from './EmitterType'; import { ParticleEmitterArgs, ParticleTransform } from './Particles'; import { GpuParticleConfig, GpuParticleRenderer } from './GpuParticleRenderer'; +import { GraphicsComponent } from '../Graphics/GraphicsComponent'; +import { Random } from '../Math/Random'; +import { vec, Vector } from '../Math/vector'; +import { clamp } from '../Math'; +import { ExcaliburGraphicsContextWebGL } from '../Graphics/Context/ExcaliburGraphicsContextWebGL'; +import { ParticleRenderer } from '../Graphics/Context/particle-renderer/particle-renderer'; export class GpuParticleEmitter extends Actor { public particle: GpuParticleConfig = { diff --git a/src/engine/Particles/GpuParticleRenderer.ts b/src/engine/Particles/GpuParticleRenderer.ts index dc3a47b58..cc7974ee0 100644 --- a/src/engine/Particles/GpuParticleRenderer.ts +++ b/src/engine/Particles/GpuParticleRenderer.ts @@ -1,4 +1,4 @@ -import { TwoPI } from '../Math/util'; +import { randomInRange, TwoPI } from '../Math/util'; import { ExcaliburGraphicsContextWebGL } from '../Graphics/Context/ExcaliburGraphicsContextWebGL'; import { GpuParticleEmitter } from './GpuParticleEmitter'; import { ParticleConfig, ParticleTransform } from './Particles'; @@ -6,6 +6,7 @@ import { Random } from '../Math/Random'; import { Sprite } from '../Graphics/Sprite'; import { EmitterType } from './EmitterType'; import { assert } from '../Util/Assert'; +import { vec } from '../Math/vector'; export interface GpuParticleConfig extends ParticleConfig { /** @@ -164,6 +165,10 @@ export class GpuParticleRenderer { let countParticle = 0; for (let i = startIndex; i < endIndex; i += this._numInputFloats) { const angle = this._random.floating(this.particle.minAngle || 0, this.particle.maxAngle || TwoPI); + const speedX = this._random.floating(this.particle.minSpeed || 0, this.particle.maxSpeed || 0); + const speedY = this._random.floating(this.particle.minSpeed || 0, this.particle.maxSpeed || 0); + const dx = speedX * Math.cos(angle); + const dy = speedY * Math.sin(angle); let ranX: number = 0; let ranY: number = 0; if (this.emitter.emitterType === EmitterType.Rectangle) { @@ -174,15 +179,13 @@ export class GpuParticleRenderer { ranX = radius * Math.cos(angle); ranY = radius * Math.sin(angle); } - + const tx = this.emitter.transform.apply(vec(ranX, ranY)); const data = [ - this.particle.transform === ParticleTransform.Local ? ranX : this.emitter.transform.pos.x + ranX, - this.particle.transform === ParticleTransform.Local ? ranY : this.emitter.transform.pos.y + ranY, // pos in world space - this._random.floating(this.particle.minSpeed || 0, this.particle.maxSpeed || 0), - this._random.floating(this.particle.minSpeed || 0, this.particle.maxSpeed || 0), // velocity - this.particle.randomRotation - ? this._random.floating(this.particle.minAngle || 0, this.particle.maxAngle || TwoPI) - : this.particle.rotation || 0, // rotation + this.particle.transform === ParticleTransform.Local ? ranX : tx.x, + this.particle.transform === ParticleTransform.Local ? ranY : tx.y, // pos in world space + dx, + dy, // velocity + this.particle.randomRotation ? randomInRange(0, TwoPI, this._random) : this.particle.rotation || 0, // rotation this.particle.angularVelocity || 0, // angular velocity this._particleLife // life ]; diff --git a/src/spec/images/GpuParticlesSpec/particles-wrapped.png b/src/spec/images/GpuParticlesSpec/particles-wrapped.png index 7c7761942..b6aa65c5b 100644 Binary files a/src/spec/images/GpuParticlesSpec/particles-wrapped.png and b/src/spec/images/GpuParticlesSpec/particles-wrapped.png differ diff --git a/src/spec/images/GpuParticlesSpec/particles.png b/src/spec/images/GpuParticlesSpec/particles.png index 797ed1000..83a048011 100644 Binary files a/src/spec/images/GpuParticlesSpec/particles.png and b/src/spec/images/GpuParticlesSpec/particles.png differ