text: 
  
  var Fireworks	= {};//////////////////////////////////////////////////////////////////////////////////
  //										//
  //////////////////////////////////////////////////////////////////////////////////
  
  Fireworks.EffectsStackBuilder	= function(emitter){
  	this._emitter	= emitter;
  };
  
  /**
   * Getter for the emitter 
  */
  Fireworks.EffectsStackBuilder.prototype.emitter	= function(){
  	return this._emitter;	
  };
  
  Fireworks.EffectsStackBuilder.prototype.back	= function(){
  	return this._emitter;
  }
  
  Fireworks.EffectsStackBuilder.prototype.createEffect	= function(name, opts){
  	var creator	= Fireworks.createEffect(name, opts).pushTo(this._emitter).back(this);
  	creator.effect().emitter(this._emitter);
  	return creator;
  }
  
  //////////////////////////////////////////////////////////////////////////////////
  //										//
  //////////////////////////////////////////////////////////////////////////////////
  
  /**
   * Basic Fireworks.Effect builder
  */
  Fireworks.createEffect	= function(name, opts){
  	// handle polymophism
  	if( typeof(name) === 'object' ){
  		opts	= name;
  		name	= undefined;
  	}
  	
  	var effect	= new Fireworks.Effect();
  	effect.opts	= opts;
  	effect.name	= name;
  	effect.back	= null;
  	var methods	= {
  		onCreate: function(val){
  			effect.onCreate	= val;
  			return methods;
  		},
  		onBirth: function(val){
  			effect.onBirth	= val;
  			return methods;
  		},
  		onUpdate: function(val){
  			effect.onUpdate	= val;
  			return methods;
  		},
  		onDeath: function(val){
  			effect.onDeath	= val;
  			return methods;
  		},
  		onPreUpdate: function(val){
  			effect.onPreUpdate	= val;
  			return methods;
  		},
  		onPreRender: function(val){
  			effect.onPreRender	= val;
  			return methods;
  		},
  		onRender: function(val){
  			effect.onRender	= val;
  			return methods;
  		},
  		onPostRender: function(val){
  			effect.onPostRender	= val;
  			return methods;
  		},
  		onIntensityChange: function(val){
  			effect.onIntensityChange= val;
  			return methods;
  		},
  		pushTo	: function(emitter){
  			emitter.effects().push(effect);
  			return methods;	
  		},
  		back	: function(value){
  			if( value === undefined )	return effect.back;	
  			effect.back	= value;
  			return methods;	
  		},
  		effect	: function(){
  			return effect;
  		}
  	}
  	return methods;
  }
  
  /**
   * An effect to apply on particles
  */
  Fireworks.Effect	= function(){
  	this._emitter	= null;
  }
  
  Fireworks.Effect.prototype.destroy	= function(){
  }
  
  /**
   * Getter/Setter for the emitter 
  */
  Fireworks.Effect.prototype.emitter	= function(value){
  	if( value === undefined )	return this._emitter;	
  	this._emitter	= value;
  	return this;	
  };
  
  /**
   * Callback called on particle creation
  */
  //Fireworks.Effect.prototype.onCreate	= function(){
  //}
  //
  /**
   * Callback called when a particle is spawning
   *
   * TODO to rename onSpawn
  */
  //Fireworks.Effect.prototype.onBirth	= function(){
  //}
  //
  //Fireworks.Effect.prototype.onDeath	= function(){
  //}
  //
  //Fireworks.Effect.prototype.onUpdate	= function(){
  //}
  
  Fireworks.createEmitter	= function(opts){
  	return new Fireworks.Emitter(opts);
  }
  
  /**
   * The emitter of particles
  */
  Fireworks.Emitter	= function(opts){
  	this._nParticles	= opts.nParticles !== undefined ? opts.nParticles : 100;
  	this._particles		= [];
  	this._effects		= [];
  	this._started		= false;
  	this._onUpdated		= null;
  	this._intensity		= 0;
  	this._maxDeltaTime	= 1/3;
  
  	this._effectsStackBuilder	= new Fireworks.EffectsStackBuilder(this)
  }
  
  Fireworks.Emitter.prototype.destroy	= function()
  {
  	this._effects.forEach(function(effect){
  		effect.destroy();
  	});
  	this._particles.forEach(function(particle){
  		particle.destroy();
  	});
  }
  
  
  //////////////////////////////////////////////////////////////////////////////////
  //		Getters								//
  //////////////////////////////////////////////////////////////////////////////////
  
  Fireworks.Emitter.prototype.effects	= function(){
  	return this._effects;
  }
  Fireworks.Emitter.prototype.effect	= function(name){
  	for(var i = 0; i < this._effects.length; i++){
  		var effect	= this._effects[i];
  		if( effect.name === name )	return effect;
  	}
  	return null;
  }
  
  Fireworks.Emitter.prototype.particles	= function(){
  	return this._particles;
  }
  Fireworks.Emitter.prototype.liveParticles	= function(){
  	return this._liveParticles;
  }
  Fireworks.Emitter.prototype.deadParticles	= function(){
  	return this._deadParticles;
  }
  Fireworks.Emitter.prototype.nParticles	= function(){
  	return this._nParticles;
  }
  
  Fireworks.Emitter.prototype.effectsStackBuilder	= function(){
  	return this._effectsStackBuilder;
  }
  
  
  /**
   * Getter/setter for intensity
  */
  Fireworks.Emitter.prototype.intensity	= function(value){
  	// if it is a getter, return value
  	if( value === undefined )	return this._intensity;
  	// if the value didnt change, return for chained api
  	if( value === this._intensity )	return this;
  	// sanity check
  	console.assert( value >= 0, 'Fireworks.Emitter.intensity: invalid value.', value);
  	console.assert( value <= 1, 'Fireworks.Emitter.intensity: invalid value.', value);
  	// backup the old value
  	var oldValue	= this._intensity;
  	// update the value
  	this._intensity	= value;
  	// notify all effects
  	this._effects.forEach(function(effect){
  		if( !effect.onIntensityChange )	return;
  		effect.onIntensityChange(this._intensity, oldValue);			
  	}.bind(this));
  	return this;	// for chained API
  }
  
  /**
   * Getter/setter for intensity
  */
  Fireworks.Emitter.prototype.maxDeltaTime	= function(value){
  	if( value === undefined )	return this._maxDeltaTime;
  	this._maxDeltaTime	= value;
  	return this;
  }
  
  //////////////////////////////////////////////////////////////////////////////////
  //		backward compatibility						//
  //////////////////////////////////////////////////////////////////////////////////
  
  Fireworks.Emitter.prototype.setParticleData	= function(particle, namespace, value){
  	particle.set(namespace, value);
  }
  
  Fireworks.Emitter.prototype.getParticleData	= function(particle, namespace){
  	return particle.get(namespace);
  }
  
  //////////////////////////////////////////////////////////////////////////////////
  //		Start function							//
  //////////////////////////////////////////////////////////////////////////////////
  
  Fireworks.Emitter.prototype.start	= function()
  {
  	console.assert( this._effects.length > 0, "At least one effect MUST be set")
  	console.assert( this._started === false );
  	
  	this._particles		= new Array(this._nParticles);
  	for(var i = 0; i < this._nParticles; i++){
  		this._particles[i]	= new Fireworks.Particle();
  	}
  
  	this._liveParticles	= [];
  	this._deadParticles	= this._particles.slice(0);
  	this._started		= true;
  
  	// onCreate on all particles
  	this._effects.forEach(function(effect){
  		if( !effect.onCreate )	return;
  		this._particles.forEach(function(particle, particleIdx){
  			effect.onCreate(particle, particleIdx);			
  		})
  	}.bind(this));
  	// set the intensity to 1
  	this.intensity(1)
  
  	return this;	// for chained API
  }
  
  Fireworks.Emitter.prototype.update	= function(deltaTime){
  	// bound the deltaTime to this._maxDeltaTime
  	deltaTime	= Math.min(this._maxDeltaTime, deltaTime)
  	// honor effect.onPreUpdate
  	this._effects.forEach(function(effect){
  		if( !effect.onPreUpdate )	return;
  		effect.onPreUpdate(deltaTime);			
  	}.bind(this));
  	// update each particles
  	this._effects.forEach(function(effect){
  		if( !effect.onUpdate )	return;
  		this._liveParticles.forEach(function(particle){
  			effect.onUpdate(particle, deltaTime);			
  		})
  	}.bind(this));
  	return this;	// for chained API
  }
  
  Fireworks.Emitter.prototype.render	= function(){
  	this._effects.forEach(function(effect){
  		if( !effect.onPreRender )	return;
  		effect.onPreRender();			
  	}.bind(this));
  	this._effects.forEach(function(effect){
  		if( !effect.onRender )	return;
  		this._liveParticles.forEach(function(particle){
  			effect.onRender(particle);			
  		})
  	}.bind(this));
  	this._effects.forEach(function(effect){
  		if( !effect.onPostRender )	return;
  		effect.onPostRender();			
  	}.bind(this));
  	return this;	// for chained API
  }
  
  //////////////////////////////////////////////////////////////////////////////////
  //										//
  //////////////////////////////////////////////////////////////////////////////////
  
  /**
   * kill this particle
  */
  Fireworks.Emitter.prototype.killParticle	= function(particle)
  {
  	var idx	= this._liveParticles.indexOf(particle);
  	console.assert( idx !== -1 )
  	this._liveParticles.splice(idx, 1)
  	this._deadParticles.push(particle);
  	// do the death on all effects
  	this.effects().forEach(function(effect){
  		effect.onDeath && effect.onDeath(particle);			
  	}.bind(this));
  }
  
  /**
   * Spawn a particle
  */
  Fireworks.Emitter.prototype.spawnParticle	= function(){
  	console.assert(this._deadParticles.length >= 1, 'no more particle available' );
  	// change the particles 
  	var particle	= this.deadParticles().pop();
  	this.liveParticles().push(particle);
  	// do the birth on all effects
  	this.effects().forEach(function(effect){
  		effect.onBirth && effect.onBirth(particle);			
  	}.bind(this));
  }
  Fireworks.createLinearGradient	= function(opts){
  	var LinearGradient	= new Fireworks.LinearGradient(opts);
  	return LinearGradient;
  }
  
  /**
   * The emitter of particles
  */
  Fireworks.LinearGradient	= function(opts){
  	this._keyPoints	= [];
  }
  
  Fireworks.LinearGradient.prototype.push	= function(x, y){
  	this._keyPoints.push({
  		x	: x,
  		y	: y
  	});
  	return this;
  }
  
  /**
   * Compute a value for this LinearGradient
  */
  Fireworks.LinearGradient.prototype.get	= function(x){
  	for( var i = 0; i < this._keyPoints.length; i++ ){
  		var keyPoint	= this._keyPoints[i];
  		if( x <= keyPoint.x )	break;
  	}
  
  	if( i === 0 )	return this._keyPoints[0].y;
  
  	console.assert(i < this._keyPoints.length )
  
  	var prev	= this._keyPoints[i-1];
  	var next	= this._keyPoints[i];
  	
  	var ratio	= (x - prev.x) / (next.x - prev.x)
  	var y		= prev.y + ratio * (next.y - prev.y)
  	
  	return y;
  };
  /**
   * The emitter of particles
  */
  Fireworks.Particle	= function(){
  }
  
  Fireworks.Particle.prototype.set	= function(key, value){
  	console.assert( this[key] === undefined, "key already defined: "+key );
  	this[key]	= value;
  	return this[key];
  }
  
  Fireworks.Particle.prototype.get	= function(key){
  	console.assert( this[key] !== undefined, "key undefined: "+key );
  	return this[key];
  }
  
  Fireworks.Particle.prototype.has	= function(key){
  	return this[key] !== undefined	? true : false;
  }
  Fireworks.Shape	= function(){
  }
  
  ///**
  // * @param {Fireworks.Vector} point the point coordinate to test
  // * @returns {Boolean} true if point is inside the shape, false otherwise
  //*/
  //Fireworks.Shape.prototype.contains	= function(point){
  //}
  //
  ///**
  // * generate a random point contained in this shape
  // * @returns {Fireworks.Vector} the just generated random point
  //*/
  //Firefly.Shape.prototype.randomPoint	= function(vector){
  //}
  Fireworks.createVector = function(x, y, z){
  	return new Fireworks.Vector(x,y,z);
  };
  
  /**
   * jme- copy of THREE.Vector3 https://github.com/mrdoob/three.js/blob/master/src/core/Vector3.js
   *
   * @author mr.doob / http://mrdoob.com/
   * @author kile / http://kile.stravaganza.org/
   * @author philogb / http://blog.thejit.org/
   * @author mikael emtinger / http://gomo.se/
   * @author egraether / http://egraether.com/
   */
  
  Fireworks.Vector = function ( x, y, z ) {
  
  	this.x = x || 0;
  	this.y = y || 0;
  	this.z = z || 0;
  
  };
  
  
  Fireworks.Vector.prototype = {
  
  	constructor: Fireworks.Vector,
  
  	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;
  
  	},
  	
  	random	: function( ) {
  		this.x	= Math.random() - 0.5;
  		this.y	= Math.random() - 0.5;
  		this.z	= Math.random() - 0.5;
  		return this;
  	}, // jme - added
  	
  	toString	: function(){
  		return JSON.stringify(this);
  	}, // jme - added
  
  	copy: function ( v ) {
  
  		this.x = v.x;
  		this.y = v.y;
  		this.z = v.z;
  
  		return this;
  
  	},
  
  	add: function ( a, b ) {
  
  		this.x = a.x + b.x;
  		this.y = a.y + b.y;
  		this.z = a.z + b.z;
  
  		return this;
  
  	},
  
  	addSelf: function ( v ) {
  
  		this.x += v.x;
  		this.y += v.y;
  		this.z += v.z;
  
  		return this;
  
  	},
  
  	addScalar: function ( s ) {
  
  		this.x += s;
  		this.y += s;
  		this.z += s;
  
  		return this;
  
  	},
  
  	sub: function ( a, b ) {
  
  		this.x = a.x - b.x;
  		this.y = a.y - b.y;
  		this.z = a.z - b.z;
  
  		return this;
  
  	},
  
  	subSelf: function ( v ) {
  
  		this.x -= v.x;
  		this.y -= v.y;
  		this.z -= v.z;
  
  		return this;
  
  	},
  
  	multiply: function ( a, b ) {
  
  		this.x = a.x * b.x;
  		this.y = a.y * b.y;
  		this.z = a.z * b.z;
  
  		return this;
  
  	},
  
  	multiplySelf: function ( v ) {
  
  		this.x *= v.x;
  		this.y *= v.y;
  		this.z *= v.z;
  
  		return this;
  
  	},
  
  	multiplyScalar: function ( s ) {
  
  		this.x *= s;
  		this.y *= s;
  		this.z *= s;
  
  		return this;
  
  	},
  
  	divideSelf: function ( v ) {
  
  		this.x /= v.x;
  		this.y /= v.y;
  		this.z /= v.z;
  
  		return this;
  
  	},
  
  	divideScalar: function ( s ) {
  
  		if ( s ) {
  
  			this.x /= s;
  			this.y /= s;
  			this.z /= s;
  
  		} else {
  
  			this.x = 0;
  			this.y = 0;
  			this.z = 0;
  
  		}
  
  		return this;
  
  	},
  
  
  	negate: function() {
  
  		return this.multiplyScalar( - 1 );
  
  	},
  
  	dot: function ( v ) {
  
  		return this.x * v.x + this.y * v.y + this.z * v.z;
  
  	},
  
  	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() );
  
  	},
  
  	setLength: function ( l ) {
  
  		return this.normalize().multiplyScalar( l );
  
  	},
  	
  	cross: function ( a, b ) {
  
  		this.x = a.y * b.z - a.z * b.y;
  		this.y = a.z * b.x - a.x * b.z;
  		this.z = a.x * b.y - a.y * b.x;
  
  		return this;
  
  	},
  
  	crossSelf: 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;
  
  	},
  
  	distanceTo: function ( v ) {
  
  		return Math.sqrt( this.distanceToSquared( v ) );
  
  	},
  
  	distanceToSquared: function ( v ) {
  
  		return new Fireworks.Vector().sub( this, v ).lengthSq();
  
  	},
  
  	equals: function ( v ) {
  
  		return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) );
  
  	},
  
  	isZero: function () {
  
  		return ( this.lengthSq() < 0.0001 /* almostZero */ );
  
  	},
  
  	clone: function () {
  
  		return new Fireworks.Vector( this.x, this.y, this.z );
  
  	}
  
  };
  /**
   * Shortcut to create Fireworks.EffectRandomDriftVelocity
  */
  Fireworks.EffectsStackBuilder.prototype.acceleration	= function(opts)
  {
  	opts		= opts		|| {};
  	var effectId	= opts.effectId	|| 'acceleration';
  	console.assert( opts.shape instanceof Fireworks.Shape );
  	// create the effect itself
  	Fireworks.createEffect(effectId, {
  		shape	: opts.shape
  	}).onCreate(function(particle){
  		particle.set('acceleration', {
  			vector	: new Fireworks.Vector()
  		});
  	}).onBirth(function(particle){
  		var acceleration= particle.get('acceleration').vector;
  		this.opts.shape.randomPoint(acceleration)
  	}).onUpdate(function(particle, deltaTime){
  		var velocity	= particle.get('velocity').vector;
  		var acceleration= particle.get('acceleration').vector;
  		velocity.x	+= acceleration.x * deltaTime;
  		velocity.y	+= acceleration.y * deltaTime;
  		velocity.z	+= acceleration.z * deltaTime;
  	}).pushTo(this._emitter);
  
  	return this;	// for chained API
  };
  /**
   * Handle the friction - aka a value between 0 and 1 which multiply the velocity
   *
   * @param {Number} value the friction number between 0 and 1
  */
  Fireworks.EffectsStackBuilder.prototype.friction	= function(value)
  {
  	// handle parameter polymorphism
  	value	= value !== undefined ? value	: 1;
  	// sanity check
  	console.assert( value >= 0 && value <= 1.0 );
  	// create the effect itself
  	Fireworks.createEffect('friction')
  	.onCreate(function(particle, particleIdx){
  		particle.set('friction', {
  			value	: value
  		});
  	})
  	.onBirth(function(particle){
  		var data	= particle.get('friction');
  		data.value	= value
  	})
  	.onUpdate(function(particle){
  		var friction	= particle.get('friction').value;
  		var velocity	= particle.get('velocity').vector;
  		velocity.multiplyScalar(friction);
  	})
  	.pushTo(this._emitter);
  	// return this for chained API
  	return this;
  };
  /**
   * Shortcut to create Fireworks.EffectRandomDriftVelocity
  */
  Fireworks.EffectsStackBuilder.prototype.lifeTime	= function(minAge, maxAge)
  {	
  	// sanity check
  	console.assert( minAge !== undefined )
  	// if maxAge isnt 
  	if( maxAge === undefined )	maxAge	= minAge;
  	console.assert( maxAge !== undefined )
  	// create the effect itself
  	var emitter	= this._emitter;
  	Fireworks.createEffect('lifeTime', {
  		minAge	: minAge,
  		maxAge	: maxAge
  	}).onCreate(function(particle){
  		var data	= particle.set('lifeTime', {
  			curAge	: 0,
  			minAge	: 0,
  			maxAge	: 0,
  			normalizedAge	: function(){
  				return (data.curAge - data.minAge) / (data.maxAge - data.minAge);
  			}
  		});
  	}).onBirth(function(particle){
  		var lifeTime	= particle.get('lifeTime');
  		lifeTime.curAge	= 0;
  		lifeTime.maxAge	= this.opts.minAge + Math.random()*(this.opts.maxAge-this.opts.minAge);
  	}).onUpdate(function(particle, deltaTime){
  		var lifeTime	= particle.get('lifeTime');
  		lifeTime.curAge	+= deltaTime;
  		if( lifeTime.curAge > lifeTime.maxAge )	emitter.killParticle(particle);
  	}).pushTo(this._emitter);
  	// return this for chained API
  	return this;
  };
  /**
   * Shortcut to create Fireworks.EffectRandomDriftVelocity
  */
  Fireworks.EffectsStackBuilder.prototype.position	= function(shape)
  {
  	console.assert( shape instanceof Fireworks.Shape );
  	Fireworks.createEffect('position', {
  		shape	: shape
  	}).onCreate(function(particle){
  		particle.set('position', {
  			vector	: new Fireworks.Vector()
  		});
  	}).onBirth(function(particle){
  		var position	= particle.get('position').vector;
  		this.opts.shape.randomPoint(position)
  	}).pushTo(this._emitter);
  	return this;	// for chained API
  };
  /**
   * Shortcut to create Fireworks.EffectRandomDriftVelocity
  */
  Fireworks.EffectsStackBuilder.prototype.radialVelocity	= function(minSpeed, maxSpeed)
  {
  	Fireworks.createEffect('radialVelocity', {
  		minSpeed	: minSpeed,
  		maxSpeed	: maxSpeed	
  	}).onCreate(function(particle){
  		particle.set('velocity', {
  			vector	: new Fireworks.Vector()
  		});
  	}).onBirth(function(particle, deltaTime){
  		var position	= particle.get('position').vector;
  		var velocity	= particle.get('velocity').vector;
  		var length	= this.opts.minSpeed + (this.opts.maxSpeed - this.opts.minSpeed)*Math.random();
  		velocity.copy(position).setLength(length);
  	}).onUpdate(function(particle, deltaTime){
  		var position	= particle.get('position').vector;
  		var velocity	= particle.get('velocity').vector;
  		position.x	+= velocity.x * deltaTime;
  		position.y	+= velocity.y * deltaTime;
  		position.z	+= velocity.z * deltaTime;
  	}).pushTo(this._emitter);
  
  	return this;	// for chained API
  };
  /**
   * Shortcut to create Fireworks.EffectRandomDriftVelocity
  */
  Fireworks.EffectsStackBuilder.prototype.randomVelocityDrift	= function(drift)
  {
  	// create the effect itself
  	Fireworks.createEffect('randomVelocityDrift', {
  		drift	: drift
  	}).onUpdate(function(particle, deltaTime){
  		var velocity	= particle.get('velocity').vector;
  		velocity.x	+= (Math.random()*2 - 1) * this.opts.drift.x * deltaTime;
  		velocity.y	+= (Math.random()*2 - 1) * this.opts.drift.y * deltaTime;
  		velocity.z	+= (Math.random()*2 - 1) * this.opts.drift.z * deltaTime;
  	}).pushTo(this._emitter);
  	// return for chained API
  	return this;	// for chained API
  };
  /**
   * Create a velocity effect
   * @param {Fireworks.Shape}	shape	set the direction of the velocity by a randompoint in this shape
   * @param {Number?}		speed	set the speed itself. if undefined, keep randompoint length for speed
  */
  Fireworks.EffectsStackBuilder.prototype.velocity	= function(shape, speed)
  {
  	Fireworks.createEffect('velocity', {
  		shape	: shape, 
  		speed	: speed !== undefined ? speed : -1
  	}).onCreate(function(particle){
  		particle.set('velocity', {
  			vector	: new Fireworks.Vector()
  		});
  	}).onBirth(function(particle){
  		var velocity	= particle.get('velocity').vector;
  		this.opts.shape.randomPoint(velocity)
  		if( this.opts.speed !== -1 )	velocity.setLength(this.opts.speed);
  	}).onUpdate(function(particle, deltaTime){
  		var position	= particle.get('position').vector;
  		var velocity	= particle.get('velocity').vector;
  		position.x	+= velocity.x * deltaTime;
  		position.y	+= velocity.y * deltaTime;
  		position.z	+= velocity.z * deltaTime;
  	}).pushTo(this._emitter);
  
  	return this;	// for chained API
  };
  /**
   * Shortcut to create Fireworks.Shape.Box
  */
  Fireworks.createShapeBox	= function(centerX, centerY, centerZ, sizeX, sizeY, sizeZ){
  	var center	= new Fireworks.Vector(centerX, centerY, centerZ);
  	var size	= new Fireworks.Vector(sizeX, sizeY, sizeZ);
  	return new Fireworks.Shape.Box(center, size);
  };
  
  /**
   * Handle a Firework.Shape forming a sphere
   *
   * @param {Fireworks.Vector} center the center of the sphape
   * @param {Fireworks.Vector} shape the size of the shape
  */
  Fireworks.Shape.Box	= function(center, size)
  {
  	this.center	= center;
  	this.size	= size;
  	this._vector	= new Fireworks.Vector();
  }
  
  // inherit from Fireworks.Effect
  Fireworks.Shape.Box.prototype = new Fireworks.Shape();
  Fireworks.Shape.Box.prototype.constructor = Fireworks.Shape.Box;
  
  Fireworks.Shape.Box.prototype.contains	= function(point){
  	// compute delta between the point and the center
  	var delta	= this._vector.sub(point, this.center);
  	// test the delta is too far
  	if( Math.abs(delta.x) > this.size.x/2 )	return false;
  	if( Math.abs(delta.y) > this.size.y/2 )	return false;
  	if( Math.abs(delta.z) > this.size.z/2 )	return false;
  	// if all tests, passed true
  	return true;
  }
  
  Fireworks.Shape.Box.prototype.randomPoint	= function(vector){
  	var point	= vector	|| this._vector;
  	// get a random point
  	point.x	= Math.random() * this.size.x - this.size.x/2;
  	point.y	= Math.random() * this.size.y - this.size.y/2;
  	point.z	= Math.random() * this.size.z - this.size.z/2;
  	// add this.center
  	point.addSelf(this.center);
  	// return the point
  	return point;
  }
  /**
   * Shortcut to create Fireworks.Shape.Point
  */
  Fireworks.createShapePoint	= function(positionX, positionY, positionZ){
  	var position	= new Fireworks.Vector(positionX, positionY, positionZ);
  	return new Fireworks.Shape.Point(position);
  };
  
  /**
   * Handle a Firework.Shape forming a point
   *
   * @param {Fireworks.Vector} position the position of the point
  */
  Fireworks.Shape.Point	= function(position)
  {
  	this.position	= position;
  	this._vector	= new Fireworks.Vector();
  }
  
  // inherit from Fireworks.Effect
  Fireworks.Shape.Point.prototype = new Fireworks.Shape();
  Fireworks.Shape.Point.prototype.constructor = Fireworks.Shape.Point;
  
  Fireworks.Shape.Point.prototype.contains	= function(point){
  	if( point.x !== this.position.x )	return false;
  	if( point.y !== this.position.y )	return false;
  	if( point.z !== this.position.z )	return false;
  	// if all tests, passed true
  	return true;
  }
  
  Fireworks.Shape.Point.prototype.randomPoint	= function(vector){
  	var point	= vector	|| this._vector;
  	// get a random point
  	point.copy(this.position);
  	// return the point
  	return point;
  }
  /**
   * Shortcut to create Fireworks.Shape.Box
  */
  Fireworks.createShapeSphere	= function(centerX, centerY, centerZ, radius, boundingbox){
  	var center	= new Fireworks.Vector(centerX, centerY, centerZ);
  	return new Fireworks.ShapeSphere(center, radius);
  };
  
  
  /**
   * Handle a Firework.Shape forming a sphere
   *
   * @param {Fireworks.Vector} center the center of the sphere
   * @param {Number} radius the radius of the sphere
  */
  Fireworks.ShapeSphere	= function(center, radius)
  {
  	this.center	= center;
  	this.radius	= radius;
  	this._vector	= new Fireworks.Vector();
  }
  
  // inherit from Fireworks.Effect
  Fireworks.ShapeSphere.prototype = new Fireworks.Shape();
  Fireworks.ShapeSphere.prototype.constructor = Fireworks.ShapeSphere;
  
  Fireworks.ShapeSphere.prototype.contains	= function(point){
  	// compute distance between the point and the center
  	var distance	= this._vector.sub(point, this.center).length();
  	// return true if this distance is <= than sphere radius
  	return distance <= this.radius;
  }
  
  Fireworks.ShapeSphere.prototype.randomPoint	= function(vector){
  	var point	= vector	|| this._vector;
  	// get a random point
  	point.x	= Math.random()-0.5;
  	point.y	= Math.random()-0.5;
  	point.z	= Math.random()-0.5;
  	// compute the length between the point 
  	var length	= Math.random()*this.radius;
  	// set the point at the proper distance;
  	point.setLength( length );
  	// add the center
  	point.addSelf(this.center);
  	// return the point
  	return point;
  }
  /**
   * Spawner deliverying paricles in one shot
   * 
   * @param {Number?} the number of particle to emit
  */
  Fireworks.EffectsStackBuilder.prototype.spawnerOneShot	= function(nParticles)
  {
  	// handle parameter polymorphism
  	nParticles	= nParticles	|| this.emitter().nParticles();
  	// define local variables
  	var emitter	= this.emitter();
  	var nSent	= 0;
  	var spawning	= true;
  
  	// create the effect itself
  	Fireworks.createEffect('spawner', {
  		start	: function(){ spawning = true;	},
  		stop	: function(){ spawning = false;	}
  	}).onPreUpdate(function(deltaTime){
  		// if spawning is false, do nothing
  		if( spawning === false )	return;
  		// if already completed, do nothing
  		if( nParticles === nSent )	return;
  		// spawn each particle
  		var amount	= nParticles - nSent;
  		amount		= Math.min(amount, emitter.deadParticles().length);
  		for(var i = 0; i < amount; i++){
  			emitter.spawnParticle();
  		}
  		// update the amount of sent particles
  		nSent	+= amount;
  	}).pushTo(this._emitter);
  	// return this for chained API
  	return this;
  };
  /**
   * Spawner deliverying paricles at a steady rate
   * 
   * @param {Number?} rate the rate at which it gonna emit
  */
  Fireworks.EffectsStackBuilder.prototype.spawnerSteadyRate	= function(rate)
  {
  	// handle parameter polymorphism
  	rate	= rate !== undefined ? rate	: 1;
  	// define local variables
  	var emitter	= this.emitter();
  	var nToCreate	= 1;
  	var spawning	= true;
  	
  	// create the effect itself
  	Fireworks.createEffect('spawner', {
  		rate	: rate,
  		start	: function(){ spawning = true;	},
  		stop	: function(){ spawning = false;	}
  	}).onPreUpdate(function(deltaTime){
  		var rate	= this.opts.rate;
  		// if spawning is false, do nothing
  		if( spawning === false )	return;
  		// update nToCreate
  		nToCreate	+= rate * deltaTime;
  		// nParticles is the interger part of nToCreate as you spawn them one by one
  		var nParticles	= Math.floor(nToCreate);
  		// dont spawn more particles than available
  		// TODO here estimate how much more is needed to never lack of it
  		nParticles	= Math.min(nParticles, emitter.deadParticles().length);
  		// update nToCreate
  		nToCreate	-= nParticles;
  		// spawn each particle
  		for(var i = 0; i < nParticles; i++){
  			emitter.spawnParticle();
  		}
  	}).pushTo(this._emitter);
  	// return this for chained API
  	return this;
  };
  /**
   * render to canvas
  */
  Fireworks.EffectsStackBuilder.prototype.renderToCanvas	= function(opts)
  {
  	opts	= opts		|| {};
  	var ctx	= opts.ctx	|| buildDefaultContext();
  	// create the effect itself
  	var effect	= Fireworks.createEffect('renderToCanvas', {
  		ctx	: ctx
  	}).pushTo(this._emitter);
  
  
  	if( opts.type === 'arc' )		ctorTypeArc(effect);
  	else if( opts.type === 'drawImage' )	ctorTypeDrawImage(effect);
  	else{
  		console.assert(false, 'renderToCanvas opts.type is invalid: ');
  	}
  
  	return this;	// for chained API
  	
  
  	function buildDefaultContext(){
  		// build canvas element
  		var canvas	= document.createElement('canvas');
  		canvas.width	= window.innerWidth;
  		canvas.height	= window.innerHeight;
  		document.body.appendChild(canvas);
  		// canvas.style
  		canvas.style.position	= "absolute";
  		canvas.style.left	= 0;
  		canvas.style.top	= 0;
  		// setup ctx
  		var ctx		= canvas.getContext('2d');
  		// return ctx
  		return ctx;
  	}
  
  	function ctorTypeArc(){
  		return effect.onCreate(function(particle, particleIdx){
  			particle.set('renderToCanvas', {
  				size	: 3
  			});
  		}).onRender(function(particle){
  			var position	= particle.get('position').vector;
  			var size	= particle.get('renderToCanvas').size;
  
  			ctx.beginPath();
  			ctx.arc(position.x + canvas.width /2, 
  				position.y + canvas.height/2, size, 0, Math.PI*2, true); 
  			ctx.fill();					
  		});
  	};
  	function ctorTypeDrawImage(){
  		// handle parameter polymorphism
  		if( typeof(opts.image) === 'string' ){
  			var images	= [new Image];
  			images[0].src	= opts.image;
  		}else if( opts.image instanceof Image ){
  			var images	= [opts.image];
  		}else if( opts.image instanceof Array ){
  			var images	= opts.image;
  		}else	console.assert(false, 'invalid .renderToCanvas() options')
  
  		return effect.onCreate(function(particle, particleIdx){
  			particle.set('renderToCanvas', {
  				scale		: 1,	// should that be there ? or in its own effect ?
  				opacity		: 1,	// should that be there ? or in its own effect ?
  				rotation	: 0*Math.PI
  			});
  		}).onRender(function(particle){
  			var position	= particle.get('position').vector;
  			var data	= particle.get('renderToCanvas');
  			var canonAge	= particle.get('lifeTime').normalizedAge();
  			var imageIdx	= Math.floor(canonAge * images.length);
  			var image	= images[imageIdx];
  			// save the context
  			ctx.save();
  			// translate in canvas's center, and the particle position
  			ctx.translate(position.x + canvas.width/2, position.y + canvas.height/2);
  			// set the scale of this particles
  			ctx.scale(data.scale, data.scale);
  			// set the rotation
  			ctx.rotate(data.rotation);
  			// set ctx.globalAlpha
  			ctx.globalAlpha	= data.opacity; 
  			// draw the image itself
  			if( image instanceof Image ){
  				ctx.drawImage(image, -image.width/2, -image.height/2);			
  			}else if( typeof(image) === 'object' ){
  				ctx.drawImage(image.image
  				 	,  image.offsetX,  image.offsetY , image.width, image.height
  					, -image.width/2, -image.height/2, image.width, image.height);
  			}else	console.assert(false);
  			// restore the context
  			ctx.restore();
  		});
  	};
  };
  /**
   * render to three.js THREE.Object3D
   * If i play with object3D.visible true/false instead of Object3D.add/remove
   * i got a lot of artefacts
  */
  Fireworks.EffectsStackBuilder.prototype.renderToThreejsObject3D	= function(opts)
  {
  	var effectId	= opts.effectId	|| 'renderToThreeParticleSystem';
  	var container	= opts.container;
  
  
  	// create the effect itself
  	Fireworks.createEffect(effectId)
  	.onCreate(function(particle, particleIdx){
  		particle.set('threejsObject3D', {
  			object3d	: opts.create()
  		});
  		console.assert(particle.get('threejsObject3D').object3d instanceof THREE.Object3D);
  		
  		var object3d	= particle.get('threejsObject3D').object3d;
  
  //		object3d.visible= false;
  	}).onBirth(function(particle){
  		var object3d	= particle.get('threejsObject3D').object3d;
  //		object3d.visible= true;
  container.add(object3d);
  	}).onDeath(function(particle){
  		var object3d	= particle.get('threejsObject3D').object3d;
  //		object3d.visible= false;
  container.remove(object3d);
  	}).onRender(function(particle){
  		var object3d	= particle.get('threejsObject3D').object3d;
  		var position	= particle.get('position').vector;
  		object3d.position.set(position.x, position.y, position.z);
  	}).pushTo(this._emitter);
  	return this;	// for chained API
  };
  /**
   * render to three.js to THREE.ParticleSystem
  */
  Fireworks.EffectsStackBuilder.prototype.renderToThreejsParticleSystem	= function(opts)
  {
  	opts			= opts			|| {};
  	var effectId		= opts.effectId		|| 'renderToThreejsParticleSystem';
  	var particleSystem	= opts.particleSystem	|| defaultParticleSystem;
  	// if opts.particleSystem is a function, call it to create the particleSystem
  	if( typeof(particleSystem) === 'function' )	particleSystem	= particleSystem(this._emitter);
  	// sanity check
  	console.assert(particleSystem instanceof THREE.ParticleSystem, "particleSystem MUST be THREE.ParticleSystem");
  	// some aliases
  	var geometry	= particleSystem.geometry;
  	console.assert(geometry.vertices.length >= this._emitter.nParticles())
  	// create the effect itself
  	Fireworks.createEffect(effectId, {
  		particleSystem	: particleSystem
  	}).onCreate(function(particle, particleIdx){
  		particle.set('threejsParticle', {
  			particleIdx	: particleIdx
  		});
  		var vertex	= geometry.vertices[particleIdx];
  		vertex.set(Infinity, Infinity, Infinity);
  	}).onDeath(function(particle){
  		var particleIdx	= particle.get('threejsParticle').particleIdx;
  		var vertex	= geometry.vertices[particleIdx];
  		vertex.set(Infinity, Infinity, Infinity);
  	}).onRender(function(particle){
  		var particleIdx	= particle.get('threejsParticle').particleIdx;
  		var vertex	= geometry.vertices[particleIdx];
  		var position	= particle.get('position').vector;
  		vertex.set(position.x, position.y, position.z);
  	}).pushTo(this._emitter);
  	return this;	// for chained API
  
  	//////////////////////////////////////////////////////////////////////////
  	//		Internal Functions					//
  	//////////////////////////////////////////////////////////////////////////
  
  	function defaultParticleSystem(emitter){
  		var geometry	= new THREE.Geometry();
  		for( var i = 0; i < emitter.nParticles(); i++ ){
  			geometry.vertices.push( new THREE.Vector3() );
  		}
  		var material	= new THREE.ParticleBasicMaterial({
  			size		: 5,
  			sizeAttenuation	: true,
  			color		: 0xE01B6A,
  			map		: generateTexture(),
  			blending	: THREE.AdditiveBlending,
  			depthWrite	: false,
  			transparent	: true
  		});
  		var particleSystem		= new THREE.ParticleSystem(geometry, material);
  		particleSystem.dynamic		= true;
  		particleSystem.sortParticles	= true;
  		return particleSystem;
  	}
  
  	function generateTexture(size){
  		size		= size || 128;
  		var canvas	= document.createElement( 'canvas' );
  		var context	= canvas.getContext( '2d' );
  		canvas.width	= canvas.height	= size;
  
  		var gradient	= context.createRadialGradient( canvas.width/2, canvas.height /2, 0, canvas.width /2, canvas.height /2, canvas.width /2 );		
  		gradient.addColorStop( 0  , 'rgba(255,255,255,1)' );
  		gradient.addColorStop( 0.5, 'rgba(255,255,255,1)' );
  		gradient.addColorStop( 0.7, 'rgba(128,128,128,1)' );
  		gradient.addColorStop( 1  , 'rgba(0,0,0,1)' );
  
  		context.beginPath();
  		context.arc(size/2, size/2, size/2, 0, Math.PI*2, false);
  		context.closePath();
  
  		context.fillStyle	= gradient;
  		//context.fillStyle	= 'rgba(128,128,128,1)';
  		context.fill();
  
  		var texture	= new THREE.Texture( canvas );
  		texture.needsUpdate = true;
  
  		return texture;
  	}
  };
  
  Fireworks.Emitter.prototype.bindTriggerDomEvents	= function(domElement){
  	var tmp	= new Fireworks.BindTriggerDomEvents(this, domElement);
  	return this;	// for chained API
  }
  
  Fireworks.BindTriggerDomEvents	= function(emitter, domElement){
  	this._domElement= domElement	|| document.body;
  
  	// bind mouse event
  	this._onMouseDown	= function(){ emitter.effect('spawner').opts.start();	};
  	this._onMouseUp		= function(){ emitter.effect('spawner').opts.stop();	};
  	this._domElement.addEventListener('mousedown'	, this._onMouseDown	);
  	this._domElement.addEventListener('mouseup'	, this._onMouseUp	);
  
  	// change emitter intensity on mousewheel		
  	this._onMouseWheel	= function(event){
  		var intensity	= emitter.intensity();
  		intensity	+= event.wheelDelta < 0 ? -0.05 : 0.05;
  		intensity	= Math.max(intensity, 0);
  		intensity	= Math.min(intensity, 1);
  		emitter.intensity(intensity);
  	};
  	this._domElement.addEventListener('mousewheel'	, this._onMouseWheel	);
  }
  
  
  Fireworks.BindTriggerDomEvents.prototype.destroy	= function(){
  	this._domElement.removeEventListener('mousedown'	, this._onMouseDown	);
  	this._domElement.removeEventListener('mouseup'		, this._onMouseUp	);
  	this._domElement.removeEventListener('mousewheel'	, this._onMouseWheel	);
  };
  Fireworks.DatGui4Emitter	= function(emitter){
  	var gui		= new dat.GUI();
  	var effects	= emitter.effects();
  	effects.forEach(function(effect, idx){
  		var effectName	= effect.name	|| "effect-"+idx;
  		var opts	= effect.opts	|| {};
  		var keys	= Object.keys(opts).filter(function(key){
  			if( opts[key] instanceof Fireworks.Vector )	return true;
  			if( typeof(opts[key]) === 'object' )		return false;
  			return true;
  		});
  		if( keys.length ){
  			var folder	= gui.addFolder('Effect: '+effectName);
  			keys.forEach(function(key){
  				if( opts[key] instanceof Fireworks.Vector ){
  					folder.add(opts[key], 'x').name(key+"X");
  					folder.add(opts[key], 'y').name(key+"Y");
  					folder.add(opts[key], 'z').name(key+"Z");
  				}else{
  					folder.add(opts, key);
  				}
  			});
  		}
  	});
  	// return the built gui
  	return gui;
  };Fireworks.ProceduralTextures	= {};
  
  Fireworks.ProceduralTextures.buildTexture	= function(size)
  {
  	size		= size || 150;
  	var canvas	= document.createElement( 'canvas' );
  	var context	= canvas.getContext( '2d' );
  	canvas.width	= canvas.height	= size;
  
  	var gradient	= context.createRadialGradient( canvas.width/2, canvas.height /2, 0, canvas.width /2, canvas.height /2, canvas.width /2 );		
  	gradient.addColorStop( 0  , 'rgba(255,255,255,1)' );
  	gradient.addColorStop( 0.5, 'rgba(255,255,255,1)' );
  	gradient.addColorStop( 0.7, 'rgba(128,128,128,1)' );
  	gradient.addColorStop( 1  , 'rgba(0,0,0,1)' );
  
  	context.beginPath();
  	context.arc(size/2, size/2, size/2, 0, Math.PI*2, false);
  	context.closePath();
  
  	context.fillStyle	= gradient;
  	//context.fillStyle	= 'rgba(128,128,128,1)';
  	context.fill();
  
  	var texture	= new THREE.Texture( canvas );
  	texture.needsUpdate = true;
  
  	return texture;
  }
  
  