Jump To …

threex.sparks.js

This THREEx helper makes it even easier to use spark.js with three.js * FIXME This is currently only with WebGL

Code

var THREEx	= THREEx 	|| {};


THREEx.Sparks	= function(opts)
{
	opts		= opts	|| {};
	this._maxParticles = opts.maxParticles	|| console.assert(false);
	this._texture	= opts.texture	|| this._buildDefaultTexture();
	var counter	= opts.counter	|| console.assert(false);
	

TODO replace this by objectpool.js

	var vertexIndexPool = {
		__pools: [],

Get a new Vector

		get: function() {
			if( this.__pools.length > 0 )	return this.__pools.pop();
			console.assert(false, "pool ran out!")
			return null;
		},

Release a vector back into the pool

		add: function(v){ this.__pools.push(v);	}
	};
	
	
	var particles	= new THREE.Geometry();
	var vertices	= particles.vertices;
	for ( i = 0; i < this._maxParticles; i++ ) {
		var position	= new THREE.Vector3(Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY);
		vertices.push(new THREE.Vertex(position));
		vertexIndexPool.add(i);
	}

to handle window resize

	this._$onWindowResize	= this._onWindowResize.bind(this);
	window.addEventListener('resize', this._$onWindowResize, false);

	var attributes	= this._attributes	= {
		size	: { type: 'f', value: [] },
		aColor	: { type: 'c', value: [] }
	};

	var uniforms	= this._uniforms	= {
		texture		: { type: "t", texture: this._texture 		},
		color		: { type: "c", value: new THREE.Color(0xffffff)	},
		sizeRatio	: { type: "f", value: this._computeSizeRatio()	}
	};

fill attributes array

	var valuesSize	= this._attributes.size.value;
	var valuesColor	= this._attributes.aColor.value;
	for(var v = 0; v < particles.vertices.length; v++ ){
		valuesSize[v]	= 99;
		valuesColor[v]	= new THREE.Color( 0x000000 );
	}

	var material	= new THREE.ShaderMaterial( {
		uniforms	: this._uniforms,
		attributes	: this._attributes,
		vertexShader	: THREEx.Sparks.vertexShaderText,
		fragmentShader	: THREEx.Sparks.fragmentShaderText,

		blending	: THREE.AdditiveBlending,
		depthWrite	: false,

depthTest : false,

		transparent	: true
	});

	this._group	= new THREE.ParticleSystem( particles, material );
	this._group.dynamic		= true;
	this._group.sortParticles	= true;	// TODO is this needed ?	

// EMITTER STUFF

	var setTargetParticle = function() {					
		var vertexIdx	= vertexIndexPool.get();
		var target	= {
			vertexIdx	: vertexIdx,
			size		: function(value){
				if( value !== undefined )	valuesSize[vertexIdx] = value;
				return valuesSize[vertexIdx];
			},
			color		: function(value){
				if( value !== undefined )	valuesColor[vertexIdx]	= value;
				return valuesColor[vertexIdx];
			}
		};
		return target;
	};


	var onParticleCreated = function(particle) {
		var vertexIdx	= particle.target.vertexIdx;

copy particle position into three.js geometry

		vertices[vertexIdx].position	= particle.position;						
	};
	
	var onParticleDead = function(particle) {
		var vertexIdx	= particle.target.vertexIdx;

Hide the particle

		valuesColor[vertexIdx].setHex( 0x000000 );
		vertices[vertexIdx].position.set(Number.POSITIVE_INFINITY,Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY);
		

Mark particle system as available by returning to pool

		vertexIndexPool.add( vertexIdx );
	};
	
	var emitter	= this._emitter	= new SPARKS.Emitter(counter);

	emitter.addInitializer(new SPARKS.Target(null, setTargetParticle));
	emitter.addCallback("created"	, onParticleCreated	);
	emitter.addCallback("dead"	, onParticleDead	);
}


THREEx.Sparks.prototype.destroy	= function()
{
	window.removeEventListener('resize', this._$onWindowResize);

	if( this._emitter.isRunning() )	this._emitter.stop();
}

//////////////////////////////////////////////////////////////////////////////// // ////////////////////////////////////////////////////////////////////////////////

THREEx.Sparks.prototype.container	= function()
{
	return this._group;
}

THREEx.Sparks.prototype.emitter		= function()
{
	return this._emitter;
}

THREEx.Sparks.prototype.update	= function()
{
	this._group.geometry.__dirtyVertices	= true;
	this._group.geometry.__dirtyColors	= true;
	this._attributes.size.needsUpdate	= true;
	this._attributes.aColor.needsUpdate	= true;
}

//////////////////////////////////////////////////////////////////////////////// handle window resize // ////////////////////////////////////////////////////////////////////////////////

THREEx.Sparks.prototype._onWindowResize	= function()
{
	this._uniforms.sizeRatio.value	= this._computeSizeRatio();
	this._uniforms.sizeRatio.needsUpdate	= true;
}


THREEx.Sparks.prototype._computeSizeRatio	= function()
{
	return window.innerHeight / 1024;
}

//////////////////////////////////////////////////////////////////////////////// Shader Text // ////////////////////////////////////////////////////////////////////////////////

THREEx.Sparks.vertexShaderText	= [
	"attribute	float	size;",
	"attribute	vec4	aColor;",
	
	"uniform	float	sizeRatio;",

	"varying	vec4	vColor;",

	"void main() {",
		"vec4 mvPosition= modelViewMatrix * vec4( position, 1.0 );",

FIXME is that the proper formula for size ? doesnt this depends on the fov ?

		"gl_PointSize	= size * sizeRatio * ( 150.0 / length( mvPosition.xyz ) );",
		"gl_Position	= projectionMatrix * mvPosition;",

		"vColor		= aColor;",
	"}"
].join('\n');
THREEx.Sparks.fragmentShaderText	= [
	"uniform vec3		color;",
	"uniform sampler2D	texture;",

	"varying vec4		vColor;",
	
	"void main() {",
		"vec4 outColor	= texture2D( texture, gl_PointCoord );",
		"gl_FragColor	= outColor * vec4( color * vColor.xyz, 1.0 );",
	"}"
].join('\n');

//////////////////////////////////////////////////////////////////////////////// Texture // ////////////////////////////////////////////////////////////////////////////////

THREEx.Sparks.prototype._buildDefaultTexture	= function(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.2, 'rgba(255,255,255,1)' );
	gradient.addColorStop( 0.4, '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;
}