326 lines
12 KiB
JavaScript
326 lines
12 KiB
JavaScript
/**
|
|
* System takes care of updating all the entities (based on their component data)
|
|
* @param apiSystem R3.API.System
|
|
* @constructor
|
|
*/
|
|
R3.System.Particle = function(
|
|
apiSystem
|
|
) {
|
|
R3.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;
|
|
};
|
|
|
|
R3.System.Particle.prototype = Object.create(R3.System.prototype);
|
|
R3.System.Particle.prototype.constructor = R3.System.Particle;
|
|
|
|
/**
|
|
* Start this system (add all event listeners)
|
|
*/
|
|
R3.System.Particle.prototype.start = function() {
|
|
|
|
R3.System.prototype.start.call(this);
|
|
|
|
this.particleEngines = R3.EntityManager.Instance.queryComponents(R3.Component.PARTICLE_ENGINE);
|
|
|
|
this.instanceCreatedSubscription = R3.Event.Subscribe(
|
|
R3.Event.INSTANCE_CREATED,
|
|
this.instanceCreated.bind(this)
|
|
);
|
|
|
|
this.removeComponentSubscription = R3.Event.Subscribe(
|
|
R3.Event.REMOVE_COMPONENT,
|
|
this.removeComponent.bind(this)
|
|
);
|
|
|
|
this.beforeRenderSubscription = R3.Event.Subscribe(
|
|
R3.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
|
|
*/
|
|
R3.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 R3.D3.ParticleEngine) {
|
|
this.particleEngines.push(data.component);
|
|
|
|
/**
|
|
* Link parent particle engines of already loaded particles
|
|
*/
|
|
R3.EntityManager.Instance.queryComponents(R3.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 R3.D3.Particle) {
|
|
R3.EntityManager.Instance.queryComponents(R3.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
|
|
*/
|
|
R3.System.Particle.prototype.removeComponent = function(data) {
|
|
|
|
if (data.component instanceof R3.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
|
|
*/
|
|
R3.System.Particle.prototype.beforeRender = function(data) {
|
|
|
|
this.totalTime += data.delta;
|
|
|
|
this.particleEngines.map(
|
|
function(particleEngine) {
|
|
|
|
if (
|
|
R3.Utils.UndefinedOrNull(particleEngine.templateParticle) ||
|
|
R3.Utils.UndefinedOrNull(particleEngine.templateParticle.mesh) ||
|
|
R3.Utils.UndefinedOrNull(particleEngine.templateParticle.mesh.parentScene) ||
|
|
R3.Utils.UndefinedOrNull(particleEngine.templateParticle.mesh.parentScene.instance) ||
|
|
!particleEngine.enabled
|
|
) {
|
|
return;
|
|
}
|
|
|
|
particleEngine.elapsed += data.delta;
|
|
|
|
particleEngine.particles = particleEngine.particles.reduce(
|
|
|
|
function(result, particle){
|
|
|
|
var speed = particle.userData.speed;
|
|
|
|
if (particle.userData.speedType === R3.D3.API.Particle.SPEED_TYPE_CONSTANT) {
|
|
speed = data.delta * particle.userData.speed;
|
|
}
|
|
|
|
if (particle.userData.speedType === R3.D3.API.Particle.SPEED_TYPE_LINEAR) {
|
|
speed = data.delta * particle.userData.speed;
|
|
particle.userData.speed += speed;
|
|
}
|
|
|
|
if (particle.userData.speedType === R3.D3.API.Particle.SPEED_TYPE_EXPONENTIAL) {
|
|
speed = Math.pow(particle.userData.speed, 2) * data.delta;
|
|
particle.userData.speed += speed;
|
|
}
|
|
|
|
if (particle.userData.speedType === R3.D3.API.Particle.SPEED_TYPE_LOGARITHMIC) {
|
|
speed = Math.log(particle.userData.speed) * data.delta;
|
|
particle.userData.speed += speed;
|
|
}
|
|
|
|
if (particle.userData.speedType === R3.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 === R3.D3.API.Particle.SPEED_TYPE_EXP) {
|
|
speed = Math.exp(particle.userData.speed) * data.delta;
|
|
particle.userData.speed += speed;
|
|
}
|
|
|
|
if (particle.userData.speedType === R3.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 === R3.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 === R3.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 === R3.D3.API.Particle.OPACITY_TYPE_FADE_IN_LINEAR) {
|
|
if (particle.userData.elapsed > particleEngine.templateParticle.fadeInAfter) {
|
|
particle.material.opacity += particleEngine.templateParticle.fadeInFactor;
|
|
}
|
|
}
|
|
|
|
if (particleEngine.templateParticle.opacityType === R3.D3.API.Particle.OPACITY_TYPE_FADE_OUT_LINEAR) {
|
|
if (particle.userData.elapsed > particleEngine.templateParticle.fadeOutAfter) {
|
|
particle.material.opacity -= particleEngine.templateParticle.fadeOutFactor;
|
|
}
|
|
}
|
|
|
|
if (particleEngine.templateParticle.opacityType === R3.D3.API.Particle.OPACITY_TYPE_FADE_IN_OUT_LINEAR) {
|
|
|
|
if (particle.userData.fadeIn) {
|
|
if (particle.userData.elapsed > particleEngine.templateParticle.fadeInAfter) {
|
|
particle.material.opacity += particleEngine.templateParticle.fadeInFactor;
|
|
}
|
|
} else {
|
|
if (particle.userData.elapsed > particleEngine.templateParticle.fadeOutAfter) {
|
|
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) {
|
|
R3.Event.Emit(
|
|
R3.Event.REMOVE_PARTICLE_ENGINE,
|
|
{
|
|
component : particleEngine
|
|
}
|
|
)
|
|
}
|
|
|
|
if (particleEngine.fired && particleEngine.particles.length === 0) {
|
|
R3.Event.Emit(
|
|
R3.Event.ENGINE_FIRED_PARTICLES_ZERO,
|
|
{
|
|
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);
|
|
}
|
|
|
|
particleEngine.fired = true;
|
|
}
|
|
|
|
} 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)
|
|
*/
|
|
R3.System.Particle.prototype.stop = function() {
|
|
|
|
R3.System.prototype.stop.call(this);
|
|
|
|
this.instanceCreatedSubscription.remove();
|
|
this.removeComponentSubscription.remove();
|
|
this.beforeRenderSubscription.remove();
|
|
|
|
};
|