src/emitter/Emitter.js
- import {
- DEFAULT_BIND_EMITTER,
- DEFAULT_BIND_EMITTER_EVENT,
- DEFAULT_DAMPING,
- DEFAULT_EMITTER_INDEX,
- DEFAULT_EMITTER_RATE,
- } from './constants';
- import EventDispatcher, {
- EMITTER_DEAD,
- PARTICLE_CREATED,
- PARTICLE_DEAD,
- PARTICLE_UPDATE,
- SYSTEM_UPDATE,
- } from '../events';
- import { INTEGRATION_TYPE_EULER, integrate } from '../math';
- import { Util, uid } from '../utils';
-
- import { InitializerUtil } from '../initializer';
- import Particle from '../core/Particle';
- import isNumber from 'lodash/isNumber';
- import { EMITTER_TYPE_EMITTER as type } from './types';
-
- /**
- * Emitters are the System engine's particle factories. They cause particles to
- * be rendered by emitting them, and store all particle initializers and behaviours.
- *
- */
- export default class Emitter extends Particle {
- /**
- * Constructs an Emitter instance.
- *
- * @param {object} properties - The properties to instantiate the emitter with
- * @return void
- */
- constructor(properties) {
- super(properties);
-
- /**
- * @desc The class type.
- * @type {string}
- */
- this.type = type;
-
- /**
- * @desc The particles emitted by this emitter.
- * @type {array}
- */
- this.particles = [];
-
- /**
- * @desc The initializers for particles emitted by this emitter.
- * @type {array}
- */
- this.initializers = [];
-
- /**
- * @desc The behaviours for particles emitted by this emitter.
- * @type {array}
- */
- this.behaviours = [];
-
- /**
- * @desc The behaviours for the emitter.
- * @type {array}
- */
- this.emitterBehaviours = [];
-
- /**
- * @desc The current emit iteration.
- * @type {integer}
- */
- this.currentEmitTime = 0;
-
- /**
- * @desc The total number of times the emitter should emit particles.
- * @type {integer}
- */
- this.totalEmitTimes = -1;
-
- /**
- * @desc The friction coefficient for all particle to emit by.
- * @type {number}
- */
- this.damping = DEFAULT_DAMPING;
-
- /**
- * @desc Ensures that particles emitted by this emitter are positioned
- * according to the emitter's properties.
- * @type {boolean}
- */
- this.bindEmitter = DEFAULT_BIND_EMITTER;
-
- /**
- * @desc Determines if the emitter will dispatch internal events. Defaults
- * to false
- * @type {boolean}
- */
- this.bindEmitterEvent = DEFAULT_BIND_EMITTER_EVENT;
-
- /**
- * @desc The number of particles to emit per second (a [particle]/b [s])
- * @type {Rate}
- */
- this.rate = DEFAULT_EMITTER_RATE;
-
- /**
- * @desc Determines if the emitter is emitting particles or not.
- * @type {boolean}
- */
- this.isEmitting = false;
-
- /**
- * @desc The emitter's id.
- * @type {string}
- */
- this.id = `emitter-${uid()}`;
- this.cID = 0;
- this.name = 'Emitter';
-
- /**
- * @desc The index of the emitter as it is added to the system.
- * @type {number|undefined}
- */
- this.index = DEFAULT_EMITTER_INDEX;
-
- /**
- * @desc The emitter's internal event dispatcher.
- * @type {EventDispatcher}
- */
- this.eventDispatcher = new EventDispatcher();
- }
-
- /**
- * Proxy method for the internal event dispatcher's dispatchEvent method.
- *
- * @param {string} event - The event to dispatch
- * @param {object<Particle>} [target=this] - The event target
- */
- dispatch(event, target = this) {
- this.eventDispatcher.dispatchEvent(event, target);
- }
-
- /**
- * Sets the emitter rate.
- *
- * @param {Rate} rate - a rate initializer object
- * @return {Emitter}
- */
- setRate(rate) {
- this.rate = rate;
-
- return this;
- }
-
- /**
- * Sets the position of the emitter.
- *
- * @param {object} newPosition - an object the new x, y and z props
- * @return {Emitter}
- */
- setPosition(newPosition = {}) {
- const { position } = this;
- const { x = position.x, y = position.y, z = position.z } = newPosition;
-
- this.position.set(x, y, z);
-
- return this;
- }
-
- /**
- * Sets the rotation of the emitter.
- *
- * @param {object} newRotation - an object the new x, y and z props
- * @return {Emitter}
- */
- setRotation(newRotation = {}) {
- const { rotation } = this;
- const { x = rotation.x, y = rotation.y, z = rotation.z } = newRotation;
-
- this.rotation.set(x, y, z);
-
- return this;
- }
-
- /**
- * Sets the total number of times the emitter should emit particles as well as
- * the emitter's life. Also intializes the emitter rate.
- * This enables the emitter to emit particles.
- *
- * @param {number} [totalEmitTimes=Infinity] - the total number of times to emit particles
- * @param {number} [life=Infinity] - the life of this emitter in milliseconds
- * @return {Emitter}
- */
- emit(totalEmitTimes = Infinity, life = Infinity) {
- this.currentEmitTime = 0;
- this.totalEmitTimes = isNumber(totalEmitTimes) ? totalEmitTimes : Infinity;
-
- if (totalEmitTimes === 1) {
- this.life = totalEmitTimes;
- } else {
- this.life = isNumber(life) ? life : Infinity;
- }
-
- this.rate.init();
- this.isEmitting = true;
-
- return this;
- }
-
- /**
- * Experimental emit method that is designed to be called from the System.emit method.
- *
- * @return {Emitter}
- */
- experimental_emit() {
- const { isEmitting, totalEmitTimes, life } = this;
-
- if (!isEmitting) {
- this.currentEmitTime = 0;
-
- if (!totalEmitTimes) {
- this.setTotalEmitTimes(Infinity);
- }
-
- if (!life) {
- this.setLife(Infinity);
- }
-
- this.rate.init();
- this.isEmitting = true;
- }
-
- return this;
- }
-
- /**
- * Sets the total emit times for the emitter.
- *
- * @param {number} [totalEmitTimes=Infinity] - the total number of times to emit particles
- * @return {Emitter}
- */
- setTotalEmitTimes(totalEmitTimes = Infinity) {
- this.totalEmitTimes = isNumber(totalEmitTimes) ? totalEmitTimes : Infinity;
-
- return this;
- }
-
- /**
- * Sets the life of the emitter.
- *
- * @param {number} [life=Infinity] - the life of this emitter in milliseconds
- * @return {Emitter}
- */
- setLife(life = Infinity) {
- if (this.totalEmitTimes === 1) {
- this.life = this.totalEmitTimes;
- } else {
- this.life = isNumber(life) ? life : Infinity;
- }
-
- return this;
- }
-
- /**
- * Stops the emitter from emitting particles.
- *
- * @return void
- */
- stopEmit() {
- this.totalEmitTimes = -1;
- this.currentEmitTime = 0;
- this.isEmitting = false;
- }
-
- /**
- * Kills all of the emitter's particles.
- *
- * @return void
- */
- removeAllParticles() {
- let i = this.particles.length;
-
- while (i--) {
- this.particles[i].dead = true;
- }
- }
-
- /**
- * Adds a particle initializer to the emitter.
- * Each initializer is run on each particle when they are created.
- *
- * @param {Initializer} initializer - The initializer to add
- * @return {Emitter}
- */
- addInitializer(initializer) {
- this.initializers.push(initializer);
-
- return this;
- }
-
- /**
- * Adds multiple particle initializers to the emitter.
- *
- * @param {array<Initializer>} initializers - an array of particle initializers
- * @return {Emitter}
- */
- addInitializers(initializers) {
- let i = initializers.length;
-
- while (i--) {
- this.addInitializer(initializers[i]);
- }
-
- return this;
- }
-
- /**
- * Sets the emitter's particle initializers.
- *
- * @param {array<Initializer>} initializers - an array of particle initializers
- * @return {Emitter}
- */
- setInitializers(initializers) {
- this.initializers = initializers;
-
- return this;
- }
-
- /**
- * Removes an initializer from the emitter's initializers array.
- *
- * @param {Initializer} initializer - The initializer to remove
- * @return {Emitter}
- */
- removeInitializer(initializer) {
- const index = this.initializers.indexOf(initializer);
-
- if (index > -1) {
- this.initializers.splice(index, 1);
- }
-
- return this;
- }
-
- /**
- * Removes all initializers.
- *
- * @return {Emitter}
- */
- removeAllInitializers() {
- Util.destroyArray(this.initializers);
-
- return this;
- }
-
- /**
- * Adds a behaviour to the emitter. All emitter behaviours are added to each particle when
- * they are emitted.
- *
- * @param {Behaviour} behaviour - The behaviour to add to the emitter
- * @return {Emitter}
- */
- addBehaviour(behaviour) {
- this.behaviours.push(behaviour);
-
- return this;
- }
-
- /**
- * Adds multiple behaviours to the emitter.
- *
- * @param {array<Behaviour>} behaviours - an array of emitter behaviours
- * @return {Emitter}
- */
- addBehaviours(behaviours) {
- let i = behaviours.length;
-
- while (i--) {
- this.addBehaviour(behaviours[i]);
- }
-
- return this;
- }
-
- /**
- * Sets the emitter's behaviours.
- *
- * @param {array<Behaviour>} behaviours - an array of emitter behaviours
- * @return {Emitter}
- */
- setBehaviours(behaviours) {
- this.behaviours = behaviours;
-
- return this;
- }
-
- /**
- * Removes the behaviour from the emitter's behaviours array.
- *
- * @param {Behaviour} behaviour - The behaviour to remove
- * @return {Emitter}
- */
- removeBehaviour(behaviour) {
- const index = this.behaviours.indexOf(behaviour);
-
- if (index > -1) {
- this.behaviours.splice(index, 1);
- }
-
- return this;
- }
-
- /**
- * Removes all behaviours from the emitter.
- *
- * @return {Emitter}
- */
- removeAllBehaviours() {
- Util.destroyArray(this.behaviours);
-
- return this;
- }
-
- /**
- * Adds an emitter behaviour to the emitter.
- *
- * @param {Behaviour} behaviour - The behaviour to add to the emitter
- * @return {Emitter}
- */
- addEmitterBehaviour(behaviour) {
- this.emitterBehaviours.push(behaviour);
-
- behaviour.initialize(this);
-
- return this;
- }
-
- /**
- * Adds multiple behaviours to the emitter.
- *
- * @param {array<Behaviour>} behaviours - an array of emitter behaviours
- * @return {Emitter}
- */
- addEmitterBehaviours(behaviours) {
- let i = behaviours.length;
-
- while (i--) {
- this.addEmitterBehaviour(behaviours[i]);
- }
-
- return this;
- }
-
- /**
- * Sets the emitter's behaviours.
- *
- * @param {array<Behaviour>} behaviours - an array of emitter behaviours
- * @return {Emitter}
- */
- setEmitterBehaviours(behaviours) {
- const length = behaviours.length;
-
- this.emitterBehaviours = behaviours;
-
- for (let i = 0; i < length; i++) {
- this.emitterBehaviours[i].initialize(this);
- }
-
- return this;
- }
-
- /**
- * Removes the behaviour from the emitter's behaviours array.
- *
- * @param {Behaviour} behaviour - The behaviour to remove
- * @return {Emitter}
- */
- removeEmitterBehaviour(behaviour) {
- const index = this.emitterBehaviours.indexOf(behaviour);
-
- if (index > -1) {
- this.emitterBehaviours.splice(index, 1);
- }
-
- return this;
- }
-
- /**
- * Removes all behaviours from the emitter.
- *
- * @return {Emitter}
- */
- removeAllEmitterBehaviours() {
- Util.destroyArray(this.emitterBehaviours);
-
- return this;
- }
-
- /**
- * Adds the event listener for the EMITTER_DEAD event.
- *
- * @param {onEmitterDead} - The function to call when the EMITTER_DEAD is dispatched.
- * @return {Emitter}
- */
- addOnEmitterDeadEventListener(onEmitterDead) {
- this.eventDispatcher.addEventListener(`${this.id}_${EMITTER_DEAD}`, () =>
- onEmitterDead()
- );
-
- return this;
- }
-
- /**
- * Creates a particle by retreiving one from the pool and setting it up with
- * the supplied initializer and behaviour.
- *
- * @return {Emitter}
- */
- createParticle() {
- const particle = this.parent.pool.get(Particle);
- const index = this.particles.length;
-
- this.setupParticle(particle, index);
- this.parent && this.parent.dispatch(PARTICLE_CREATED, particle);
- this.bindEmitterEvent && this.dispatch(PARTICLE_CREATED, particle);
-
- return particle;
- }
-
- /**
- * Sets up a particle by running all initializers on it and setting its behaviours.
- * Also adds the particle to this.particles.
- *
- * @param {Particle} particle - The particle to setup
- * @return void
- */
- setupParticle(particle, index) {
- const { initializers, behaviours } = this;
-
- InitializerUtil.initialize(this, particle, initializers);
-
- particle.addBehaviours(behaviours);
- particle.parent = this;
- particle.index = index;
-
- this.particles.push(particle);
- }
-
- /**
- * Updates the emitter according to the time passed by calling the generate
- * and integrate methods. The generate method creates particles, the integrate
- * method updates existing particles.
- *
- * If the emitter age is greater than time, the emitter is killed.
- *
- * This method also indexes/deindexes particles.
- *
- * @param {number} time - System engine time
- * @return void
- */
- update(time) {
- if (!this.isEmitting && this.particles.length === 0) {
- return;
- }
-
- this.age += time;
-
- if (this.dead || this.age >= this.life) {
- this.destroy();
- }
-
- if (this.isEmitting)
- {
- this.generate(time);
- }
-
- this.integrate(time);
-
- let i = this.particles.length;
-
- while (i--) {
- const particle = this.particles[i];
-
- if (particle.dead) {
- this.parent && this.parent.dispatch(PARTICLE_DEAD, particle);
- this.bindEmitterEvent && this.dispatch(PARTICLE_DEAD, particle);
- this.parent.pool.expire(particle.reset());
- this.particles.splice(i, 1);
- if(this.particles.length === 0)
- {
- this.parent && this.parent.dispatch(SYSTEM_UPDATE);
- }
- }
- }
-
- this.updateEmitterBehaviours(time);
- }
-
- /**
- * Updates the emitter's emitter behaviours.
- *
- * @param {number} time - System engine time
- * @return void
- */
- updateEmitterBehaviours(time) {
- if (this.sleep) {
- return;
- }
-
- const length = this.emitterBehaviours.length;
-
- for (let i = 0; i < length; i++) {
- this.emitterBehaviours[i].applyBehaviour(this, time, i);
- }
- }
-
- /**
- * Runs the integration algorithm on the emitter and all particles.
- * Updates the particles with the timstamp passed.
- *
- * @param {number} time - System engine time
- * @return void
- */
- integrate(time) {
- const integrationType = this.parent
- ? this.parent.integrationType
- : INTEGRATION_TYPE_EULER;
- const damping = 1 - this.damping;
-
- integrate(this, time, damping, integrationType);
-
- let index = this.particles.length;
-
- while (index--) {
- const particle = this.particles[index];
-
- particle.update(time, index);
- integrate(particle, time, damping, integrationType);
-
- this.parent && this.parent.dispatch(PARTICLE_UPDATE, particle);
- this.bindEmitterEvent && this.dispatch(PARTICLE_UPDATE, particle);
- }
- }
-
- /**
- * Generates new particles.
- *
- * @param {number} time - System engine time
- * @return void
- */
- generate(time) {
- if (this.totalEmitTimes === 1) {
- let i = this.rate.getValue(99999);
-
- if (i > 0) {
- this.cID = i;
- }
-
- while (i--) {
- this.createParticle();
- }
-
- this.totalEmitTimes = 0;
-
- return;
- }
-
- this.currentEmitTime += time;
-
- if (this.currentEmitTime < this.totalEmitTimes) {
- let i = this.rate.getValue(time);
-
- if (i > 0) {
- this.cID = i;
- }
-
- while (i--) {
- this.createParticle();
- }
- }
- }
-
- /**
- * Kills the emitter.
- *
- * @return void
- */
- destroy() {
- this.dead = true;
- this.energy = 0;
- this.totalEmitTimes = -1;
-
- if (this.particles.length == 0) {
- this.isEmitting = false;
- this.removeAllInitializers();
- this.removeAllBehaviours();
- this.dispatch(`${this.id}_${EMITTER_DEAD}`);
-
- this.parent && this.parent.removeEmitter(this);
- }
- }
- }