UNPKG

three.proton

Version:

three.Proton is an easily customizable html5 particle engine for three.js

1,811 lines (1,445 loc) 114 kB
/*! * three.proton v0.1.3 * https://github.com/a-jie/three.proton * * Copyright 2011-2017, A-JIE * Licensed under the MIT license * http://www.opensource.org/licenses/mit-license * */ (function (root, factory) { if (typeof define === 'function' && define.amd) { define([], factory); } else if (typeof exports === 'object') { module.exports = factory(); } else { root.Proton = factory(); } }(this, function () { //the max particle number in pool Proton.POOL_MAX = 500; Proton.TIME_STEP = 60; Proton.PI = 3.142; Proton.DR = Proton.PI / 180; //1:100 Proton.MEASURE = 100; Proton.EULER = 'euler'; Proton.RK2 = 'runge-kutta2'; Proton.RK4 = 'runge-kutta4'; Proton.VERLET = 'verlet'; Proton.PARTICLE_CREATED = 'partilcleCreated'; Proton.PARTICLE_UPDATE = 'partilcleUpdate'; Proton.PARTICLE_SLEEP = 'particleSleep'; Proton.PARTICLE_DEAD = 'partilcleDead'; Proton.PROTON_UPDATE = 'protonUpdate'; Proton.PROTON_UPDATE_AFTER = 'protonUpdateAfter'; Proton.EMITTER_ADDED = 'emitterAdded'; Proton.EMITTER_REMOVED = 'emitterRemoved'; Proton.bindEmtterEvent = false; /** * @name Proton is a particle engine for three.js * * @class Proton * @param {number} preParticles input any number * @param {number} integrationType input any number * @example var proton = new Proton(200); */ function Proton(preParticles, integrationType) { this.preParticles = Proton.Util.initValue(preParticles, Proton.POOL_MAX); this.integrationType = Proton.Util.initValue(integrationType, Proton.EULER); this.emitters = []; this.renderers = []; this.pool = new Proton.Pool(); Proton.integrator = new Proton.Integration(this.integrationType); } Proton.prototype = { /** * @name add a type of Renderer * * @method addRender * @param {Renderer} render */ addRender: function(renderer) { this.renderers.push(renderer); renderer.init(this); }, /** * @name add a type of Renderer * * @method addRender * @param {Renderer} render */ removeRender: function(renderer) { this.renderers.splice(this.renderers.indexOf(renderer), 1); renderer.remove(this); }, /** * add the Emitter * * @method addEmitter * @param {Emitter} emitter */ addEmitter: function(emitter) { this.emitters.push(emitter); emitter.parent = this; this.dispatchEvent("EMITTER_ADDED", emitter); }, removeEmitter: function(emitter) { if (emitter.parent != this) return; this.emitters.splice(this.emitters.indexOf(emitter), 1); emitter.parent = null; this.dispatchEvent("EMITTER_REMOVED", emitter); }, update: function($delta) { this.dispatchEvent("PROTON_UPDATE", this); var delta = $delta || 0.0167; if (delta > 0) { var i = this.emitters.length; while (i--) this.emitters[i].update(delta); } this.dispatchEvent("PROTON_UPDATE_AFTER", this); }, /** * getCount * @name get the count of particle * @return (number) particles count */ getCount: function() { var total = 0; var i, length = this.emitters.length; for (i = 0; i < length; i++) total += this.emitters[i].particles.length; return total; }, /** * destroy * @name destroy the proton */ destroy: function() { var i = 0, length = this.emitters.length; for (i; i < length; i++) { this.emitters[i].destroy(); delete this.emitters[i]; } this.emitters.length = 0; this.pool.destroy(); } }; /* * EventDispatcher * Visit http://createjs.com/ for documentation, updates and examples. * **/ function EventDispatcher() { this.initialize(); }; EventDispatcher.initialize = function(target) { target.addEventListener = p.addEventListener; target.removeEventListener = p.removeEventListener; target.removeAllEventListeners = p.removeAllEventListeners; target.hasEventListener = p.hasEventListener; target.dispatchEvent = p.dispatchEvent; }; var p = EventDispatcher.prototype; p._listeners = null; p.initialize = function() {}; p.addEventListener = function(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; }; p.removeEventListener = function(type, listener) { if (!this._listeners) return; if (!this._listeners[type]) return; var arr = this._listeners[type]; for (var i = 0, l = arr.length; i < l; i++) { if (arr[i] == listener) { if (l == 1) { delete(this._listeners[type]); } // allows for faster checks. else { arr.splice(i, 1); } break; } } }; p.removeAllEventListeners = function(type) { if (!type) this._listeners = null; else if (this._listeners) delete(this._listeners[type]); }; p.dispatchEvent = function(eventName, eventTarget) { var ret = false, listeners = this._listeners; if (eventName && listeners) { var arr = listeners[eventName]; if (!arr) return ret; arr = arr.slice(); // to avoid issues with items being removed or added during the dispatch var handler, i = arr.length; while (i--) { var handler = arr[i]; ret = ret || handler(eventTarget); } } return !!ret; }; p.hasEventListener = function(type) { var listeners = this._listeners; return !!(listeners && listeners[type]); }; EventDispatcher.initialize(Proton.prototype); Proton.EventDispatcher = EventDispatcher; var Util = Util || { initValue: function(value, defaults) { var value = (value != null && value != undefined) ? value : defaults; return value; }, isArray: function(value) { return Object.prototype.toString.call(value) === '[object Array]'; }, destroyArray: function(array) { array.length = 0; }, destroyObject: function(obj) { for (var o in obj) delete obj[o]; }, isUndefined: function() { for (var id in arguments) { var arg = arguments[id]; if (arg !== undefined) return false; } return true; }, setVectorByObj: function(target, pOBJ) { if (pOBJ["x"] !== undefined) target.p.x = pOBJ["x"]; if (pOBJ["y"] !== undefined) target.p.y = pOBJ["y"]; if (pOBJ["z"] !== undefined) target.p.z = pOBJ["z"]; if (pOBJ["vx"] !== undefined) target.v.x = pOBJ["vx"]; if (pOBJ["vy"] !== undefined) target.v.y = pOBJ["vy"]; if (pOBJ["vz"] !== undefined) target.v.z = pOBJ["vz"]; if (pOBJ["ax"] !== undefined) target.a.x = pOBJ["ax"]; if (pOBJ["ay"] !== undefined) target.a.y = pOBJ["ay"]; if (pOBJ["az"] !== undefined) target.a.z = pOBJ["az"]; if (pOBJ["p"] !== undefined) target.p.copy(pOBJ["p"]); if (pOBJ["v"] !== undefined) target.v.copy(pOBJ["v"]); if (pOBJ["a"] !== undefined) target.a.copy(pOBJ["a"]); if (pOBJ["position"] !== undefined) target.p.copy(pOBJ["position"]); if (pOBJ["velocity"] !== undefined) target.v.copy(pOBJ["velocity"]); if (pOBJ["accelerate"] !== undefined) target.a.copy(pOBJ["accelerate"]); }, //set prototype setPrototypeByObj: function(target, proObj, filters) { for (var key in proObj) { if (target.hasOwnProperty(key)) { if (filters) { if (filters.indexOf(key) < 0) target[key] = Util._getValue(proObj[key]); } else { target[key] = Util._getValue(proObj[key]); } } } return target; }, _getValue: function(pan) { if (pan instanceof Span) return pan.getValue(); else return pan; }, inherits: function(subClass, superClass) { subClass._super_ = superClass; if (Object['create']) { subClass.prototype = Object.create(superClass.prototype, { constructor: { value: subClass } }); } else { var F = function() {}; F.prototype = superClass.prototype; subClass.prototype = new F(); subClass.prototype.constructor = subClass; } } }; Proton.Util = Util; var ColorUtil = ColorUtil || { getRGB: function(color) { var rgb = {}; if (typeof color === 'number') { hex = Math.floor(color); rgb.r = (color >> 16 & 255) / 255; rgb.g = (color >> 8 & 255) / 255; rgb.b = (color & 255) / 255; } else if (typeof color === 'string') { var m; if (m = /^(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(,\s*([0-9]*\.?[0-9]+)\s*)?$/.exec(color)) { rgb.r = Math.min(255, parseInt(m[1], 10)) / 255; rgb.g = Math.min(255, parseInt(m[2], 10)) / 255; rgb.b = Math.min(255, parseInt(m[3], 10)) / 255; } else if (m = /^\#([A-Fa-f0-9]+)$/.exec(color)) { var hex = m[1]; rgb.r = parseInt(hex.charAt(0) + hex.charAt(1), 16) / 255; rgb.g = parseInt(hex.charAt(2) + hex.charAt(3), 16) / 255; rgb.b = parseInt(hex.charAt(4) + hex.charAt(5), 16) / 255; } } else if (color instanceof THREE.Color) { rgb.r = color.r; rgb.g = color.g; rgb.b = color.b; } return rgb; } }; Proton.ColorUtil = ColorUtil; var THREEUtil = { toScreenPos: function() { var vector = new THREE.Vector3(); return function(pos, camera, canvas) { vector.copy(pos); // map to normalized device coordinate (NDC) space vector.project(camera); // map to 2D screen space vector.x = Math.round((vector.x + 1) * canvas.width / 2); vector.y = Math.round((-vector.y + 1) * canvas.height / 2); vector.z = 0; return vector; } }(), toSpacePos: function() { var vector = new THREE.Vector3(), dir = new THREE.Vector3(), distance; return function(pos, camera, canvas) { vector.set((pos.x / canvas.width) * 2 - 1, -(pos.y / canvas.height) * 2 + 1, 0.5); vector.unproject(camera); dir.copy(vector.sub(camera.position).normalize()); distance = -camera.position.z / dir.z; vector.copy(camera.position); vector.add(dir.multiplyScalar(distance)); return vector; } }(), getTexture: function() { var store = {}; return function(img) { if (img instanceof THREE.Texture) { return img; } else if (typeof img == "string") { var id = Proton.PUID.hash(img); if (!store[id]) store[id] = new THREE.Texture(img);; return store[id]; } else if (img instanceof Image) { var id = Proton.PUID.hash(img.src); if (!store[id]) store[id] = new THREE.Texture(img);; return store[id]; } } }() }; Proton.THREEUtil = THREEUtil; var PUID = PUID || { _id: 0, _uids: {}, id: function(obj) { for (var id in this._uids) { if (this._uids[id] == obj) return id; } var nid = "PUID_" + (this._id++); this._uids[nid] = obj; return nid; }, hash: function(str) { return; } } Proton.PUID = PUID; Particle.ID = 0; /** * the Particle class * @param {Number} pObj - the parameters of particle config; * @example * var p = new Proton.Particle({life:3,dead:false}); * or * var p = new Proton.Particle({mass:1,radius:100}); * @constructor */ function Particle(pOBJ) { /** * @property {Number} id - The particle's id */ this.id = 'particle_' + Particle.ID++; this.name = 'Particle'; this.reset("init"); Proton.Util.setPrototypeByObj(this, pOBJ); } Particle.prototype = { getDirection: function() { return Math.atan2(this.v.x, -this.v.y) * (180 / Proton.PI); }, /** * @property {Number} life - The particle's life * @property {Number} age - The particle's age * @property {Number} energy - The particle's energy loss * @property {Boolean} dead - The particle is dead? * @property {Boolean} sleep - The particle is sleep? * @property {Object} target - The particle's target * @property {Object} body - The particle's body * @property {Number} mass - The particle's mass * @property {Number} radius - The particle's radius * @property {Number} alpha - The particle's alpha * @property {Number} scale - The particle's scale * @property {Number} rotation - The particle's rotation * @property {String|Number} color - The particle's color * @property {Function} easing - The particle's easing * @property {Proton.Vector3D} p - The particle's position * @property {Proton.Vector3D} v - The particle's velocity * @property {Proton.Vector3D} a - The particle's acceleration * @property {Array} behaviours - The particle's behaviours array * @property {Object} transform - The particle's transform collection */ reset: function(init) { this.life = Infinity; this.age = 0; //energy loss this.energy = 1; this.dead = false; this.sleep = false; this.body = null; this.parent = null; this.mass = 1; this.radius = 10; this.alpha = 1; this.scale = 1; this.useColor = false; this.useAlpha = false; this.easing = Proton.ease.setEasingByName(Proton.ease.easeLinear); if (init) { this.p = new Proton.Vector3D(); this.v = new Proton.Vector3D(); this.a = new Proton.Vector3D(); this.old = {}; this.old.p = this.p.clone(); this.old.v = this.v.clone(); this.old.a = this.a.clone(); this.behaviours = []; this.transform = {}; this.color = { r: 0, g: 0, b: 0 }; this.rotation = new Proton.Vector3D; } else { this.p.set(0, 0, 0); this.v.set(0, 0, 0); this.a.set(0, 0, 0); this.old.p.set(0, 0, 0); this.old.v.set(0, 0, 0); this.old.a.set(0, 0, 0); this.color.r = 0; this.color.g = 0; this.color.b = 0; this.rotation.clear(); Proton.Util.destroyObject(this.transform); this.removeAllBehaviours(); } return this; }, update: function(time, index) { if (!this.sleep) { this.age += time; var i = this.behaviours.length; while (i--) { this.behaviours[i] && this.behaviours[i].applyBehaviour(this, time, index) } } else { //sleep } if (this.age >= this.life) { this.destroy(); } else { var scale = this.easing(this.age / this.life); this.energy = Math.max(1 - scale, 0); } }, addBehaviour: function(behaviour) { this.behaviours.push(behaviour); behaviour.initialize(this); }, addBehaviours: function(behaviours) { var i = behaviours.length; while (i--) { this.addBehaviour(behaviours[i]); } }, removeBehaviour: function(behaviour) { var index = this.behaviours.indexOf(behaviour); if (index > -1) { this.behaviours.splice(index, 1); } }, removeAllBehaviours: function() { Proton.Util.destroyArray(this.behaviours); }, /** * Destory this particle * @method destroy */ destroy: function() { this.removeAllBehaviours(); this.energy = 0; this.dead = true; this.parent = null; } }; Proton.Particle = Particle; function Pool() { this.cID = 0; this.list = {}; } Pool.prototype = { create: function(obj) { this.cID++; if (typeof obj == "function") return new obj; else return obj.clone(); }, getCount: function() { var count = 0; for (var id in this.list) count += this.list[id].length; return count++;; }, get: function(obj) { var p, puid = obj.__puid || Proton.PUID.id(obj); if (this.list[puid] && this.list[puid].length > 0) p = this.list[puid].pop(); else p = this.create(obj); p.__puid = obj.__puid || puid; return p; }, expire: function(obj) { return this._getList(obj.__puid).push(obj); }, destroy: function() { for (var id in this.list) { this.list[id].length = 0; delete this.list[id]; } }, _getList: function(uid) { uid = uid || "default"; if (!this.list[uid]) this.list[uid] = []; return this.list[uid]; } } Proton.Pool = Pool; var MathUtils = { randomAToB: function(a, b, INT) { if (!INT) return a + Math.random() * (b - a); else return ((Math.random() * (b - a)) >> 0) + a; }, randomFloating: function(center, f, INT) { return MathUtils.randomAToB(center - f, center + f, INT); }, randomZone: function(display) { }, degreeTransform: function(a) { return a * Proton.PI / 180; }, toColor16: function getRGB(num) { return "#" + num.toString(16); }, randomColor: function() { return '#' + ('00000' + (Math.random() * 0x1000000 << 0).toString(16)).slice(-6); }, lerp: function(a, b, energy) { return b + (a - b) * energy }, getNormal: function(v, n) { if (v.x == 0 && v.y == 0) { if (v.z == 0) n.set(1, 0, 1); else n.set(1, 1, -v.y / v.z); } else { if (v.x == 0) n.set(1, 0, 1); else n.set(-v.y / v.x, 1, 1); } return n.normalize(); }, /** * Rodrigues' Rotation Formula * https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula * v′ = vcos(θ) + k(k⋅v)(1−cos(θ)) + (k*v)sin(θ) */ axisRotate: function(v0, v, k, tha) { var cos = Math.cos(tha); var sin = Math.sin(tha); var p = k.dot(v) * (1 - cos); v0.copy(k); v0.cross(v).scalar(sin); v0.addValue(v.x * cos, v.y * cos, v.z * cos); v0.addValue(k.x * p, k.y * p, k.z * p); } } Proton.MathUtils = MathUtils; //数值积分 var Integration = function(type) { this.type = Proton.Util.initValue(type, Proton.EULER); } Integration.prototype = { integrate: function(particles, time, damping) { this.euler(particles, time, damping); }, euler: function(particle, time, damping) { if (!particle.sleep) { particle.old.p.copy(particle.p); particle.old.v.copy(particle.v); particle.a.scalar(1 / particle.mass); particle.v.add(particle.a.scalar(time)); particle.p.add(particle.old.v.scalar(time)); damping && particle.v.scalar(damping); particle.a.clear(); } } } Proton.Integration = Integration; //@author mrdoob / http://mrdoob.com/ var Vector3D = function(x, y, z) { this.x = x || 0; this.y = y || 0; this.z = z || 0; } Vector3D.prototype = { set: function(x, y, z) { this.x = x; this.y = y; this.z = z; return this; }, setX: function(x) { this.x = x; return this; }, setY: function(y) { this.y = y; return this; }, setZ: function(z) { this.z = z; return this; }, getGradient: function() { if (this.x != 0) return Math.atan2(this.y, this.x); else if (this.y > 0) return Proton.PI / 2; else if (this.y < 0) return -Proton.PI / 2; }, copy: function(v) { this.x = v.x; this.y = v.y; this.z = v.z; return this; }, add: function(v, w) { if (w !== undefined) return this.addVectors(v, w); this.x += v.x; this.y += v.y; this.z += v.z; return this; }, addValue: function(a, b, c) { this.x += a; this.y += b; this.z += c; return this; }, addVectors: function(a, b) { this.x = a.x + b.x; this.y = a.y + b.y; this.z = a.z + b.z; return this; }, addScalar: function(s) { this.x += s; this.y += s; this.z += s; return this; }, sub: function(v, w) { if (w !== undefined) return this.subVectors(v, w); this.x -= v.x; this.y -= v.y; this.z -= v.z; return this; }, subVectors: function(a, b) { this.x = a.x - b.x; this.y = a.y - b.y; this.z = a.z - b.z; return this; }, scalar: function(s) { this.x *= s; this.y *= s; this.z *= s; return this; }, divideScalar: function(s) { if (s !== 0) { this.x /= s; this.y /= s; this.z /= s; } else { this.set(0, 0, 0); } return this; }, negate: function() { return this.scalar(-1); }, dot: function(v) { return this.x * v.x + this.y * v.y + this.z * v.z; }, cross: function(v) { var x = this.x, y = this.y, z = this.z; this.x = y * v.z - z * v.y; this.y = z * v.x - x * v.z; this.z = x * v.y - y * v.x; return this; }, lengthSq: function() { return this.x * this.x + this.y * this.y + this.z * this.z; }, length: function() { return Math.sqrt(this.lengthSq()); }, normalize: function() { return this.divideScalar(this.length()); }, distanceTo: function(v) { return Math.sqrt(this.distanceToSquared(v)); }, crossVectors: function(a, b) { var ax = a.x, ay = a.y, az = a.z; var bx = b.x, by = b.y, bz = b.z; this.x = ay * bz - az * by; this.y = az * bx - ax * bz; this.z = ax * by - ay * bx; return this; }, // eulerFromDir: function() { // var quaternion, dir, up; // return function rotateFromDir(direction) { // if (quaternion === undefined) quaternion = new Proton.Quaternion(); // if (dir === undefined) dir = new Proton.Vector3D; // if (up === undefined) up = new Proton.Vector3D(0, 0, 1); // //quaternion.setFromUnitVectors(up, dir.copy(direction).normalize()); // console.log(quaternion.setFromUnitVectors(up, dir.copy(direction).normalize())); // this.applyQuaternion(quaternion.setFromUnitVectors(up, dir.copy(direction).normalize())); // console.log(this); // return this; // }; // }(), eulerFromDir: function(dir) { }, applyEuler: function() { var quaternion; return function applyEuler(euler) { if (quaternion === undefined) quaternion = new Proton.Quaternion(); this.applyQuaternion(quaternion.setFromEuler(euler)); return this; }; }(), applyAxisAngle: function() { var quaternion; return function applyAxisAngle(axis, angle) { if (quaternion === undefined) quaternion = new Proton.Quaternion(); this.applyQuaternion(quaternion.setFromAxisAngle(axis, angle)); return this; }; }(), applyQuaternion: function(q) { var x = this.x; var y = this.y; var z = this.z; var qx = q.x; var qy = q.y; var qz = q.z; var qw = q.w; // calculate quat * vector var ix = qw * x + qy * z - qz * y; var iy = qw * y + qz * x - qx * z; var iz = qw * z + qx * y - qy * x; var iw = -qx * x - qy * y - qz * z; // calculate result * inverse quat this.x = ix * qw + iw * -qx + iy * -qz - iz * -qy; this.y = iy * qw + iw * -qy + iz * -qx - ix * -qz; this.z = iz * qw + iw * -qz + ix * -qy - iy * -qx; return this; }, distanceToSquared: function(v) { var dx = this.x - v.x, dy = this.y - v.y, dz = this.z - v.z; return dx * dx + dy * dy + dz * dz; }, lerp: function(v, alpha) { this.x += (v.x - this.x) * alpha; this.y += (v.y - this.y) * alpha; this.z += (v.z - this.z) * alpha; return this; }, equals: function(v) { return ((v.x === this.x) && (v.y === this.y) && (v.z === this.z)); }, clear: function() { this.x = 0.0; this.y = 0.0; this.z = 0.0; return this; }, clone: function() { return new Proton.Vector3D(this.x, this.y, this.z); }, toString: function() { return "x:" + this.x + "y:" + this.y + "z:" + this.z; } }; Proton.Vector3D = Vector3D; var Polar3D = function(radius, theta, phi) { this.radius = radius || 1; this.phi = phi || 0; this.theta = theta || 0; } Polar3D.prototype = { set: function(radius, theta, phi) { this.radius = radius || 1; this.phi = phi || 0; this.theta = theta || 0; return this; }, setRadius: function(radius) { this.radius = radius; return this; }, setPhi: function(phi) { this.phi = phi; return this; }, setTheta: function(theta) { this.theta = theta; return this; }, copy: function(p) { this.radius = p.radius; this.phi = p.phi; this.theta = p.theta; return this; }, toVector3D: function() { return new Proton.Vector3D(this.getX(), this.getY(), this.getZ()); }, getX: function() { return this.radius * Math.sin(this.theta) * Math.cos(this.phi); }, getY: function() { return -this.radius * Math.sin(this.theta) * Math.sin(this.phi); }, getZ: function() { return this.radius * Math.cos(this.theta); }, normalize: function() { this.radius = 1; return this; }, equals: function(v) { return ((v.radius === this.radius) && (v.phi === this.phi) && (v.theta === this.theta)); }, clear: function() { this.radius = 0.0; this.phi = 0.0; this.theta = 0.0; return this; }, clone: function() { return new Polar3D(this.radius, this.phi, this.theta); } }; Proton.Polar3D = Polar3D; /** * Span Class. Get a random Number from a to b. Or from c-a to c+b * @param {Number|Array} a - min number * @param {Number} b - max number * @param {Number} center - the center's z value * @example * var span = new Proton.Span(0,30); * or * var span = new Proton.Span(["#fff","#ff0","#000"]); * or * var span = new Proton.Span(5,1,"center"); * @extends {Zone} * @constructor */ function Span(a, b, center) { this._isArray = false; if (Proton.Util.isArray(a)) { this._isArray = true; this.a = a; } else { this.a = Proton.Util.initValue(a, 1); this.b = Proton.Util.initValue(b, this.a); this._center = Proton.Util.initValue(center, false); } } /** * Span.getValue function * @name get a random Number from a to b. Or get a random Number from c-a to c+b * @param {number} INT or int * @return {number} a random Number */ Span.prototype = { getValue: function(INT) { if (this._isArray) { return this.a[(this.a.length * Math.random()) >> 0]; } else { if (!this._center) return Proton.MathUtils.randomAToB(this.a, this.b, INT); else return Proton.MathUtils.randomFloating(this.a, this.b, INT); } } } /** * Proton.createSpan function * @name get a instance of Span * @param {number} a min number * @param {number} b max number * @param {number} c center number * @return {number} return a instance of Span */ Proton.createSpan = function(a, b, c) { if (a instanceof Span) return a; if (b === undefined) { return new Span(a); } else { if (c === undefined) return new Span(a, b); else return new Span(a, b, c); } } Proton.Span = Span; /** * ArraySpan name get a random Color from a colors array * @param {String|Array} colors - colors array * @example * var span = new Proton.ArraySpan(["#fff","#ff0","#000"]); * or * var span = new Proton.ArraySpan("#ff0"); * @extends {Proton.Span} * @constructor */ function ArraySpan(colors) { this._arr = Proton.Util.isArray(colors) ? colors : [colors]; } Proton.Util.inherits(ArraySpan, Proton.Span); /** * getValue function * @name get a random Color * @return {string} a hex color */ ArraySpan.prototype.getValue = function() { var color = this._arr[(this._arr.length * Math.random()) >> 0]; if (color == 'random' || color == 'Random') return Proton.MathUtils.randomColor(); else return color; } /** * Proton.createArraySpan function * @name get a instance of Span * @param {number} a min number * @param {number} b max number * @param {number} c center number * @return {number} return a instance of Span */ Proton.createArraySpan = function(arr) { if (!arr) return null; if (arr instanceof Proton.ArraySpan) return arr; else return new Proton.ArraySpan(arr); } Proton.ArraySpan = ArraySpan; var Quaternion = function(x, y, z, w) { this.x = x || 0; this.y = y || 0; this.z = z || 0; this.w = (w !== undefined) ? w : 1; }; Quaternion.prototype = { set: function(x, y, z, w) { this.x = x; this.y = y; this.z = z; this.w = w; return this; }, clone: function() { return new Proton.Quaternion(this.x, this.y, this.z, this.w); }, copy: function(quaternion) { this.x = quaternion.x; this.y = quaternion.y; this.z = quaternion.z; this.w = quaternion.w; return this; }, setFromEuler: function(euler) { // http://www.mathworks.com/matlabcentral/fileexchange/ // 20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/ // content/SpinCalc.m var c1 = Math.cos(euler.x / 2); var c2 = Math.cos(euler.y / 2); var c3 = Math.cos(euler.z / 2); var s1 = Math.sin(euler.x / 2); var s2 = Math.sin(euler.y / 2); var s3 = Math.sin(euler.z / 2); this.x = s1 * c2 * c3 + c1 * s2 * s3; this.y = c1 * s2 * c3 - s1 * c2 * s3; this.z = c1 * c2 * s3 + s1 * s2 * c3; this.w = c1 * c2 * c3 - s1 * s2 * s3; return this; }, setFromAxisAngle: function(axis, angle) { // http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm // assumes axis is normalized var halfAngle = angle / 2, s = Math.sin(halfAngle); this.x = axis.x * s; this.y = axis.y * s; this.z = axis.z * s; this.w = Math.cos(halfAngle); return this; }, // setFromUnitVectors: function() { // var v1, r; // var EPS = 0.000001; // return function(vFrom, vTo) { // if (v1 === undefined) v1 = new Proton.Vector3D(); // r = vFrom.dot(vTo) + 1; // if (r < EPS) { // r = 0; // if (Math.abs(vFrom.x) > Math.abs(vFrom.z)) { // v1.set(-vFrom.y, vFrom.x, 0); // } else { // v1.set(0, -vFrom.z, vFrom.y); // } // } else { // v1.crossVectors(vFrom, vTo); // } // this.x = v1.x; // this.y = v1.y; // this.z = v1.z; // this.w = r; // return this.normalize(); // }; // }(), normalize: function() { var l = this.length(); if (l === 0) { this.x = 0; this.y = 0; this.z = 0; this.w = 1; } else { l = 1 / l; this.x *= l; this.y *= l; this.z *= l; this.w *= l; } return this; }, length: function() { return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w); }, dot: function(v) { return this.x * v.x + this.y * v.y + this.z * v.z + this.w * v.w; } }; Proton.Quaternion = Quaternion; function Box(x, y, z, w, h, d) { this.x = x; this.y = y; this.z = z; this.width = w; this.height = h; this.depth = d; this.bottom = this.y + this.height; this.right = this.x + this.width; this.right = this.x + this.width; } Box.prototype = { contains: function(x, y, z) { if ( x <= this.right && x >= this.x && y <= this.bottom && y >= this.y && z <= this.depth && z >= this.z ) return true else return false } } Proton.Box = Box; /** * The Behaviour class is the base for the other Behaviour * * @class Behaviour * @constructor */ function Behaviour(life, easing) { /** * The behaviour's id; * @property id * @type {String} id */ this.id = 'Behaviour_' + Behaviour.id++; this.life = Proton.Util.initValue(life, Infinity); /** * The behaviour's decaying trend, for example Proton.easeOutQuart; * @property easing * @type {String} * @default Proton.easeLinear */ this.easing = Proton.Util.initValue(easing, Proton.ease.setEasingByName(Proton.ease.easeLinear)); this.age = 0; this.energy = 1; /** * The behaviour is Dead; * @property dead * @type {Boolean} */ this.dead = false; /** * The behaviour name; * @property name * @type {string} */ this.name = 'Behaviour'; } Behaviour.id = 0; Behaviour.prototype = { /** * Reset this behaviour's parameters * * @method reset * @param {Number} this behaviour's life * @param {String} this behaviour's easing */ reset: function(life, easing) { this.life = Proton.Util.initValue(life, Infinity); this.easing = Proton.Util.initValue(easing, Proton.ease.setEasingByName(Proton.ease.easeLinear)); }, /** * Normalize a force by 1:100; * * @method normalizeForce * @param {Proton.Vector2D} force */ normalizeForce: function(force) { return force.scalar(Proton.MEASURE); }, /** * Normalize a value by 1:100; * * @method normalizeValue * @param {Number} value */ normalizeValue: function(value) { return value * Proton.MEASURE; }, /** * Initialize the behaviour's parameters for all particles * * @method initialize * @param {Proton.Particle} particle */ initialize: function(particle) {}, /** * Apply this behaviour for all particles every time * * @method applyBehaviour * @param {Proton.Particle} particle * @param {Number} the integrate time 1/ms * @param {Int} the particle index */ applyBehaviour: function(particle, time, index) { if (this.dead) return; this.age += time; if (this.age >= this.life) { this.energy = 0; this.dead = true; return; } var scale = this.easing(particle.age / particle.life); this.energy = Math.max(1 - scale, 0); }, /** * Destory this behaviour * @method destroy */ destroy: function() { } }; Proton.Behaviour = Behaviour; /** * The number of particles per second emission (a [particle]/b [s]); * @class Proton.Rate * @constructor * @param {Array or Number or Proton.Span} numPan the number of each emission; * @param {Array or Number or Proton.Span} timePan the time of each emission; * for example: new Proton.Rate(new Proton.Span(10, 20), new Proton.Span(.1, .25)); */ function Rate(numPan, timePan) { this.numPan = Proton.createSpan(Proton.Util.initValue(numPan, 1)); this.timePan = Proton.createSpan(Proton.Util.initValue(timePan, 1)); this.startTime = 0; this.nextTime = 0; this.init(); } Rate.prototype = { init: function() { this.startTime = 0; this.nextTime = this.timePan.getValue(); }, getValue: function(time) { this.startTime += time; if (this.startTime >= this.nextTime) { this.init(); if (this.numPan.b == 1) { if (this.numPan.getValue("Float") > 0.5) return 1; else return 0; } else { return this.numPan.getValue("Int"); } } return 0; } } Proton.Rate = Rate; function Initialize() { this.name = "Initialize"; } Initialize.prototype.reset = function() { } Initialize.prototype.init = function(emitter, particle) { if (particle) { this.initialize(particle); } else { this.initialize(emitter); } }; ///sub class init Initialize.prototype.initialize = function(target) {}; Proton.Initialize = Initialize; var InitializeUtil = { initialize: function(emitter, particle, initializes) { var i = initializes.length; while (i--) { var initialize = initializes[i]; if (initialize instanceof Proton.Initialize) initialize.init(emitter, particle); else InitializeUtil.init(emitter, particle, initialize); } InitializeUtil.bindEmitter(emitter, particle); }, //////////////////////init////////////////////// init: function(emitter, particle, initialize) { Proton.Util.setPrototypeByObj(particle, initialize); Proton.Util.setVectorByObj(particle, initialize); }, bindEmitter: function(emitter, particle) { if (emitter.bindEmitter) { particle.p.add(emitter.p); particle.v.add(emitter.v); particle.a.add(emitter.a); particle.v.applyEuler(emitter.rotation); } } } Proton.InitializeUtil = InitializeUtil; /** * Life is init particle's Life * @param {Number} a - the Life's start point * @param {Number} b - the Life's end point * @param {String} c - span's center * @example * var life = new Proton.Life(3,5); * or * var life = new Proton.Life(Infinity); * @extends {Initialize} * @constructor */ function Life(a, b, c) { Life._super_.call(this); this.lifePan = Proton.createSpan(a, b, c); } Proton.Util.inherits(Life, Proton.Initialize); Life.prototype.initialize = function(target) { if (this.lifePan.a == Infinity || this.lifePan.a == "infi") target.life = Infinity; else target.life = this.lifePan.getValue(); }; Proton.Life = Life; /** * Position is init particle's Position * @param {Zone} zone - the Position zone * @example * var Position = new Proton.Position(new Proton.PointZone(30,100,0)); * or * var Position = new Proton.Position(Infinity); * @extends {Proton.Initialize} * @constructor */ function Position() { Position._super_.call(this); this.reset.apply(this, arguments); } Proton.Util.inherits(Position, Proton.Initialize); Position.prototype.reset = function() { if (!this.zones) this.zones = []; else this.zones.length = 0; var args = Array.prototype.slice.call(arguments); this.zones = this.zones.concat(args); }; Position.prototype.addZone = function() { var args = Array.prototype.slice.call(arguments); this.zones = this.zones.concat(args); }; Position.prototype.initialize = function() { var zone; return function(target) { var zone = this.zones[(Math.random() * this.zones.length) >> 0]; zone.getPosition(); target.p.x = zone.vector.x; target.p.y = zone.vector.y; target.p.z = zone.vector.z; } }(); Proton.Position = Position; Proton.P = Position; /** * Velocity is init particle's Velocity * @param {Number} a - the Life's start point * @param {Number} b - the Life's end point * @param {String} c - span's center * @example * var life = new Proton.Life(3,5); * or * var life = new Proton.Life(Infinity); * @extends {Initialize} * @constructor */ //radius and tha function Velocity(a, b, c) { Velocity._super_.call(this); this.reset(a, b, c); this.dirVec = new Proton.Vector3D(0, 0, 0); this.name = "Velocity"; } Proton.Util.inherits(Velocity, Proton.Initialize); Velocity.prototype.reset = function(a, b, c) { //[vector,tha] if (a instanceof Proton.Vector3D) { this.radiusPan = Proton.createSpan(1); this.dir = a.clone(); this.tha = b * Proton.DR; this._useV = true; } //[polar,tha] else if (a instanceof Proton.Polar3D) { this.tha = b * Proton.DR; this.dirVec = a.toVector3D(); this._useV = false; } //[radius,vector,tha] else { this.radiusPan = Proton.createSpan(a); this.dir = b.clone().normalize(); this.tha = c * Proton.DR; this._useV = true; } }; Velocity.prototype.normalize = function(vr) { return vr * Proton.MEASURE; } Velocity.prototype.initialize = function() { var tha; var normal = new Proton.Vector3D(0, 0, 1); var v = new Proton.Vector3D(0, 0, 0); return function initialize(target) { tha = this.tha * Math.random(); this._useV && this.dirVec.copy(this.dir).scalar(this.radiusPan.getValue()); Proton.MathUtils.getNormal(this.dirVec, normal); v.copy(this.dirVec).applyAxisAngle(normal, tha); v.applyAxisAngle(this.dirVec.normalize(), Math.random() * Proton.PI * 2); //use axisRotate methods //Proton.MathUtils.axisRotate(this.v1, this.dirVec, normal, tha); //Proton.MathUtils.axisRotate(this.v2, this.v1, this.dirVec.normalize(), Math.random() * Proton.PI * 2); target.v.copy(v); return this; }; }() Proton.Velocity = Velocity; Proton.V = Velocity; /** * Mass is init particle's Mass * @param {Number} a - the Mass's start point * @param {Number} b - the Mass's end point * @param {String} c - span's center * @example * var Mass = new Proton.Mass(3,5); * or * var Mass = new Proton.Mass(Infinity); * @extends {Initialize} * @constructor */ function Mass(a, b, c) { Mass._super_.call(this); this.massPan = Proton.createSpan(a, b, c); } Proton.Util.inherits(Mass, Proton.Initialize); Mass.prototype.initialize = function(target) { target.mass = this.massPan.getValue(); }; Proton.