UNPKG

proton-engine

Version:

Proton is a simple and powerful javascript particle animation engine.

1,672 lines (1,592 loc) 671 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Proton = {})); })(this, (function (exports) { 'use strict'; function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } var WebGLUtil = { /** * @memberof Proton#Proton.WebGLUtil * @method ipot * * @todo add description * @todo add length description * * @param {Number} length * * @return {Boolean} */ ipot: function ipot(length) { return (length & length - 1) === 0; }, /** * @memberof Proton#Proton.WebGLUtil * @method nhpot * * @todo add description * @todo add length description * * @param {Number} length * * @return {Number} */ nhpot: function nhpot(length) { --length; for (var i = 1; i < 32; i <<= 1) { length = length | length >> i; } return length + 1; }, /** * @memberof Proton#Proton.WebGLUtil * @method makeTranslation * * @todo add description * @todo add tx, ty description * @todo add return description * * @param {Number} tx either 0 or 1 * @param {Number} ty either 0 or 1 * * @return {Object} */ makeTranslation: function makeTranslation(tx, ty) { return [1, 0, 0, 0, 1, 0, tx, ty, 1]; }, /** * @memberof Proton#Proton.WebGLUtil * @method makeRotation * * @todo add description * @todo add return description * * @param {Number} angleInRadians * * @return {Object} */ makeRotation: function makeRotation(angleInRadians) { var c = Math.cos(angleInRadians); var s = Math.sin(angleInRadians); return [c, -s, 0, s, c, 0, 0, 0, 1]; }, /** * @memberof Proton#Proton.WebGLUtil * @method makeScale * * @todo add description * @todo add tx, ty description * @todo add return description * * @param {Number} sx either 0 or 1 * @param {Number} sy either 0 or 1 * * @return {Object} */ makeScale: function makeScale(sx, sy) { return [sx, 0, 0, 0, sy, 0, 0, 0, 1]; }, /** * @memberof Proton#Proton.WebGLUtil * @method matrixMultiply * * @todo add description * @todo add a, b description * @todo add return description * * @param {Object} a * @param {Object} b * * @return {Object} */ matrixMultiply: function matrixMultiply(a, b) { var a00 = a[0 * 3 + 0]; var a01 = a[0 * 3 + 1]; var a02 = a[0 * 3 + 2]; var a10 = a[1 * 3 + 0]; var a11 = a[1 * 3 + 1]; var a12 = a[1 * 3 + 2]; var a20 = a[2 * 3 + 0]; var a21 = a[2 * 3 + 1]; var a22 = a[2 * 3 + 2]; var b00 = b[0 * 3 + 0]; var b01 = b[0 * 3 + 1]; var b02 = b[0 * 3 + 2]; var b10 = b[1 * 3 + 0]; var b11 = b[1 * 3 + 1]; var b12 = b[1 * 3 + 2]; var b20 = b[2 * 3 + 0]; var b21 = b[2 * 3 + 1]; var b22 = b[2 * 3 + 2]; return [a00 * b00 + a01 * b10 + a02 * b20, a00 * b01 + a01 * b11 + a02 * b21, a00 * b02 + a01 * b12 + a02 * b22, a10 * b00 + a11 * b10 + a12 * b20, a10 * b01 + a11 * b11 + a12 * b21, a10 * b02 + a11 * b12 + a12 * b22, a20 * b00 + a21 * b10 + a22 * b20, a20 * b01 + a21 * b11 + a22 * b21, a20 * b02 + a21 * b12 + a22 * b22]; } }; var DomUtil = { /** * Creates and returns a new canvas. The opacity is by default set to 0 * * @memberof Proton#Proton.DomUtil * @method createCanvas * * @param {String} $id the canvas' id * @param {Number} $width the canvas' width * @param {Number} $height the canvas' height * @param {String} [$position=absolute] the canvas' position, default is 'absolute' * * @return {Object} */ createCanvas: function createCanvas(id, width, height, position) { if (position === void 0) { position = "absolute"; } var dom = document.createElement("canvas"); dom.id = id; dom.width = width; dom.height = height; dom.style.opacity = 0; dom.style.position = position; this.transform(dom, -500, -500, 0, 0); return dom; }, createDiv: function createDiv(id, width, height) { var dom = document.createElement("div"); dom.id = id; dom.style.position = "absolute"; this.resize(dom, width, height); return dom; }, resize: function resize(dom, width, height) { dom.style.width = width + "px"; dom.style.height = height + "px"; dom.style.marginLeft = -width / 2 + "px"; dom.style.marginTop = -height / 2 + "px"; }, /** * Adds a transform: translate(), scale(), rotate() to a given div dom for all browsers * * @memberof Proton#Proton.DomUtil * @method transform * * @param {HTMLDivElement} div * @param {Number} $x * @param {Number} $y * @param {Number} $scale * @param {Number} $rotate */ transform: function transform(div, x, y, scale, rotate) { div.style.willChange = "transform"; var transform = "translate(" + x + "px, " + y + "px) scale(" + scale + ") rotate(" + rotate + "deg)"; this.css3(div, "transform", transform); }, transform3d: function transform3d(div, x, y, scale, rotate) { div.style.willChange = "transform"; var transform = "translate3d(" + x + "px, " + y + "px, 0) scale(" + scale + ") rotate(" + rotate + "deg)"; this.css3(div, "backfaceVisibility", "hidden"); this.css3(div, "transform", transform); }, css3: function css3(div, key, val) { var bkey = key.charAt(0).toUpperCase() + key.substr(1); div.style["Webkit" + bkey] = val; div.style["Moz" + bkey] = val; div.style["O" + bkey] = val; div.style["ms" + bkey] = val; div.style["" + key] = val; } }; var imgsCache = {}; var canvasCache = {}; var canvasId = 0; var ImgUtil = { /** * This will get the image data. It could be necessary to create a Proton.Zone. * * @memberof Proton#Proton.Util * @method getImageData * * @param {HTMLCanvasElement} context any canvas, must be a 2dContext 'canvas.getContext('2d')' * @param {Object} image could be any dom image, e.g. document.getElementById('thisIsAnImgTag'); * @param {Proton.Rectangle} rect */ getImageData: function getImageData(context, image, rect) { context.drawImage(image, rect.x, rect.y); var imagedata = context.getImageData(rect.x, rect.y, rect.width, rect.height); context.clearRect(rect.x, rect.y, rect.width, rect.height); return imagedata; }, /** * @memberof Proton#Proton.Util * @method getImgFromCache * * @todo add description * @todo describe func * * @param {Mixed} img * @param {Proton.Particle} particle * @param {Boolean} drawCanvas set to true if a canvas should be saved into particle.data.canvas * @param {Boolean} func */ getImgFromCache: function getImgFromCache(img, callback, param) { var src = typeof img === "string" ? img : img.src; if (imgsCache[src]) { callback(imgsCache[src], param); } else { var image = new Image(); image.onload = function (e) { imgsCache[src] = e.target; callback(imgsCache[src], param); }; image.src = src; } }, getCanvasFromCache: function getCanvasFromCache(img, callback, param) { var src = img.src; if (!canvasCache[src]) { var width = WebGLUtil.nhpot(img.width); var height = WebGLUtil.nhpot(img.height); var canvas = DomUtil.createCanvas("proton_canvas_cache_" + ++canvasId, width, height); var context = canvas.getContext("2d"); context.drawImage(img, 0, 0, img.width, img.height); canvasCache[src] = canvas; } callback && callback(canvasCache[src], param); return canvasCache[src]; } }; var Util = { /** * Returns the default if the value is null or undefined * * @memberof Proton#Proton.Util * @method initValue * * @param {Mixed} value a specific value, could be everything but null or undefined * @param {Mixed} defaults the default if the value is null or undefined */ initValue: function initValue(value, defaults) { value = value !== null && value !== undefined ? value : defaults; return value; }, /** * Checks if the value is a valid array * * @memberof Proton#Proton.Util * @method isArray * * @param {Array} value Any array * * @returns {Boolean} */ isArray: function isArray(value) { return Object.prototype.toString.call(value) === "[object Array]"; }, /** * Destroyes the given array * * @memberof Proton#Proton.Util * @method emptyArray * * @param {Array} array Any array */ emptyArray: function emptyArray(arr) { if (arr) arr.length = 0; }, toArray: function toArray(arr) { return this.isArray(arr) ? arr : [arr]; }, sliceArray: function sliceArray(arr1, index, arr2) { this.emptyArray(arr2); for (var i = index; i < arr1.length; i++) { arr2.push(arr1[i]); } }, getRandFromArray: function getRandFromArray(arr) { if (!arr) return null; return arr[Math.floor(arr.length * Math.random())]; }, /** * Destroyes the given object * * @memberof Proton#Proton.Util * @method emptyObject * * @param {Object} obj Any object */ emptyObject: function emptyObject(obj, ignore) { if (ignore === void 0) { ignore = null; } for (var key in obj) { if (ignore && ignore.indexOf(key) > -1) continue; delete obj[key]; } }, /** * Makes an instance of a class and binds the given array * * @memberof Proton#Proton.Util * @method classApply * * @param {Function} constructor A class to make an instance from * @param {Array} [args] Any array to bind it to the constructor * * @return {Object} The instance of constructor, optionally bind with args */ classApply: function classApply(constructor, args) { if (args === void 0) { args = null; } if (!args) { return new constructor(); } else { var FactoryFunc = constructor.bind.apply(constructor, [null].concat(args)); return new FactoryFunc(); } }, /** * This will get the image data. It could be necessary to create a Proton.Zone. * * @memberof Proton#Proton.Util * @method getImageData * * @param {HTMLCanvasElement} context any canvas, must be a 2dContext 'canvas.getContext('2d')' * @param {Object} image could be any dom image, e.g. document.getElementById('thisIsAnImgTag'); * @param {Proton.Rectangle} rect */ getImageData: function getImageData(context, image, rect) { return ImgUtil.getImageData(context, image, rect); }, destroyAll: function destroyAll(arr, param) { if (param === void 0) { param = null; } var i = arr.length; while (i--) { try { arr[i].destroy(param); } catch (e) {} delete arr[i]; } arr.length = 0; }, assign: function assign(target, source) { if (typeof Object.assign !== "function") { for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } return target; } else { return Object.assign(target, source); } } }; var idsMap = {}; var Puid = { _index: 0, _cache: {}, id: function id(type) { if (idsMap[type] === undefined || idsMap[type] === null) idsMap[type] = 0; return type + "_" + idsMap[type]++; }, getId: function getId(target) { var uid = this.getIdFromCache(target); if (uid) return uid; uid = "PUID_" + this._index++; this._cache[uid] = target; return uid; }, getIdFromCache: function getIdFromCache(target) { var obj, id; for (id in this._cache) { obj = this._cache[id]; if (obj === target) return id; if (this.isBody(obj, target) && obj.src === target.src) return id; } return null; }, isBody: function isBody(obj, target) { return typeof obj === "object" && typeof target === "object" && obj.isInner && target.isInner; }, getTarget: function getTarget(uid) { return this._cache[uid]; } }; /** * Pool is the cache pool of the proton engine, it is very important. * * get(target, params, uid) * Class * uid = Puid.getId -> Puid save target cache * target.__puid = uid * * body * uid = Puid.getId -> Puid save target cache * * * expire(target) * cache[target.__puid] push target * */ var Pool = /*#__PURE__*/function () { /** * @memberof! Proton# * @constructor * @alias Proton.Pool * * @todo add description * @todo add description of properties * * @property {Number} total * @property {Object} cache */ function Pool(num) { this.total = 0; this.cache = {}; } /** * @todo add description * * @method get * @memberof Proton#Proton.Pool * * @param {Object|Function} target * @param {Object} [params] just add if `target` is a function * * @return {Object} */ var _proto = Pool.prototype; _proto.get = function get(target, params, uid) { var p; uid = uid || target.__puid || Puid.getId(target); if (this.cache[uid] && this.cache[uid].length > 0) { p = this.cache[uid].pop(); } else { p = this.createOrClone(target, params); } p.__puid = target.__puid || uid; return p; } /** * @todo add description * * @method set * @memberof Proton#Proton.Pool * * @param {Object} target * * @return {Object} */; _proto.expire = function expire(target) { return this.getCache(target.__puid).push(target); } /** * Creates a new class instance * * @todo add more documentation * * @method create * @memberof Proton#Proton.Pool * * @param {Object|Function} target any Object or Function * @param {Object} [params] just add if `target` is a function * * @return {Object} */; _proto.createOrClone = function createOrClone(target, params) { this.total++; if (this.create) { return this.create(target, params); } else if (typeof target === "function") { return Util.classApply(target, params); } else { return target.clone(); } } /** * @todo add description - what is in the cache? * * @method getCount * @memberof Proton#Proton.Pool * * @return {Number} */; _proto.getCount = function getCount() { var count = 0; for (var id in this.cache) { count += this.cache[id].length; } return count++; } /** * Destroyes all items from Pool.cache * * @method destroy * @memberof Proton#Proton.Pool */; _proto.destroy = function destroy() { for (var id in this.cache) { this.cache[id].length = 0; delete this.cache[id]; } } /** * Returns Pool.cache * * @method getCache * @memberof Proton#Proton.Pool * @private * * @param {Number} uid the unique id * * @return {Object} */; _proto.getCache = function getCache(uid) { if (uid === void 0) { uid = "default"; } if (!this.cache[uid]) this.cache[uid] = []; return this.cache[uid]; }; return Pool; }(); var Stats = /*#__PURE__*/function () { function Stats(proton) { this.proton = proton; this.container = null; this.type = 1; this.emitterIndex = 0; this.rendererIndex = 0; } var _proto = Stats.prototype; _proto.update = function update(style, body) { this.add(style, body); var emitter = this.getEmitter(); var renderer = this.getRenderer(); var str = ""; switch (this.type) { case 2: str += "emitter:" + this.proton.emitters.length + "<br>"; if (emitter) str += "em speed:" + emitter.emitSpeed + "<br>"; if (emitter) str += "pos:" + this.getEmitterPos(emitter); break; case 3: if (emitter) str += "initializes:" + emitter.initializes.length + "<br>"; if (emitter) str += '<span style="display:inline-block;">' + this.concatArr(emitter.initializes) + "</span><br>"; if (emitter) str += "behaviours:" + emitter.behaviours.length + "<br>"; if (emitter) str += '<span style="display:inline-block;">' + this.concatArr(emitter.behaviours) + "</span><br>"; break; case 4: if (renderer) str += renderer.name + "<br>"; if (renderer) str += "body:" + this.getCreatedNumber(renderer) + "<br>"; break; default: str += "particles:" + this.proton.getCount() + "<br>"; str += "pool:" + this.proton.pool.getCount() + "<br>"; str += "total:" + this.proton.pool.total; } this.container.innerHTML = str; }; _proto.add = function add(style, body) { var _this = this; if (!this.container) { this.type = 1; this.container = document.createElement("div"); this.container.style.cssText = ["position:absolute;bottom:0px;left:0;cursor:pointer;", "opacity:0.9;z-index:10000;padding:10px;font-size:12px;font-family:Helvetica,Arial,sans-serif;", "width:120px;height:50px;background-color:#002;color:#0ff;"].join(""); this.container.addEventListener("click", function (e) { _this.type++; if (_this.type > 4) _this.type = 1; }, false); var bg, color; switch (style) { case 2: bg = "#201"; color = "#f08"; break; case 3: bg = "#020"; color = "#0f0"; break; default: bg = "#002"; color = "#0ff"; } this.container.style["background-color"] = bg; this.container.style["color"] = color; } if (!this.container.parentNode) { body = body || this.body || document.body; body.appendChild(this.container); } }; _proto.getEmitter = function getEmitter() { return this.proton.emitters[this.emitterIndex]; }; _proto.getRenderer = function getRenderer() { return this.proton.renderers[this.rendererIndex]; }; _proto.concatArr = function concatArr(arr) { var result = ""; if (!arr || !arr.length) return result; for (var i = 0; i < arr.length; i++) { result += (arr[i].name || "").substr(0, 1) + "."; } return result; }; _proto.getCreatedNumber = function getCreatedNumber(renderer) { return renderer.pool.total || renderer.cpool && renderer.cpool.total || 0; }; _proto.getEmitterPos = function getEmitterPos(e) { return Math.round(e.p.x) + "," + Math.round(e.p.y); }; _proto.destroy = function destroy() { if (this.container && this.container.parentNode) { var body = this.body || document.body; body.removeChild(this.container); } this.proton = null; this.container = null; }; return Stats; }(); /* * EventDispatcher * This code reference since http://createjs.com/. * **/ var EventDispatcher = /*#__PURE__*/function () { function EventDispatcher() { this._listeners = null; } EventDispatcher.bind = function bind(target) { target.prototype.dispatchEvent = EventDispatcher.prototype.dispatchEvent; target.prototype.hasEventListener = EventDispatcher.prototype.hasEventListener; target.prototype.addEventListener = EventDispatcher.prototype.addEventListener; target.prototype.removeEventListener = EventDispatcher.prototype.removeEventListener; target.prototype.removeAllEventListeners = EventDispatcher.prototype.removeAllEventListeners; }; var _proto = EventDispatcher.prototype; _proto.addEventListener = function addEventListener(type, listener) { if (!this._listeners) { this._listeners = {}; } else { this.removeEventListener(type, listener); } if (!this._listeners[type]) this._listeners[type] = []; this._listeners[type].push(listener); return listener; }; _proto.removeEventListener = function removeEventListener(type, listener) { if (!this._listeners) return; if (!this._listeners[type]) return; var arr = this._listeners[type]; var length = arr.length; for (var i = 0; i < length; i++) { if (arr[i] === listener) { if (length === 1) { delete this._listeners[type]; } // allows for faster checks. else { arr.splice(i, 1); } break; } } }; _proto.removeAllEventListeners = function removeAllEventListeners(type) { if (!type) this._listeners = null;else if (this._listeners) delete this._listeners[type]; }; _proto.dispatchEvent = function dispatchEvent(type, args) { var result = false; var listeners = this._listeners; if (type && listeners) { var arr = listeners[type]; if (!arr) return result; // arr = arr.slice(); // to avoid issues with items being removed or added during the dispatch var handler; var i = arr.length; while (i--) { handler = arr[i]; result = result || handler(args); } } return !!result; }; _proto.hasEventListener = function hasEventListener(type) { var listeners = this._listeners; return !!(listeners && listeners[type]); }; return EventDispatcher; }(); var PI = 3.1415926; var INFINITY = Infinity; var MathUtil = { PI: PI, PIx2: PI * 2, PI_2: PI / 2, PI_180: PI / 180, N180_PI: 180 / PI, Infinity: -999, isInfinity: function isInfinity(num) { return num === this.Infinity || num === INFINITY; }, randomAToB: function randomAToB(a, b, isInt) { if (isInt === void 0) { isInt = false; } if (!isInt) return a + Math.random() * (b - a);else return (Math.random() * (b - a) >> 0) + a; }, randomFloating: function randomFloating(center, f, isInt) { return this.randomAToB(center - f, center + f, isInt); }, randomColor: function randomColor() { return "#" + ("00000" + (Math.random() * 0x1000000 << 0).toString(16)).slice(-6); }, randomZone: function randomZone(display) {}, floor: function floor(num, k) { if (k === void 0) { k = 4; } var digits = Math.pow(10, k); return Math.floor(num * digits) / digits; }, degreeTransform: function degreeTransform(a) { return a * PI / 180; }, toColor16: function toColor16(num) { return "#" + num.toString(16); } }; var Integration = /*#__PURE__*/function () { function Integration(type) { this.type = type; } var _proto = Integration.prototype; _proto.calculate = function calculate(particles, time, damping) { this.eulerIntegrate(particles, time, damping); } // Euler Integrate // https://rosettacode.org/wiki/Euler_method ; _proto.eulerIntegrate = function eulerIntegrate(particle, time, damping) { if (!particle.sleep) { particle.old.p.copy(particle.p); particle.old.v.copy(particle.v); particle.a.multiplyScalar(1 / particle.mass); particle.v.add(particle.a.multiplyScalar(time)); particle.p.add(particle.old.v.multiplyScalar(time)); if (damping) particle.v.multiplyScalar(damping); particle.a.clear(); } }; return Integration; }(); var Proton = /*#__PURE__*/function () { // measure 1:100 // event name /** * 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 */ function Proton(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. */ var _proto = Proton.prototype; /** * add a type of Renderer * * @method addRenderer * @memberof Proton * @instance * * @param {Renderer} render */ _proto.addRenderer = function addRenderer(render) { render.init(this); this.renderers.push(render); } /** * @name add a type of Renderer * * @method addRenderer * @param {Renderer} render */; _proto.removeRenderer = function removeRenderer(render) { var index = this.renderers.indexOf(render); this.renderers.splice(index, 1); render.remove(this); } /** * add the Emitter * * @method addEmitter * @memberof Proton * @instance * * @param {Emitter} emitter */; _proto.addEmitter = function 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 */; _proto.removeEmitter = function removeEmitter(emitter) { var 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 */; _proto.update = function 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); } } }; _proto.emittersUpdate = function emittersUpdate(elapsed) { var i = this.emitters.length; while (i--) { this.emitters[i].update(elapsed); } } /** * @todo add description * * @method amendChangeTabsBug * @memberof Proton * @instance */; _proto.amendChangeTabsBug = function 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 */; _proto.getCount = function getCount() { var total = 0; var i = this.emitters.length; while (i--) { total += this.emitters[i].particles.length; } return total; }; _proto.getAllParticles = function getAllParticles() { var particles = []; var i = this.emitters.length; while (i--) { particles = particles.concat(this.emitters[i].particles); } return particles; }; _proto.destroyAllEmitters = function destroyAllEmitters() { Util.destroyAll(this.emitters); } /** * Destroys everything related to this Proton instance. This includes all emitters, and all properties * * @method destroy * @memberof Proton * @instance */; _proto.destroy = function destroy(remove) { var _this = this; if (remove === void 0) { remove = false; } var destroyOther = function 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(); } }; _createClass(Proton, [{ key: "fps", get: /** * 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. */ function get() { return this._fps; }, set: function set(fps) { this._fps = fps; this._interval = fps === "auto" ? Proton.DEFAULT_INTERVAL : MathUtil.floor(1 / fps, 7); } }]); return Proton; }(); Proton.USE_CLOCK = false; Proton.MEASURE = 100; Proton.EULER = "euler"; Proton.RK2 = "runge-kutta2"; Proton.PARTICLE_CREATED = "PARTICLE_CREATED"; Proton.PARTICLE_UPDATE = "PARTICLE_UPDATE"; Proton.PARTICLE_SLEEP = "PARTICLE_SLEEP"; Proton.PARTICLE_DEAD = "PARTICLE_DEAD"; Proton.EMITTER_ADDED = "EMITTER_ADDED"; Proton.EMITTER_REMOVED = "EMITTER_REMOVED"; Proton.PROTON_UPDATE = "PROTON_UPDATE"; Proton.PROTON_UPDATE_AFTER = "PROTON_UPDATE_AFTER"; Proton.DEFAULT_INTERVAL = 0.0167; Proton.amendChangeTabsBug = true; EventDispatcher.bind(Proton); var Rgb = /*#__PURE__*/function () { function Rgb(r, g, b) { if (r === void 0) { r = 255; } if (g === void 0) { g = 255; } if (b === void 0) { b = 255; } this.r = r; this.g = g; this.b = b; } var _proto = Rgb.prototype; _proto.reset = function reset() { this.r = 255; this.g = 255; this.b = 255; }; return Rgb; }(); /** * Represents a span of values or an array. */ var Span = /*#__PURE__*/function () { /** * @type {boolean} * @private */ /** * @type {number|number[]} * @private */ /** * @type {number} * @private */ /** * @type {boolean} * @private */ /** * Creates a new Span instance. * @param {number|number[]} a - The first value or an array of values. * @param {number} [b] - The second value (if a is not an array). * @param {boolean} [center=false] - Whether to use center-based calculation. */ function Span(a, b, center) { this.isArray = void 0; this.a = void 0; this.b = void 0; this.center = void 0; if (Util.isArray(a)) { this.isArray = true; this.a = a; } else { this.isArray = false; this.a = Util.initValue(a, 1); this.b = Util.initValue(b, this.a); this.center = Util.initValue(center, false); } } /** * Gets a value from the span. * @param {boolean} [isInt=false] - Whether to return an integer value. * @returns {number} A random value from the span. */ var _proto = Span.prototype; _proto.getValue = function getValue(isInt) { if (isInt === void 0) { isInt = false; } if (this.isArray) { return Util.getRandFromArray(this.a); } else { if (!this.center) { return MathUtil.randomAToB(this.a, this.b, isInt); } else { return MathUtil.randomFloating(this.a, this.b, isInt); } } } /** * Returns a new Span object. * @param {*|Span} a - The first value or a Span object. * @param {*} [b] - The second value. * @param {*} [c] - The third value. * @returns {Span} A new Span instance. */; Span.setSpanValue = function setSpanValue(a, b, c) { if (a instanceof Span) { return a; } else { if (b === undefined) { return new Span(a); } else { if (c === undefined) return new Span(a, b);else return new Span(a, b, c); } } } /** * Returns the value from a Span, if the param is not a Span it will return the given parameter. * @param {*|Span} pan - The value or Span to get the value from. * @returns {*} The value of Span OR the parameter if it is not a Span. */; Span.getSpanValue = function getSpanValue(pan) { return pan instanceof Span ? pan.getValue() : pan; }; return Span; }(); var PropUtil = { hasProp: function hasProp(target, key) { if (!target) return false; return target[key] !== undefined; // return obj.hasOwnProperty(key); }, /** * set the prototype in a given prototypeObject * * @memberof Proton#Proton.Util * @method setProp * * @todo add description for param `target` * @todo translate desription from chinese to english * * @param {Object} target * @param {Object} prototypeObject An object of single prototypes * * @return {Object} target */ setProp: function setProp(target, props) { for (var prop in props) { if (target.hasOwnProperty(prop)) { target[prop] = Span.getSpanValue(props[prop]); } } return target; }, /** * @memberof Proton#Proton.Util * @method setVectorVal * * @todo add description for param `target` * @todo add description for param `conf` * @todo add description for function * * @param {Object} target * @param {Object} conf */ setVectorVal: function setVectorVal(particle, conf) { if (conf === void 0) { conf = null; } if (!conf) return; if (this.hasProp(conf, "x")) particle.p.x = conf["x"]; if (this.hasProp(conf, "y")) particle.p.y = conf["y"]; if (this.hasProp(conf, "vx")) particle.v.x = conf["vx"]; if (this.hasProp(conf, "vy")) particle.v.y = conf["vy"]; if (this.hasProp(conf, "ax")) particle.a.x = conf["ax"]; if (this.hasProp(conf, "ay")) particle.a.y = conf["ay"]; if (this.hasProp(conf, "p")) particle.p.copy(conf["p"]); if (this.hasProp(conf, "v")) particle.v.copy(conf["v"]); if (this.hasProp(conf, "a")) particle.a.copy(conf["a"]); if (this.hasProp(conf, "position")) particle.p.copy(conf["position"]); if (this.hasProp(conf, "velocity")) particle.v.copy(conf["velocity"]); if (this.hasProp(conf, "accelerate")) particle.a.copy(conf["accelerate"]); } }; var ease = { easeLinear: function easeLinear(value) { return value; }, easeInQuad: function easeInQuad(value) { return Math.pow(value, 2); }, easeOutQuad: function easeOutQuad(value) { return -(Math.pow(value - 1, 2) - 1); }, easeInOutQuad: function easeInOutQuad(value) { if ((value /= 0.5) < 1) return 0.5 * Math.pow(value, 2); return -0.5 * ((value -= 2) * value - 2); }, easeInCubic: function easeInCubic(value) { return Math.pow(value, 3); }, easeOutCubic: function easeOutCubic(value) { return Math.pow(value - 1, 3) + 1; }, easeInOutCubic: function easeInOutCubic(value) { if ((value /= 0.5) < 1) return 0.5 * Math.pow(value, 3); return 0.5 * (Math.pow(value - 2, 3) + 2); }, easeInQuart: function easeInQuart(value) { return Math.pow(value, 4); }, easeOutQuart: function easeOutQuart(value) { return -(Math.pow(value - 1, 4) - 1); }, easeInOutQuart: function easeInOutQuart(value) { if ((value /= 0.5) < 1) return 0.5 * Math.pow(value, 4); return -0.5 * ((value -= 2) * Math.pow(value, 3) - 2); }, easeInSine: function easeInSine(value) { return -Math.cos(value * MathUtil.PI_2) + 1; }, easeOutSine: function easeOutSine(value) { return Math.sin(value * MathUtil.PI_2); }, easeInOutSine: function easeInOutSine(value) { return -0.5 * (Math.cos(Math.PI * value) - 1); }, easeInExpo: function easeInExpo(value) { return value === 0 ? 0 : Math.pow(2, 10 * (value - 1)); }, easeOutExpo: function easeOutExpo(value) { return value === 1 ? 1 : -Math.pow(2, -10 * value) + 1; }, easeInOutExpo: function easeInOutExpo(value) { if (value === 0) return 0; if (value === 1) return 1; if ((value /= 0.5) < 1) return 0.5 * Math.pow(2, 10 * (value - 1)); return 0.5 * (-Math.pow(2, -10 * --value) + 2); }, easeInCirc: function easeInCirc(value) { return -(Math.sqrt(1 - value * value) - 1); }, easeOutCirc: function easeOutCirc(value) { return Math.sqrt(1 - Math.pow(value - 1, 2)); }, easeInOutCirc: function easeInOutCirc(value) { if ((value /= 0.5) < 1) return -0.5 * (Math.sqrt(1 - value * value) - 1); return 0.5 * (Math.sqrt(1 - (value -= 2) * value) + 1); }, easeInBack: function easeInBack(value) { var s = 1.70158; return value * value * ((s + 1) * value - s); }, easeOutBack: function easeOutBack(value) { var s = 1.70158; return (value = value - 1) * value * ((s + 1) * value + s) + 1; }, easeInOutBack: function easeInOutBack(value) { var s = 1.70158; if ((value /= 0.5) < 1) return 0.5 * (value * value * (((s *= 1.525) + 1) * value - s)); return 0.5 * ((value -= 2) * value * (((s *= 1.525) + 1) * value + s) + 2); }, getEasing: function getEasing(ease) { if (typeof ease === "function") return ease;else return this[ease] || this.easeLinear; } }; var Vector2D = /*#__PURE__*/function () { /** @type {number} */ /** @type {number} */ /** * Creates a new Vector2D instance. * @param {number} [x=0] - The x coordinate. * @param {number} [y=0] - The y coordinate. */ function Vector2D(x, y) { this.x = void 0; this.y = void 0; this.x = x || 0; this.y = y || 0; } /** * Sets the x and y components of this vector. * @param {number} x - The x coordinate. * @param {number} y - The y coordinate. * @returns {Vector2D} This vector. */ var _proto = Vector2D.prototype; _proto.set = function set(x, y) { this.x = x; this.y = y; return this; } /** * Sets the x component of this vector. * @param {number} x - The x coordinate. * @returns {Vector2D} This vector. */; _proto.setX = function setX(x) { this.x = x; return this; } /** * Sets the y component of this vector. * @param {number} y - The y coordinate. * @returns {Vector2D} This vector. */; _proto.setY = function setY(y) { this.y = y; return this; } /** * Calculates the gradient (angle) of this vector. * @returns {number} The gradient in radians. */; _proto.getGradient = function getGradient() { if (this.x !== 0) return Math.atan2(this.y, this.x);else if (this.y > 0) return MathUtil.PI_2;else if (this.y < 0) return -MathUtil.PI_2; } /** * Copies the values of another vector to this one. * @param {Vector2D} v - The vector to copy from. * @returns {Vector2D} This vector. */; _proto.copy = function copy(v) { this.x = v.x; this.y = v.y; return this; } /** * Adds another vector to this one. * @param {Vector2D} v - The vector to add. * @param {Vector2D} [w] - An optional second vector to add. * @returns {Vector2D} This vector. */; _proto.add = function add(v, w) { if (w !== undefined) { return this.addVectors(v, w); } this.x += v.x; this.y += v.y; return this; } /** * Adds scalar values to this vector's components. * @param {number} a - Value to add to x. * @param {number} b - Value to add to y. * @returns {Vector2D} This vector. */; _proto.addXY = function addXY(a, b) { this.x += a; this.y += b; return this; } /** * Adds two vectors and sets the result to this vector. * @param {Vector2D} a - The first vector to add. * @param {Vector2D} b - The second vector to add. * @returns {Vector2D} This vector. */; _proto.addVectors = function addVectors(a, b) { this.x = a.x + b.x; this.y = a.y + b.y; return this; } /** * Subtracts another vector from this one. * @param {Vector2D} v - The vector to subtract. * @param {Vector2D} [w] - An optional second vector to subtract. * @returns {Vector2D} This vector. */; _proto.sub = function sub(v, w) { if (w !== undefined) { return this.subVectors(v, w); } this.x -= v.x; this.y -= v.y; return this; } /** * Subtracts one vector from another and sets the result to this vector. * @param {Vector2D} a - The vector to subtract from. * @param {Vector2D} b - The vector to subtract. * @returns {Vector2D} This vector. */; _proto.subVectors = function subVectors(a, b) { this.x = a.x - b.x; this.y = a.y - b.y; return this; } /** * Divides this vector by a scalar. * @param {number} s - The scalar to divide by. * @returns {Vector2D} This vector. */; _proto.divideScalar = function divideScalar(s) { if (s !== 0) { this.x /= s; this.y /= s; } else { this.set(0, 0); } return this; } /** * Multiplies this vector by a scalar. * @param {number} s - The scalar to multiply by. * @returns {Vector2D} This vector. */; _proto.multiplyScalar = function multiplyScalar(s) { this.x *= s; this.y *= s; return this; } /** * Negates this vector (inverts its direction). * @returns {Vector2D} This vector. */; _proto.negate = function negate() { return this.multiplyScalar(-1); } /** * Calculates the dot product of this vector with another. * @param {Vector2D} v - The other vector. * @returns {number} The dot product. */; _proto.dot = function dot(v) { return this.x * v.x + this.y * v.y; } /** * Calculates the squared length of this vector. * @returns {number} The squared length. */; _proto.lengthSq = function lengthSq() { return this.x * this.x + this.y * this.y; } /** * Calculates the length of this vector. * @returns {number} The length. */; _proto.length = function length() { return Math.sqrt(this.x * this.x + this.y * this.y); } /** * Normalizes this vector (makes it unit length). * @returns {Vector2D} This vector. */; _proto.normalize = function normalize() { return this.divideScalar(this.length()); } /** * Calculates the distance to another vector. * @param {Vector2D} v - The other vector. * @returns {number} The distance. */; _proto.distanceTo = function distanceTo(v) { return Math.sqrt(this.distanceToSquared(v)); } /** * Rotates this vector by an angle. * @param {number} tha - The angle to rotate by (in radians). * @returns {Vector2D} This vector. */; _proto.rotate = function rotate(tha) { var x = this.x; var y = this.y; this.x = x * Math.cos(tha) + y * Math.sin(tha); this.y = -x * Math.sin(tha) + y * Math.cos(tha); return this; } /** * Calculates the squared distance to another vector. * @param {Vector2D} v - The other vector. * @returns {number} The squared distance. */; _proto.distanceToSquared = function distanceToSquared(v) { var dx = this.x - v.x; var dy = this.y - v.y; return dx * dx + dy * dy; } /** * Linearly interpolates this vector toward another vector. * @param {Vector2D} v - The target vector. * @param {number} alpha - The interpolation factor (0-1). * @returns {Vector2D} This vector. */; _proto.lerp = function lerp(v, alpha) { this.x += (v.x - this.x) * alpha; this.y += (v.y - this.y) * alpha; return this; } /** * Checks if this vector is equal to another vector. * @param {Vector2D} v - The other vector. * @returns {boolean} True if the vectors are equal, false otherwise. */; _proto.equals = function equals(v) { return v.x === this.x && v.y === this.y; } /** * Sets this vector to zero. * @returns {Vector2D} This vector. */; _proto.clear = function clear() { this.x = 0.0; this.y = 0.0; return this; } /** * Creates a new vector with the same x and y values as this one. * @returns {Vector2D} A new Vector2D instance. */; _proto.clone = function clone() { return new Vector2D(this.x, this.y); }; return Vector2D; }(); /** * Represents a particle in a particle system. * @class Particle */ var Particle = /*#__PURE__*/function () { /** @type {string} The unique identifier of the particle */ /** @type {{p:Vector2D,v:Vector2D,a:Vector2D}} Old state of the particle */ /** @type {object} Custom data associated with the particle */ /** @type {Behaviour[]} Array of behaviours applied to the particle */ /** @type {Vector2D} Current position of the particle */ /** @type {Vector2D} Current velocity of the particle */ /** @type {Vector2D} Current acceleration of the particle */ /** @type {Rgb} Color of the particle */ /** * Creates a new Particle instance. * @param {Object} [conf] Configuration object for the particle */ function Particle(conf) { this.id = ""; this.old = null; this.data = null; this.behaviours = null; this.p = null; this.v = null; this.a = null; this.rgb = null; this.name = "Particle"; this.id = Puid.id(this.name); this.old = {}; this.data = {}; this.behaviours = []; this.p = new Vector2D(); this.v = new Vector2D(); this.a = new Vector2D(); this.old.p = new Vector2D(); this.old.v = new Vector2D(); this.old.a = new Vector2D(); this.rgb = new Rgb(); this.reset(