diff --git a/README.md b/README.md index ed2b698..1f8c988 100644 --- a/README.md +++ b/README.md @@ -172,6 +172,18 @@ var particleEmitter = new ShaderParticleEmitter({ // [OPTIONAL] Acceleration variance. accelerationSpread: new THREE.Vector3(0, 0, 0), + // [OPTIONAL] The intial angle of each particle + angle: 0, + + // [OPTIONAL] How fast each particle is spinning + angularVelocity: 0, + + // [OPTIONAL] Particle spin velocity variance + angularVelocitySpread: 0, + + // [OPTIONAL] Align the particles in the direciton they are moving + angleAlignVelocity: true + // [OPTIONAL] Velocity base vector. velocity: new THREE.Vector3(0, 0, 0), @@ -269,4 +281,4 @@ See the [issues page](https://github.com/squarefeet/ShaderParticleEngine/issues) Thanks ====== -Huge thanks are extended to [Stemkoski](http://stemkoski.github.io/Three.js/) for his initial particle engine, and to [Mr Doob, AlteredQualia, et. al](https://github.com/mrdoob/three.js/graphs/contributors) for their awesome work on [THREE.js](http://threejs.org/). \ No newline at end of file +Huge thanks are extended to [Stemkoski](http://stemkoski.github.io/Three.js/) for his initial particle engine, and to [Mr Doob, AlteredQualia, et. al](https://github.com/mrdoob/three.js/graphs/contributors) for their awesome work on [THREE.js](http://threejs.org/). diff --git a/build/ShaderParticles.js b/build/ShaderParticles.js index d31a96b..a81f771 100644 --- a/build/ShaderParticles.js +++ b/build/ShaderParticles.js @@ -342,6 +342,7 @@ function ShaderParticleGroup( options ) { sizeEnd: { type: 'f', value: [] }, angle: { type: 'f', value: [] }, + angularVelocity: { type: 'f', value: [] }, angleAlignVelocity: { type: 'f', value: [] }, colorStart: { type: 'c', value: [] }, @@ -444,6 +445,7 @@ ShaderParticleGroup.prototype = { sizeStart = a.sizeStart.value, sizeEnd = a.sizeEnd.value, angle = a.angle.value, + angularVelocity = a.angularVelocity.value, angleAlignVelocity = a.angleAlignVelocity.value, colorStart = a.colorStart.value, colorMiddle = a.colorMiddle.value, @@ -477,6 +479,7 @@ ShaderParticleGroup.prototype = { sizeEnd[i] = emitter.sizeEnd; angle[i] = that._randomFloat( emitter.angle, emitter.angleSpread ); + angularVelocity[i] = that._randomFloat( emitter.angularVelocity, emitter.angularVelocitySpread ); angleAlignVelocity[i] = emitter.angleAlignVelocity ? 1.0 : 0.0; age[i] = 0.0; @@ -723,6 +726,7 @@ ShaderParticleGroup.shaders = { 'attribute float sizeStart;', 'attribute float sizeEnd;', 'attribute float angle;', + 'attribute float angularVelocity;', 'attribute float angleAlignVelocity;', // values to be passed to the fragment shader @@ -779,7 +783,7 @@ ShaderParticleGroup.shaders = { 'vAngle = -atan(pos.y, pos.x);', '}', 'else {', - 'vAngle = 0.0;', + 'vAngle = angle + ( angularVelocity * age );', '}', // Determine point size . @@ -880,6 +884,9 @@ function ShaderParticleEmitter( options ) { that.angleSpread = parseFloat( typeof options.angleSpread === 'number' ? options.angleSpread : 0 ); that.angleAlignVelocity = options.angleAlignVelocity || false; + that.angularVelocity = parseFloat( typeof options.angularVelocity === 'number' ? options.angularVelocity : 0 ); + that.angularVelocitySpread = parseFloat( typeof options.angularVelocitySpread === 'number' ? options.angularVelocitySpread : 0 ); + that.colorStart = options.colorStart instanceof THREE.Color ? options.colorStart : new THREE.Color( 'white' ); that.colorStartSpread = options.colorStartSpread instanceof THREE.Vector3 ? options.colorStartSpread : new THREE.Vector3(0,0,0); that.colorEnd = options.colorEnd instanceof THREE.Color ? options.colorEnd : that.colorStart.clone(); diff --git a/build/ShaderParticles.min.js b/build/ShaderParticles.min.js index 3da893e..8b88167 100644 --- a/build/ShaderParticles.min.js +++ b/build/ShaderParticles.min.js @@ -5,4 +5,4 @@ * * Shader-Particles may be freely distributed under the MIT license (See LICENSE.txt at root of this repository.) */ -function ShaderParticleGroup(a){var b=this;b.fixedTimeStep=parseFloat(a.fixedTimeStep||.016),b.maxAge=parseFloat(a.maxAge||3),b.texture=a.texture||null,b.hasPerspective=parseInt("number"==typeof a.hasPerspective?a.hasPerspective:1,10),b.colorize=parseInt(a.colorize||1,10),b.blending="number"==typeof a.blending?a.blending:THREE.AdditiveBlending,b.transparent=a.transparent||!0,b.alphaTest=a.alphaTest||.5,b.depthWrite=a.depthWrite||!1,b.depthTest=a.depthTest||!0,b.uniforms={duration:{type:"f",value:b.maxAge},texture:{type:"t",value:b.texture},hasPerspective:{type:"i",value:b.hasPerspective},colorize:{type:"i",value:b.colorize}},b.attributes={acceleration:{type:"v3",value:[]},velocity:{type:"v3",value:[]},alive:{type:"f",value:[]},age:{type:"f",value:[]},sizeStart:{type:"f",value:[]},sizeEnd:{type:"f",value:[]},angle:{type:"f",value:[]},angleAlignVelocity:{type:"f",value:[]},colorStart:{type:"c",value:[]},colorMiddle:{type:"c",value:[]},colorEnd:{type:"c",value:[]},opacityStart:{type:"f",value:[]},opacityMiddle:{type:"f",value:[]},opacityEnd:{type:"f",value:[]}},b.emitters=[],b._pool=[],b._poolCreationSettings=null,b._createNewWhenPoolEmpty=0,b.maxAgeMilliseconds=1e3*b.maxAge,b.geometry=new THREE.Geometry,b.material=new THREE.ShaderMaterial({uniforms:b.uniforms,attributes:b.attributes,vertexShader:ShaderParticleGroup.shaders.vertex,fragmentShader:ShaderParticleGroup.shaders.fragment,blending:b.blending,transparent:b.transparent,alphaTest:b.alphaTest,depthWrite:b.depthWrite,depthTest:b.depthTest}),b.mesh=new THREE.ParticleSystem(b.geometry,b.material),b.mesh.dynamic=!0}function ShaderParticleEmitter(a){a=a||{};var b=this;b.particlesPerSecond="number"==typeof a.particlesPerSecond?a.particlesPerSecond:100,b.type="cube"===a.type||"sphere"===a.type||"disk"===a.type?a.type:"cube",b.position=a.position instanceof THREE.Vector3?a.position:new THREE.Vector3,b.positionSpread=a.positionSpread instanceof THREE.Vector3?a.positionSpread:new THREE.Vector3,b.radius="number"==typeof a.radius?a.radius:10,b.radiusSpread="number"==typeof a.radiusSpread?a.radiusSpread:0,b.radiusScale=a.radiusScale instanceof THREE.Vector3?a.radiusScale:new THREE.Vector3(1,1,1),b.radiusSpreadClamp="number"==typeof a.radiusSpreadClamp?a.radiusSpreadClamp:0,b.acceleration=a.acceleration instanceof THREE.Vector3?a.acceleration:new THREE.Vector3,b.accelerationSpread=a.accelerationSpread instanceof THREE.Vector3?a.accelerationSpread:new THREE.Vector3,b.velocity=a.velocity instanceof THREE.Vector3?a.velocity:new THREE.Vector3,b.velocitySpread=a.velocitySpread instanceof THREE.Vector3?a.velocitySpread:new THREE.Vector3,b.speed=parseFloat("number"==typeof a.speed?a.speed:0),b.speedSpread=parseFloat("number"==typeof a.speedSpread?a.speedSpread:0),b.sizeStart=parseFloat("number"==typeof a.sizeStart?a.sizeStart:1),b.sizeStartSpread=parseFloat("number"==typeof a.sizeStartSpread?a.sizeStartSpread:0),b.sizeEnd=parseFloat("number"==typeof a.sizeEnd?a.sizeEnd:b.sizeStart),b.angle=parseFloat("number"==typeof a.angle?a.angle:0),b.angleSpread=parseFloat("number"==typeof a.angleSpread?a.angleSpread:0),b.angleAlignVelocity=a.angleAlignVelocity||!1,b.colorStart=a.colorStart instanceof THREE.Color?a.colorStart:new THREE.Color("white"),b.colorStartSpread=a.colorStartSpread instanceof THREE.Vector3?a.colorStartSpread:new THREE.Vector3(0,0,0),b.colorEnd=a.colorEnd instanceof THREE.Color?a.colorEnd:b.colorStart.clone(),b.colorMiddle=a.colorMiddle instanceof THREE.Color?a.colorMiddle:(new THREE.Color).addColors(b.colorStart,b.colorEnd).multiplyScalar(.5),b.opacityStart=parseFloat("undefined"!=typeof a.opacityStart?a.opacityStart:1),b.opacityEnd=parseFloat("number"==typeof a.opacityEnd?a.opacityEnd:0),b.opacityMiddle=parseFloat("undefined"!=typeof a.opacityMiddle?a.opacityMiddle:Math.abs(b.opacityEnd+b.opacityStart)/2),b.emitterDuration="number"==typeof a.emitterDuration?a.emitterDuration:null,b.alive=parseInt("number"==typeof a.alive?a.alive:1,10),b.isStatic="number"==typeof a.isStatic?a.isStatic:0,b.isDynamic=a.dynamic||!1,b.numParticles=0,b.attributes=null,b.vertices=null,b.verticesIndex=0,b.age=0,b.maxAge=0,b.particleIndex=0,b.__id=null,b.userData={}}!function(){this.shaderParticleUtils={randomVector3:function(a,b){var c=new THREE.Vector3;return c.copy(a),c.x+=Math.random()*b.x-b.x/2,c.y+=Math.random()*b.y-b.y/2,c.z+=Math.random()*b.z-b.z/2,c},randomColor:function(a,b){var c=new THREE.Color;return c.copy(a),c.r+=Math.random()*b.x-b.x/2,c.g+=Math.random()*b.y-b.y/2,c.b+=Math.random()*b.z-b.z/2,c.r=Math.max(0,Math.min(c.r,1)),c.g=Math.max(0,Math.min(c.g,1)),c.b=Math.max(0,Math.min(c.b,1)),c},randomFloat:function(a,b){return a+b*(Math.random()-.5)},randomVector3OnSphere:function(a,b,c,d,e){var f=2*Math.random()-1,g=6.2832*Math.random(),h=Math.sqrt(1-f*f),i=new THREE.Vector3(h*Math.cos(g),h*Math.sin(g),f),j=this._randomFloat(b,c);return e&&(j=Math.round(j/e)*e),i.multiplyScalar(j),d&&i.multiply(d),i.add(a),i},randomVector3OnDisk:function(a,b,c,d,e){var f=6.2832*Math.random(),g=this._randomFloat(b,c);e&&(g=Math.round(g/e)*e);var h=new THREE.Vector3(Math.cos(f),Math.sin(f),0).multiplyScalar(g);return d&&h.multiply(d),h.add(a),h},randomVelocityVector3OnSphere:function(a,b,c,d,e){var f=(new THREE.Vector3).subVectors(a,b);return f.normalize().multiplyScalar(Math.abs(this._randomFloat(c,d))),e&&f.multiply(e),f},randomizeExistingVector3:function(a,b,c){a.copy(b),a.x+=Math.random()*c.x-c.x/2,a.y+=Math.random()*c.y-c.y/2,a.z+=Math.random()*c.z-c.z/2},randomizeExistingColor:function(a,b,c){a.copy(b),a.r+=Math.random()*c.x-c.x/2,a.g+=Math.random()*c.y-c.y/2,a.b+=Math.random()*c.z-c.z/2,a.r=Math.max(0,Math.min(a.r,1)),a.g=Math.max(0,Math.min(a.g,1)),a.b=Math.max(0,Math.min(a.b,1))},randomizeExistingVector3OnSphere:function(a,b,c,d,e,f){var g=2*Math.random()-1,h=6.2832*Math.random(),i=Math.sqrt(1-g*g),j=this._randomFloat(c,d);f&&(j=Math.round(j/f)*f),a.set(i*Math.cos(h)*j,i*Math.sin(h)*j,g*j).multiply(e),a.add(b)},randomizeExistingVector3OnDisk:function(a,b,c,d,e,f){var g=6.2832*Math.random(),h=Math.abs(this._randomFloat(c,d));f&&(h=Math.round(h/f)*f),a.set(Math.cos(g),Math.sin(g),0).multiplyScalar(h),e&&a.multiply(e),a.add(b)},randomizeExistingVelocityVector3OnSphere:function(a,b,c,d,e){a.copy(c).sub(b).normalize().multiplyScalar(Math.abs(this._randomFloat(d,e)))},generateID:function(){var a="xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx";return a=a.replace(/[xy]/g,function(a){var b=Math.random(),c=16*b|0,d="x"===a?c:3&c|8;return d.toString(16)})}}}(),ShaderParticleGroup.prototype={_flagUpdate:function(){var a=this;return a.attributes.age.needsUpdate=!0,a.attributes.alive.needsUpdate=!0,a.attributes.angle.needsUpdate=!0,a.attributes.angleAlignVelocity.needsUpdate=!0,a.attributes.velocity.needsUpdate=!0,a.attributes.acceleration.needsUpdate=!0,a.geometry.verticesNeedUpdate=!0,a},addEmitter:function(a){var b=this;a.numParticles=a.duration?a.particlesPerSecond*(b.maxAgeu;++u)"sphere"===a.type?(c[u]=b._randomVector3OnSphere(a.position,a.radius,a.radiusSpread,a.radiusScale,a.radiusSpreadClamp),h[u]=b._randomVelocityVector3OnSphere(c[u],a.position,a.speed,a.speedSpread)):"disk"===a.type?(c[u]=b._randomVector3OnDisk(a.position,a.radius,a.radiusSpread,a.radiusScale,a.radiusSpreadClamp),h[u]=b._randomVelocityVector3OnSphere(c[u],a.position,a.speed,a.speedSpread)):(c[u]=b._randomVector3(a.position,a.positionSpread),h[u]=b._randomVector3(a.velocity,a.velocitySpread)),g[u]=b._randomVector3(a.acceleration,a.accelerationSpread),k[u]=b._randomFloat(a.sizeStart,a.sizeStartSpread),l[u]=a.sizeEnd,m[u]=b._randomFloat(a.angle,a.angleSpread),n[u]=a.angleAlignVelocity?1:0,j[u]=0,i[u]=a.isStatic?1:0,o[u]=b._randomColor(a.colorStart,a.colorStartSpread),p[u]=a.colorMiddle,q[u]=a.colorEnd,r[u]=a.opacityStart,s[u]=a.opacityMiddle,t[u]=a.opacityEnd;return a.verticesIndex=parseFloat(d),a.attributes=f,a.vertices=b.geometry.vertices,a.maxAge=b.maxAge,a.__id=b._generateID(),a.isStatic||b.emitters.push(a),b},removeEmitter:function(a){var b,c=this.emitters;if(a instanceof ShaderParticleEmitter)b=a.__id;else{if("string"!=typeof a)return void console.warn("Invalid emitter or emitter ID passed to ShaderParticleGroup#removeEmitter.");b=a}for(var d=0,e=c.length;e>d;++d)if(c[d].__id===b){c.splice(d,1);break}},tick:function(a){var b=this,c=b.emitters,d=c.length;if(a=a||b.fixedTimeStep,0!==d){for(var e=0;d>e;++e)c[e].tick(a);return b._flagUpdate(),b}},getFromPool:function(){var a=this,b=a._pool,c=a._createNewWhenPoolEmpty;return b.length?b.pop():c?new ShaderParticleEmitter(a._poolCreationSettings):null},releaseIntoPool:function(a){return a instanceof ShaderParticleEmitter?(a.reset(),this._pool.unshift(a),this):void console.error("Will not add non-emitter to particle group pool:",a)},getPool:function(){return this._pool},addPool:function(a,b,c){var d,e=this;e._poolCreationSettings=b,e._createNewWhenPoolEmpty=!!c;for(var f=0;a>f;++f)d=new ShaderParticleEmitter(b),e.addEmitter(d),e.releaseIntoPool(d);return e},_triggerSingleEmitter:function(a){var b=this,c=b.getFromPool();return null===c?void console.log("ShaderParticleGroup pool ran out."):(a&&c.position.copy(a),c.enable(),setTimeout(function(){c.disable(),b.releaseIntoPool(c)},b.maxAgeMilliseconds),b)},triggerPoolEmitter:function(a,b){var c=this;if("number"==typeof a&&a>1)for(var d=0;a>d;++d)c._triggerSingleEmitter(b);else c._triggerSingleEmitter(b);return c}};for(var i in shaderParticleUtils)ShaderParticleGroup.prototype["_"+i]=shaderParticleUtils[i];ShaderParticleGroup.shaders={vertex:["uniform float duration;","uniform int hasPerspective;","attribute vec3 colorStart;","attribute vec3 colorMiddle;","attribute vec3 colorEnd;","attribute float opacityStart;","attribute float opacityMiddle;","attribute float opacityEnd;","attribute vec3 acceleration;","attribute vec3 velocity;","attribute float alive;","attribute float age;","attribute float sizeStart;","attribute float sizeEnd;","attribute float angle;","attribute float angleAlignVelocity;","varying vec4 vColor;","varying float vAngle;","vec4 GetPos() {","vec3 newPos = vec3( position );","vec3 a = acceleration * age;","vec3 v = velocity * age;","v = v + (a * age);","newPos = newPos + v;","vec4 mvPosition = modelViewMatrix * vec4( newPos, 1.0 );","return mvPosition;","}","void main() {","float positionInTime = (age / duration);","float lerpAmount1 = (age / (0.5 * duration));","float lerpAmount2 = ((age - 0.5 * duration) / (0.5 * duration));","float halfDuration = duration / 2.0;","vAngle = angle;","if( alive > 0.5 ) {","if( positionInTime < 0.5) {","vColor = vec4( mix(colorStart, colorMiddle, lerpAmount1), mix(opacityStart, opacityMiddle, lerpAmount1) );","}","else {","vColor = vec4( mix(colorMiddle, colorEnd, lerpAmount2), mix(opacityMiddle, opacityEnd, lerpAmount2) );","}","vec4 pos = GetPos();","if( angleAlignVelocity == 1.0 ) {","vAngle = -atan(pos.y, pos.x);","}","else {","vAngle = 0.0;","}","float pointSize = mix( sizeStart, sizeEnd, positionInTime );","if( hasPerspective == 1 ) {","pointSize = pointSize * ( 300.0 / length( pos.xyz ) );","}","gl_PointSize = pointSize;","gl_Position = projectionMatrix * pos;","}","else {","vColor = vec4( 0.0, 0.0, 0.0, 0.0 );","gl_Position = vec4(1000000000.0, 1000000000.0, 1000000000.0, 0.0);","}","}"].join("\n"),fragment:["uniform sampler2D texture;","uniform int colorize;","varying vec4 vColor;","varying float vAngle;","void main() {","float c = cos(vAngle);","float s = sin(vAngle);","vec2 rotatedUV = vec2(c * (gl_PointCoord.x - 0.5) + s * (gl_PointCoord.y - 0.5) + 0.5,","c * (gl_PointCoord.y - 0.5) - s * (gl_PointCoord.x - 0.5) + 0.5);","vec4 rotatedTexture = texture2D( texture, rotatedUV );","if( colorize == 1 ) {","gl_FragColor = vColor * rotatedTexture;","}","else {","gl_FragColor = rotatedTexture;","}","}"].join("\n")},ShaderParticleEmitter.prototype={_resetParticle:function(a){var b=this,c=b.type,d=b.positionSpread,e=b.vertices[a],f=b.attributes,g=f.velocity.value[a],h=b.velocitySpread,i=b.accelerationSpread;"cube"===c&&0===d.x&&0===d.y&&0===d.z||"sphere"===c&&0===b.radius||"disk"===c&&0===b.radius?(e.copy(b.position),b._randomizeExistingVector3(g,b.velocity,h),"cube"===c&&b._randomizeExistingVector3(b.attributes.acceleration.value[a],b.acceleration,i)):"cube"===c?(b._randomizeExistingVector3(e,b.position,d),b._randomizeExistingVector3(g,b.velocity,h),b._randomizeExistingVector3(b.attributes.acceleration.value[a],b.acceleration,i)):"sphere"===c?(b._randomizeExistingVector3OnSphere(e,b.position,b.radius,b.radiusSpread,b.radiusScale,b.radiusSpreadClamp),b._randomizeExistingVelocityVector3OnSphere(g,b.position,e,b.speed,b.speedSpread)):"disk"===c&&(b._randomizeExistingVector3OnDisk(e,b.position,b.radius,b.radiusSpread,b.radiusScale,b.radiusSpreadClamp),b._randomizeExistingVelocityVector3OnSphere(g,b.position,e,b.speed,b.speedSpread)),b.isDynamic&&b._checkValues(a)},_checkValues:function(a){var b=this,c=b.attributes;(0!==b.sizeStartSpread||c.sizeStart.value[a]!==b.sizeStart)&&(c.sizeStart.value[a]=b._randomFloat(b.sizeStart,b.sizeStartSpread),c.sizeStart.needsUpdate=!0),c.sizeEnd.value[a]!==b.sizeEnd&&(c.sizeEnd.value[a]=b.sizeEnd,c.sizeEnd.needsUpdate=!0),c.opacityStart.value[a]!==b.opacityStart&&(c.opacityStart.value[a]=b.opacityStart,c.opacityStart.needsUpdate=!0),c.opacityMiddle.value[a]!==b.opacityMiddle&&(c.opacityMiddle.value[a]=b.opacityMiddle,c.opacityMiddle.needsUpdate=!0),c.opacityEnd.value[a]!==b.opacityEnd&&(c.opacityEnd.value[a]=b.opacityEnd,c.opacityEnd.needsUpdate=!0),c.angleAlignVelocity.value[a]!==b.angleAlignVelocity?(c.angleAlignVelocity.value[a]=b.angleAlignVelocity?1:0,c.angleAlignVelocity.needsUpdate=!0):b.angleAlignVelocity||c.angle.value[a]===b.angle||(c.angle.value[a]=b.angle,c.angle.needsUpdate=!0)},tick:function(a){if(!this.isStatic){for(var b=this,c=b.attributes,d=c.alive.value,e=c.age.value,f=b.verticesIndex,g=b.numParticles,h=f+g,i=b.particlesPerSecond,j=i*a,k=b.maxAge,l=b.age,m=b.emitterDuration,n=b.particleIndex,o=f;h>o;++o)1===d[o]&&(e[o]+=a),e[o]>=k&&(e[o]=0,d[o]=0);if(0===b.alive)return void(b.age=0);if("number"==typeof m&&l>m)return b.alive=0,void(b.age=0);var p=Math.max(Math.min(h,n+j),0);for(o=0|n;p>o;++o)1!==d[o]&&(d[o]=1,b._resetParticle(o));b.particleIndex+=j,b.particleIndex<0&&(b.particleIndex=0),n>=f+b.numParticles&&(b.particleIndex=parseFloat(f)),b.age+=a,b.age<0&&(b.age=0)}},reset:function(a){var b=this;if(b.age=0,b.alive=0,a)for(var c=b.verticesIndex,d=b.verticesIndex+b.numParticles,e=b.attributes,f=e.alive.value,g=e.age.value,h=c;d>h;++h)f[h]=0,g[h]=0;return b},enable:function(){this.alive=1},disable:function(){this.alive=0},_setRandomVector3Attribute:function(a,b,c){var d=this,e=d.verticesIndex,f=e+d.numParticles,g=d.attributes.alive.value;c=c||new THREE.Vector3;for(var h=e;f>h;++h)0===g[h]&&d._randomizeExistingVector3(a.value[h],b,c)},_setRandomColorAttribute:function(a,b,c){var d=this,e=d.verticesIndex,f=e+d.numParticles;c=c||new THREE.Vector3;for(var g=e;f>g;++g)d._randomizeExistingColor(a.value[g],b,c)},_setRandomFloatAttribute:function(a,b,c){var d=this,e=d.verticesIndex,f=e+d.numParticles,g=d.attributes.alive.value;c=c||0;for(var h=e;f>h;++h)0===g[h]&&(a.value[h]=d._randomFloat(b,c))},setOption:function(a,b){var c=this;if("undefined"==typeof c.attributes[a]&&"undefined"==typeof c[a])return void console.log("Won't set",a+".","Invalid property.");if(c.attributes[a])c[a]=b,"number"==typeof c[a]?c._setRandomFloatAttribute(c.attributes[a],c[a],c[a+"Spread"]):c[a]instanceof THREE.Vector3?c._setRandomVector3Attribute(c.attributes[a],c[a],c[a+"Spread"]):c[a]instanceof THREE.Color&&c._setRandomColorAttribute(c.attributes[a],c[a],c[a+"Spread"]),c.attributes[a].needsUpdate=!0;else if(c[a]&&(c[a]=b,a.indexOf("Spread")>-1&&"cube"===c.type)){var d=a.replace("Spread","");c.setOption(d,c[d])}}};for(var i in shaderParticleUtils)ShaderParticleEmitter.prototype["_"+i]=shaderParticleUtils[i]; \ No newline at end of file +function ShaderParticleGroup(a){var b=this;b.fixedTimeStep=parseFloat(a.fixedTimeStep||.016),b.maxAge=parseFloat(a.maxAge||3),b.texture=a.texture||null,b.hasPerspective=parseInt("number"==typeof a.hasPerspective?a.hasPerspective:1,10),b.colorize=parseInt(a.colorize||1,10),b.blending="number"==typeof a.blending?a.blending:THREE.AdditiveBlending,b.transparent=a.transparent||!0,b.alphaTest=a.alphaTest||.5,b.depthWrite=a.depthWrite||!1,b.depthTest=a.depthTest||!0,b.uniforms={duration:{type:"f",value:b.maxAge},texture:{type:"t",value:b.texture},hasPerspective:{type:"i",value:b.hasPerspective},colorize:{type:"i",value:b.colorize}},b.attributes={acceleration:{type:"v3",value:[]},velocity:{type:"v3",value:[]},alive:{type:"f",value:[]},age:{type:"f",value:[]},sizeStart:{type:"f",value:[]},sizeEnd:{type:"f",value:[]},angle:{type:"f",value:[]},angularVelocity:{type:"f",value:[]},angleAlignVelocity:{type:"f",value:[]},colorStart:{type:"c",value:[]},colorMiddle:{type:"c",value:[]},colorEnd:{type:"c",value:[]},opacityStart:{type:"f",value:[]},opacityMiddle:{type:"f",value:[]},opacityEnd:{type:"f",value:[]}},b.emitters=[],b._pool=[],b._poolCreationSettings=null,b._createNewWhenPoolEmpty=0,b.maxAgeMilliseconds=1e3*b.maxAge,b.geometry=new THREE.Geometry,b.material=new THREE.ShaderMaterial({uniforms:b.uniforms,attributes:b.attributes,vertexShader:ShaderParticleGroup.shaders.vertex,fragmentShader:ShaderParticleGroup.shaders.fragment,blending:b.blending,transparent:b.transparent,alphaTest:b.alphaTest,depthWrite:b.depthWrite,depthTest:b.depthTest}),b.mesh=new THREE.ParticleSystem(b.geometry,b.material),b.mesh.dynamic=!0}function ShaderParticleEmitter(a){a=a||{};var b=this;b.particlesPerSecond="number"==typeof a.particlesPerSecond?a.particlesPerSecond:100,b.type="cube"===a.type||"sphere"===a.type||"disk"===a.type?a.type:"cube",b.position=a.position instanceof THREE.Vector3?a.position:new THREE.Vector3,b.positionSpread=a.positionSpread instanceof THREE.Vector3?a.positionSpread:new THREE.Vector3,b.radius="number"==typeof a.radius?a.radius:10,b.radiusSpread="number"==typeof a.radiusSpread?a.radiusSpread:0,b.radiusScale=a.radiusScale instanceof THREE.Vector3?a.radiusScale:new THREE.Vector3(1,1,1),b.radiusSpreadClamp="number"==typeof a.radiusSpreadClamp?a.radiusSpreadClamp:0,b.acceleration=a.acceleration instanceof THREE.Vector3?a.acceleration:new THREE.Vector3,b.accelerationSpread=a.accelerationSpread instanceof THREE.Vector3?a.accelerationSpread:new THREE.Vector3,b.velocity=a.velocity instanceof THREE.Vector3?a.velocity:new THREE.Vector3,b.velocitySpread=a.velocitySpread instanceof THREE.Vector3?a.velocitySpread:new THREE.Vector3,b.speed=parseFloat("number"==typeof a.speed?a.speed:0),b.speedSpread=parseFloat("number"==typeof a.speedSpread?a.speedSpread:0),b.sizeStart=parseFloat("number"==typeof a.sizeStart?a.sizeStart:1),b.sizeStartSpread=parseFloat("number"==typeof a.sizeStartSpread?a.sizeStartSpread:0),b.sizeEnd=parseFloat("number"==typeof a.sizeEnd?a.sizeEnd:b.sizeStart),b.angle=parseFloat("number"==typeof a.angle?a.angle:0),b.angleSpread=parseFloat("number"==typeof a.angleSpread?a.angleSpread:0),b.angleAlignVelocity=a.angleAlignVelocity||!1,b.angularVelocity=parseFloat("number"==typeof a.angularVelocity?a.angularVelocity:0),b.angularVelocitySpread=parseFloat("number"==typeof a.angularVelocitySpread?a.angularVelocitySpread:0),b.colorStart=a.colorStart instanceof THREE.Color?a.colorStart:new THREE.Color("white"),b.colorStartSpread=a.colorStartSpread instanceof THREE.Vector3?a.colorStartSpread:new THREE.Vector3(0,0,0),b.colorEnd=a.colorEnd instanceof THREE.Color?a.colorEnd:b.colorStart.clone(),b.colorMiddle=a.colorMiddle instanceof THREE.Color?a.colorMiddle:(new THREE.Color).addColors(b.colorStart,b.colorEnd).multiplyScalar(.5),b.opacityStart=parseFloat("undefined"!=typeof a.opacityStart?a.opacityStart:1),b.opacityEnd=parseFloat("number"==typeof a.opacityEnd?a.opacityEnd:0),b.opacityMiddle=parseFloat("undefined"!=typeof a.opacityMiddle?a.opacityMiddle:Math.abs(b.opacityEnd+b.opacityStart)/2),b.emitterDuration="number"==typeof a.emitterDuration?a.emitterDuration:null,b.alive=parseInt("number"==typeof a.alive?a.alive:1,10),b.isStatic="number"==typeof a.isStatic?a.isStatic:0,b.isDynamic=a.dynamic||!1,b.numParticles=0,b.attributes=null,b.vertices=null,b.verticesIndex=0,b.age=0,b.maxAge=0,b.particleIndex=0,b.__id=null,b.userData={}}!function(){this.shaderParticleUtils={randomVector3:function(a,b){var c=new THREE.Vector3;return c.copy(a),c.x+=Math.random()*b.x-b.x/2,c.y+=Math.random()*b.y-b.y/2,c.z+=Math.random()*b.z-b.z/2,c},randomColor:function(a,b){var c=new THREE.Color;return c.copy(a),c.r+=Math.random()*b.x-b.x/2,c.g+=Math.random()*b.y-b.y/2,c.b+=Math.random()*b.z-b.z/2,c.r=Math.max(0,Math.min(c.r,1)),c.g=Math.max(0,Math.min(c.g,1)),c.b=Math.max(0,Math.min(c.b,1)),c},randomFloat:function(a,b){return a+b*(Math.random()-.5)},randomVector3OnSphere:function(a,b,c,d,e){var f=2*Math.random()-1,g=6.2832*Math.random(),h=Math.sqrt(1-f*f),i=new THREE.Vector3(h*Math.cos(g),h*Math.sin(g),f),j=this._randomFloat(b,c);return e&&(j=Math.round(j/e)*e),i.multiplyScalar(j),d&&i.multiply(d),i.add(a),i},randomVector3OnDisk:function(a,b,c,d,e){var f=6.2832*Math.random(),g=this._randomFloat(b,c);e&&(g=Math.round(g/e)*e);var h=new THREE.Vector3(Math.cos(f),Math.sin(f),0).multiplyScalar(g);return d&&h.multiply(d),h.add(a),h},randomVelocityVector3OnSphere:function(a,b,c,d,e){var f=(new THREE.Vector3).subVectors(a,b);return f.normalize().multiplyScalar(Math.abs(this._randomFloat(c,d))),e&&f.multiply(e),f},randomizeExistingVector3:function(a,b,c){a.copy(b),a.x+=Math.random()*c.x-c.x/2,a.y+=Math.random()*c.y-c.y/2,a.z+=Math.random()*c.z-c.z/2},randomizeExistingColor:function(a,b,c){a.copy(b),a.r+=Math.random()*c.x-c.x/2,a.g+=Math.random()*c.y-c.y/2,a.b+=Math.random()*c.z-c.z/2,a.r=Math.max(0,Math.min(a.r,1)),a.g=Math.max(0,Math.min(a.g,1)),a.b=Math.max(0,Math.min(a.b,1))},randomizeExistingVector3OnSphere:function(a,b,c,d,e,f){var g=2*Math.random()-1,h=6.2832*Math.random(),i=Math.sqrt(1-g*g),j=this._randomFloat(c,d);f&&(j=Math.round(j/f)*f),a.set(i*Math.cos(h)*j,i*Math.sin(h)*j,g*j).multiply(e),a.add(b)},randomizeExistingVector3OnDisk:function(a,b,c,d,e,f){var g=6.2832*Math.random(),h=Math.abs(this._randomFloat(c,d));f&&(h=Math.round(h/f)*f),a.set(Math.cos(g),Math.sin(g),0).multiplyScalar(h),e&&a.multiply(e),a.add(b)},randomizeExistingVelocityVector3OnSphere:function(a,b,c,d,e){a.copy(c).sub(b).normalize().multiplyScalar(Math.abs(this._randomFloat(d,e)))},generateID:function(){var a="xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx";return a=a.replace(/[xy]/g,function(a){var b=Math.random(),c=16*b|0,d="x"===a?c:3&c|8;return d.toString(16)})}}}(),ShaderParticleGroup.prototype={_flagUpdate:function(){var a=this;return a.attributes.age.needsUpdate=!0,a.attributes.alive.needsUpdate=!0,a.attributes.angle.needsUpdate=!0,a.attributes.angleAlignVelocity.needsUpdate=!0,a.attributes.velocity.needsUpdate=!0,a.attributes.acceleration.needsUpdate=!0,a.geometry.verticesNeedUpdate=!0,a},addEmitter:function(a){var b=this;a.numParticles=a.duration?a.particlesPerSecond*(b.maxAgev;++v)"sphere"===a.type?(c[v]=b._randomVector3OnSphere(a.position,a.radius,a.radiusSpread,a.radiusScale,a.radiusSpreadClamp),h[v]=b._randomVelocityVector3OnSphere(c[v],a.position,a.speed,a.speedSpread)):"disk"===a.type?(c[v]=b._randomVector3OnDisk(a.position,a.radius,a.radiusSpread,a.radiusScale,a.radiusSpreadClamp),h[v]=b._randomVelocityVector3OnSphere(c[v],a.position,a.speed,a.speedSpread)):(c[v]=b._randomVector3(a.position,a.positionSpread),h[v]=b._randomVector3(a.velocity,a.velocitySpread)),g[v]=b._randomVector3(a.acceleration,a.accelerationSpread),k[v]=b._randomFloat(a.sizeStart,a.sizeStartSpread),l[v]=a.sizeEnd,m[v]=b._randomFloat(a.angle,a.angleSpread),n[v]=b._randomFloat(a.angularVelocity,a.angularVelocitySpread),o[v]=a.angleAlignVelocity?1:0,j[v]=0,i[v]=a.isStatic?1:0,p[v]=b._randomColor(a.colorStart,a.colorStartSpread),q[v]=a.colorMiddle,r[v]=a.colorEnd,s[v]=a.opacityStart,t[v]=a.opacityMiddle,u[v]=a.opacityEnd;return a.verticesIndex=parseFloat(d),a.attributes=f,a.vertices=b.geometry.vertices,a.maxAge=b.maxAge,a.__id=b._generateID(),a.isStatic||b.emitters.push(a),b},removeEmitter:function(a){var b,c=this.emitters;if(a instanceof ShaderParticleEmitter)b=a.__id;else{if("string"!=typeof a)return console.warn("Invalid emitter or emitter ID passed to ShaderParticleGroup#removeEmitter."),void 0;b=a}for(var d=0,e=c.length;e>d;++d)if(c[d].__id===b){c.splice(d,1);break}},tick:function(a){var b=this,c=b.emitters,d=c.length;if(a=a||b.fixedTimeStep,0!==d){for(var e=0;d>e;++e)c[e].tick(a);return b._flagUpdate(),b}},getFromPool:function(){var a=this,b=a._pool,c=a._createNewWhenPoolEmpty;return b.length?b.pop():c?new ShaderParticleEmitter(a._poolCreationSettings):null},releaseIntoPool:function(a){return a instanceof ShaderParticleEmitter?(a.reset(),this._pool.unshift(a),this):(console.error("Will not add non-emitter to particle group pool:",a),void 0)},getPool:function(){return this._pool},addPool:function(a,b,c){var d,e=this;e._poolCreationSettings=b,e._createNewWhenPoolEmpty=!!c;for(var f=0;a>f;++f)d=new ShaderParticleEmitter(b),e.addEmitter(d),e.releaseIntoPool(d);return e},_triggerSingleEmitter:function(a){var b=this,c=b.getFromPool();return null===c?(console.log("ShaderParticleGroup pool ran out."),void 0):(a&&c.position.copy(a),c.enable(),setTimeout(function(){c.disable(),b.releaseIntoPool(c)},b.maxAgeMilliseconds),b)},triggerPoolEmitter:function(a,b){var c=this;if("number"==typeof a&&a>1)for(var d=0;a>d;++d)c._triggerSingleEmitter(b);else c._triggerSingleEmitter(b);return c}};for(var i in shaderParticleUtils)ShaderParticleGroup.prototype["_"+i]=shaderParticleUtils[i];ShaderParticleGroup.shaders={vertex:["uniform float duration;","uniform int hasPerspective;","attribute vec3 colorStart;","attribute vec3 colorMiddle;","attribute vec3 colorEnd;","attribute float opacityStart;","attribute float opacityMiddle;","attribute float opacityEnd;","attribute vec3 acceleration;","attribute vec3 velocity;","attribute float alive;","attribute float age;","attribute float sizeStart;","attribute float sizeEnd;","attribute float angle;","attribute float angularVelocity;","attribute float angleAlignVelocity;","varying vec4 vColor;","varying float vAngle;","vec4 GetPos() {","vec3 newPos = vec3( position );","vec3 a = acceleration * age;","vec3 v = velocity * age;","v = v + (a * age);","newPos = newPos + v;","vec4 mvPosition = modelViewMatrix * vec4( newPos, 1.0 );","return mvPosition;","}","void main() {","float positionInTime = (age / duration);","float lerpAmount1 = (age / (0.5 * duration));","float lerpAmount2 = ((age - 0.5 * duration) / (0.5 * duration));","float halfDuration = duration / 2.0;","vAngle = angle;","if( alive > 0.5 ) {","if( positionInTime < 0.5) {","vColor = vec4( mix(colorStart, colorMiddle, lerpAmount1), mix(opacityStart, opacityMiddle, lerpAmount1) );","}","else {","vColor = vec4( mix(colorMiddle, colorEnd, lerpAmount2), mix(opacityMiddle, opacityEnd, lerpAmount2) );","}","vec4 pos = GetPos();","if( angleAlignVelocity == 1.0 ) {","vAngle = -atan(pos.y, pos.x);","}","else {","vAngle = angle + ( angularVelocity * age );","}","float pointSize = mix( sizeStart, sizeEnd, positionInTime );","if( hasPerspective == 1 ) {","pointSize = pointSize * ( 300.0 / length( pos.xyz ) );","}","gl_PointSize = pointSize;","gl_Position = projectionMatrix * pos;","}","else {","vColor = vec4( 0.0, 0.0, 0.0, 0.0 );","gl_Position = vec4(1000000000.0, 1000000000.0, 1000000000.0, 0.0);","}","}"].join("\n"),fragment:["uniform sampler2D texture;","uniform int colorize;","varying vec4 vColor;","varying float vAngle;","void main() {","float c = cos(vAngle);","float s = sin(vAngle);","vec2 rotatedUV = vec2(c * (gl_PointCoord.x - 0.5) + s * (gl_PointCoord.y - 0.5) + 0.5,","c * (gl_PointCoord.y - 0.5) - s * (gl_PointCoord.x - 0.5) + 0.5);","vec4 rotatedTexture = texture2D( texture, rotatedUV );","if( colorize == 1 ) {","gl_FragColor = vColor * rotatedTexture;","}","else {","gl_FragColor = rotatedTexture;","}","}"].join("\n")},ShaderParticleEmitter.prototype={_resetParticle:function(a){var b=this,c=b.type,d=b.positionSpread,e=b.vertices[a],f=b.attributes,g=f.velocity.value[a],h=b.velocitySpread,i=b.accelerationSpread;"cube"===c&&0===d.x&&0===d.y&&0===d.z||"sphere"===c&&0===b.radius||"disk"===c&&0===b.radius?(e.copy(b.position),b._randomizeExistingVector3(g,b.velocity,h),"cube"===c&&b._randomizeExistingVector3(b.attributes.acceleration.value[a],b.acceleration,i)):"cube"===c?(b._randomizeExistingVector3(e,b.position,d),b._randomizeExistingVector3(g,b.velocity,h),b._randomizeExistingVector3(b.attributes.acceleration.value[a],b.acceleration,i)):"sphere"===c?(b._randomizeExistingVector3OnSphere(e,b.position,b.radius,b.radiusSpread,b.radiusScale,b.radiusSpreadClamp),b._randomizeExistingVelocityVector3OnSphere(g,b.position,e,b.speed,b.speedSpread)):"disk"===c&&(b._randomizeExistingVector3OnDisk(e,b.position,b.radius,b.radiusSpread,b.radiusScale,b.radiusSpreadClamp),b._randomizeExistingVelocityVector3OnSphere(g,b.position,e,b.speed,b.speedSpread)),b.isDynamic&&b._checkValues(a)},_checkValues:function(a){var b=this,c=b.attributes;(0!==b.sizeStartSpread||c.sizeStart.value[a]!==b.sizeStart)&&(c.sizeStart.value[a]=b._randomFloat(b.sizeStart,b.sizeStartSpread),c.sizeStart.needsUpdate=!0),c.sizeEnd.value[a]!==b.sizeEnd&&(c.sizeEnd.value[a]=b.sizeEnd,c.sizeEnd.needsUpdate=!0),c.opacityStart.value[a]!==b.opacityStart&&(c.opacityStart.value[a]=b.opacityStart,c.opacityStart.needsUpdate=!0),c.opacityMiddle.value[a]!==b.opacityMiddle&&(c.opacityMiddle.value[a]=b.opacityMiddle,c.opacityMiddle.needsUpdate=!0),c.opacityEnd.value[a]!==b.opacityEnd&&(c.opacityEnd.value[a]=b.opacityEnd,c.opacityEnd.needsUpdate=!0),c.angleAlignVelocity.value[a]!==b.angleAlignVelocity?(c.angleAlignVelocity.value[a]=b.angleAlignVelocity?1:0,c.angleAlignVelocity.needsUpdate=!0):b.angleAlignVelocity||c.angle.value[a]===b.angle||(c.angle.value[a]=b.angle,c.angle.needsUpdate=!0)},tick:function(a){if(!this.isStatic){for(var b=this,c=b.attributes,d=c.alive.value,e=c.age.value,f=b.verticesIndex,g=b.numParticles,h=f+g,i=b.particlesPerSecond,j=i*a,k=b.maxAge,l=b.age,m=b.emitterDuration,n=b.particleIndex,o=f;h>o;++o)1===d[o]&&(e[o]+=a),e[o]>=k&&(e[o]=0,d[o]=0);if(0===b.alive)return b.age=0,void 0;if("number"==typeof m&&l>m)return b.alive=0,b.age=0,void 0;var p=Math.max(Math.min(h,n+j),0);for(o=0|n;p>o;++o)1!==d[o]&&(d[o]=1,b._resetParticle(o));b.particleIndex+=j,b.particleIndex<0&&(b.particleIndex=0),n>=f+b.numParticles&&(b.particleIndex=parseFloat(f)),b.age+=a,b.age<0&&(b.age=0)}},reset:function(a){var b=this;if(b.age=0,b.alive=0,a)for(var c=b.verticesIndex,d=b.verticesIndex+b.numParticles,e=b.attributes,f=e.alive.value,g=e.age.value,h=c;d>h;++h)f[h]=0,g[h]=0;return b},enable:function(){this.alive=1},disable:function(){this.alive=0},_setRandomVector3Attribute:function(a,b,c){var d=this,e=d.verticesIndex,f=e+d.numParticles,g=d.attributes.alive.value;c=c||new THREE.Vector3;for(var h=e;f>h;++h)0===g[h]&&d._randomizeExistingVector3(a.value[h],b,c)},_setRandomColorAttribute:function(a,b,c){var d=this,e=d.verticesIndex,f=e+d.numParticles;c=c||new THREE.Vector3;for(var g=e;f>g;++g)d._randomizeExistingColor(a.value[g],b,c)},_setRandomFloatAttribute:function(a,b,c){var d=this,e=d.verticesIndex,f=e+d.numParticles,g=d.attributes.alive.value;c=c||0;for(var h=e;f>h;++h)0===g[h]&&(a.value[h]=d._randomFloat(b,c))},setOption:function(a,b){var c=this;if("undefined"==typeof c.attributes[a]&&"undefined"==typeof c[a])return console.log("Won't set",a+".","Invalid property."),void 0;if(c.attributes[a])c[a]=b,"number"==typeof c[a]?c._setRandomFloatAttribute(c.attributes[a],c[a],c[a+"Spread"]):c[a]instanceof THREE.Vector3?c._setRandomVector3Attribute(c.attributes[a],c[a],c[a+"Spread"]):c[a]instanceof THREE.Color&&c._setRandomColorAttribute(c.attributes[a],c[a],c[a+"Spread"]),c.attributes[a].needsUpdate=!0;else if(c[a]&&(c[a]=b,a.indexOf("Spread")>-1&&"cube"===c.type)){var d=a.replace("Spread","");c.setOption(d,c[d])}}};for(var i in shaderParticleUtils)ShaderParticleEmitter.prototype["_"+i]=shaderParticleUtils[i]; \ No newline at end of file diff --git a/src/ShaderParticleEmitter.js b/src/ShaderParticleEmitter.js index 6479bb9..b61a8ed 100644 --- a/src/ShaderParticleEmitter.js +++ b/src/ShaderParticleEmitter.js @@ -48,6 +48,9 @@ function ShaderParticleEmitter( options ) { that.angleSpread = parseFloat( typeof options.angleSpread === 'number' ? options.angleSpread : 0 ); that.angleAlignVelocity = options.angleAlignVelocity || false; + that.angularVelocity = parseFloat( typeof options.angularVelocity === 'number' ? options.angularVelocity : 0 ); + that.angularVelocitySpread = parseFloat( typeof options.angularVelocitySpread === 'number' ? options.angularVelocitySpread : 0 ); + that.colorStart = options.colorStart instanceof THREE.Color ? options.colorStart : new THREE.Color( 'white' ); that.colorStartSpread = options.colorStartSpread instanceof THREE.Vector3 ? options.colorStartSpread : new THREE.Vector3(0,0,0); that.colorEnd = options.colorEnd instanceof THREE.Color ? options.colorEnd : that.colorStart.clone(); diff --git a/src/ShaderParticleGroup.js b/src/ShaderParticleGroup.js index 43d4855..55298d8 100644 --- a/src/ShaderParticleGroup.js +++ b/src/ShaderParticleGroup.js @@ -47,6 +47,7 @@ function ShaderParticleGroup( options ) { sizeEnd: { type: 'f', value: [] }, angle: { type: 'f', value: [] }, + angularVelocity: { type: 'f', value: [] }, angleAlignVelocity: { type: 'f', value: [] }, colorStart: { type: 'c', value: [] }, @@ -149,6 +150,7 @@ ShaderParticleGroup.prototype = { sizeStart = a.sizeStart.value, sizeEnd = a.sizeEnd.value, angle = a.angle.value, + angularVelocity = a.angularVelocity.value, angleAlignVelocity = a.angleAlignVelocity.value, colorStart = a.colorStart.value, colorMiddle = a.colorMiddle.value, @@ -182,6 +184,7 @@ ShaderParticleGroup.prototype = { sizeEnd[i] = emitter.sizeEnd; angle[i] = that._randomFloat( emitter.angle, emitter.angleSpread ); + angularVelocity[i] = that._randomFloat( emitter.angularVelocity, emitter.angularVelocitySpread ); angleAlignVelocity[i] = emitter.angleAlignVelocity ? 1.0 : 0.0; age[i] = 0.0; @@ -428,6 +431,7 @@ ShaderParticleGroup.shaders = { 'attribute float sizeStart;', 'attribute float sizeEnd;', 'attribute float angle;', + 'attribute float angularVelocity;', 'attribute float angleAlignVelocity;', // values to be passed to the fragment shader @@ -484,7 +488,7 @@ ShaderParticleGroup.shaders = { 'vAngle = -atan(pos.y, pos.x);', '}', 'else {', - 'vAngle = 0.0;', + 'vAngle = angle + ( angularVelocity * age );', '}', // Determine point size .