proton-engine
Version:
Proton is a simple and powerful javascript particle animation engine.
1,679 lines (1,598 loc) • 659 kB
JavaScript
this.Proton = (function () {
'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();
conf && PropUtil.setProp(this, conf);
}
/**
* Gets the direction of the particle's movement in degrees.
* @returns {number} The direction in degrees
*/
var _proto = Particle.prototype;
_proto.getDirection = function getDirection() {
return Math.atan2(t