|
@@ -0,0 +1,503 @@
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * GPU Particle System
|
|
|
|
+ * @author flimshaw - Charlie Hoey - http://charliehoey.com
|
|
|
|
+ *
|
|
|
|
+ * A simple to use, general purpose GPU system. Particles are spawn-and-forget with
|
|
|
|
+ * several options available, and do not require monitoring or cleanup after spawning.
|
|
|
|
+ * Because the paths of all particles are completely deterministic once spawned, the scale
|
|
|
|
+ * and direction of time is also variable.
|
|
|
|
+ *
|
|
|
|
+ * Currently uses a static wrapping perlin noise texture for turbulence, and a small png texture for
|
|
|
|
+ * particles, but adding support for a particle texture atlas or changing to a different type of turbulence
|
|
|
|
+ * would be a fairly light day's work.
|
|
|
|
+ *
|
|
|
|
+ * Shader and javascript packing code derrived from several Stack Overflow examples.
|
|
|
|
+ *
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+THREE.GPUParticleSystem = function( options ) {
|
|
|
|
+
|
|
|
|
+ var self = this;
|
|
|
|
+ var options = options || {};
|
|
|
|
+
|
|
|
|
+ // parse options and use defaults
|
|
|
|
+ self.PARTICLE_COUNT = options.maxParticles || 1000000;
|
|
|
|
+ self.PARTICLE_CONTAINERS = options.containerCount || 1;
|
|
|
|
+ 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: [
|
|
|
|
+
|
|
|
|
+ '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;',
|
|
|
|
+
|
|
|
|
+ '#define FLOAT_MAX 1.70141184e38',
|
|
|
|
+ '#define FLOAT_MIN 1.17549435e-38',
|
|
|
|
+
|
|
|
|
+ 'lowp vec4 encode_float(highp float v) {',
|
|
|
|
+ 'highp float av = abs(v);',
|
|
|
|
+
|
|
|
|
+ '//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;',
|
|
|
|
+ '}',
|
|
|
|
+
|
|
|
|
+ 'highp vec4 c = vec4(0,0,0,0);',
|
|
|
|
+
|
|
|
|
+ '//Compute exponent and mantissa',
|
|
|
|
+ 'highp float e = floor(log2(av));',
|
|
|
|
+ 'highp float m = av * pow(2.0, -e) - 1.0;',
|
|
|
|
+
|
|
|
|
+ //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);',
|
|
|
|
+
|
|
|
|
+ '//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;',
|
|
|
|
+
|
|
|
|
+ '//Unpack sign bit',
|
|
|
|
+ 'c[0] += 128.0 * step(0.0, -v);',
|
|
|
|
+
|
|
|
|
+ '//Scale back to range',
|
|
|
|
+ 'return c / 255.0;',
|
|
|
|
+ '}',
|
|
|
|
+
|
|
|
|
+ '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 = fract(depth * bit_shift);',
|
|
|
|
+ 'res -= res.xxyz * bit_mask;',
|
|
|
|
+ 'return res;',
|
|
|
|
+ '}',
|
|
|
|
+
|
|
|
|
+ '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;',
|
|
|
|
+ '}',
|
|
|
|
+
|
|
|
|
+ 'uniform float uTime;',
|
|
|
|
+ 'uniform float uScale;',
|
|
|
|
+ 'uniform sampler2D tNoise;',
|
|
|
|
+
|
|
|
|
+ 'attribute vec4 particlePositionsStartTime;',
|
|
|
|
+ 'attribute vec4 particleVelColSizeLife;',
|
|
|
|
+
|
|
|
|
+ 'varying vec4 vColor;',
|
|
|
|
+ 'varying float lifeLeft;',
|
|
|
|
+
|
|
|
|
+ 'varying vec2 vUv;',
|
|
|
|
+
|
|
|
|
+ 'void main() {',
|
|
|
|
+
|
|
|
|
+ '// unpack things from our attributes',
|
|
|
|
+ 'vColor = encode_float( particleVelColSizeLife.y );',
|
|
|
|
+
|
|
|
|
+ '// 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;',
|
|
|
|
+ 'vUv = uv;',
|
|
|
|
+
|
|
|
|
+ 'vec3 newPosition;',
|
|
|
|
+
|
|
|
|
+ 'float timeElapsed = uTime - particlePositionsStartTime.a;',
|
|
|
|
+
|
|
|
|
+ 'lifeLeft = 1. - (timeElapsed / particleVelColSizeLife.w);',
|
|
|
|
+
|
|
|
|
+ 'gl_PointSize = ( uScale * particleVelColSizeLife.z ) * lifeLeft;',
|
|
|
|
+
|
|
|
|
+ '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 );',
|
|
|
|
+
|
|
|
|
+ 'vec3 noise = texture2D( tNoise, vec2( newPosition.x * .015 + (uTime * .05), newPosition.y * .02 + (uTime * .015) )).rgb;',
|
|
|
|
+ 'vec3 noiseVel = ( noise.rgb - .5 ) * 30.;',
|
|
|
|
+
|
|
|
|
+ 'newPosition = mix(newPosition, newPosition + vec3(noiseVel * ( turbulence * 5. ) ), (timeElapsed / particleVelColSizeLife.a) );',
|
|
|
|
+
|
|
|
|
+ 'if( velocity.y > 0. && velocity.y < .05 ) {',
|
|
|
|
+ 'lifeLeft = 0.;',
|
|
|
|
+ '}',
|
|
|
|
+
|
|
|
|
+ 'if( velocity.x < -1.45 ) {',
|
|
|
|
+ 'lifeLeft = 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"),
|
|
|
|
+
|
|
|
|
+ fragmentShader: [
|
|
|
|
+
|
|
|
|
+ '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));',
|
|
|
|
+ '}',
|
|
|
|
+
|
|
|
|
+ 'varying vec4 vColor;',
|
|
|
|
+ 'varying float lifeLeft;',
|
|
|
|
+ 'varying vec2 vUv;',
|
|
|
|
+
|
|
|
|
+ 'uniform sampler2D tSprite;',
|
|
|
|
+
|
|
|
|
+ 'void main() {',
|
|
|
|
+
|
|
|
|
+ '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;',
|
|
|
|
+ '}',
|
|
|
|
+
|
|
|
|
+ 'vec4 tex = texture2D( tSprite, gl_PointCoord );',
|
|
|
|
+
|
|
|
|
+ 'gl_FragColor = vec4( vColor.rgb * tex.a, alpha * tex.a );',
|
|
|
|
+ '}'
|
|
|
|
+
|
|
|
|
+ ].join("\n")
|
|
|
|
+
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ // preload a million random numbers
|
|
|
|
+ self.rand = [];
|
|
|
|
+
|
|
|
|
+ for(var i=1e5; i > 0; i--) {
|
|
|
|
+ self.rand.push( Math.random() - .5 );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ self.random = function() {
|
|
|
|
+ return ++i >= self.rand.length ? self.rand[i=1] : self.rand[i];
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ self.particleNoiseTex = THREE.ImageUtils.loadTexture("textures/perlin-512.png");
|
|
|
|
+ self.particleNoiseTex.wrapS = self.particleNoiseTex.wrapT = THREE.RepeatWrapping;
|
|
|
|
+
|
|
|
|
+ self.particleSpriteTex = THREE.ImageUtils.loadTexture("textures/particle2.png");
|
|
|
|
+ self.particleSpriteTex.wrapS = self.particleSpriteTex.wrapT = THREE.RepeatWrapping;
|
|
|
|
+
|
|
|
|
+ self.particleShaderMat = new THREE.ShaderMaterial( {
|
|
|
|
+ transparent: true,
|
|
|
|
+ depthWrite: false,
|
|
|
|
+ uniforms: {
|
|
|
|
+ "uTime": { type: "f", value: 0.0 },
|
|
|
|
+ "uScale": { type: "f", value: 1.0 },
|
|
|
|
+ "tNoise": { type: "t", value: self.particleNoiseTex },
|
|
|
|
+ "tSprite": { type: "t", value: self.particleSpriteTex }
|
|
|
|
+ },
|
|
|
|
+ attributes: {
|
|
|
|
+ "particlePositionsStartTime": { type: "v4", value: [] },
|
|
|
|
+ "particleVelColSizeLife": { type: "v4", value: [] }
|
|
|
|
+ },
|
|
|
|
+ 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.init = function() {
|
|
|
|
+
|
|
|
|
+ for( var i = 0; i < self.PARTICLE_CONTAINERS; i++ ) {
|
|
|
|
+
|
|
|
|
+ var c = new THREE.GPUParticleContainer( self.PARTICLES_PER_CONTAINER, self );
|
|
|
|
+ self.particleContainers.push( c );
|
|
|
|
+ self.add( c );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ this.spawnParticle = function( options ) {
|
|
|
|
+
|
|
|
|
+ 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 ) ];
|
|
|
|
+
|
|
|
|
+ currentContainer.spawnParticle( options );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ this.update = function( time ) {
|
|
|
|
+ for( var i = 0; i < self.PARTICLE_CONTAINERS; i++ ) {
|
|
|
|
+
|
|
|
|
+ self.particleContainers[i].update( time );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ this.init();
|
|
|
|
+
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+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;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function rgbToHex(r, g, b) {
|
|
|
|
+ return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function hexToRgb(hex) {
|
|
|
|
+ var r = hex >> 16;
|
|
|
|
+ var g = (hex & 0x00FF00) >> 8;
|
|
|
|
+ var b = hex & 0x0000FF;
|
|
|
|
+
|
|
|
|
+ if( r > 0 ) r--;
|
|
|
|
+ if( g > 0 ) g--;
|
|
|
|
+ if( b > 0 ) b--;
|
|
|
|
+
|
|
|
|
+ return [r,g,b];
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ self.particles = [];
|
|
|
|
+ self.deadParticles = [];
|
|
|
|
+ self.particlesAvailableSlot = [];
|
|
|
|
+
|
|
|
|
+ // create a container for particles
|
|
|
|
+ self.particleUpdate = false;
|
|
|
|
+
|
|
|
|
+ // Shader Based Particle System
|
|
|
|
+ self.particleShaderGeo = new THREE.BufferGeometry();
|
|
|
|
+
|
|
|
|
+ // 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 );
|
|
|
|
+
|
|
|
|
+ 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
|
|
|
|
+
|
|
|
|
+ self.particleVertices[ i*3 + 0 ] = 0; //x
|
|
|
|
+ self.particleVertices[ i*3 + 1 ] = 0; //y
|
|
|
|
+ self.particleVertices[ i*3 + 2 ] = 0.0; //z
|
|
|
|
+
|
|
|
|
+ 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
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ self.particleShaderGeo.addAttribute( 'position', new THREE.BufferAttribute( self.particleVertices, 3 ) );
|
|
|
|
+ self.particleShaderGeo.addAttribute( 'particlePositionsStartTime', new THREE.DynamicBufferAttribute( self.particlePositionsStartTime, 4 ) );
|
|
|
|
+ self.particleShaderGeo.addAttribute( 'particleVelColSizeLife', new THREE.DynamicBufferAttribute( self.particleVelColSizeLife, 4 ) );
|
|
|
|
+
|
|
|
|
+ self.posStart = self.particleShaderGeo.getAttribute( 'particlePositionsStartTime' )
|
|
|
|
+ self.velCol = self.particleShaderGeo.getAttribute( 'particleVelColSizeLife' );
|
|
|
|
+
|
|
|
|
+ self.particleShaderMat = self.GPUParticleSystem.particleShaderMat;
|
|
|
|
+
|
|
|
|
+ this.init = function() {
|
|
|
|
+ self.particleSystem = new THREE.PointCloud( self.particleShaderGeo, self.particleShaderMat );
|
|
|
|
+ self.particleSystem.frustumCulled = false;
|
|
|
|
+ this.add( self.particleSystem ) ;
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ 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.
|
|
|
|
+ , i;
|
|
|
|
+
|
|
|
|
+ var maxVel = 2;
|
|
|
|
+ var maxSource = 250;
|
|
|
|
+ this.offset = 0;
|
|
|
|
+ this.count = 0;
|
|
|
|
+
|
|
|
|
+ this.spawnParticle = function( options ) {
|
|
|
|
+
|
|
|
|
+ options = options || {};
|
|
|
|
+
|
|
|
|
+ // 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;
|
|
|
|
+
|
|
|
|
+ // convert turbulence rating to something we can pack into a vec4
|
|
|
|
+ var turbulence = Math.floor( turbulence * 254 );
|
|
|
|
+
|
|
|
|
+ // 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 ) ) );
|
|
|
|
+
|
|
|
|
+ self.velCol.array[ i*4 + 0 ] = decodeFloat( velX, velY, velZ, turbulence ); //vel
|
|
|
|
+
|
|
|
|
+ var rgb = hexToRgb( color );
|
|
|
|
+
|
|
|
|
+ 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;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ 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
|
|
|
|
+
|
|
|
|
+ if( this.offset == 0 ) {
|
|
|
|
+ this.offset = self.PARTICLE_CURSOR;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ self.count++;
|
|
|
|
+
|
|
|
|
+ self.PARTICLE_CURSOR++;
|
|
|
|
+
|
|
|
|
+ if( self.PARTICLE_CURSOR >= self.PARTICLE_COUNT ) {
|
|
|
|
+ self.PARTICLE_CURSOR = 0;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ self.particleUpdate = true;
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ this.update = function( time ) {
|
|
|
|
+
|
|
|
|
+ self.time = time;
|
|
|
|
+ self.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;
|
|
|
|
+ } else {
|
|
|
|
+ self.posStart.updateRange.offset = 0;
|
|
|
|
+ self.posStart.updateRange.count = self.velCol.updateRange.count = ( self.PARTICLE_COUNT * 4 );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ self.posStart.needsUpdate = true;
|
|
|
|
+ self.velCol.needsUpdate = true;
|
|
|
|
+
|
|
|
|
+ self.offset = 0;
|
|
|
|
+ self.count = 0;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ this.init();
|
|
|
|
+
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+THREE.GPUParticleContainer.prototype = Object.create(THREE.Object3D.prototype);
|
|
|
|
+THREE.GPUParticleContainer.prototype.constructor = THREE.GPUParticleContainer;
|