

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;
}

