// 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); 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, 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){ valuesSize[vertexIdx] = value; }, color : function(){ 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 );", "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; } ////////////////////////////////////////////////////////////////////////////////// // Custom initializer TODO put it elsewhere // ////////////////////////////////////////////////////////////////////////////////// THREEx.Sparks.ColorSizeInitializer = function(color, size){ this._color = color; this._size = size; } THREEx.Sparks.ColorSizeInitializer.prototype.initialize = function(emitter, particle) { if( this._color !== undefined ) particle.target.color().copy(this._color); if( this._size !== undefined ) particle.target.size(this._size); }