diff --git a/examples/js/GPUParticleSystem.js b/examples/js/GPUParticleSystem.js index b88373fb46b4ba3cffede9ddc8c63cdc22f1a6c9..4d2c6c5f7c486b9713aef4345a42f6a7cd7cb1b4 100644 --- a/examples/js/GPUParticleSystem.js +++ b/examples/js/GPUParticleSystem.js @@ -15,156 +15,115 @@ * */ -THREE.GPUParticleSystem = function(options) { +THREE.GPUParticleSystem = function( options ) { - var self = this; - var options = options || {}; + THREE.Object3D.apply( this, arguments ); + + options = options || {}; // parse options and use defaults - self.PARTICLE_COUNT = options.maxParticles || 1000000; - self.PARTICLE_CONTAINERS = options.containerCount || 1; - - self.PARTICLE_NOISE_TEXTURE = options.particleNoiseTex || null; - self.PARTICLE_SPRITE_TEXTURE = options.particleSpriteTex || null; - - self.PARTICLES_PER_CONTAINER = Math.ceil(self.PARTICLE_COUNT / self.PARTICLE_CONTAINERS); - self.PARTICLE_CURSOR = 0; - self.time = 0; - - - // Custom vertex and fragement shader - var GPUParticleShader = { - vertexShader: [ + this.PARTICLE_COUNT = options.maxParticles || 1000000; + this.PARTICLE_CONTAINERS = options.containerCount || 1; - 'precision highp float;', - 'const vec4 bitSh = vec4(256. * 256. * 256., 256. * 256., 256., 1.);', - 'const vec4 bitMsk = vec4(0.,vec3(1./256.0));', - 'const vec4 bitShifts = vec4(1.) / bitSh;', + this.PARTICLE_NOISE_TEXTURE = options.particleNoiseTex || null; + this.PARTICLE_SPRITE_TEXTURE = options.particleSpriteTex || null; - '#define FLOAT_MAX 1.70141184e38', - '#define FLOAT_MIN 1.17549435e-38', + this.PARTICLES_PER_CONTAINER = Math.ceil( this.PARTICLE_COUNT / this.PARTICLE_CONTAINERS ); + this.PARTICLE_CURSOR = 0; + this.time = 0; + this.particleContainers = []; + this.rand = []; - 'lowp vec4 encode_float(highp float v) {', - 'highp float av = abs(v);', + // custom vertex and fragement shader - '//Handle special cases', - 'if(av < FLOAT_MIN) {', - 'return vec4(0.0, 0.0, 0.0, 0.0);', - '} else if(v > FLOAT_MAX) {', - 'return vec4(127.0, 128.0, 0.0, 0.0) / 255.0;', - '} else if(v < -FLOAT_MAX) {', - 'return vec4(255.0, 128.0, 0.0, 0.0) / 255.0;', - '}', + var GPUParticleShader = { - 'highp vec4 c = vec4(0,0,0,0);', + vertexShader: [ - '//Compute exponent and mantissa', - 'highp float e = floor(log2(av));', - 'highp float m = av * pow(2.0, -e) - 1.0;', + 'uniform float uTime;', + 'uniform float uScale;', + 'uniform sampler2D tNoise;', - //Unpack mantissa - 'c[1] = floor(128.0 * m);', - 'm -= c[1] / 128.0;', - 'c[2] = floor(32768.0 * m);', - 'm -= c[2] / 32768.0;', - 'c[3] = floor(8388608.0 * m);', + 'attribute vec3 positionStart;', + 'attribute float startTime;', + 'attribute vec3 velocity;', + 'attribute float turbulence;', + 'attribute vec3 color;', + 'attribute float size;', + 'attribute float lifeTime;', - '//Unpack exponent', - 'highp float ebias = e + 127.0;', - 'c[0] = floor(ebias / 2.0);', - 'ebias -= c[0] * 2.0;', - 'c[1] += floor(ebias) * 128.0;', + 'varying vec4 vColor;', + 'varying float lifeLeft;', - '//Unpack sign bit', - 'c[0] += 128.0 * step(0.0, -v);', + 'void main() {', - '//Scale back to range', - 'return c / 255.0;', - '}', + // unpack things from our attributes' - 'vec4 pack(const in float depth)', - '{', - 'const vec4 bit_shift = vec4(256.0*256.0*256.0, 256.0*256.0, 256.0, 1.0);', - 'const vec4 bit_mask = vec4(0.0, 1.0/256.0, 1.0/256.0, 1.0/256.0);', - 'vec4 res = mod(depth*bit_shift*vec4(255), vec4(256))/vec4(255);', - 'res -= res.xxyz * bit_mask;', - 'return res;', - '}', + ' vColor = vec4( color, 1.0 );', - 'float unpack(const in vec4 rgba_depth)', - '{', - 'const vec4 bit_shift = vec4(1.0/(256.0*256.0*256.0), 1.0/(256.0*256.0), 1.0/256.0, 1.0);', - 'float depth = dot(rgba_depth, bit_shift);', - 'return depth;', - '}', + // convert our velocity back into a value we can use' - 'uniform float uTime;', - 'uniform float uScale;', - 'uniform sampler2D tNoise;', + ' vec3 newPosition;', + ' vec3 v;', - 'attribute vec4 particlePositionsStartTime;', - 'attribute vec4 particleVelColSizeLife;', + ' float timeElapsed = uTime - startTime;', - 'varying vec4 vColor;', - 'varying float lifeLeft;', + ' lifeLeft = 1.0 - ( timeElapsed / lifeTime );', - 'void main() {', + ' gl_PointSize = ( uScale * size ) * lifeLeft;', - '// unpack things from our attributes', - 'vColor = encode_float( particleVelColSizeLife.y );', + ' v.x = ( velocity.x - 0.5 ) * 3.0;', + ' v.y = ( velocity.y - 0.5 ) * 3.0;', + ' v.z = ( velocity.z - 0.5 ) * 3.0;', - '// convert our velocity back into a value we can use', - 'vec4 velTurb = encode_float( particleVelColSizeLife.x );', - 'vec3 velocity = vec3( velTurb.xyz );', - 'float turbulence = velTurb.w;', + ' newPosition = positionStart + ( v * 10.0 ) * ( uTime - startTime );', - 'vec3 newPosition;', + ' vec3 noise = texture2D( tNoise, vec2( newPosition.x * 0.015 + ( uTime * 0.05 ), newPosition.y * 0.02 + ( uTime * 0.015 ) ) ).rgb;', + ' vec3 noiseVel = ( noise.rgb - 0.5 ) * 30.0;', - 'float timeElapsed = uTime - particlePositionsStartTime.a;', + ' newPosition = mix( newPosition, newPosition + vec3( noiseVel * ( turbulence * 5.0 ) ), ( timeElapsed / lifeTime ) );', - 'lifeLeft = 1. - (timeElapsed / particleVelColSizeLife.w);', + ' if( v.y > 0. && v.y < .05 ) {', - 'gl_PointSize = ( uScale * particleVelColSizeLife.z ) * lifeLeft;', + ' lifeLeft = 0.0;', - 'velocity.x = ( velocity.x - .5 ) * 3.;', - 'velocity.y = ( velocity.y - .5 ) * 3.;', - 'velocity.z = ( velocity.z - .5 ) * 3.;', + ' }', - 'newPosition = particlePositionsStartTime.xyz + ( velocity * 10. ) * ( uTime - particlePositionsStartTime.a );', + ' if( v.x < - 1.45 ) {', - 'vec3 noise = texture2D( tNoise, vec2( newPosition.x * .015 + (uTime * .05), newPosition.y * .02 + (uTime * .015) )).rgb;', - 'vec3 noiseVel = ( noise.rgb - .5 ) * 30.;', + ' lifeLeft = 0.0;', - 'newPosition = mix(newPosition, newPosition + vec3(noiseVel * ( turbulence * 5. ) ), (timeElapsed / particleVelColSizeLife.a) );', + ' }', - 'if( velocity.y > 0. && velocity.y < .05 ) {', - 'lifeLeft = 0.;', - '}', + ' if( timeElapsed > 0.0 ) {', - 'if( velocity.x < -1.45 ) {', - 'lifeLeft = 0.;', - '}', + ' gl_Position = projectionMatrix * modelViewMatrix * vec4( newPosition, 1.0 );', + + ' } else {', + + ' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', + ' lifeLeft = 0.0;', + ' gl_PointSize = 0.;', + + ' }', - 'if( timeElapsed > 0. ) {', - 'gl_Position = projectionMatrix * modelViewMatrix * vec4( newPosition, 1.0 );', - '} else {', - 'gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', - 'lifeLeft = 0.;', - 'gl_PointSize = 0.;', - '}', '}' - ].join("\n"), + ].join( '\n' ), fragmentShader: [ - 'float scaleLinear(float value, vec2 valueDomain) {', - 'return (value - valueDomain.x) / (valueDomain.y - valueDomain.x);', + 'float scaleLinear( float value, vec2 valueDomain ) {', + + ' return ( value - valueDomain.x ) / ( valueDomain.y - valueDomain.x );', + '}', - 'float scaleLinear(float value, vec2 valueDomain, vec2 valueRange) {', - 'return mix(valueRange.x, valueRange.y, scaleLinear(value, valueDomain));', + 'float scaleLinear( float value, vec2 valueDomain, vec2 valueRange ) {', + + ' return mix( valueRange.x, valueRange.y, scaleLinear( value, valueDomain ) );', + '}', 'varying vec4 vColor;', @@ -174,335 +133,368 @@ THREE.GPUParticleSystem = function(options) { 'void main() {', - 'float alpha = 0.;', + ' float alpha = 0.;', - 'if( lifeLeft > .995 ) {', - 'alpha = scaleLinear( lifeLeft, vec2(1., .995), vec2(0., 1.));//mix( 0., 1., ( lifeLeft - .95 ) * 100. ) * .75;', - '} else {', - 'alpha = lifeLeft * .75;', - '}', + ' if( lifeLeft > 0.995 ) {', + + ' alpha = scaleLinear( lifeLeft, vec2( 1.0, 0.995 ), vec2( 0.0, 1.0 ) );', - 'vec4 tex = texture2D( tSprite, gl_PointCoord );', + ' } else {', + + ' alpha = lifeLeft * 0.75;', + + ' }', + + ' vec4 tex = texture2D( tSprite, gl_PointCoord );', + ' gl_FragColor = vec4( vColor.rgb * tex.a, alpha * tex.a );', - 'gl_FragColor = vec4( vColor.rgb * tex.a, alpha * tex.a );', '}' - ].join("\n") + ].join( '\n' ) }; // preload a million random numbers - self.rand = []; - for (var i = 1e5; i > 0; i--) { - self.rand.push(Math.random() - .5); + var i; + + for ( i = 1e5; i > 0; i-- ) { + + this.rand.push( Math.random() - 0.5 ); + } - self.random = function() { - return ++i >= self.rand.length ? self.rand[i = 1] : self.rand[i]; + this.random = function() { + + return ++ i >= this.rand.length ? this.rand[ i = 1 ] : this.rand[ i ]; + }; var textureLoader = new THREE.TextureLoader(); - self.particleNoiseTex = self.PARTICLE_NOISE_TEXTURE || textureLoader.load("textures/perlin-512.png"); - self.particleNoiseTex.wrapS = self.particleNoiseTex.wrapT = THREE.RepeatWrapping; + this.particleNoiseTex = this.PARTICLE_NOISE_TEXTURE || textureLoader.load( 'textures/perlin-512.png' ); + this.particleNoiseTex.wrapS = this.particleNoiseTex.wrapT = THREE.RepeatWrapping; - self.particleSpriteTex = self.PARTICLE_SPRITE_TEXTURE || textureLoader.load("textures/particle2.png"); - self.particleSpriteTex.wrapS = self.particleSpriteTex.wrapT = THREE.RepeatWrapping; + this.particleSpriteTex = this.PARTICLE_SPRITE_TEXTURE || textureLoader.load( 'textures/particle2.png' ); + this.particleSpriteTex.wrapS = this.particleSpriteTex.wrapT = THREE.RepeatWrapping; - self.particleShaderMat = new THREE.ShaderMaterial({ + this.particleShaderMat = new THREE.ShaderMaterial( { transparent: true, depthWrite: false, uniforms: { - "uTime": { + 'uTime': { value: 0.0 }, - "uScale": { + 'uScale': { value: 1.0 }, - "tNoise": { - value: self.particleNoiseTex + 'tNoise': { + value: this.particleNoiseTex }, - "tSprite": { - value: self.particleSpriteTex + 'tSprite': { + value: this.particleSpriteTex } }, blending: THREE.AdditiveBlending, vertexShader: GPUParticleShader.vertexShader, fragmentShader: GPUParticleShader.fragmentShader - }); + } ); // define defaults for all values - self.particleShaderMat.defaultAttributeValues.particlePositionsStartTime = [0, 0, 0, 0]; - self.particleShaderMat.defaultAttributeValues.particleVelColSizeLife = [0, 0, 0, 0]; - - self.particleContainers = []; - - // extend Object3D - THREE.Object3D.apply(this, arguments); + this.particleShaderMat.defaultAttributeValues.particlePositionsStartTime = [ 0, 0, 0, 0 ]; + this.particleShaderMat.defaultAttributeValues.particleVelColSizeLife = [ 0, 0, 0, 0 ]; this.init = function() { - for (var i = 0; i < self.PARTICLE_CONTAINERS; i++) { + for ( var i = 0; i < this.PARTICLE_CONTAINERS; i ++ ) { - var c = new THREE.GPUParticleContainer(self.PARTICLES_PER_CONTAINER, self); - self.particleContainers.push(c); - self.add(c); + var c = new THREE.GPUParticleContainer( this.PARTICLES_PER_CONTAINER, this ); + this.particleContainers.push( c ); + this.add( c ); } }; - this.spawnParticle = function(options) { + this.spawnParticle = function( options ) { + + this.PARTICLE_CURSOR ++; + + if ( this.PARTICLE_CURSOR >= this.PARTICLE_COUNT ) { + + this.PARTICLE_CURSOR = 1; - self.PARTICLE_CURSOR++; - if (self.PARTICLE_CURSOR >= self.PARTICLE_COUNT) { - self.PARTICLE_CURSOR = 1; } - var currentContainer = self.particleContainers[Math.floor(self.PARTICLE_CURSOR / self.PARTICLES_PER_CONTAINER)]; + var currentContainer = this.particleContainers[ Math.floor( this.PARTICLE_CURSOR / this.PARTICLES_PER_CONTAINER ) ]; - currentContainer.spawnParticle(options); + currentContainer.spawnParticle( options ); }; - this.update = function(time) { - for (var i = 0; i < self.PARTICLE_CONTAINERS; i++) { + this.update = function( time ) { + + for ( var i = 0; i < this.PARTICLE_CONTAINERS; i ++ ) { - self.particleContainers[i].update(time); + this.particleContainers[ i ].update( time ); } + + }; + + this.dispose = function() { + + this.particleShaderMat.dispose(); + this.particleNoiseTex.dispose(); + this.particleSpriteTex.dispose(); + + for ( var i = 0; i < this.PARTICLE_CONTAINERS; i ++ ) { + + this.particleContainers[ i ].dispose(); + + } + }; this.init(); }; -THREE.GPUParticleSystem.prototype = Object.create(THREE.Object3D.prototype); +THREE.GPUParticleSystem.prototype = Object.create( THREE.Object3D.prototype ); THREE.GPUParticleSystem.prototype.constructor = THREE.GPUParticleSystem; // Subclass for particle containers, allows for very large arrays to be spread out -THREE.GPUParticleContainer = function(maxParticles, particleSystem) { - - var self = this; - self.PARTICLE_COUNT = maxParticles || 100000; - self.PARTICLE_CURSOR = 0; - self.time = 0; - self.DPR = window.devicePixelRatio; - self.GPUParticleSystem = particleSystem; - - var particlesPerArray = Math.floor(self.PARTICLE_COUNT / self.MAX_ATTRIBUTES); - - // extend Object3D - THREE.Object3D.apply(this, arguments); - - // construct a couple small arrays used for packing variables into floats etc - var UINT8_VIEW = new Uint8Array(4); - var FLOAT_VIEW = new Float32Array(UINT8_VIEW.buffer); - - function decodeFloat(x, y, z, w) { - UINT8_VIEW[0] = Math.floor(w); - UINT8_VIEW[1] = Math.floor(z); - UINT8_VIEW[2] = Math.floor(y); - UINT8_VIEW[3] = Math.floor(x); - return FLOAT_VIEW[0] - } - function componentToHex(c) { - var hex = c.toString(16); - return hex.length == 1 ? "0" + hex : hex; - } +THREE.GPUParticleContainer = function( maxParticles, particleSystem ) { - function rgbToHex(r, g, b) { - return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b); - } + THREE.Object3D.apply( this, arguments ); - function hexToRgb(hex) { - var r = hex >> 16; - var g = (hex & 0x00FF00) >> 8; - var b = hex & 0x0000FF; + this.PARTICLE_COUNT = maxParticles || 100000; + this.PARTICLE_CURSOR = 0; + this.time = 0; + this.offset = 0; + this.count = 0; + this.DPR = window.devicePixelRatio; + this.GPUParticleSystem = particleSystem; + this.particleUpdate = false; - if (r > 0) r--; - if (g > 0) g--; - if (b > 0) b--; + // geometry - return [r, g, b]; - } + this.particleShaderGeo = new THREE.BufferGeometry(); - self.particles = []; - self.deadParticles = []; - self.particlesAvailableSlot = []; + this.particleShaderGeo.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( this.PARTICLE_COUNT * 3 ), 3 ) ); + this.particleShaderGeo.addAttribute( 'positionStart', new THREE.BufferAttribute( new Float32Array( this.PARTICLE_COUNT * 3 ), 3 ) ); + this.particleShaderGeo.addAttribute( 'startTime', new THREE.BufferAttribute( new Float32Array( this.PARTICLE_COUNT ), 1 ) ); + this.particleShaderGeo.addAttribute( 'velocity', new THREE.BufferAttribute( new Float32Array( this.PARTICLE_COUNT * 3 ), 3 ) ); + this.particleShaderGeo.addAttribute( 'turbulence', new THREE.BufferAttribute( new Float32Array( this.PARTICLE_COUNT ), 1 ) ); + this.particleShaderGeo.addAttribute( 'color', new THREE.BufferAttribute( new Float32Array( this.PARTICLE_COUNT * 3 ), 3 ) ); + this.particleShaderGeo.addAttribute( 'size', new THREE.BufferAttribute( new Float32Array( this.PARTICLE_COUNT ), 1 ) ); + this.particleShaderGeo.addAttribute( 'lifeTime', new THREE.BufferAttribute( new Float32Array( this.PARTICLE_COUNT ), 1 ) ); - // create a container for particles - self.particleUpdate = false; + // material - // Shader Based Particle System - self.particleShaderGeo = new THREE.BufferGeometry(); + this.particleShaderMat = this.GPUParticleSystem.particleShaderMat; - // new hyper compressed attributes - self.particleVertices = new Float32Array(self.PARTICLE_COUNT * 3); // position - self.particlePositionsStartTime = new Float32Array(self.PARTICLE_COUNT * 4); // position - self.particleVelColSizeLife = new Float32Array(self.PARTICLE_COUNT * 4); + var position = new THREE.Vector3(); + var velocity = new THREE.Vector3(); + var color = new THREE.Color(); - for (var i = 0; i < self.PARTICLE_COUNT; i++) { - self.particlePositionsStartTime[i * 4 + 0] = 100; //x - self.particlePositionsStartTime[i * 4 + 1] = 0; //y - self.particlePositionsStartTime[i * 4 + 2] = 0.0; //z - self.particlePositionsStartTime[i * 4 + 3] = 0.0; //startTime + this.spawnParticle = function( options ) { - self.particleVertices[i * 3 + 0] = 0; //x - self.particleVertices[i * 3 + 1] = 0; //y - self.particleVertices[i * 3 + 2] = 0.0; //z + var positionStartAttribute = this.particleShaderGeo.getAttribute( 'positionStart' ); + var startTimeAttribute = this.particleShaderGeo.getAttribute( 'startTime' ); + var velocityAttribute = this.particleShaderGeo.getAttribute( 'velocity' ); + var turbulenceAttribute = this.particleShaderGeo.getAttribute( 'turbulence' ); + var colorAttribute = this.particleShaderGeo.getAttribute( 'color' ); + var sizeAttribute = this.particleShaderGeo.getAttribute( 'size' ); + var lifeTimeAttribute = this.particleShaderGeo.getAttribute( 'lifeTime' ); - self.particleVelColSizeLife[i * 4 + 0] = decodeFloat(128, 128, 0, 0); //vel - self.particleVelColSizeLife[i * 4 + 1] = decodeFloat(0, 254, 0, 254); //color - self.particleVelColSizeLife[i * 4 + 2] = 1.0; //size - self.particleVelColSizeLife[i * 4 + 3] = 0.0; //lifespan - } + options = options || {}; - self.particleShaderGeo.addAttribute('position', new THREE.BufferAttribute(self.particleVertices, 3)); - self.particleShaderGeo.addAttribute('particlePositionsStartTime', new THREE.BufferAttribute(self.particlePositionsStartTime, 4).setDynamic(true)); - self.particleShaderGeo.addAttribute('particleVelColSizeLife', new THREE.BufferAttribute(self.particleVelColSizeLife, 4).setDynamic(true)); + // setup reasonable default values for all arguments - self.posStart = self.particleShaderGeo.getAttribute('particlePositionsStartTime'); - self.velCol = self.particleShaderGeo.getAttribute('particleVelColSizeLife'); + position = options.position !== undefined ? position.copy( options.position ) : position.set( 0, 0, 0 ); + velocity = options.velocity !== undefined ? velocity.copy( options.velocity ) : velocity.set( 0, 0, 0 ); + color = options.color !== undefined ? color.set( options.color ) : color.set( 0xffffff ); - self.particleShaderMat = self.GPUParticleSystem.particleShaderMat; + var positionRandomness = options.positionRandomness !== undefined ? options.positionRandomness : 0; + var velocityRandomness = options.velocityRandomness !== undefined ? options.velocityRandomness : 0; + var colorRandomness = options.colorRandomness !== undefined ? options.colorRandomness : 1; + var turbulence = options.turbulence !== undefined ? options.turbulence : 1; + var lifetime = options.lifetime !== undefined ? options.lifetime : 5; + var size = options.size !== undefined ? options.size : 10; + var sizeRandomness = options.sizeRandomness !== undefined ? options.sizeRandomness : 0; + var smoothPosition = options.smoothPosition !== undefined ? options.smoothPosition : false; - this.init = function() { - self.particleSystem = new THREE.Points(self.particleShaderGeo, self.particleShaderMat); - self.particleSystem.frustumCulled = false; - this.add(self.particleSystem); - }; + if ( this.DPR !== undefined ) size *= this.DPR; - var options = {}, - position = new THREE.Vector3(), - velocity = new THREE.Vector3(), - positionRandomness = 0., - velocityRandomness = 0., - color = 0xffffff, - colorRandomness = 0., - turbulence = 0., - lifetime = 0., - size = 0., - sizeRandomness = 0., - smoothPosition = false, - i; - - var maxVel = 2; - var maxSource = 250; - this.offset = 0; - this.count = 0; + i = this.PARTICLE_CURSOR; - this.spawnParticle = function(options) { + // position - options = options || {}; + positionStartAttribute.array[ i * 3 + 0 ] = position.x + ( particleSystem.random() * positionRandomness ); + positionStartAttribute.array[ i * 3 + 1 ] = position.y + ( particleSystem.random() * positionRandomness ); + positionStartAttribute.array[ i * 3 + 2 ] = position.z + ( particleSystem.random() * positionRandomness ); + + if ( smoothPosition === true ) { + + positionStartAttribute.array[ i * 3 + 0 ] += - ( velocity.x * particleSystem.random() ); + positionStartAttribute.array[ i * 3 + 1 ] += - ( velocity.y * particleSystem.random() ); + positionStartAttribute.array[ i * 3 + 2 ] += - ( velocity.z * particleSystem.random() ); - // setup reasonable default values for all arguments - position = options.position !== undefined ? position.copy(options.position) : position.set(0., 0., 0.); - velocity = options.velocity !== undefined ? velocity.copy(options.velocity) : velocity.set(0., 0., 0.); - positionRandomness = options.positionRandomness !== undefined ? options.positionRandomness : 0.0; - velocityRandomness = options.velocityRandomness !== undefined ? options.velocityRandomness : 0.0; - color = options.color !== undefined ? options.color : 0xffffff; - colorRandomness = options.colorRandomness !== undefined ? options.colorRandomness : 1.0; - turbulence = options.turbulence !== undefined ? options.turbulence : 1.0; - lifetime = options.lifetime !== undefined ? options.lifetime : 5.0; - size = options.size !== undefined ? options.size : 10; - sizeRandomness = options.sizeRandomness !== undefined ? options.sizeRandomness : 0.0; - smoothPosition = options.smoothPosition !== undefined ? options.smoothPosition : false; - - if (self.DPR !== undefined) size *= self.DPR; - - i = self.PARTICLE_CURSOR; - - self.posStart.array[i * 4 + 0] = position.x + ((particleSystem.random()) * positionRandomness); // - ( velocity.x * particleSystem.random() ); //x - self.posStart.array[i * 4 + 1] = position.y + ((particleSystem.random()) * positionRandomness); // - ( velocity.y * particleSystem.random() ); //y - self.posStart.array[i * 4 + 2] = position.z + ((particleSystem.random()) * positionRandomness); // - ( velocity.z * particleSystem.random() ); //z - self.posStart.array[i * 4 + 3] = self.time + (particleSystem.random() * 2e-2); //startTime - - if (smoothPosition === true) { - self.posStart.array[i * 4 + 0] += -(velocity.x * particleSystem.random()); //x - self.posStart.array[i * 4 + 1] += -(velocity.y * particleSystem.random()); //y - self.posStart.array[i * 4 + 2] += -(velocity.z * particleSystem.random()); //z } - var velX = velocity.x + (particleSystem.random()) * velocityRandomness; - var velY = velocity.y + (particleSystem.random()) * velocityRandomness; - var velZ = velocity.z + (particleSystem.random()) * velocityRandomness; + // velocity - // convert turbulence rating to something we can pack into a vec4 - var turbulence = Math.floor(turbulence * 254); + var maxVel = 2; - // clamp our value to between 0. and 1. - velX = Math.floor(maxSource * ((velX - -maxVel) / (maxVel - -maxVel))); - velY = Math.floor(maxSource * ((velY - -maxVel) / (maxVel - -maxVel))); - velZ = Math.floor(maxSource * ((velZ - -maxVel) / (maxVel - -maxVel))); + var velX = velocity.x + particleSystem.random() * velocityRandomness; + var velY = velocity.y + particleSystem.random() * velocityRandomness; + var velZ = velocity.z + particleSystem.random() * velocityRandomness; - self.velCol.array[i * 4 + 0] = decodeFloat(velX, velY, velZ, turbulence); //vel + velX = THREE.Math.clamp( ( velX - ( - maxVel ) ) / ( maxVel - ( - maxVel ) ), 0, 1 ); + velY = THREE.Math.clamp( ( velY - ( - maxVel ) ) / ( maxVel - ( - maxVel ) ), 0, 1 ); + velZ = THREE.Math.clamp( ( velZ - ( - maxVel ) ) / ( maxVel - ( - maxVel ) ), 0, 1 ); - var rgb = hexToRgb(color); + velocityAttribute.array[ i * 3 + 0 ] = velX; + velocityAttribute.array[ i * 3 + 1 ] = velY; + velocityAttribute.array[ i * 3 + 2 ] = velZ; - for (var c = 0; c < rgb.length; c++) { - rgb[c] = Math.floor(rgb[c] + ((particleSystem.random()) * colorRandomness) * 254); - if (rgb[c] > 254) rgb[c] = 254; - if (rgb[c] < 0) rgb[c] = 0; - } + // color + + color.r = THREE.Math.clamp( color.r + particleSystem.random() * colorRandomness, 0, 1 ); + color.g = THREE.Math.clamp( color.g + particleSystem.random() * colorRandomness, 0, 1 ); + color.b = THREE.Math.clamp( color.b + particleSystem.random() * colorRandomness, 0, 1 ); + + colorAttribute.array[ i * 3 + 0 ] = color.r; + colorAttribute.array[ i * 3 + 1 ] = color.g; + colorAttribute.array[ i * 3 + 2 ] = color.b; - self.velCol.array[i * 4 + 1] = decodeFloat(rgb[0], rgb[1], rgb[2], 254); //color - self.velCol.array[i * 4 + 2] = size + (particleSystem.random()) * sizeRandomness; //size - self.velCol.array[i * 4 + 3] = lifetime; //lifespan + // turbulence, size, lifetime and starttime + + turbulenceAttribute.array[ i ] = turbulence; + sizeAttribute.array[ i ] = size + particleSystem.random() * sizeRandomness; + lifeTimeAttribute.array[ i ] = lifetime; + startTimeAttribute.array[ i ] = this.time + particleSystem.random() * 2e-2; + + // offset + + if ( this.offset === 0 ) { + + this.offset = this.PARTICLE_CURSOR; - if (this.offset == 0) { - this.offset = self.PARTICLE_CURSOR; } - self.count++; + // counter and cursor - self.PARTICLE_CURSOR++; + this.count ++; + this.PARTICLE_CURSOR ++; + + if ( this.PARTICLE_CURSOR >= this.PARTICLE_COUNT ) { + + this.PARTICLE_CURSOR = 0; - if (self.PARTICLE_CURSOR >= self.PARTICLE_COUNT) { - self.PARTICLE_CURSOR = 0; } - self.particleUpdate = true; + this.particleUpdate = true; }; - this.update = function(time) { + this.init = function() { - self.time = time; - self.particleShaderMat.uniforms['uTime'].value = time; + this.particleSystem = new THREE.Points( this.particleShaderGeo, this.particleShaderMat ); + this.particleSystem.frustumCulled = false; + this.add( this.particleSystem ); + + }; + + this.update = function( time ) { + + this.time = time; + this.particleShaderMat.uniforms.uTime.value = time; this.geometryUpdate(); }; this.geometryUpdate = function() { - if (self.particleUpdate == true) { - self.particleUpdate = false; - // if we can get away with a partial buffer update, do so - if (self.offset + self.count < self.PARTICLE_COUNT) { - self.posStart.updateRange.offset = self.velCol.updateRange.offset = self.offset * 4; - self.posStart.updateRange.count = self.velCol.updateRange.count = self.count * 4; + if ( this.particleUpdate === true ) { + + this.particleUpdate = false; + + var positionStartAttribute = this.particleShaderGeo.getAttribute( 'positionStart' ); + var startTimeAttribute = this.particleShaderGeo.getAttribute( 'startTime' ); + var velocityAttribute = this.particleShaderGeo.getAttribute( 'velocity' ); + var turbulenceAttribute = this.particleShaderGeo.getAttribute( 'turbulence' ); + var colorAttribute = this.particleShaderGeo.getAttribute( 'color' ); + var sizeAttribute = this.particleShaderGeo.getAttribute( 'size' ); + var lifeTimeAttribute = this.particleShaderGeo.getAttribute( 'lifeTime' ); + + if ( this.offset + this.count < this.PARTICLE_COUNT ) { + + positionStartAttribute.updateRange.offset = this.offset * positionStartAttribute.itemSize; + startTimeAttribute.updateRange.offset = this.offset * startTimeAttribute.itemSize; + velocityAttribute.updateRange.offset = this.offset * velocityAttribute.itemSize; + turbulenceAttribute.updateRange.offset = this.offset * turbulenceAttribute.itemSize; + colorAttribute.updateRange.offset = this.offset * colorAttribute.itemSize; + sizeAttribute.updateRange.offset = this.offset * sizeAttribute.itemSize; + lifeTimeAttribute.updateRange.offset = this.offset * lifeTimeAttribute.itemSize; + + positionStartAttribute.updateRange.count = this.count * positionStartAttribute.itemSize; + startTimeAttribute.updateRange.count = this.count * startTimeAttribute.itemSize; + velocityAttribute.updateRange.count = this.count * velocityAttribute.itemSize; + turbulenceAttribute.updateRange.count = this.count * turbulenceAttribute.itemSize; + colorAttribute.updateRange.count = this.count * colorAttribute.itemSize; + sizeAttribute.updateRange.count = this.count * sizeAttribute.itemSize; + lifeTimeAttribute.updateRange.count = this.count * lifeTimeAttribute.itemSize; + } else { - self.posStart.updateRange.offset = 0; - self.posStart.updateRange.count = self.velCol.updateRange.count = (self.PARTICLE_COUNT * 4); + + positionStartAttribute.updateRange.offset = 0; + startTimeAttribute.updateRange.offset = 0; + velocityAttribute.updateRange.offset = 0; + turbulenceAttribute.updateRange.offset = 0; + colorAttribute.updateRange.offset = 0; + sizeAttribute.updateRange.offset = 0; + lifeTimeAttribute.updateRange.offset = 0; + + positionStartAttribute.updateRange.count = positionStartAttribute.count; + startTimeAttribute.updateRange.count = startTimeAttribute.count; + velocityAttribute.updateRange.count = velocityAttribute.count; + turbulenceAttribute.updateRange.count = turbulenceAttribute.count; + colorAttribute.updateRange.count = colorAttribute.count; + sizeAttribute.updateRange.count = sizeAttribute.count; + lifeTimeAttribute.updateRange.count = lifeTimeAttribute.count; + } - self.posStart.needsUpdate = true; - self.velCol.needsUpdate = true; + positionStartAttribute.needsUpdate = true; + startTimeAttribute.needsUpdate = true; + velocityAttribute.needsUpdate = true; + turbulenceAttribute.needsUpdate = true; + colorAttribute.needsUpdate = true; + sizeAttribute.needsUpdate = true; + lifeTimeAttribute.needsUpdate = true; + + this.offset = 0; + this.count = 0; - self.offset = 0; - self.count = 0; } + + }; + + this.dispose = function() { + + this.particleShaderGeo.dispose(); + }; this.init(); }; -THREE.GPUParticleContainer.prototype = Object.create(THREE.Object3D.prototype); +THREE.GPUParticleContainer.prototype = Object.create( THREE.Object3D.prototype ); THREE.GPUParticleContainer.prototype.constructor = THREE.GPUParticleContainer;