UNPKG

proton-engine

Version:

Proton is a simple and powerful javascript particle animation engine.

265 lines (227 loc) 6.5 kB
import Pool from "./Pool"; import Util from "../utils/Util"; import Stats from "../debug/Stats"; import EventDispatcher from "../events/EventDispatcher"; import MathUtil from "../math/MathUtil"; import Integration from "../math/Integration"; export default class Proton { static USE_CLOCK = false; // measure 1:100 static MEASURE = 100; static EULER = "euler"; static RK2 = "runge-kutta2"; // event name static PARTICLE_CREATED = "PARTICLE_CREATED"; static PARTICLE_UPDATE = "PARTICLE_UPDATE"; static PARTICLE_SLEEP = "PARTICLE_SLEEP"; static PARTICLE_DEAD = "PARTICLE_DEAD"; static EMITTER_ADDED = "EMITTER_ADDED"; static EMITTER_REMOVED = "EMITTER_REMOVED"; static PROTON_UPDATE = "PROTON_UPDATE"; static PROTON_UPDATE_AFTER = "PROTON_UPDATE_AFTER"; static DEFAULT_INTERVAL = 0.0167; static amendChangeTabsBug = true; /** * The constructor to add emitters * * @constructor Proton * * @todo add more documentation of the single properties and parameters * * @param {Number | undefined} [integrationType=Proton.EULER] * * @property {String} [integrationType=Proton.EULER] * @property {Array} emitters All added emitter * @property {Array} renderers All added renderer * @property {Number} time The active time * @property {Number} oldtime The old time */ constructor(integrationType) { this.emitters = []; this.renderers = []; this.time = 0; this.now = 0; this.then = 0; this.elapsed = 0; this.stats = new Stats(this); this.pool = new Pool(80); this.integrationType = Util.initValue(integrationType, Proton.EULER); this.integrator = new Integration(this.integrationType); this._fps = "auto"; this._interval = Proton.DEFAULT_INTERVAL; } /** * Sets the frames per second (FPS) for the Proton system. * @param {number|string} fps - The desired FPS. Use "auto" for default behavior, or a number for a specific FPS. */ set fps(fps) { this._fps = fps; this._interval = fps === "auto" ? Proton.DEFAULT_INTERVAL : MathUtil.floor(1 / fps, 7); } /** * Gets the current frames per second (FPS) setting. * @returns {number|string} The current FPS setting. Returns "auto" if set to default, or a number representing the specific FPS. */ get fps() { return this._fps; } /** * add a type of Renderer * * @method addRenderer * @memberof Proton * @instance * * @param {Renderer} render */ addRenderer(render) { render.init(this); this.renderers.push(render); } /** * @name add a type of Renderer * * @method addRenderer * @param {Renderer} render */ removeRenderer(render) { const index = this.renderers.indexOf(render); this.renderers.splice(index, 1); render.remove(this); } /** * add the Emitter * * @method addEmitter * @memberof Proton * @instance * * @param {Emitter} emitter */ addEmitter(emitter) { this.emitters.push(emitter); emitter.parent = this; this.dispatchEvent(Proton.EMITTER_ADDED, emitter); } /** * Removes an Emitter * * @method removeEmitter * @memberof Proton * @instance * * @param {Proton.Emitter} emitter */ removeEmitter(emitter) { const index = this.emitters.indexOf(emitter); this.emitters.splice(index, 1); emitter.parent = null; this.dispatchEvent(Proton.EMITTER_REMOVED, emitter); } /** * Updates all added emitters * * @method update * @memberof Proton * @instance */ update() { // 'auto' is the default browser refresh rate, the vast majority is 60fps if (this._fps === "auto") { this.dispatchEvent(Proton.PROTON_UPDATE); if (Proton.USE_CLOCK) { if (!this.then) this.then = new Date().getTime(); this.now = new Date().getTime(); this.elapsed = (this.now - this.then) * 0.001; // Fix bugs such as chrome browser switching tabs causing excessive time difference this.amendChangeTabsBug(); if (this.elapsed > 0) this.emittersUpdate(this.elapsed); this.then = this.now; } else { this.emittersUpdate(Proton.DEFAULT_INTERVAL); } this.dispatchEvent(Proton.PROTON_UPDATE_AFTER); } // If the fps frame rate is set else { if (!this.then) this.then = new Date().getTime(); this.now = new Date().getTime(); this.elapsed = (this.now - this.then) * 0.001; if (this.elapsed > this._interval) { this.dispatchEvent(Proton.PROTON_UPDATE); this.emittersUpdate(this._interval); // https://stackoverflow.com/questions/19764018/controlling-fps-with-requestanimationframe this.then = this.now - (this.elapsed % this._interval) * 1000; this.dispatchEvent(Proton.PROTON_UPDATE_AFTER); } } } emittersUpdate(elapsed) { let i = this.emitters.length; while (i--) this.emitters[i].update(elapsed); } /** * @todo add description * * @method amendChangeTabsBug * @memberof Proton * @instance */ amendChangeTabsBug() { if (!Proton.amendChangeTabsBug) return; if (this.elapsed > 0.5) { this.then = new Date().getTime(); this.elapsed = 0; } } /** * Counts all particles from all emitters * * @method getCount * @memberof Proton * @instance */ getCount() { let total = 0; let i = this.emitters.length; while (i--) total += this.emitters[i].particles.length; return total; } getAllParticles() { let particles = []; let i = this.emitters.length; while (i--) particles = particles.concat(this.emitters[i].particles); return particles; } destroyAllEmitters() { Util.destroyAll(this.emitters); } /** * Destroys everything related to this Proton instance. This includes all emitters, and all properties * * @method destroy * @memberof Proton * @instance */ destroy(remove = false) { const destroyOther = () => { this.time = 0; this.then = 0; this.pool.destroy(); this.stats.destroy(); Util.destroyAll(this.emitters); Util.destroyAll(this.renderers, this.getAllParticles()); this.integrator = null; this.renderers = null; this.emitters = null; this.stats = null; this.pool = null; }; if (remove) { setTimeout(destroyOther, 200); } else { destroyOther(); } } } EventDispatcher.bind(Proton);