three.proton
Version:
three.Proton is an easily customizable html5 particle engine for three.js
1,811 lines (1,445 loc) • 114 kB
JavaScript
/*!
* 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.