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