/** * System takes care of updating all the entities (based on their component data) * @param apiSystem GameLib.API.System * @constructor */ GameLib.System.Particle = function( apiSystem ) { GameLib.System.call( this, apiSystem ); /** * this holds a reference to engine components and does some initial setup work so we don't have to do it during render * like calculate frequency etc.. * @type {Array} */ this.engines = []; this.totalTime = 0; this.instanceCreatedSubscription = null; this.removeComponentSubscription = null; this.beforeRenderSubscription = null; }; GameLib.System.Particle.prototype = Object.create(GameLib.System.prototype); GameLib.System.Particle.prototype.constructor = GameLib.System.Particle; /** * Start this system (add all event listeners) */ GameLib.System.Particle.prototype.start = function() { GameLib.System.prototype.start.call(this); this.particleEngines = GameLib.EntityManager.Instance.queryComponents(GameLib.Component.PARTICLE_ENGINE); this.instanceCreatedSubscription = GameLib.Event.Subscribe( GameLib.Event.INSTANCE_CREATED, this.instanceCreated.bind(this) ); this.removeComponentSubscription = GameLib.Event.Subscribe( GameLib.Event.REMOVE_COMPONENT, this.removeComponent.bind(this) ); this.beforeRenderSubscription = GameLib.Event.Subscribe( GameLib.Event.BEFORE_RENDER, this.beforeRender.bind(this) ); }; /** * From now on we want to track everything about a component, only from the systems that are active * @param data */ GameLib.System.Particle.prototype.instanceCreated = function(data) { /** * If we loaded a ParticleEngine - store a reference to it for later, also link all particles with this as parent */ if (data.component instanceof GameLib.D3.ParticleEngine) { this.particleEngines.push(data.component); /** * Link parent particle engines of already loaded particles */ GameLib.EntityManager.Instance.queryComponents(GameLib.Component.PARTICLE).map( function(particle){ if (particle.parentParticleEngine === data.component.id) { particle.parentParticleEngine = data.component; } } ) } /** * If we load a Particle, check to see if its engine loaded and link it. */ if (data.component instanceof GameLib.D3.Particle) { GameLib.EntityManager.Instance.queryComponents(GameLib.Component.PARTICLE_ENGINE).map( function (particleEngine) { if (data.component.parentParticleEngine === particleEngine.id) { data.component.parentParticleEngine = particleEngine; } } ); } }; /** * Removes a particle engine from this system * @param data */ GameLib.System.Particle.prototype.removeComponent = function(data) { if (data.component instanceof GameLib.D3.ParticleEngine) { var index = this.particleEngines.indexOf(data.component); if (index !== -1) { // console.log('removing particle engine from system' + data.component.name); this.particleEngines.splice(index, 1); } else { // console.log('failed to find the particle engine in the system : ' + data.component.name); } } }; /** * This is what actually happens to all particles before render * @param data */ GameLib.System.Particle.prototype.beforeRender = function(data) { this.totalTime += data.delta; this.particleEngines.map( function(particleEngine) { if ( GameLib.Utils.UndefinedOrNull(particleEngine.templateParticle) || GameLib.Utils.UndefinedOrNull(particleEngine.templateParticle.mesh) || GameLib.Utils.UndefinedOrNull(particleEngine.templateParticle.mesh.parentScene) || GameLib.Utils.UndefinedOrNull(particleEngine.templateParticle.mesh.parentScene.instance)) { return; } particleEngine.elapsed += data.delta; particleEngine.particles = particleEngine.particles.reduce( function(result, particle){ var speed = particle.userData.speed; if (particle.userData.speedType === GameLib.D3.API.Particle.SPEED_TYPE_CONSTANT) { speed = data.delta * particle.userData.speed; } if (particle.userData.speedType === GameLib.D3.API.Particle.SPEED_TYPE_LINEAR) { speed = data.delta * particle.userData.speed; particle.userData.speed += speed; } if (particle.userData.speedType === GameLib.D3.API.Particle.SPEED_TYPE_EXPONENTIAL) { speed = Math.pow(particle.userData.speed, 2) * data.delta; particle.userData.speed += speed; } if (particle.userData.speedType === GameLib.D3.API.Particle.SPEED_TYPE_LOGARITHMIC) { speed = Math.log(particle.userData.speed) * data.delta; particle.userData.speed += speed; } if (particle.userData.speedType === GameLib.D3.API.Particle.SPEED_TYPE_ONE_OVER_LOG) { speed = 1 / Math.log(particle.userData.speed) * data.delta; particle.userData.speed += speed; } if (particle.userData.speedType === GameLib.D3.API.Particle.SPEED_TYPE_EXP) { speed = Math.exp(particle.userData.speed) * data.delta; particle.userData.speed += speed; } if (particle.userData.speedType === GameLib.D3.API.Particle.SPEED_TYPE_ONE_OVER_EXP) { speed = 1 / Math.exp(particle.userData.speed) * data.delta; particle.userData.speed += speed; } particle.position.x += particle.userData.direction.x * speed; particle.position.y += particle.userData.direction.y * speed; particle.position.z += particle.userData.direction.z * speed; if (particleEngine.templateParticle.scaleType === GameLib.D3.API.Particle.SCALE_TYPE_CONSTANT) { /** * Do nothing - scale should already be set */ /* particle.scale.x = particle.userData.scale.x; particle.scale.y = particle.userData.scale.y; particle.scale.z = particle.userData.scale.z; */ } if (particleEngine.templateParticle.scaleType === GameLib.D3.API.Particle.SCALE_TYPE_LINEAR) { particle.scale.x += particle.userData.scale.x * data.delta; particle.scale.y += particle.userData.scale.x * data.delta; particle.scale.z += particle.userData.scale.x * data.delta; } if (particleEngine.camera && particleEngine.camera.instance) { particle.quaternion.copy(particleEngine.camera.instance.quaternion); } if (particleEngine.templateParticle.opacityType === GameLib.D3.API.Particle.OPACITY_TYPE_FADE_IN_LINEAR) { particle.material.opacity += particleEngine.templateParticle.fadeInFactor; } if (particleEngine.templateParticle.opacityType === GameLib.D3.API.Particle.OPACITY_TYPE_FADE_OUT_LINEAR) { particle.material.opacity -= particleEngine.templateParticle.fadeOutFactor; } if (particleEngine.templateParticle.opacityType === GameLib.D3.API.Particle.OPACITY_TYPE_FADE_IN_OUT_LINEAR) { if (particle.userData.fadeIn) { particle.material.opacity += particleEngine.templateParticle.fadeInFactor; } else { particle.material.opacity -= particleEngine.templateParticle.fadeOutFactor; } if (particle.material.opacity >= 1) { particle.userData.fadeIn = false; } } particle.userData.elapsed += data.delta; if ( particle.userData.elapsed > particle.userData.lifeTime || particle.material.opacity < 0 ) { particle.userData.scene.remove(particle); particle.geometry.dispose(); //particle.material.map.dispose(); particle.material.dispose(); } else { result.push(particle); } return result; }, [] ); if (particleEngine.disabledForRemoval && particleEngine.particles.length === 0) { GameLib.Event.Emit( GameLib.Event.REMOVE_PARTICLE_ENGINE, { component : particleEngine } ) } if (particleEngine.enabled && !particleEngine.disabledForRemoval) { var instanceClone = null; if (particleEngine.pulse) { if (particleEngine.particles.length === 0) { particleEngine.elapsed = 0; /** * This is a 'pulse' engine - so spawn all the particles at once and spawn again when all particles * are gone */ for (var i = 0; i < particleEngine.particlesPerSecond; i++) { instanceClone = particleEngine.templateParticle.cloneInstance(); particleEngine.particles.push(instanceClone); } } } else { /** * This is a 'stream' engine - spawn particles one at a time when its time to do so */ if (particleEngine.elapsed > particleEngine.frequency) { particleEngine.elapsed = 0; instanceClone = particleEngine.templateParticle.cloneInstance(); particleEngine.particles.push(instanceClone); } } } }.bind(this) ) }; /** * Stop this system (remove all event listeners) */ GameLib.System.Particle.prototype.stop = function() { GameLib.System.prototype.stop.call(this); this.instanceCreatedSubscription.remove(); this.removeComponentSubscription.remove(); this.beforeRenderSubscription.remove(); };