From c44f1445b3bd1b6b24206cfd2fa64244f26de3a4 Mon Sep 17 00:00:00 2001 From: Luke Moody Date: Sun, 28 Jun 2020 13:02:42 +0100 Subject: [PATCH 1/5] Fixes #134. Switched from `BufferAttribute.dynamic` to `BufferAttribute.usage`. Replaced use of `texture` uniform with `tex`. Bumped package to `1.1.0`. --- build/SPE.js | 28 +- build/SPE.min.js | 14 +- package-lock.json | 1472 ++++++++++++++++++++++++++++ package.json | 7 +- src/core/SPE.Group.js | 6 +- src/helpers/SPE.ShaderAttribute.js | 14 +- src/shaders/SPE.shaderChunks.js | 4 +- 7 files changed, 1516 insertions(+), 29 deletions(-) create mode 100644 package-lock.json diff --git a/build/SPE.js b/build/SPE.js index 92ee19c..f8f2169 100644 --- a/build/SPE.js +++ b/build/SPE.js @@ -547,7 +547,9 @@ SPE.ShaderAttribute.prototype.resetUpdateRange = function() { SPE.ShaderAttribute.prototype.resetDynamic = function() { 'use strict'; - this.bufferAttribute.dynamic = this.dynamicBuffer; + this.bufferAttribute.usage = this.dynamicBuffer ? + THREE.DynamicDrawUsage : + THREE.StaticDrawUsage; }; /** @@ -571,7 +573,12 @@ SPE.ShaderAttribute.prototype.forceUpdateAll = function() { this.bufferAttribute.array = this.typedArray.array; this.bufferAttribute.updateRange.offset = 0; this.bufferAttribute.updateRange.count = -1; - this.bufferAttribute.dynamic = false; + // this.bufferAttribute.dynamic = false; + // this.bufferAttribute.usage = this.dynamicBuffer ? + // THREE.DynamicDrawUsage : + // THREE.StaticDrawUsage; + + this.bufferAttribute.usage = THREE.StaticDrawUsage; this.bufferAttribute.needsUpdate = true; }; @@ -640,7 +647,10 @@ SPE.ShaderAttribute.prototype._createBufferAttribute = function( size ) { } this.bufferAttribute = new THREE.BufferAttribute( this.typedArray.array, this.componentSize ); - this.bufferAttribute.dynamic = this.dynamicBuffer; + // this.bufferAttribute.dynamic = this.dynamicBuffer; + this.bufferAttribute.usage = this.dynamicBuffer ? + THREE.DynamicDrawUsage : + THREE.StaticDrawUsage; }; /** @@ -669,7 +679,7 @@ SPE.shaderChunks = { uniforms: [ 'uniform float deltaTime;', 'uniform float runTime;', - 'uniform sampler2D texture;', + 'uniform sampler2D tex;', 'uniform vec4 textureAnimation;', 'uniform float scale;', ].join( '\n' ), @@ -910,7 +920,7 @@ SPE.shaderChunks = { ' #endif', '', - ' vec4 rotatedTexture = texture2D( texture, vUv );', + ' vec4 rotatedTexture = texture2D( tex, vUv );', ].join( '\n' ) }; @@ -1952,7 +1962,7 @@ SPE.Group = function( options ) { // Map of uniforms to be applied to the ShaderMaterial instance. this.uniforms = { - texture: { + tex: { type: 't', value: this.texture }, @@ -1967,7 +1977,7 @@ SPE.Group = function( options ) { }, fogColor: { type: 'c', - value: null + value: this.fog ? new THREE.Color() : null }, fogNear: { type: 'f', @@ -2328,9 +2338,9 @@ SPE.Group.prototype.getFromPool = function() { } else if ( createNew ) { var emitter = new SPE.Emitter( this._poolCreationSettings ); - + this.addEmitter( emitter ); - + return emitter; } diff --git a/build/SPE.min.js b/build/SPE.min.js index dcc9d56..6cfdcc8 100644 --- a/build/SPE.min.js +++ b/build/SPE.min.js @@ -9,16 +9,16 @@ var SPE={distributions:{BOX:1,SPHERE:2,DISC:3,LINE:4},valueOverLifetimeLength:4} SPE.TypedArrayHelper.prototype.splice=function(a,b){"use strict";a*=this.componentSize,b*=this.componentSize;for(var c=[],d=this.array,e=d.length,f=0;f=b)&&c.push(d[f]);return this.setFromArray(0,c),this},SPE.TypedArrayHelper.prototype.setFromArray=function(a,b){"use strict";var c=b.length,d=a+c;return d>this.array.length?this.grow(d):d=81&&(this.bufferAttribute.count=this.bufferAttribute.array.length/this.bufferAttribute.itemSize),void(this.bufferAttribute.needsUpdate=!0);this.bufferAttribute=new THREE.BufferAttribute(this.typedArray.array,this.componentSize),this.bufferAttribute.dynamic=this.dynamicBuffer},SPE.ShaderAttribute.prototype.getLength=function(){"use strict";return null===this.typedArray?0:this.typedArray.array.length},SPE.shaderChunks={defines:["#define PACKED_COLOR_SIZE 256.0","#define PACKED_COLOR_DIVISOR 255.0"].join("\n"), -uniforms:["uniform float deltaTime;","uniform float runTime;","uniform sampler2D texture;","uniform vec4 textureAnimation;","uniform float scale;"].join("\n"),attributes:["attribute vec4 acceleration;","attribute vec3 velocity;","attribute vec4 rotation;","attribute vec3 rotationCenter;","attribute vec4 params;","attribute vec4 size;","attribute vec4 angle;","attribute vec4 color;","attribute vec4 opacity;"].join("\n"),varyings:["varying vec4 vColor;","#ifdef SHOULD_ROTATE_TEXTURE"," varying float vAngle;","#endif","#ifdef SHOULD_CALCULATE_SPRITE"," varying vec4 vSpriteSheet;","#endif"].join("\n"), +b.count=Math.min(this.updateMax-this.updateMin+this.componentSize,this.typedArray.array.length),a.needsUpdate=!0},SPE.ShaderAttribute.prototype.resetUpdateRange=function(){"use strict";this.updateMin=0,this.updateMax=0},SPE.ShaderAttribute.prototype.resetDynamic=function(){"use strict";this.bufferAttribute.usage=this.dynamicBuffer?THREE.DynamicDrawUsage:THREE.StaticDrawUsage},SPE.ShaderAttribute.prototype.splice=function(a,b){"use strict";this.typedArray.splice(a,b),this.forceUpdateAll()},SPE.ShaderAttribute.prototype.forceUpdateAll=function(){"use strict";this.bufferAttribute.array=this.typedArray.array,this.bufferAttribute.updateRange.offset=0,this.bufferAttribute.updateRange.count=-1,this.bufferAttribute.usage=THREE.StaticDrawUsage,this.bufferAttribute.needsUpdate=!0},SPE.ShaderAttribute.prototype._ensureTypedArray=function(a){"use strict" +;null!==this.typedArray&&this.typedArray.size===a*this.componentSize||(null!==this.typedArray&&this.typedArray.size!==a?this.typedArray.setSize(a):null===this.typedArray&&(this.typedArray=new SPE.TypedArrayHelper(this.arrayType,a,this.componentSize)))},SPE.ShaderAttribute.prototype._createBufferAttribute=function(a){"use strict";if(this._ensureTypedArray(a),null!==this.bufferAttribute)return this.bufferAttribute.array=this.typedArray.array,parseFloat(THREE.REVISION)>=81&&(this.bufferAttribute.count=this.bufferAttribute.array.length/this.bufferAttribute.itemSize),void(this.bufferAttribute.needsUpdate=!0);this.bufferAttribute=new THREE.BufferAttribute(this.typedArray.array,this.componentSize),this.bufferAttribute.usage=this.dynamicBuffer?THREE.DynamicDrawUsage:THREE.StaticDrawUsage},SPE.ShaderAttribute.prototype.getLength=function(){"use strict";return null===this.typedArray?0:this.typedArray.array.length},SPE.shaderChunks={ +defines:["#define PACKED_COLOR_SIZE 256.0","#define PACKED_COLOR_DIVISOR 255.0"].join("\n"),uniforms:["uniform float deltaTime;","uniform float runTime;","uniform sampler2D tex;","uniform vec4 textureAnimation;","uniform float scale;"].join("\n"),attributes:["attribute vec4 acceleration;","attribute vec3 velocity;","attribute vec4 rotation;","attribute vec3 rotationCenter;","attribute vec4 params;","attribute vec4 size;","attribute vec4 angle;","attribute vec4 color;","attribute vec4 opacity;"].join("\n"),varyings:["varying vec4 vColor;","#ifdef SHOULD_ROTATE_TEXTURE"," varying float vAngle;","#endif","#ifdef SHOULD_CALCULATE_SPRITE"," varying vec4 vSpriteSheet;","#endif"].join("\n"), branchAvoidanceFunctions:["float when_gt(float x, float y) {"," return max(sign(x - y), 0.0);","}","float when_lt(float x, float y) {"," return min( max(1.0 - sign(x - y), 0.0), 1.0 );","}","float when_eq( float x, float y ) {"," return 1.0 - abs( sign( x - y ) );","}","float when_ge(float x, float y) {"," return 1.0 - when_lt(x, y);","}","float when_le(float x, float y) {"," return 1.0 - when_gt(x, y);","}","float and(float a, float b) {"," return a * b;","}","float or(float a, float b) {"," return min(a + b, 1.0);","}"].join("\n"),unpackColor:["vec3 unpackColor( in float hex ) {"," vec3 c = vec3( 0.0 );"," float r = mod( (hex / PACKED_COLOR_SIZE / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );"," float g = mod( (hex / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );"," float b = mod( hex, PACKED_COLOR_SIZE );"," c.r = r / PACKED_COLOR_DIVISOR;"," c.g = g / PACKED_COLOR_DIVISOR;"," c.b = b / PACKED_COLOR_DIVISOR;"," return c;","}"].join("\n"), unpackRotationAxis:["vec3 unpackRotationAxis( in float hex ) {"," vec3 c = vec3( 0.0 );"," float r = mod( (hex / PACKED_COLOR_SIZE / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );"," float g = mod( (hex / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );"," float b = mod( hex, PACKED_COLOR_SIZE );"," c.r = r / PACKED_COLOR_DIVISOR;"," c.g = g / PACKED_COLOR_DIVISOR;"," c.b = b / PACKED_COLOR_DIVISOR;"," c *= vec3( 2.0 );"," c -= vec3( 1.0 );"," return c;","}"].join("\n"), floatOverLifetime:["float getFloatOverLifetime( in float positionInTime, in vec4 attr ) {"," highp float value = 0.0;"," float deltaAge = positionInTime * float( VALUE_OVER_LIFETIME_LENGTH - 1 );"," float fIndex = 0.0;"," float shouldApplyValue = 0.0;"," value += attr[ 0 ] * when_eq( deltaAge, 0.0 );",""," for( int i = 0; i < VALUE_OVER_LIFETIME_LENGTH - 1; ++i ) {"," fIndex = float( i );"," shouldApplyValue = and( when_gt( deltaAge, fIndex ), when_le( deltaAge, fIndex + 1.0 ) );"," value += shouldApplyValue * mix( attr[ i ], attr[ i + 1 ], deltaAge - fIndex );"," }",""," return value;","}"].join("\n"), colorOverLifetime:["vec3 getColorOverLifetime( in float positionInTime, in vec3 color1, in vec3 color2, in vec3 color3, in vec3 color4 ) {"," vec3 value = vec3( 0.0 );"," value.x = getFloatOverLifetime( positionInTime, vec4( color1.x, color2.x, color3.x, color4.x ) );"," value.y = getFloatOverLifetime( positionInTime, vec4( color1.y, color2.y, color3.y, color4.y ) );"," value.z = getFloatOverLifetime( positionInTime, vec4( color1.z, color2.z, color3.z, color4.z ) );"," return value;","}"].join("\n"),paramFetchingFunctions:["float getAlive() {"," return params.x;","}","float getAge() {"," return params.y;","}","float getMaxAge() {"," return params.z;","}","float getWiggle() {"," return params.w;","}"].join("\n"), forceFetchingFunctions:["vec4 getPosition( in float age ) {"," return modelViewMatrix * vec4( position, 1.0 );","}","vec3 getVelocity( in float age ) {"," return velocity * age;","}","vec3 getAcceleration( in float age ) {"," return acceleration.xyz * age;","}"].join("\n"), rotationFunctions:["#ifdef SHOULD_ROTATE_PARTICLES"," mat4 getRotationMatrix( in vec3 axis, in float angle) {"," axis = normalize(axis);"," float s = sin(angle);"," float c = cos(angle);"," float oc = 1.0 - c;",""," return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0,"," oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0,"," oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0,"," 0.0, 0.0, 0.0, 1.0);"," }",""," vec3 getRotation( in vec3 pos, in float positionInTime ) {"," if( rotation.y == 0.0 ) {"," return pos;"," }",""," vec3 axis = unpackRotationAxis( rotation.x );"," vec3 center = rotationCenter;"," vec3 translated;"," mat4 rotationMatrix;"," float angle = 0.0;"," angle += when_eq( rotation.z, 0.0 ) * rotation.y;"," angle += when_gt( rotation.z, 0.0 ) * mix( 0.0, rotation.y, positionInTime );"," translated = rotationCenter - pos;"," rotationMatrix = getRotationMatrix( axis, angle );"," return center - vec3( rotationMatrix * vec4( translated, 0.0 ) );"," }","#endif"].join("\n"), -rotateTexture:[" vec2 vUv = vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y );",""," #ifdef SHOULD_ROTATE_TEXTURE"," float x = gl_PointCoord.x - 0.5;"," float y = 1.0 - gl_PointCoord.y - 0.5;"," float c = cos( -vAngle );"," float s = sin( -vAngle );"," vUv = vec2( c * x + s * y + 0.5, c * y - s * x + 0.5 );"," #endif",""," #ifdef SHOULD_CALCULATE_SPRITE"," float framesX = vSpriteSheet.x;"," float framesY = vSpriteSheet.y;"," float columnNorm = vSpriteSheet.z;"," float rowNorm = vSpriteSheet.w;"," vUv.x = gl_PointCoord.x * framesX + columnNorm;"," vUv.y = 1.0 - (gl_PointCoord.y * framesY + rowNorm);"," #endif",""," vec4 rotatedTexture = texture2D( texture, vUv );"].join("\n")},SPE.shaders={ +rotateTexture:[" vec2 vUv = vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y );",""," #ifdef SHOULD_ROTATE_TEXTURE"," float x = gl_PointCoord.x - 0.5;"," float y = 1.0 - gl_PointCoord.y - 0.5;"," float c = cos( -vAngle );"," float s = sin( -vAngle );"," vUv = vec2( c * x + s * y + 0.5, c * y - s * x + 0.5 );"," #endif",""," #ifdef SHOULD_CALCULATE_SPRITE"," float framesX = vSpriteSheet.x;"," float framesY = vSpriteSheet.y;"," float columnNorm = vSpriteSheet.z;"," float rowNorm = vSpriteSheet.w;"," vUv.x = gl_PointCoord.x * framesX + columnNorm;"," vUv.y = 1.0 - (gl_PointCoord.y * framesY + rowNorm);"," #endif",""," vec4 rotatedTexture = texture2D( tex, vUv );"].join("\n")},SPE.shaders={ vertex:[SPE.shaderChunks.defines,SPE.shaderChunks.uniforms,SPE.shaderChunks.attributes,SPE.shaderChunks.varyings,THREE.ShaderChunk.common,THREE.ShaderChunk.logdepthbuf_pars_vertex,THREE.ShaderChunk.fog_pars_vertex,SPE.shaderChunks.branchAvoidanceFunctions,SPE.shaderChunks.unpackColor,SPE.shaderChunks.unpackRotationAxis,SPE.shaderChunks.floatOverLifetime,SPE.shaderChunks.colorOverLifetime,SPE.shaderChunks.paramFetchingFunctions,SPE.shaderChunks.forceFetchingFunctions,SPE.shaderChunks.rotationFunctions,"void main() {"," highp float age = getAge();"," highp float alive = getAlive();"," highp float maxAge = getMaxAge();"," highp float positionInTime = (age / maxAge);"," highp float isAlive = when_gt( alive, 0.0 );"," #ifdef SHOULD_WIGGLE_PARTICLES"," float wiggleAmount = positionInTime * getWiggle();"," float wiggleSin = isAlive * sin( wiggleAmount );"," float wiggleCos = isAlive * cos( wiggleAmount );"," #endif"," vec3 vel = getVelocity( age );"," vec3 accel = getAcceleration( age );"," vec3 force = vec3( 0.0 );"," vec3 pos = vec3( position );"," float drag = 1.0 - (positionInTime * 0.5) * acceleration.w;"," force += vel;"," force *= drag;"," force += accel * age;"," pos += force;"," #ifdef SHOULD_WIGGLE_PARTICLES"," pos.x += wiggleSin;"," pos.y += wiggleCos;"," pos.z += wiggleSin;"," #endif"," #ifdef SHOULD_ROTATE_PARTICLES"," pos = getRotation( pos, positionInTime );"," #endif"," vec4 mvPosition = modelViewMatrix * vec4( pos, 1.0 );"," highp float pointSize = getFloatOverLifetime( positionInTime, size ) * isAlive;"," #ifdef HAS_PERSPECTIVE"," float perspective = scale / length( mvPosition.xyz );"," #else"," float perspective = 1.0;"," #endif"," float pointSizePerspective = pointSize * perspective;"," #ifdef COLORIZE"," vec3 c = isAlive * getColorOverLifetime("," positionInTime,"," unpackColor( color.x ),"," unpackColor( color.y ),"," unpackColor( color.z ),"," unpackColor( color.w )"," );"," #else"," vec3 c = vec3(1.0);"," #endif"," float o = isAlive * getFloatOverLifetime( positionInTime, opacity );"," vColor = vec4( c, o );"," #ifdef SHOULD_ROTATE_TEXTURE"," vAngle = isAlive * getFloatOverLifetime( positionInTime, angle );"," #endif"," #ifdef SHOULD_CALCULATE_SPRITE"," float framesX = textureAnimation.x;"," float framesY = textureAnimation.y;"," float loopCount = textureAnimation.w;"," float totalFrames = textureAnimation.z;"," float frameNumber = mod( (positionInTime * loopCount) * totalFrames, totalFrames );"," float column = floor(mod( frameNumber, framesX ));"," float row = floor( (frameNumber - column) / framesX );"," float columnNorm = column / framesX;"," float rowNorm = row / framesY;"," vSpriteSheet.x = 1.0 / framesX;"," vSpriteSheet.y = 1.0 / framesY;"," vSpriteSheet.z = columnNorm;"," vSpriteSheet.w = rowNorm;"," #endif"," gl_PointSize = pointSizePerspective;"," gl_Position = projectionMatrix * mvPosition;",THREE.ShaderChunk.logdepthbuf_vertex,THREE.ShaderChunk.fog_vertex,"}"].join("\n"), fragment:[SPE.shaderChunks.uniforms,THREE.ShaderChunk.common,THREE.ShaderChunk.fog_pars_fragment,THREE.ShaderChunk.logdepthbuf_pars_fragment,SPE.shaderChunks.varyings,SPE.shaderChunks.branchAvoidanceFunctions,"void main() {"," vec3 outgoingLight = vColor.xyz;"," "," #ifdef ALPHATEST"," if ( vColor.w < float(ALPHATEST) ) discard;"," #endif",SPE.shaderChunks.rotateTexture,THREE.ShaderChunk.logdepthbuf_fragment," outgoingLight = vColor.xyz * rotatedTexture.xyz;"," gl_FragColor = vec4( outgoingLight.xyz, rotatedTexture.w * vColor.w );",THREE.ShaderChunk.fog_fragment,"}"].join("\n")},SPE.utils={types:{BOOLEAN:"boolean",STRING:"string",NUMBER:"number",OBJECT:"object"},ensureTypedArg:function(a,b,c){"use strict";return typeof a===b?a:c},ensureArrayTypedArg:function(a,b,c){"use strict";if(Array.isArray(a)){for(var d=a.length-1;d>=0;--d)if(typeof a[d]!==b)return c;return a}return this.ensureTypedArg(a,b,c)},ensureInstanceOf:function(a,b,c){"use strict" ;return void 0!==b&&a instanceof b?a:c},ensureArrayInstanceOf:function(a,b,c){"use strict";if(Array.isArray(a)){for(var d=a.length-1;d>=0;--d)if(void 0!==b&&a[d]instanceof b==!1)return c;return a}return this.ensureInstanceOf(a,b,c)},ensureValueOverLifetimeCompliance:function(a,b,c){"use strict";b=b||3,c=c||3,!1===Array.isArray(a._value)&&(a._value=[a._value]),!1===Array.isArray(a._spread)&&(a._spread=[a._spread]);var d=this.clamp(a._value.length,b,c),e=this.clamp(a._spread.length,b,c),f=Math.max(d,e);a._value.length!==f&&(a._value=this.interpolateArray(a._value,f)),a._spread.length!==f&&(a._spread=this.interpolateArray(a._spread,f))},interpolateArray:function(a,b){"use strict";for(var c=a.length,d=["function"==typeof a[0].clone?a[0].clone():a[0]],e=(c-1)/(b-1),f=1;f1||this.textureFrames.y>1},this.attributes={position:new SPE.ShaderAttribute("v3",!0),acceleration:new SPE.ShaderAttribute("v4",!0),velocity:new SPE.ShaderAttribute("v3",!0),rotation:new SPE.ShaderAttribute("v4",!0),rotationCenter:new SPE.ShaderAttribute("v3",!0),params:new SPE.ShaderAttribute("v4",!0),size:new SPE.ShaderAttribute("v4",!0),angle:new SPE.ShaderAttribute("v4",!0),color:new SPE.ShaderAttribute("v4",!0),opacity:new SPE.ShaderAttribute("v4",!0)},this.attributeKeys=Object.keys(this.attributes),this.attributeCount=this.attributeKeys.length,this.material=new THREE.ShaderMaterial({uniforms:this.uniforms,vertexShader:SPE.shaders.vertex,fragmentShader:SPE.shaders.fragment,blending:this.blending,transparent:this.transparent,alphaTest:this.alphaTest,depthWrite:this.depthWrite,depthTest:this.depthTest,defines:this.defines,fog:this.fog}),this.geometry=new THREE.BufferGeometry, -this.mesh=new THREE.Points(this.geometry,this.material),null===this.maxParticleCount&&console.warn("SPE.Group: No maxParticleCount specified. Adding emitters after rendering will probably cause errors.")},SPE.Group.constructor=SPE.Group,SPE.Group.prototype._updateDefines=function(){"use strict";var a,b=this.emitters,c=b.length-1,d=this.defines;for(c;c>=0;--c)a=b[c],d.SHOULD_CALCULATE_SPRITE||(d.SHOULD_ROTATE_TEXTURE=d.SHOULD_ROTATE_TEXTURE||!!Math.max(Math.max.apply(null,a.angle.value),Math.max.apply(null,a.angle.spread))),d.SHOULD_ROTATE_PARTICLES=d.SHOULD_ROTATE_PARTICLES||!!Math.max(a.rotation.angle,a.rotation.angleSpread),d.SHOULD_WIGGLE_PARTICLES=d.SHOULD_WIGGLE_PARTICLES||!!Math.max(a.wiggle.value,a.wiggle.spread);this.material.needsUpdate=!0},SPE.Group.prototype._applyAttributesToGeometry=function(){"use strict";var a,b,c=this.attributes,d=this.geometry,e=d.attributes;for(var f in c)c.hasOwnProperty(f)&&(a=c[f],b=e[f], +this.depthWrite=b.ensureTypedArg(a.depthWrite,c.BOOLEAN,!1),this.depthTest=b.ensureTypedArg(a.depthTest,c.BOOLEAN,!0),this.fog=b.ensureTypedArg(a.fog,c.BOOLEAN,!0),this.scale=b.ensureTypedArg(a.scale,c.NUMBER,300),this.emitters=[],this.emitterIDs=[],this._pool=[],this._poolCreationSettings=null,this._createNewWhenPoolEmpty=0,this._attributesNeedRefresh=!1,this._attributesNeedDynamicReset=!1,this.particleCount=0,this.uniforms={tex:{type:"t",value:this.texture},textureAnimation:{type:"v4",value:new THREE.Vector4(this.textureFrames.x,this.textureFrames.y,this.textureFrameCount,Math.max(Math.abs(this.textureLoop),1))},fogColor:{type:"c",value:this.fog?new THREE.Color:null},fogNear:{type:"f",value:10},fogFar:{type:"f",value:200},fogDensity:{type:"f",value:.5},deltaTime:{type:"f",value:0},runTime:{type:"f",value:0},scale:{type:"f",value:this.scale}},this.defines={HAS_PERSPECTIVE:this.hasPerspective,COLORIZE:this.colorize,VALUE_OVER_LIFETIME_LENGTH:SPE.valueOverLifetimeLength, +SHOULD_ROTATE_TEXTURE:!1,SHOULD_ROTATE_PARTICLES:!1,SHOULD_WIGGLE_PARTICLES:!1,SHOULD_CALCULATE_SPRITE:this.textureFrames.x>1||this.textureFrames.y>1},this.attributes={position:new SPE.ShaderAttribute("v3",!0),acceleration:new SPE.ShaderAttribute("v4",!0),velocity:new SPE.ShaderAttribute("v3",!0),rotation:new SPE.ShaderAttribute("v4",!0),rotationCenter:new SPE.ShaderAttribute("v3",!0),params:new SPE.ShaderAttribute("v4",!0),size:new SPE.ShaderAttribute("v4",!0),angle:new SPE.ShaderAttribute("v4",!0),color:new SPE.ShaderAttribute("v4",!0),opacity:new SPE.ShaderAttribute("v4",!0)},this.attributeKeys=Object.keys(this.attributes),this.attributeCount=this.attributeKeys.length,this.material=new THREE.ShaderMaterial({uniforms:this.uniforms,vertexShader:SPE.shaders.vertex,fragmentShader:SPE.shaders.fragment,blending:this.blending,transparent:this.transparent,alphaTest:this.alphaTest,depthWrite:this.depthWrite,depthTest:this.depthTest,defines:this.defines,fog:this.fog}), +this.geometry=new THREE.BufferGeometry,this.mesh=new THREE.Points(this.geometry,this.material),null===this.maxParticleCount&&console.warn("SPE.Group: No maxParticleCount specified. Adding emitters after rendering will probably cause errors.")},SPE.Group.constructor=SPE.Group,SPE.Group.prototype._updateDefines=function(){"use strict";var a,b=this.emitters,c=b.length-1,d=this.defines;for(c;c>=0;--c)a=b[c],d.SHOULD_CALCULATE_SPRITE||(d.SHOULD_ROTATE_TEXTURE=d.SHOULD_ROTATE_TEXTURE||!!Math.max(Math.max.apply(null,a.angle.value),Math.max.apply(null,a.angle.spread))),d.SHOULD_ROTATE_PARTICLES=d.SHOULD_ROTATE_PARTICLES||!!Math.max(a.rotation.angle,a.rotation.angleSpread),d.SHOULD_WIGGLE_PARTICLES=d.SHOULD_WIGGLE_PARTICLES||!!Math.max(a.wiggle.value,a.wiggle.spread);this.material.needsUpdate=!0},SPE.Group.prototype._applyAttributesToGeometry=function(){"use strict";var a,b,c=this.attributes,d=this.geometry,e=d.attributes;for(var f in c)c.hasOwnProperty(f)&&(a=c[f],b=e[f], b?b.array=a.typedArray.array:d.addAttribute(f,a.bufferAttribute),a.bufferAttribute.needsUpdate=!0);this.geometry.setDrawRange(0,this.particleCount)},SPE.Group.prototype.addEmitter=function(a){"use strict";if(a instanceof SPE.Emitter==!1)return void console.error("`emitter` argument must be instance of SPE.Emitter. Was provided with:",a);if(this.emitterIDs.indexOf(a.uuid)>-1)return void console.error("Emitter already exists in this group. Will not add again.");if(null!==a.group)return void console.error("Emitter already belongs to another group. Will not add to requested group.");var b=this.attributes,c=this.particleCount,d=c+a.particleCount;this.particleCount=d,null!==this.maxParticleCount&&this.particleCount>this.maxParticleCount&&console.warn("SPE.Group: maxParticleCount exceeded. Requesting",this.particleCount,"particles, can support only",this.maxParticleCount),a._calculatePPSValue(a.maxAge._value+a.maxAge._spread),a._setBufferUpdateRanges(this.attributeKeys), a._setAttributeOffset(c),a.group=this,a.attributes=this.attributes;for(var e in b)b.hasOwnProperty(e)&&b[e]._createBufferAttribute(null!==this.maxParticleCount?this.maxParticleCount:this.particleCount);for(var f=c;f= 0.5.2", + "fs-extra": ">= 0.6.0", + "highlight.js": ">= 8.0.x", + "marked": ">= 0.2.7", + "underscore": ">= 1.0.0" + } + }, + "entities": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", + "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==", + "dev": true + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esprima": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", + "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=", + "dev": true + }, + "eventemitter2": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", + "integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=", + "dev": true + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "findup-sync": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.1.3.tgz", + "integrity": "sha1-fz56l7gjksZTvwZYm9hRkOk8NoM=", + "dev": true, + "requires": { + "glob": "~3.2.9", + "lodash": "~2.4.1" + }, + "dependencies": { + "glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", + "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", + "dev": true, + "requires": { + "inherits": "2", + "minimatch": "0.3" + } + }, + "lodash": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", + "dev": true + }, + "minimatch": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", + "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", + "dev": true, + "requires": { + "lru-cache": "2", + "sigmund": "~1.0.0" + } + } + } + }, + "fs-extra": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", + "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^1.0.0" + }, + "dependencies": { + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + } + } + }, + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true + }, + "getobject": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz", + "integrity": "sha1-BHpEl4n6Fg0Bj1SG7ZEyC27HiFw=", + "dev": true + }, + "glob": { + "version": "3.1.21", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", + "integrity": "sha1-0p4KBV3qUTj00H7UDomC6DwgZs0=", + "dev": true, + "requires": { + "graceful-fs": "~1.2.0", + "inherits": "1", + "minimatch": "~0.2.11" + }, + "dependencies": { + "inherits": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", + "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=", + "dev": true + } + } + }, + "graceful-fs": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", + "integrity": "sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q=", + "dev": true + }, + "grunt": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/grunt/-/grunt-0.4.5.tgz", + "integrity": "sha1-VpN81RlDJK3/bSB2MYMqnWuk5/A=", + "dev": true, + "requires": { + "async": "~0.1.22", + "coffee-script": "~1.3.3", + "colors": "~0.6.2", + "dateformat": "1.0.2-1.2.3", + "eventemitter2": "~0.4.13", + "exit": "~0.1.1", + "findup-sync": "~0.1.2", + "getobject": "~0.1.0", + "glob": "~3.1.21", + "grunt-legacy-log": "~0.1.0", + "grunt-legacy-util": "~0.2.0", + "hooker": "~0.2.3", + "iconv-lite": "~0.2.11", + "js-yaml": "~2.0.5", + "lodash": "~0.9.2", + "minimatch": "~0.2.12", + "nopt": "~1.0.10", + "rimraf": "~2.2.8", + "underscore.string": "~2.2.1", + "which": "~1.0.5" + } + }, + "grunt-contrib-concat": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/grunt-contrib-concat/-/grunt-contrib-concat-0.5.1.tgz", + "integrity": "sha1-lTxu/f39LBB6uchQd/LUsk0xzUk=", + "dev": true, + "requires": { + "chalk": "^0.5.1", + "source-map": "^0.3.0" + } + }, + "grunt-contrib-uglify": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/grunt-contrib-uglify/-/grunt-contrib-uglify-0.9.2.tgz", + "integrity": "sha1-GmHG8hJBDkq7T3yJFTcXsQFWAmA=", + "dev": true, + "requires": { + "chalk": "^1.0.0", + "lodash": "^3.2.0", + "maxmin": "^1.0.0", + "uglify-js": "^2.4.24", + "uri-path": "0.0.2" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "grunt-docco2": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/grunt-docco2/-/grunt-docco2-0.2.1.tgz", + "integrity": "sha1-ZghyYavBkcjofI77/LeIEr/krMs=", + "dev": true, + "requires": { + "docco": "~0.6.3" + } + }, + "grunt-jsdoc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/grunt-jsdoc/-/grunt-jsdoc-1.1.0.tgz", + "integrity": "sha1-S87+YFh6ZGpbcg/pq/lMxIh/6FQ=", + "dev": true, + "requires": { + "jsdoc": "^3.4.0" + } + }, + "grunt-legacy-log": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-0.1.3.tgz", + "integrity": "sha1-7ClCboAwIa9ZAp+H0vnNczWgVTE=", + "dev": true, + "requires": { + "colors": "~0.6.2", + "grunt-legacy-log-utils": "~0.1.1", + "hooker": "~0.2.3", + "lodash": "~2.4.1", + "underscore.string": "~2.3.3" + }, + "dependencies": { + "lodash": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", + "dev": true + }, + "underscore.string": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", + "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=", + "dev": true + } + } + }, + "grunt-legacy-log-utils": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-0.1.1.tgz", + "integrity": "sha1-wHBrndkGThFvNvI/5OawSGcsD34=", + "dev": true, + "requires": { + "colors": "~0.6.2", + "lodash": "~2.4.1", + "underscore.string": "~2.3.3" + }, + "dependencies": { + "lodash": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", + "dev": true + }, + "underscore.string": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", + "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=", + "dev": true + } + } + }, + "grunt-legacy-util": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-0.2.0.tgz", + "integrity": "sha1-kzJIhNv343qf98Am3/RR2UqeVUs=", + "dev": true, + "requires": { + "async": "~0.1.22", + "exit": "~0.1.1", + "getobject": "~0.1.0", + "hooker": "~0.2.3", + "lodash": "~0.9.2", + "underscore.string": "~2.2.1", + "which": "~1.0.5" + } + }, + "gzip-size": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-1.0.0.tgz", + "integrity": "sha1-Zs+LEBBHInuVus5uodoMF37Vwi8=", + "dev": true, + "requires": { + "browserify-zlib": "^0.1.4", + "concat-stream": "^1.4.1" + } + }, + "has-ansi": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-0.1.0.tgz", + "integrity": "sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4=", + "dev": true, + "requires": { + "ansi-regex": "^0.2.0" + } + }, + "highlight.js": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.1.1.tgz", + "integrity": "sha512-b4L09127uVa+9vkMgPpdUQP78ickGbHEQTWeBrQFTJZ4/n2aihWOGS0ZoUqAwjVmfjhq/C76HRzkqwZhK4sBbg==", + "dev": true + }, + "hooker": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", + "integrity": "sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk=", + "dev": true + }, + "hosted-git-info": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "dev": true + }, + "iconv-lite": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.2.11.tgz", + "integrity": "sha1-HOYKOleGSiktEyH/RgnKS7llrcg=", + "dev": true + }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-finite": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", + "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", + "dev": true + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "js-yaml": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-2.0.5.tgz", + "integrity": "sha1-olrmUJmZ6X3yeMZxnaEb0Gh3Q6g=", + "dev": true, + "requires": { + "argparse": "~ 0.1.11", + "esprima": "~ 1.0.2" + } + }, + "js2xmlparser": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.1.tgz", + "integrity": "sha512-KrPTolcw6RocpYjdC7pL7v62e55q7qOMHvLX1UCLc5AAS8qeJ6nukarEJAF2KL2PZxlbGueEbINqZR2bDe/gUw==", + "dev": true, + "requires": { + "xmlcreate": "^2.0.3" + } + }, + "jsdoc": { + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.4.tgz", + "integrity": "sha512-3G9d37VHv7MFdheviDCjUfQoIjdv4TC5zTTf5G9VODLtOnVS6La1eoYBDlbWfsRT3/Xo+j2MIqki2EV12BZfwA==", + "dev": true, + "requires": { + "@babel/parser": "^7.9.4", + "bluebird": "^3.7.2", + "catharsis": "^0.8.11", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.1", + "klaw": "^3.0.0", + "markdown-it": "^10.0.0", + "markdown-it-anchor": "^5.2.7", + "marked": "^0.8.2", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "taffydb": "2.6.2", + "underscore": "~1.10.2" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + }, + "marked": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.8.2.tgz", + "integrity": "sha512-EGwzEeCcLniFX51DhTpmTom+dSA/MG/OBUDjnWtHbEnjAH180VzUeAw+oE4+Zv+CoYBWyRlYOTR0N8SO9R1PVw==", + "dev": true + }, + "underscore": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.10.2.tgz", + "integrity": "sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg==", + "dev": true + } + } + }, + "jsonfile": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz", + "integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^1.0.0" + }, + "dependencies": { + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true, + "optional": true + } + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + }, + "klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.9" + }, + "dependencies": { + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + } + } + }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "dev": true + }, + "linkify-it": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", + "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", + "dev": true, + "requires": { + "uc.micro": "^1.0.1" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + }, + "dependencies": { + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + } + } + }, + "lodash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz", + "integrity": "sha1-jzSZxSRdNG1oLlsNO0B2fgnxqSw=", + "dev": true + }, + "longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "dev": true + }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, + "requires": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + } + }, + "lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", + "dev": true + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + }, + "markdown-it": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz", + "integrity": "sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "entities": "~2.0.0", + "linkify-it": "^2.0.0", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + } + } + }, + "markdown-it-anchor": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-5.3.0.tgz", + "integrity": "sha512-/V1MnLL/rgJ3jkMWo84UR+K+jF1cxNG1a+KwqeXqTIJ+jtA8aWSHuigx8lTzauiIjBDbwF3NcWQMotd0Dm39jA==", + "dev": true + }, + "marked": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-1.1.0.tgz", + "integrity": "sha512-EkE7RW6KcXfMHy2PA7Jg0YJE1l8UPEZE8k45tylzmZM30/r1M1MUXWQfJlrSbsTeh7m/XTwHbWUENvAJZpp1YA==", + "dev": true + }, + "maxmin": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/maxmin/-/maxmin-1.1.0.tgz", + "integrity": "sha1-cTZehKmd2Piz99X94vANHn9zvmE=", + "dev": true, + "requires": { + "chalk": "^1.0.0", + "figures": "^1.0.1", + "gzip-size": "^1.0.0", + "pretty-bytes": "^1.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", + "dev": true + }, + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "requires": { + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" + } + }, + "minimatch": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", + "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", + "dev": true, + "requires": { + "lru-cache": "2", + "sigmund": "~1.0.0" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=", + "dev": true + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + } + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "pretty-bytes": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-1.0.4.tgz", + "integrity": "sha1-CiLoIQYJrTVUL4yNXSFZr/B1HIQ=", + "dev": true, + "requires": { + "get-stdin": "^4.0.1", + "meow": "^3.1.0" + } + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, + "requires": { + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" + } + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "requires": { + "is-finite": "^1.0.0" + } + }, + "requizzle": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.3.tgz", + "integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + }, + "dependencies": { + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + } + } + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "dev": true, + "requires": { + "align-text": "^0.1.1" + } + }, + "rimraf": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", + "dev": true + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true + }, + "source-map": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.3.0.tgz", + "integrity": "sha1-hYb7mloAXltQHiHNGLbyG0V60fk=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz", + "integrity": "sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA=", + "dev": true, + "requires": { + "ansi-regex": "^0.2.1" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, + "requires": { + "get-stdin": "^4.0.1" + } + }, + "strip-json-comments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.0.tgz", + "integrity": "sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==", + "dev": true + }, + "supports-color": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz", + "integrity": "sha1-2S3iaU6z9nMjlz1649i1W0wiGQo=", + "dev": true + }, + "taffydb": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", + "integrity": "sha1-fLy2S1oUG2ou/CxdLGe04VCyomg=", + "dev": true + }, + "three": { + "version": "0.118.3", + "resolved": "https://registry.npmjs.org/three/-/three-0.118.3.tgz", + "integrity": "sha512-ijECXrNzDkHieoeh2H69kgawTGH8DiamhR4uBN8jEM7VHSKvfTdEvOoHsA8Aq7dh7PHAxhlqBsN5arBI3KixSw==" + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "dev": true + }, + "uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "dev": true, + "requires": { + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.10.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "dev": true, + "optional": true + }, + "underscore": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", + "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=", + "dev": true + }, + "underscore.string": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.2.1.tgz", + "integrity": "sha1-18D6KvXVoaZ/QlPa7pgTLnM/Dxk=", + "dev": true + }, + "universalify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", + "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", + "dev": true + }, + "uri-path": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/uri-path/-/uri-path-0.0.2.tgz", + "integrity": "sha1-gD6wHy/rF5J9zOD2GH5yt19T9VQ=", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "which": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/which/-/which-1.0.9.tgz", + "integrity": "sha1-RgwdoPgQED0DIam2M6+eV15kSG8=", + "dev": true + }, + "window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "dev": true + }, + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true + }, + "xmlcreate": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.3.tgz", + "integrity": "sha512-HgS+X6zAztGa9zIK3Y3LXuJes33Lz9x+YyTxgrkIdabu2vqcGOWwdfCpf1hWLRrd553wd4QCDf6BBO6FfdsRiQ==", + "dev": true + }, + "yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "dev": true, + "requires": { + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", + "window-size": "0.1.0" + }, + "dependencies": { + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true + } + } + } + } +} diff --git a/package.json b/package.json index 4e17e9e..d065c5e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "shader-particle-engine", "description": "A GLSL-focused particle engine for THREE.js.", - "version": "1.0.6", + "version": "1.1.0", "author": "Luke Moody (https://github.com/squarefeet/)", "license": "MIT", "repository": { @@ -29,7 +29,8 @@ "grunt-jsdoc": "^1.0.0" }, "scripts": { - "test": "grunt" + "test": "grunt", + "build": "grunt" }, "main": "build/SPE.min.js", "directories": { @@ -37,6 +38,6 @@ "example": "examples" }, "dependencies": { - "three": "^0.84.0" + "three": "^0.118.3" } } diff --git a/src/core/SPE.Group.js b/src/core/SPE.Group.js index 06293a5..1e1d585 100644 --- a/src/core/SPE.Group.js +++ b/src/core/SPE.Group.js @@ -119,7 +119,7 @@ SPE.Group = function( options ) { // Map of uniforms to be applied to the ShaderMaterial instance. this.uniforms = { - texture: { + tex: { type: 't', value: this.texture }, @@ -495,9 +495,9 @@ SPE.Group.prototype.getFromPool = function() { } else if ( createNew ) { var emitter = new SPE.Emitter( this._poolCreationSettings ); - + this.addEmitter( emitter ); - + return emitter; } diff --git a/src/helpers/SPE.ShaderAttribute.js b/src/helpers/SPE.ShaderAttribute.js index d5ac9ad..95e1deb 100644 --- a/src/helpers/SPE.ShaderAttribute.js +++ b/src/helpers/SPE.ShaderAttribute.js @@ -99,8 +99,6 @@ SPE.ShaderAttribute.prototype.flagUpdate = function() { range.offset = this.updateMin; range.count = Math.min( ( this.updateMax - this.updateMin ) + this.componentSize, this.typedArray.array.length ); - // console.log( range.offset, range.count, this.typedArray.array.length ); - // console.log( 'flagUpdate:', range.offset, range.count ); attr.needsUpdate = true; }; @@ -118,7 +116,9 @@ SPE.ShaderAttribute.prototype.resetUpdateRange = function() { SPE.ShaderAttribute.prototype.resetDynamic = function() { 'use strict'; - this.bufferAttribute.dynamic = this.dynamicBuffer; + this.bufferAttribute.usage = this.dynamicBuffer ? + THREE.DynamicDrawUsage : + THREE.StaticDrawUsage; }; /** @@ -142,7 +142,8 @@ SPE.ShaderAttribute.prototype.forceUpdateAll = function() { this.bufferAttribute.array = this.typedArray.array; this.bufferAttribute.updateRange.offset = 0; this.bufferAttribute.updateRange.count = -1; - this.bufferAttribute.dynamic = false; + + this.bufferAttribute.usage = THREE.StaticDrawUsage; this.bufferAttribute.needsUpdate = true; }; @@ -211,7 +212,10 @@ SPE.ShaderAttribute.prototype._createBufferAttribute = function( size ) { } this.bufferAttribute = new THREE.BufferAttribute( this.typedArray.array, this.componentSize ); - this.bufferAttribute.dynamic = this.dynamicBuffer; + + this.bufferAttribute.usage = this.dynamicBuffer ? + THREE.DynamicDrawUsage : + THREE.StaticDrawUsage; }; /** diff --git a/src/shaders/SPE.shaderChunks.js b/src/shaders/SPE.shaderChunks.js index 2257739..2860b75 100644 --- a/src/shaders/SPE.shaderChunks.js +++ b/src/shaders/SPE.shaderChunks.js @@ -9,7 +9,7 @@ SPE.shaderChunks = { uniforms: [ 'uniform float deltaTime;', 'uniform float runTime;', - 'uniform sampler2D texture;', + 'uniform sampler2D tex;', 'uniform vec4 textureAnimation;', 'uniform float scale;', ].join( '\n' ), @@ -250,6 +250,6 @@ SPE.shaderChunks = { ' #endif', '', - ' vec4 rotatedTexture = texture2D( texture, vUv );', + ' vec4 rotatedTexture = texture2D( tex, vUv );', ].join( '\n' ) }; \ No newline at end of file From d4b1aa66ece0d13de5c1221d5ca333bf23bf2637 Mon Sep 17 00:00:00 2001 From: Luke Moody Date: Sun, 28 Jun 2020 15:23:58 +0100 Subject: [PATCH 2/5] Upgraded to webpack. Removed Grunt dependency. Bumped package to 2.0.0. --- .eslintrc.js | 186 + .jsbeautifyrc | 18 - .jshintrc | 64 - bower.json | 19 - build/SPE.js | 3598 ---------- build/SPE.min.js | 52 - dist/SPE.js | 12 + docs/api/Emitter.html | 213 - docs/api/SPE.Emitter.html | 848 --- docs/api/SPE.Group.html | 1497 ----- docs/api/SPE.ShaderAttribute.html | 1416 ---- docs/api/SPE.TypedArrayHelper.html | 3308 --------- docs/api/SPE.html | 427 -- docs/api/SPE.js.html | 132 - docs/api/constants_distributions.js.html | 88 + docs/api/constants_typeSizeMap.js.html | 100 + docs/api/constants_valueTypes.js.html | 77 + docs/api/core_Emitter.js.html | 1005 +++ docs/api/core_Group.js.html | 775 +++ docs/api/core_SPE.Emitter.js.html | 1041 --- docs/api/core_SPE.Group.js.html | 798 --- docs/api/core_SPE.utils.js.html | 755 --- docs/api/core_utils.js.html | 755 +++ docs/api/global.html | 5939 ++++++++++++++++- docs/api/helpers_SPE.ShaderAttribute.js.html | 269 - docs/api/helpers_SPE.TypedArrayHelper.js.html | 381 -- docs/api/helpers_ShaderAttribute.js.html | 223 + docs/api/helpers_TypedArrayHelper.js.html | 346 + docs/api/index.html | 4 +- .../{SPE.utils.html => module.html#.exports} | 1488 ++++- docs/api/scripts/linenumber.js | 18 +- docs/api/styles/jsdoc-default.css | 79 +- docs/source/SPE.html | 5342 --------------- docs/source/docco.css | 24 +- docs/source/public/fonts/novecento-bold.eot | Bin 18190 -> 0 bytes docs/source/public/fonts/novecento-bold.ttf | Bin 48136 -> 0 bytes docs/source/public/fonts/novecento-bold.woff | Bin 20576 -> 0 bytes docs/source/public/fonts/roboto-black.eot | Bin 0 -> 20702 bytes docs/source/public/fonts/roboto-black.ttf | Bin 0 -> 44828 bytes docs/source/public/fonts/roboto-black.woff | Bin 0 -> 24536 bytes docs/source/src/constants/distributions.html | 145 + docs/source/src/constants/globals.html | 110 + docs/source/src/constants/typeSizeMap.html | 155 + docs/source/src/constants/valueTypes.html | 133 + docs/source/src/core/Emitter.html | 1500 +++++ docs/source/src/core/Group.html | 1485 +++++ docs/source/src/core/utils.html | 1046 +++ docs/source/src/helpers/ShaderAttribute.html | 363 + docs/source/src/helpers/TypedArrayHelper.html | 416 ++ docs/source/src/shaders/shaderChunks.html | 508 ++ docs/source/src/shaders/shaders.html | 491 ++ examples/activeMultiplier.html | 4 +- examples/basic.html | 4 +- examples/clock.html | 4 +- examples/clouds.html | 4 +- examples/distributions.html | 4 +- examples/explosion.html | 4 +- examples/fog.html | 4 +- examples/js/THREE-r118.js | 1064 +++ examples/js/THREE-r84.js | 859 --- examples/mouseFollow.html | 4 +- examples/multipleEmitters.html | 8 +- examples/orbit.html | 8 +- examples/pool.html | 4 +- examples/rotation.html | 4 +- examples/runtimeChanging.html | 4 +- package-lock.json | 4596 ++++++++++++- package.json | 28 +- presets/README.md | 6 - presets/WheresTheFloor.js | 32 - src/SPE.js | 88 - src/constants/distributions.js | 38 + src/constants/globals.js | 3 + src/constants/typeSizeMap.js | 49 + src/constants/valueTypes.js | 26 + src/core/Emitter.js | 955 +++ src/core/Group.js | 724 ++ src/core/SPE.Emitter.js | 1001 --- src/core/SPE.Group.js | 752 --- src/core/SPE.utils.js | 732 -- src/core/utils.js | 704 ++ src/helpers/SPE.ShaderAttribute.js | 233 - src/helpers/SPE.TypedArrayHelper.js | 331 - src/helpers/ShaderAttribute.js | 172 + src/helpers/TypedArrayHelper.js | 296 + src/index.js | 8 + src/shaders/SPE.shaderChunks.js | 255 - src/shaders/shaderChunks.js | 255 + src/shaders/{SPE.shaders.js => shaders.js} | 45 +- webpack.config.js | 37 + 90 files changed, 26126 insertions(+), 24872 deletions(-) create mode 100644 .eslintrc.js delete mode 100644 .jsbeautifyrc delete mode 100644 .jshintrc delete mode 100644 bower.json delete mode 100644 build/SPE.js delete mode 100644 build/SPE.min.js create mode 100644 dist/SPE.js delete mode 100644 docs/api/Emitter.html delete mode 100644 docs/api/SPE.Emitter.html delete mode 100644 docs/api/SPE.Group.html delete mode 100644 docs/api/SPE.ShaderAttribute.html delete mode 100644 docs/api/SPE.TypedArrayHelper.html delete mode 100644 docs/api/SPE.html delete mode 100644 docs/api/SPE.js.html create mode 100644 docs/api/constants_distributions.js.html create mode 100644 docs/api/constants_typeSizeMap.js.html create mode 100644 docs/api/constants_valueTypes.js.html create mode 100644 docs/api/core_Emitter.js.html create mode 100644 docs/api/core_Group.js.html delete mode 100644 docs/api/core_SPE.Emitter.js.html delete mode 100644 docs/api/core_SPE.Group.js.html delete mode 100644 docs/api/core_SPE.utils.js.html create mode 100644 docs/api/core_utils.js.html delete mode 100644 docs/api/helpers_SPE.ShaderAttribute.js.html delete mode 100644 docs/api/helpers_SPE.TypedArrayHelper.js.html create mode 100644 docs/api/helpers_ShaderAttribute.js.html create mode 100644 docs/api/helpers_TypedArrayHelper.js.html rename docs/api/{SPE.utils.html => module.html#.exports} (71%) delete mode 100644 docs/source/SPE.html delete mode 100644 docs/source/public/fonts/novecento-bold.eot delete mode 100644 docs/source/public/fonts/novecento-bold.ttf delete mode 100644 docs/source/public/fonts/novecento-bold.woff create mode 100755 docs/source/public/fonts/roboto-black.eot create mode 100755 docs/source/public/fonts/roboto-black.ttf create mode 100755 docs/source/public/fonts/roboto-black.woff create mode 100644 docs/source/src/constants/distributions.html create mode 100644 docs/source/src/constants/globals.html create mode 100644 docs/source/src/constants/typeSizeMap.html create mode 100644 docs/source/src/constants/valueTypes.html create mode 100644 docs/source/src/core/Emitter.html create mode 100644 docs/source/src/core/Group.html create mode 100644 docs/source/src/core/utils.html create mode 100644 docs/source/src/helpers/ShaderAttribute.html create mode 100644 docs/source/src/helpers/TypedArrayHelper.html create mode 100644 docs/source/src/shaders/shaderChunks.html create mode 100644 docs/source/src/shaders/shaders.html create mode 100755 examples/js/THREE-r118.js delete mode 100755 examples/js/THREE-r84.js delete mode 100644 presets/README.md delete mode 100644 presets/WheresTheFloor.js delete mode 100644 src/SPE.js create mode 100644 src/constants/distributions.js create mode 100644 src/constants/globals.js create mode 100644 src/constants/typeSizeMap.js create mode 100644 src/constants/valueTypes.js create mode 100644 src/core/Emitter.js create mode 100644 src/core/Group.js delete mode 100644 src/core/SPE.Emitter.js delete mode 100644 src/core/SPE.Group.js delete mode 100644 src/core/SPE.utils.js create mode 100644 src/core/utils.js delete mode 100644 src/helpers/SPE.ShaderAttribute.js delete mode 100644 src/helpers/SPE.TypedArrayHelper.js create mode 100644 src/helpers/ShaderAttribute.js create mode 100644 src/helpers/TypedArrayHelper.js create mode 100644 src/index.js delete mode 100644 src/shaders/SPE.shaderChunks.js create mode 100644 src/shaders/shaderChunks.js rename src/shaders/{SPE.shaders.js => shaders.js} (87%) create mode 100644 webpack.config.js diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..dd9fcbe --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,186 @@ +// https://eslint.org/docs/user-guide/configuring + +module.exports = { + root: true, + + parserOptions: { + parser: 'babel-eslint', + ecmaVersion: 2017, + sourceType: 'module', + }, + + env: { + browser: true, + }, + + extends: [ + 'eslint:recommended', + ], + + // required to lint *.vue files + plugins: [], + + globals: { + Promise: true, + Uint32Array: true, + Float32Array: true, + module: true, + require: true, + __dirname: true, + __filename: true, + process: true, + describe: true, + it: true, + before: true, + after: true, + beforeEach: true, + afterEach: true, + Buffer: true, + Set: true, + expect: true, + }, + + // add your custom rules here + rules: { + quotes: [ 'error', 'single' ], + + 'quote-props': [ 'error', 'as-needed' ], + + // allow debugger during development + 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', + + semi: 'error', + + 'comma-dangle': [ 'error', { + arrays: 'always-multiline', + objects: 'always-multiline', + imports: 'always-multiline', + exports: 'always-multiline', + functions: 'ignore', + } ], + + 'lines-around-comment': [ 'error', { + beforeLineComment: false, + beforeBlockComment: true, + } ], + + 'newline-per-chained-call': [ 'error', { + ignoreChainWithDepth: 2, + } ], + + 'arrow-parens': [ + 'error', + 'as-needed', + ], + + 'space-in-parens': [ + 'error', + 'always', + ], + + 'computed-property-spacing': [ + 'error', + 'always', + ], + + 'object-curly-spacing': [ + 'error', + 'always', + ], + + 'array-bracket-spacing': [ + 'error', + 'always', + ], + + 'no-cond-assign': 'error', + 'no-constant-condition': 'error', + 'no-dupe-args': 'error', + 'no-sparse-arrays': 'error', + 'no-unreachable': 'error', + 'valid-typeof': 'error', + curly: [ + 'error', + 'all', + ], + 'dot-notation': 'error', + 'guard-for-in': 'error', + 'no-caller': 'error', + 'no-eval': 'error', + 'no-lone-blocks': 'error', + 'no-new-func': 'error', + 'no-new-wrappers': 'error', + 'no-param-reassign': [ + 'error', { + props: false, + }, + ], + 'no-redeclare': 'error', + 'no-self-compare': 'error', + 'no-with': 'error', + radix: 'error', + 'vars-on-top': 'error', + 'wrap-iife': 'error', + yoda: [ + 'error', + 'never', + ], + 'no-shadow': 'error', + 'no-undef': 'error', + 'no-unused-vars': 'error', + 'brace-style': [ + 'error', + 'stroustrup', + ], + camelcase: [ + 'error', { + properties: 'always', + }, + ], + 'func-style': [ + 'error', + 'declaration', + ], + 'max-nested-callbacks': [ + 'error', + 7, + ], + 'no-array-constructor': 'error', + 'no-mixed-spaces-and-tabs': 'error', + 'no-nested-ternary': 'error', + 'no-var': 'off', + 'object-shorthand': [ + 'error', + 'always', + ], + 'prefer-const': 'error', + 'max-params': 'off', + 'space-before-function-paren': [ + 'error', { + anonymous: 'never', + named: 'never', + }, + ], + 'prefer-arrow-callback': 'off', + 'max-depth': [ + 'error', + 4, + ], + 'no-implicit-coercion': 'off', + complexity: 'off', + 'prefer-template': 'off', + 'babel/new-cap': 'off', + 'new-cap': [ + 'error', { + properties: false, + }, + ], + 'no-loop-func': 'off', + 'no-warning-comments': [ 'error', { + terms: [ 'fixme' ], + location: 'anywhere', + } ], + 'no-console': 'off', + indent: [ 'error', 'tab', { SwitchCase: 1 } ], + }, +}; diff --git a/.jsbeautifyrc b/.jsbeautifyrc deleted file mode 100644 index da12aa7..0000000 --- a/.jsbeautifyrc +++ /dev/null @@ -1,18 +0,0 @@ -{ - "indent_size": 4, - "indent_char": " ", - "indent_level": 0, - "indent_with_tabs": false, - "preserve_newlines": true, - "max_preserve_newlines": 10, - "jslint_happy": false, - "brace_style": "end-expand", - "keep_array_indentation": false, - "keep_function_indentation": false, - "space_before_conditional": true, - "break_chained_methods": false, - "eval_code": false, - "unescape_strings": false, - "wrap_line_length": 0, - "space_in_paren": true -} diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index 50940a0..0000000 --- a/.jshintrc +++ /dev/null @@ -1,64 +0,0 @@ -{ - // Settings - "passfail" : true, // Stop on first error. - "maxerr" : 100, // Maximum errors before stopping. - - // Predefined globals whom JSHint will ignore. - "browser" : true, // Standard browser globals e.g. `window`, `document`. - - "node" : false, - "rhino" : false, - "couch" : false, - - "jquery" : false, - "prototypejs" : false, - "mootools" : false, - "dojo" : false, - - "predef" : [ // Extra globals. - "SPE", - "THREE", - "define", - "module" - ], - - // Development. - "debug" : false, // Allow debugger statements e.g. browser breakpoints. - "devel" : true, // Allow development statements e.g. `console.log();`. - - // EcmaScript 5. - "strict" : true, // Require `use strict` pragma in every file. - "globalstrict" : false, // Allow global "use strict" (also enables 'strict'). - - // The Good Parts. - "asi" : false, // Tolerate Automatic Semicolon Insertion (no semicolons). - "laxbreak" : false, // Tolerate unsafe line breaks e.g. `return [\n] x` without semicolons. - "bitwise" : false, // Prohibit bitwise operators (&, |, ^, etc.). - "boss" : true, // Tolerate assignments inside if, for & while. Usually conditions & loops are for comparison, not assignments. - "curly" : true, // Require {} for every new block or scope. - "eqeqeq" : false, // Require triple equals i.e. `===`. - "eqnull" : false, // Tolerate use of `== null`. - "evil" : false, // Tolerate use of `eval`. - "expr" : true, // Tolerate `ExpressionStatement` as Programs. - "forin" : false, // Tolerate `for in` loops without `hasOwnPrototype`. - "immed" : true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );` - "latedef" : true, // Prohibit variable use before definition. - "loopfunc" : true, // Allow functions to be defined within loops. - "noarg" : true, // Prohibit use of `arguments.caller` and `arguments.callee`. - "regexp" : false, // Prohibit `.` and `[^...]` in regular expressions. - "regexdash" : false, // Tolerate unescaped last dash i.e. `[-...]`. - "scripturl" : true, // Tolerate script-targeted URLs. - "shadow" : true, // Allows re-define variables later in code e.g. `var x=1; x=2;`. - "supernew" : false, // Tolerate `new function () { ... };` and `new Object;`. - "undef" : true, // Require all non-global variables be declared before they are used. - - // Persone styling prefrences. - "newcap" : true, // Require capitalization of all constructor functions e.g. `new F()`. - "noempty" : true, // Prohibit use of empty blocks. - "nonew" : true, // Prohibit use of constructors for side-effects. - "nomen" : false, // Prohibit use of initial or trailing underbars in names. - "onevar" : false, // Allow only one `var` statement per function. - "plusplus" : false, // Prohibit use of `++` & `--`. - "sub" : false, // Tolerate all forms of subscript notation besides dot notation e.g. `dict['key']` instead of `dict.key`. - "trailing" : true // Prohibit trailing whitespaces. -} diff --git a/bower.json b/bower.json deleted file mode 100644 index 00ff839..0000000 --- a/bower.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "ShaderParticleEngine", - "version": "1.0.6", - "homepage": "http://squarefeet.github.io/ShaderParticleEngine/", - "authors": [ - "Luke Moody" - ], - "ignore": [ - "examples", - "presets", - "src", - "tests", - "docs" - ], - "description": "", - "main": "build/SPE.min.js", - "moduleType": [], - "license": "MIT" -} diff --git a/build/SPE.js b/build/SPE.js deleted file mode 100644 index f8f2169..0000000 --- a/build/SPE.js +++ /dev/null @@ -1,3598 +0,0 @@ -/* shader-particle-engine 1.0.6 - * - * (c) 2015 Luke Moody (http://www.github.com/squarefeet) - * Originally based on Lee Stemkoski's original work (https://github.com/stemkoski/stemkoski.github.com/blob/master/Three.js/js/ParticleEngine.js). - * - * shader-particle-engine may be freely distributed under the MIT license (See LICENSE at root of this repository.) - */ -/** - * @typedef {Number} distribution - * @property {Number} SPE.distributions.BOX Values will be distributed within a box. - * @property {Number} SPE.distributions.SPHERE Values will be distributed within a sphere. - * @property {Number} SPE.distributions.DISC Values will be distributed within a 2D disc. - */ - -/** - * Namespace for Shader Particle Engine. - * - * All SPE-related code sits under this namespace. - * - * @type {Object} - * @namespace - */ -var SPE = { - - /** - * A map of supported distribution types used - * by SPE.Emitter instances. - * - * These distribution types can be applied to - * an emitter globally, which will affect the - * `position`, `velocity`, and `acceleration` - * value calculations for an emitter, or they - * can be applied on a per-property basis. - * - * @enum {Number} - */ - distributions: { - /** - * Values will be distributed within a box. - * @type {Number} - */ - BOX: 1, - - /** - * Values will be distributed on a sphere. - * @type {Number} - */ - SPHERE: 2, - - /** - * Values will be distributed on a 2d-disc shape. - * @type {Number} - */ - DISC: 3, - - /** - * Values will be distributed along a line. - * @type {Number} - */ - LINE: 4 - }, - - - /** - * Set this value to however many 'steps' you - * want value-over-lifetime properties to have. - * - * It's adjustable to fix an interpolation problem: - * - * Assuming you specify an opacity value as [0, 1, 0] - * and the `valueOverLifetimeLength` is 4, then the - * opacity value array will be reinterpolated to - * be [0, 0.66, 0.66, 0]. - * This isn't ideal, as particles would never reach - * full opacity. - * - * NOTE: - * This property affects the length of ALL - * value-over-lifetime properties for ALL - * emitters and ALL groups. - * - * Only values >= 3 && <= 4 are allowed. - * - * @type {Number} - */ - valueOverLifetimeLength: 4 -}; - -// Module loader support: -if ( typeof define === 'function' && define.amd ) { - define( 'spe', SPE ); -} -else if ( typeof exports !== 'undefined' && typeof module !== 'undefined' ) { - module.exports = SPE; -} - - -/** - * A helper class for TypedArrays. - * - * Allows for easy resizing, assignment of various component-based - * types (Vector2s, Vector3s, Vector4s, Mat3s, Mat4s), - * as well as Colors (where components are `r`, `g`, `b`), - * Numbers, and setting from other TypedArrays. - * - * @author Luke Moody - * @constructor - * @param {Function} TypedArrayConstructor The constructor to use (Float32Array, Uint8Array, etc.) - * @param {Number} size The size of the array to create - * @param {Number} componentSize The number of components per-value (ie. 3 for a vec3, 9 for a Mat3, etc.) - * @param {Number} indexOffset The index in the array from which to start assigning values. Default `0` if none provided - */ -SPE.TypedArrayHelper = function( TypedArrayConstructor, size, componentSize, indexOffset ) { - 'use strict'; - - this.componentSize = componentSize || 1; - this.size = ( size || 1 ); - this.TypedArrayConstructor = TypedArrayConstructor || Float32Array; - this.array = new TypedArrayConstructor( size * this.componentSize ); - this.indexOffset = indexOffset || 0; -}; - -SPE.TypedArrayHelper.constructor = SPE.TypedArrayHelper; - -/** - * Sets the size of the internal array. - * - * Delegates to `this.shrink` or `this.grow` depending on size - * argument's relation to the current size of the internal array. - * - * Note that if the array is to be shrunk, data will be lost. - * - * @param {Number} size The new size of the array. - */ -SPE.TypedArrayHelper.prototype.setSize = function( size, noComponentMultiply ) { - 'use strict'; - - var currentArraySize = this.array.length; - - if ( !noComponentMultiply ) { - size = size * this.componentSize; - } - - if ( size < currentArraySize ) { - return this.shrink( size ); - } - else if ( size > currentArraySize ) { - return this.grow( size ); - } - else { - console.info( 'TypedArray is already of size:', size + '.', 'Will not resize.' ); - } -}; - -/** - * Shrinks the internal array. - * - * @param {Number} size The new size of the typed array. Must be smaller than `this.array.length`. - * @return {SPE.TypedArrayHelper} Instance of this class. - */ -SPE.TypedArrayHelper.prototype.shrink = function( size ) { - 'use strict'; - - this.array = this.array.subarray( 0, size ); - this.size = size; - return this; -}; - -/** - * Grows the internal array. - * @param {Number} size The new size of the typed array. Must be larger than `this.array.length`. - * @return {SPE.TypedArrayHelper} Instance of this class. - */ -SPE.TypedArrayHelper.prototype.grow = function( size ) { - 'use strict'; - - var existingArray = this.array, - newArray = new this.TypedArrayConstructor( size ); - - newArray.set( existingArray ); - this.array = newArray; - this.size = size; - - return this; -}; - - -/** - * Perform a splice operation on this array's buffer. - * @param {Number} start The start index of the splice. Will be multiplied by the number of components for this attribute. - * @param {Number} end The end index of the splice. Will be multiplied by the number of components for this attribute. - * @returns {Object} The SPE.TypedArrayHelper instance. - */ -SPE.TypedArrayHelper.prototype.splice = function( start, end ) { - 'use strict'; - start *= this.componentSize; - end *= this.componentSize; - - var data = [], - array = this.array, - size = array.length; - - for ( var i = 0; i < size; ++i ) { - if ( i < start || i >= end ) { - data.push( array[ i ] ); - } - // array[ i ] = 0; - } - - this.setFromArray( 0, data ); - - return this; -}; - - -/** - * Copies from the given TypedArray into this one, using the index argument - * as the start position. Alias for `TypedArray.set`. Will automatically resize - * if the given source array is of a larger size than the internal array. - * - * @param {Number} index The start position from which to copy into this array. - * @param {TypedArray} array The array from which to copy; the source array. - * @return {SPE.TypedArrayHelper} Instance of this class. - */ -SPE.TypedArrayHelper.prototype.setFromArray = function( index, array ) { - 'use strict'; - - var sourceArraySize = array.length, - newSize = index + sourceArraySize; - - if ( newSize > this.array.length ) { - this.grow( newSize ); - } - else if ( newSize < this.array.length ) { - this.shrink( newSize ); - } - - this.array.set( array, this.indexOffset + index ); - - return this; -}; - -/** - * Set a Vector2 value at `index`. - * - * @param {Number} index The index at which to set the vec2 values from. - * @param {Vector2} vec2 Any object that has `x` and `y` properties. - * @return {SPE.TypedArrayHelper} Instance of this class. - */ -SPE.TypedArrayHelper.prototype.setVec2 = function( index, vec2 ) { - 'use strict'; - - return this.setVec2Components( index, vec2.x, vec2.y ); -}; - -/** - * Set a Vector2 value using raw components. - * - * @param {Number} index The index at which to set the vec2 values from. - * @param {Number} x The Vec2's `x` component. - * @param {Number} y The Vec2's `y` component. - * @return {SPE.TypedArrayHelper} Instance of this class. - */ -SPE.TypedArrayHelper.prototype.setVec2Components = function( index, x, y ) { - 'use strict'; - - var array = this.array, - i = this.indexOffset + ( index * this.componentSize ); - - array[ i ] = x; - array[ i + 1 ] = y; - return this; -}; - -/** - * Set a Vector3 value at `index`. - * - * @param {Number} index The index at which to set the vec3 values from. - * @param {Vector3} vec2 Any object that has `x`, `y`, and `z` properties. - * @return {SPE.TypedArrayHelper} Instance of this class. - */ -SPE.TypedArrayHelper.prototype.setVec3 = function( index, vec3 ) { - 'use strict'; - - return this.setVec3Components( index, vec3.x, vec3.y, vec3.z ); -}; - -/** - * Set a Vector3 value using raw components. - * - * @param {Number} index The index at which to set the vec3 values from. - * @param {Number} x The Vec3's `x` component. - * @param {Number} y The Vec3's `y` component. - * @param {Number} z The Vec3's `z` component. - * @return {SPE.TypedArrayHelper} Instance of this class. - */ -SPE.TypedArrayHelper.prototype.setVec3Components = function( index, x, y, z ) { - 'use strict'; - - var array = this.array, - i = this.indexOffset + ( index * this.componentSize ); - - array[ i ] = x; - array[ i + 1 ] = y; - array[ i + 2 ] = z; - return this; -}; - -/** - * Set a Vector4 value at `index`. - * - * @param {Number} index The index at which to set the vec4 values from. - * @param {Vector4} vec2 Any object that has `x`, `y`, `z`, and `w` properties. - * @return {SPE.TypedArrayHelper} Instance of this class. - */ -SPE.TypedArrayHelper.prototype.setVec4 = function( index, vec4 ) { - 'use strict'; - - return this.setVec4Components( index, vec4.x, vec4.y, vec4.z, vec4.w ); -}; - -/** - * Set a Vector4 value using raw components. - * - * @param {Number} index The index at which to set the vec4 values from. - * @param {Number} x The Vec4's `x` component. - * @param {Number} y The Vec4's `y` component. - * @param {Number} z The Vec4's `z` component. - * @param {Number} w The Vec4's `w` component. - * @return {SPE.TypedArrayHelper} Instance of this class. - */ -SPE.TypedArrayHelper.prototype.setVec4Components = function( index, x, y, z, w ) { - 'use strict'; - - var array = this.array, - i = this.indexOffset + ( index * this.componentSize ); - - array[ i ] = x; - array[ i + 1 ] = y; - array[ i + 2 ] = z; - array[ i + 3 ] = w; - return this; -}; - -/** - * Set a Matrix3 value at `index`. - * - * @param {Number} index The index at which to set the matrix values from. - * @param {Matrix3} mat3 The 3x3 matrix to set from. Must have a TypedArray property named `elements` to copy from. - * @return {SPE.TypedArrayHelper} Instance of this class. - */ -SPE.TypedArrayHelper.prototype.setMat3 = function( index, mat3 ) { - 'use strict'; - - return this.setFromArray( this.indexOffset + ( index * this.componentSize ), mat3.elements ); -}; - -/** - * Set a Matrix4 value at `index`. - * - * @param {Number} index The index at which to set the matrix values from. - * @param {Matrix4} mat3 The 4x4 matrix to set from. Must have a TypedArray property named `elements` to copy from. - * @return {SPE.TypedArrayHelper} Instance of this class. - */ -SPE.TypedArrayHelper.prototype.setMat4 = function( index, mat4 ) { - 'use strict'; - - return this.setFromArray( this.indexOffset + ( index * this.componentSize ), mat4.elements ); -}; - -/** - * Set a Color value at `index`. - * - * @param {Number} index The index at which to set the vec3 values from. - * @param {Color} color Any object that has `r`, `g`, and `b` properties. - * @return {SPE.TypedArrayHelper} Instance of this class. - */ -SPE.TypedArrayHelper.prototype.setColor = function( index, color ) { - 'use strict'; - - return this.setVec3Components( index, color.r, color.g, color.b ); -}; - -/** - * Set a Number value at `index`. - * - * @param {Number} index The index at which to set the vec3 values from. - * @param {Number} numericValue The number to assign to this index in the array. - * @return {SPE.TypedArrayHelper} Instance of this class. - */ -SPE.TypedArrayHelper.prototype.setNumber = function( index, numericValue ) { - 'use strict'; - - this.array[ this.indexOffset + ( index * this.componentSize ) ] = numericValue; - return this; -}; - -/** - * Returns the value of the array at the given index, taking into account - * the `indexOffset` property of this class. - * - * Note that this function ignores the component size and will just return a - * single value. - * - * @param {Number} index The index in the array to fetch. - * @return {Number} The value at the given index. - */ -SPE.TypedArrayHelper.prototype.getValueAtIndex = function( index ) { - 'use strict'; - - return this.array[ this.indexOffset + index ]; -}; - -/** - * Returns the component value of the array at the given index, taking into account - * the `indexOffset` property of this class. - * - * If the componentSize is set to 3, then it will return a new TypedArray - * of length 3. - * - * @param {Number} index The index in the array to fetch. - * @return {TypedArray} The component value at the given index. - */ -SPE.TypedArrayHelper.prototype.getComponentValueAtIndex = function( index ) { - 'use strict'; - - return this.array.subarray( this.indexOffset + ( index * this.componentSize ) ); -}; - -/** - * A helper to handle creating and updating a THREE.BufferAttribute instance. - * - * @author Luke Moody - * @constructor - * @param {String} type The buffer attribute type. See SPE.ShaderAttribute.typeSizeMap for valid values. - * @param {Boolean=} dynamicBuffer Whether this buffer attribute should be marked as dynamic or not. - * @param {Function=} arrayType A reference to a TypedArray constructor. Defaults to Float32Array if none provided. - */ -SPE.ShaderAttribute = function( type, dynamicBuffer, arrayType ) { - 'use strict'; - - var typeMap = SPE.ShaderAttribute.typeSizeMap; - - this.type = typeof type === 'string' && typeMap.hasOwnProperty( type ) ? type : 'f'; - this.componentSize = typeMap[ this.type ]; - this.arrayType = arrayType || Float32Array; - this.typedArray = null; - this.bufferAttribute = null; - this.dynamicBuffer = !!dynamicBuffer; - - this.updateMin = 0; - this.updateMax = 0; -}; - -SPE.ShaderAttribute.constructor = SPE.ShaderAttribute; - -/** - * A map of uniform types to their component size. - * @enum {Number} - */ -SPE.ShaderAttribute.typeSizeMap = { - /** - * Float - * @type {Number} - */ - f: 1, - - /** - * Vec2 - * @type {Number} - */ - v2: 2, - - /** - * Vec3 - * @type {Number} - */ - v3: 3, - - /** - * Vec4 - * @type {Number} - */ - v4: 4, - - /** - * Color - * @type {Number} - */ - c: 3, - - /** - * Mat3 - * @type {Number} - */ - m3: 9, - - /** - * Mat4 - * @type {Number} - */ - m4: 16 -}; - -/** - * Calculate the minimum and maximum update range for this buffer attribute using - * component size independant min and max values. - * - * @param {Number} min The start of the range to mark as needing an update. - * @param {Number} max The end of the range to mark as needing an update. - */ -SPE.ShaderAttribute.prototype.setUpdateRange = function( min, max ) { - 'use strict'; - - this.updateMin = Math.min( min * this.componentSize, this.updateMin * this.componentSize ); - this.updateMax = Math.max( max * this.componentSize, this.updateMax * this.componentSize ); -}; - -/** - * Calculate the number of indices that this attribute should mark as needing - * updating. Also marks the attribute as needing an update. - */ -SPE.ShaderAttribute.prototype.flagUpdate = function() { - 'use strict'; - - var attr = this.bufferAttribute, - range = attr.updateRange; - - range.offset = this.updateMin; - range.count = Math.min( ( this.updateMax - this.updateMin ) + this.componentSize, this.typedArray.array.length ); - // console.log( range.offset, range.count, this.typedArray.array.length ); - // console.log( 'flagUpdate:', range.offset, range.count ); - attr.needsUpdate = true; -}; - - - -/** - * Reset the index update counts for this attribute - */ -SPE.ShaderAttribute.prototype.resetUpdateRange = function() { - 'use strict'; - - this.updateMin = 0; - this.updateMax = 0; -}; - -SPE.ShaderAttribute.prototype.resetDynamic = function() { - 'use strict'; - this.bufferAttribute.usage = this.dynamicBuffer ? - THREE.DynamicDrawUsage : - THREE.StaticDrawUsage; -}; - -/** - * Perform a splice operation on this attribute's buffer. - * @param {Number} start The start index of the splice. Will be multiplied by the number of components for this attribute. - * @param {Number} end The end index of the splice. Will be multiplied by the number of components for this attribute. - */ -SPE.ShaderAttribute.prototype.splice = function( start, end ) { - 'use strict'; - - this.typedArray.splice( start, end ); - - // Reset the reference to the attribute's typed array - // since it has probably changed. - this.forceUpdateAll(); -}; - -SPE.ShaderAttribute.prototype.forceUpdateAll = function() { - 'use strict'; - - this.bufferAttribute.array = this.typedArray.array; - this.bufferAttribute.updateRange.offset = 0; - this.bufferAttribute.updateRange.count = -1; - // this.bufferAttribute.dynamic = false; - // this.bufferAttribute.usage = this.dynamicBuffer ? - // THREE.DynamicDrawUsage : - // THREE.StaticDrawUsage; - - this.bufferAttribute.usage = THREE.StaticDrawUsage; - this.bufferAttribute.needsUpdate = true; -}; - -/** - * Make sure this attribute has a typed array associated with it. - * - * If it does, then it will ensure the typed array is of the correct size. - * - * If not, a new SPE.TypedArrayHelper instance will be created. - * - * @param {Number} size The size of the typed array to create or update to. - */ -SPE.ShaderAttribute.prototype._ensureTypedArray = function( size ) { - 'use strict'; - - // Condition that's most likely to be true at the top: no change. - if ( this.typedArray !== null && this.typedArray.size === size * this.componentSize ) { - return; - } - - // Resize the array if we need to, telling the TypedArrayHelper to - // ignore it's component size when evaluating size. - else if ( this.typedArray !== null && this.typedArray.size !== size ) { - this.typedArray.setSize( size ); - } - - // This condition should only occur once in an attribute's lifecycle. - else if ( this.typedArray === null ) { - this.typedArray = new SPE.TypedArrayHelper( this.arrayType, size, this.componentSize ); - } -}; - - -/** - * Creates a THREE.BufferAttribute instance if one doesn't exist already. - * - * Ensures a typed array is present by calling _ensureTypedArray() first. - * - * If a buffer attribute exists already, then it will be marked as needing an update. - * - * @param {Number} size The size of the typed array to create if one doesn't exist, or resize existing array to. - */ -SPE.ShaderAttribute.prototype._createBufferAttribute = function( size ) { - 'use strict'; - - // Make sure the typedArray is present and correct. - this._ensureTypedArray( size ); - - // Don't create it if it already exists, but do - // flag that it needs updating on the next render - // cycle. - if ( this.bufferAttribute !== null ) { - this.bufferAttribute.array = this.typedArray.array; - - // Since THREE.js version 81, dynamic count calculation was removed - // so I need to do it manually here. - // - // In the next minor release, I may well remove this check and force - // dependency on THREE r81+. - if ( parseFloat( THREE.REVISION ) >= 81 ) { - this.bufferAttribute.count = this.bufferAttribute.array.length / this.bufferAttribute.itemSize; - } - - this.bufferAttribute.needsUpdate = true; - return; - } - - this.bufferAttribute = new THREE.BufferAttribute( this.typedArray.array, this.componentSize ); - // this.bufferAttribute.dynamic = this.dynamicBuffer; - this.bufferAttribute.usage = this.dynamicBuffer ? - THREE.DynamicDrawUsage : - THREE.StaticDrawUsage; -}; - -/** - * Returns the length of the typed array associated with this attribute. - * @return {Number} The length of the typed array. Will be 0 if no typed array has been created yet. - */ -SPE.ShaderAttribute.prototype.getLength = function() { - 'use strict'; - - if ( this.typedArray === null ) { - return 0; - } - - return this.typedArray.array.length; -}; - - -SPE.shaderChunks = { - // Register color-packing define statements. - defines: [ - '#define PACKED_COLOR_SIZE 256.0', - '#define PACKED_COLOR_DIVISOR 255.0' - ].join( '\n' ), - - // All uniforms used by vertex / fragment shaders - uniforms: [ - 'uniform float deltaTime;', - 'uniform float runTime;', - 'uniform sampler2D tex;', - 'uniform vec4 textureAnimation;', - 'uniform float scale;', - ].join( '\n' ), - - // All attributes used by the vertex shader. - // - // Note that some attributes are squashed into other ones: - // - // * Drag is acceleration.w - attributes: [ - 'attribute vec4 acceleration;', - 'attribute vec3 velocity;', - 'attribute vec4 rotation;', - 'attribute vec3 rotationCenter;', - 'attribute vec4 params;', - 'attribute vec4 size;', - 'attribute vec4 angle;', - 'attribute vec4 color;', - 'attribute vec4 opacity;' - ].join( '\n' ), - - // - varyings: [ - 'varying vec4 vColor;', - '#ifdef SHOULD_ROTATE_TEXTURE', - ' varying float vAngle;', - '#endif', - - '#ifdef SHOULD_CALCULATE_SPRITE', - ' varying vec4 vSpriteSheet;', - '#endif' - ].join( '\n' ), - - - // Branch-avoiding comparison fns - // - http://theorangeduck.com/page/avoiding-shader-conditionals - branchAvoidanceFunctions: [ - 'float when_gt(float x, float y) {', - ' return max(sign(x - y), 0.0);', - '}', - - 'float when_lt(float x, float y) {', - ' return min( max(1.0 - sign(x - y), 0.0), 1.0 );', - '}', - - 'float when_eq( float x, float y ) {', - ' return 1.0 - abs( sign( x - y ) );', - '}', - - 'float when_ge(float x, float y) {', - ' return 1.0 - when_lt(x, y);', - '}', - - 'float when_le(float x, float y) {', - ' return 1.0 - when_gt(x, y);', - '}', - - // Branch-avoiding logical operators - // (to be used with above comparison fns) - 'float and(float a, float b) {', - ' return a * b;', - '}', - - 'float or(float a, float b) {', - ' return min(a + b, 1.0);', - '}', - ].join( '\n' ), - - - // From: - // - http://stackoverflow.com/a/12553149 - // - https://stackoverflow.com/questions/22895237/hexadecimal-to-rgb-values-in-webgl-shader - unpackColor: [ - 'vec3 unpackColor( in float hex ) {', - ' vec3 c = vec3( 0.0 );', - - ' float r = mod( (hex / PACKED_COLOR_SIZE / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );', - ' float g = mod( (hex / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );', - ' float b = mod( hex, PACKED_COLOR_SIZE );', - - ' c.r = r / PACKED_COLOR_DIVISOR;', - ' c.g = g / PACKED_COLOR_DIVISOR;', - ' c.b = b / PACKED_COLOR_DIVISOR;', - - ' return c;', - '}', - ].join( '\n' ), - - unpackRotationAxis: [ - 'vec3 unpackRotationAxis( in float hex ) {', - ' vec3 c = vec3( 0.0 );', - - ' float r = mod( (hex / PACKED_COLOR_SIZE / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );', - ' float g = mod( (hex / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );', - ' float b = mod( hex, PACKED_COLOR_SIZE );', - - ' c.r = r / PACKED_COLOR_DIVISOR;', - ' c.g = g / PACKED_COLOR_DIVISOR;', - ' c.b = b / PACKED_COLOR_DIVISOR;', - - ' c *= vec3( 2.0 );', - ' c -= vec3( 1.0 );', - - ' return c;', - '}', - ].join( '\n' ), - - floatOverLifetime: [ - 'float getFloatOverLifetime( in float positionInTime, in vec4 attr ) {', - ' highp float value = 0.0;', - ' float deltaAge = positionInTime * float( VALUE_OVER_LIFETIME_LENGTH - 1 );', - ' float fIndex = 0.0;', - ' float shouldApplyValue = 0.0;', - - // This might look a little odd, but it's faster in the testing I've done than using branches. - // Uses basic maths to avoid branching. - // - // Take a look at the branch-avoidance functions defined above, - // and be sure to check out The Orange Duck site where I got this - // from (link above). - - // Fix for static emitters (age is always zero). - ' value += attr[ 0 ] * when_eq( deltaAge, 0.0 );', - '', - ' for( int i = 0; i < VALUE_OVER_LIFETIME_LENGTH - 1; ++i ) {', - ' fIndex = float( i );', - ' shouldApplyValue = and( when_gt( deltaAge, fIndex ), when_le( deltaAge, fIndex + 1.0 ) );', - ' value += shouldApplyValue * mix( attr[ i ], attr[ i + 1 ], deltaAge - fIndex );', - ' }', - '', - ' return value;', - '}', - ].join( '\n' ), - - colorOverLifetime: [ - 'vec3 getColorOverLifetime( in float positionInTime, in vec3 color1, in vec3 color2, in vec3 color3, in vec3 color4 ) {', - ' vec3 value = vec3( 0.0 );', - ' value.x = getFloatOverLifetime( positionInTime, vec4( color1.x, color2.x, color3.x, color4.x ) );', - ' value.y = getFloatOverLifetime( positionInTime, vec4( color1.y, color2.y, color3.y, color4.y ) );', - ' value.z = getFloatOverLifetime( positionInTime, vec4( color1.z, color2.z, color3.z, color4.z ) );', - ' return value;', - '}', - ].join( '\n' ), - - paramFetchingFunctions: [ - 'float getAlive() {', - ' return params.x;', - '}', - - 'float getAge() {', - ' return params.y;', - '}', - - 'float getMaxAge() {', - ' return params.z;', - '}', - - 'float getWiggle() {', - ' return params.w;', - '}', - ].join( '\n' ), - - forceFetchingFunctions: [ - 'vec4 getPosition( in float age ) {', - ' return modelViewMatrix * vec4( position, 1.0 );', - '}', - - 'vec3 getVelocity( in float age ) {', - ' return velocity * age;', - '}', - - 'vec3 getAcceleration( in float age ) {', - ' return acceleration.xyz * age;', - '}', - ].join( '\n' ), - - - rotationFunctions: [ - // Huge thanks to: - // - http://www.neilmendoza.com/glsl-rotation-about-an-arbitrary-axis/ - '#ifdef SHOULD_ROTATE_PARTICLES', - ' mat4 getRotationMatrix( in vec3 axis, in float angle) {', - ' axis = normalize(axis);', - ' float s = sin(angle);', - ' float c = cos(angle);', - ' float oc = 1.0 - c;', - '', - ' return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0,', - ' oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0,', - ' oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0,', - ' 0.0, 0.0, 0.0, 1.0);', - ' }', - '', - ' vec3 getRotation( in vec3 pos, in float positionInTime ) {', - ' if( rotation.y == 0.0 ) {', - ' return pos;', - ' }', - '', - ' vec3 axis = unpackRotationAxis( rotation.x );', - ' vec3 center = rotationCenter;', - ' vec3 translated;', - ' mat4 rotationMatrix;', - - ' float angle = 0.0;', - ' angle += when_eq( rotation.z, 0.0 ) * rotation.y;', - ' angle += when_gt( rotation.z, 0.0 ) * mix( 0.0, rotation.y, positionInTime );', - ' translated = rotationCenter - pos;', - ' rotationMatrix = getRotationMatrix( axis, angle );', - ' return center - vec3( rotationMatrix * vec4( translated, 0.0 ) );', - ' }', - '#endif' - ].join( '\n' ), - - - // Fragment chunks - rotateTexture: [ - ' vec2 vUv = vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y );', - '', - ' #ifdef SHOULD_ROTATE_TEXTURE', - ' float x = gl_PointCoord.x - 0.5;', - ' float y = 1.0 - gl_PointCoord.y - 0.5;', - ' float c = cos( -vAngle );', - ' float s = sin( -vAngle );', - - ' vUv = vec2( c * x + s * y + 0.5, c * y - s * x + 0.5 );', - ' #endif', - '', - - // Spritesheets overwrite angle calculations. - ' #ifdef SHOULD_CALCULATE_SPRITE', - ' float framesX = vSpriteSheet.x;', - ' float framesY = vSpriteSheet.y;', - ' float columnNorm = vSpriteSheet.z;', - ' float rowNorm = vSpriteSheet.w;', - - ' vUv.x = gl_PointCoord.x * framesX + columnNorm;', - ' vUv.y = 1.0 - (gl_PointCoord.y * framesY + rowNorm);', - ' #endif', - - '', - ' vec4 rotatedTexture = texture2D( tex, vUv );', - ].join( '\n' ) -}; - -SPE.shaders = { - vertex: [ - SPE.shaderChunks.defines, - SPE.shaderChunks.uniforms, - SPE.shaderChunks.attributes, - SPE.shaderChunks.varyings, - - THREE.ShaderChunk.common, - THREE.ShaderChunk.logdepthbuf_pars_vertex, - THREE.ShaderChunk.fog_pars_vertex, - - SPE.shaderChunks.branchAvoidanceFunctions, - SPE.shaderChunks.unpackColor, - SPE.shaderChunks.unpackRotationAxis, - SPE.shaderChunks.floatOverLifetime, - SPE.shaderChunks.colorOverLifetime, - SPE.shaderChunks.paramFetchingFunctions, - SPE.shaderChunks.forceFetchingFunctions, - SPE.shaderChunks.rotationFunctions, - - - 'void main() {', - - - // - // Setup... - // - ' highp float age = getAge();', - ' highp float alive = getAlive();', - ' highp float maxAge = getMaxAge();', - ' highp float positionInTime = (age / maxAge);', - ' highp float isAlive = when_gt( alive, 0.0 );', - - ' #ifdef SHOULD_WIGGLE_PARTICLES', - ' float wiggleAmount = positionInTime * getWiggle();', - ' float wiggleSin = isAlive * sin( wiggleAmount );', - ' float wiggleCos = isAlive * cos( wiggleAmount );', - ' #endif', - - // - // Forces - // - - // Get forces & position - ' vec3 vel = getVelocity( age );', - ' vec3 accel = getAcceleration( age );', - ' vec3 force = vec3( 0.0 );', - ' vec3 pos = vec3( position );', - - // Calculate the required drag to apply to the forces. - ' float drag = 1.0 - (positionInTime * 0.5) * acceleration.w;', - - // Integrate forces... - ' force += vel;', - ' force *= drag;', - ' force += accel * age;', - ' pos += force;', - - - // Wiggly wiggly wiggle! - ' #ifdef SHOULD_WIGGLE_PARTICLES', - ' pos.x += wiggleSin;', - ' pos.y += wiggleCos;', - ' pos.z += wiggleSin;', - ' #endif', - - - // Rotate the emitter around it's central point - ' #ifdef SHOULD_ROTATE_PARTICLES', - ' pos = getRotation( pos, positionInTime );', - ' #endif', - - // Convert pos to a world-space value - ' vec4 mvPosition = modelViewMatrix * vec4( pos, 1.0 );', - - // Determine point size. - ' highp float pointSize = getFloatOverLifetime( positionInTime, size ) * isAlive;', - - // Determine perspective - ' #ifdef HAS_PERSPECTIVE', - ' float perspective = scale / length( mvPosition.xyz );', - ' #else', - ' float perspective = 1.0;', - ' #endif', - - // Apply perpective to pointSize value - ' float pointSizePerspective = pointSize * perspective;', - - - // - // Appearance - // - - // Determine color and opacity for this particle - ' #ifdef COLORIZE', - ' vec3 c = isAlive * getColorOverLifetime(', - ' positionInTime,', - ' unpackColor( color.x ),', - ' unpackColor( color.y ),', - ' unpackColor( color.z ),', - ' unpackColor( color.w )', - ' );', - ' #else', - ' vec3 c = vec3(1.0);', - ' #endif', - - ' float o = isAlive * getFloatOverLifetime( positionInTime, opacity );', - - // Assign color to vColor varying. - ' vColor = vec4( c, o );', - - // Determine angle - ' #ifdef SHOULD_ROTATE_TEXTURE', - ' vAngle = isAlive * getFloatOverLifetime( positionInTime, angle );', - ' #endif', - - // If this particle is using a sprite-sheet as a texture, we'll have to figure out - // what frame of the texture the particle is using at it's current position in time. - ' #ifdef SHOULD_CALCULATE_SPRITE', - ' float framesX = textureAnimation.x;', - ' float framesY = textureAnimation.y;', - ' float loopCount = textureAnimation.w;', - ' float totalFrames = textureAnimation.z;', - ' float frameNumber = mod( (positionInTime * loopCount) * totalFrames, totalFrames );', - - ' float column = floor(mod( frameNumber, framesX ));', - ' float row = floor( (frameNumber - column) / framesX );', - - ' float columnNorm = column / framesX;', - ' float rowNorm = row / framesY;', - - ' vSpriteSheet.x = 1.0 / framesX;', - ' vSpriteSheet.y = 1.0 / framesY;', - ' vSpriteSheet.z = columnNorm;', - ' vSpriteSheet.w = rowNorm;', - ' #endif', - - // - // Write values - // - - // Set PointSize according to size at current point in time. - ' gl_PointSize = pointSizePerspective;', - ' gl_Position = projectionMatrix * mvPosition;', - - THREE.ShaderChunk.logdepthbuf_vertex, - THREE.ShaderChunk.fog_vertex, - - '}' - ].join( '\n' ), - - fragment: [ - SPE.shaderChunks.uniforms, - - THREE.ShaderChunk.common, - THREE.ShaderChunk.fog_pars_fragment, - THREE.ShaderChunk.logdepthbuf_pars_fragment, - - SPE.shaderChunks.varyings, - - SPE.shaderChunks.branchAvoidanceFunctions, - - 'void main() {', - ' vec3 outgoingLight = vColor.xyz;', - ' ', - ' #ifdef ALPHATEST', - ' if ( vColor.w < float(ALPHATEST) ) discard;', - ' #endif', - - SPE.shaderChunks.rotateTexture, - - THREE.ShaderChunk.logdepthbuf_fragment, - - ' outgoingLight = vColor.xyz * rotatedTexture.xyz;', - ' gl_FragColor = vec4( outgoingLight.xyz, rotatedTexture.w * vColor.w );', - - THREE.ShaderChunk.fog_fragment, - - '}' - ].join( '\n' ) -}; - - -/** - * A bunch of utility functions used throughout the library. - * @namespace - * @type {Object} - */ -SPE.utils = { - /** - * A map of types used by `SPE.utils.ensureTypedArg` and - * `SPE.utils.ensureArrayTypedArg` to compare types against. - * - * @enum {String} - */ - types: { - /** - * Boolean type. - * @type {String} - */ - BOOLEAN: 'boolean', - - /** - * String type. - * @type {String} - */ - STRING: 'string', - - /** - * Number type. - * @type {String} - */ - NUMBER: 'number', - - /** - * Object type. - * @type {String} - */ - OBJECT: 'object' - }, - - /** - * Given a value, a type, and a default value to fallback to, - * ensure the given argument adheres to the type requesting, - * returning the default value if type check is false. - * - * @param {(boolean|string|number|object)} arg The value to perform a type-check on. - * @param {String} type The type the `arg` argument should adhere to. - * @param {(boolean|string|number|object)} defaultValue A default value to fallback on if the type check fails. - * @return {(boolean|string|number|object)} The given value if type check passes, or the default value if it fails. - */ - ensureTypedArg: function( arg, type, defaultValue ) { - 'use strict'; - - if ( typeof arg === type ) { - return arg; - } - else { - return defaultValue; - } - }, - - /** - * Given an array of values, a type, and a default value, - * ensure the given array's contents ALL adhere to the provided type, - * returning the default value if type check fails. - * - * If the given value to check isn't an Array, delegates to SPE.utils.ensureTypedArg. - * - * @param {Array|boolean|string|number|object} arg The array of values to check type of. - * @param {String} type The type that should be adhered to. - * @param {(boolean|string|number|object)} defaultValue A default fallback value. - * @return {(boolean|string|number|object)} The given value if type check passes, or the default value if it fails. - */ - ensureArrayTypedArg: function( arg, type, defaultValue ) { - 'use strict'; - - // If the argument being checked is an array, loop through - // it and ensure all the values are of the correct type, - // falling back to the defaultValue if any aren't. - if ( Array.isArray( arg ) ) { - for ( var i = arg.length - 1; i >= 0; --i ) { - if ( typeof arg[ i ] !== type ) { - return defaultValue; - } - } - - return arg; - } - - // If the arg isn't an array then just fallback to - // checking the type. - return this.ensureTypedArg( arg, type, defaultValue ); - }, - - /** - * Ensures the given value is an instance of a constructor function. - * - * @param {Object} arg The value to check instance of. - * @param {Function} instance The constructor of the instance to check against. - * @param {Object} defaultValue A default fallback value if instance check fails - * @return {Object} The given value if type check passes, or the default value if it fails. - */ - ensureInstanceOf: function( arg, instance, defaultValue ) { - 'use strict'; - - if ( instance !== undefined && arg instanceof instance ) { - return arg; - } - else { - return defaultValue; - } - }, - - /** - * Given an array of values, ensure the instances of all items in the array - * matches the given instance constructor falling back to a default value if - * the check fails. - * - * If given value isn't an Array, delegates to `SPE.utils.ensureInstanceOf`. - * - * @param {Array|Object} arg The value to perform the instanceof check on. - * @param {Function} instance The constructor of the instance to check against. - * @param {Object} defaultValue A default fallback value if instance check fails - * @return {Object} The given value if type check passes, or the default value if it fails. - */ - ensureArrayInstanceOf: function( arg, instance, defaultValue ) { - 'use strict'; - - // If the argument being checked is an array, loop through - // it and ensure all the values are of the correct type, - // falling back to the defaultValue if any aren't. - if ( Array.isArray( arg ) ) { - for ( var i = arg.length - 1; i >= 0; --i ) { - if ( instance !== undefined && arg[ i ] instanceof instance === false ) { - return defaultValue; - } - } - - return arg; - } - - // If the arg isn't an array then just fallback to - // checking the type. - return this.ensureInstanceOf( arg, instance, defaultValue ); - }, - - /** - * Ensures that any "value-over-lifetime" properties of an emitter are - * of the correct length (as dictated by `SPE.valueOverLifetimeLength`). - * - * Delegates to `SPE.utils.interpolateArray` for array resizing. - * - * If properties aren't arrays, then property values are put into one. - * - * @param {Object} property The property of an SPE.Emitter instance to check compliance of. - * @param {Number} minLength The minimum length of the array to create. - * @param {Number} maxLength The maximum length of the array to create. - */ - ensureValueOverLifetimeCompliance: function( property, minLength, maxLength ) { - 'use strict'; - - minLength = minLength || 3; - maxLength = maxLength || 3; - - // First, ensure both properties are arrays. - if ( Array.isArray( property._value ) === false ) { - property._value = [ property._value ]; - } - - if ( Array.isArray( property._spread ) === false ) { - property._spread = [ property._spread ]; - } - - var valueLength = this.clamp( property._value.length, minLength, maxLength ), - spreadLength = this.clamp( property._spread.length, minLength, maxLength ), - desiredLength = Math.max( valueLength, spreadLength ); - - if ( property._value.length !== desiredLength ) { - property._value = this.interpolateArray( property._value, desiredLength ); - } - - if ( property._spread.length !== desiredLength ) { - property._spread = this.interpolateArray( property._spread, desiredLength ); - } - }, - - /** - * Performs linear interpolation (lerp) on an array. - * - * For example, lerping [1, 10], with a `newLength` of 10 will produce [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]. - * - * Delegates to `SPE.utils.lerpTypeAgnostic` to perform the actual - * interpolation. - * - * @param {Array} srcArray The array to lerp. - * @param {Number} newLength The length the array should be interpolated to. - * @return {Array} The interpolated array. - */ - interpolateArray: function( srcArray, newLength ) { - 'use strict'; - - var sourceLength = srcArray.length, - newArray = [ typeof srcArray[ 0 ].clone === 'function' ? srcArray[ 0 ].clone() : srcArray[ 0 ] ], - factor = ( sourceLength - 1 ) / ( newLength - 1 ); - - - for ( var i = 1; i < newLength - 1; ++i ) { - var f = i * factor, - before = Math.floor( f ), - after = Math.ceil( f ), - delta = f - before; - - newArray[ i ] = this.lerpTypeAgnostic( srcArray[ before ], srcArray[ after ], delta ); - } - - newArray.push( - typeof srcArray[ sourceLength - 1 ].clone === 'function' ? - srcArray[ sourceLength - 1 ].clone() : - srcArray[ sourceLength - 1 ] - ); - - return newArray; - }, - - /** - * Clamp a number to between the given min and max values. - * @param {Number} value The number to clamp. - * @param {Number} min The minimum value. - * @param {Number} max The maximum value. - * @return {Number} The clamped number. - */ - clamp: function( value, min, max ) { - 'use strict'; - - return Math.max( min, Math.min( value, max ) ); - }, - - /** - * If the given value is less than the epsilon value, then return - * a randomised epsilon value if specified, or just the epsilon value if not. - * Works for negative numbers as well as positive. - * - * @param {Number} value The value to perform the operation on. - * @param {Boolean} randomise Whether the value should be randomised. - * @return {Number} The result of the operation. - */ - zeroToEpsilon: function( value, randomise ) { - 'use strict'; - - var epsilon = 0.00001, - result = value; - - result = randomise ? Math.random() * epsilon * 10 : epsilon; - - if ( value < 0 && value > -epsilon ) { - result = -result; - } - - // if ( value === 0 ) { - // result = randomise ? Math.random() * epsilon * 10 : epsilon; - // } - // else if ( value > 0 && value < epsilon ) { - // result = randomise ? Math.random() * epsilon * 10 : epsilon; - // } - // else if ( value < 0 && value > -epsilon ) { - // result = -( randomise ? Math.random() * epsilon * 10 : epsilon ); - // } - - return result; - }, - - /** - * Linearly interpolates two values of various types. The given values - * must be of the same type for the interpolation to work. - * @param {(number|Object)} start The start value of the lerp. - * @param {(number|object)} end The end value of the lerp. - * @param {Number} delta The delta posiiton of the lerp operation. Ideally between 0 and 1 (inclusive). - * @return {(number|object|undefined)} The result of the operation. Result will be undefined if - * the start and end arguments aren't a supported type, or - * if their types do not match. - */ - lerpTypeAgnostic: function( start, end, delta ) { - 'use strict'; - - var types = this.types, - out; - - if ( typeof start === types.NUMBER && typeof end === types.NUMBER ) { - return start + ( ( end - start ) * delta ); - } - else if ( start instanceof THREE.Vector2 && end instanceof THREE.Vector2 ) { - out = start.clone(); - out.x = this.lerp( start.x, end.x, delta ); - out.y = this.lerp( start.y, end.y, delta ); - return out; - } - else if ( start instanceof THREE.Vector3 && end instanceof THREE.Vector3 ) { - out = start.clone(); - out.x = this.lerp( start.x, end.x, delta ); - out.y = this.lerp( start.y, end.y, delta ); - out.z = this.lerp( start.z, end.z, delta ); - return out; - } - else if ( start instanceof THREE.Vector4 && end instanceof THREE.Vector4 ) { - out = start.clone(); - out.x = this.lerp( start.x, end.x, delta ); - out.y = this.lerp( start.y, end.y, delta ); - out.z = this.lerp( start.z, end.z, delta ); - out.w = this.lerp( start.w, end.w, delta ); - return out; - } - else if ( start instanceof THREE.Color && end instanceof THREE.Color ) { - out = start.clone(); - out.r = this.lerp( start.r, end.r, delta ); - out.g = this.lerp( start.g, end.g, delta ); - out.b = this.lerp( start.b, end.b, delta ); - return out; - } - else { - console.warn( 'Invalid argument types, or argument types do not match:', start, end ); - } - }, - - /** - * Perform a linear interpolation operation on two numbers. - * @param {Number} start The start value. - * @param {Number} end The end value. - * @param {Number} delta The position to interpolate to. - * @return {Number} The result of the lerp operation. - */ - lerp: function( start, end, delta ) { - 'use strict'; - return start + ( ( end - start ) * delta ); - }, - - /** - * Rounds a number to a nearest multiple. - * - * @param {Number} n The number to round. - * @param {Number} multiple The multiple to round to. - * @return {Number} The result of the round operation. - */ - roundToNearestMultiple: function( n, multiple ) { - 'use strict'; - - var remainder = 0; - - if ( multiple === 0 ) { - return n; - } - - remainder = Math.abs( n ) % multiple; - - if ( remainder === 0 ) { - return n; - } - - if ( n < 0 ) { - return -( Math.abs( n ) - remainder ); - } - - return n + multiple - remainder; - }, - - /** - * Check if all items in an array are equal. Uses strict equality. - * - * @param {Array} array The array of values to check equality of. - * @return {Boolean} Whether the array's values are all equal or not. - */ - arrayValuesAreEqual: function( array ) { - 'use strict'; - - for ( var i = 0; i < array.length - 1; ++i ) { - if ( array[ i ] !== array[ i + 1 ] ) { - return false; - } - } - - return true; - }, - - // colorsAreEqual: function() { - // var colors = Array.prototype.slice.call( arguments ), - // numColors = colors.length; - - // for ( var i = 0, color1, color2; i < numColors - 1; ++i ) { - // color1 = colors[ i ]; - // color2 = colors[ i + 1 ]; - - // if ( - // color1.r !== color2.r || - // color1.g !== color2.g || - // color1.b !== color2.b - // ) { - // return false - // } - // } - - // return true; - // }, - - - /** - * Given a start value and a spread value, create and return a random - * number. - * @param {Number} base The start value. - * @param {Number} spread The size of the random variance to apply. - * @return {Number} A randomised number. - */ - randomFloat: function( base, spread ) { - 'use strict'; - return base + spread * ( Math.random() - 0.5 ); - }, - - - - /** - * Given an SPE.ShaderAttribute instance, and various other settings, - * assign values to the attribute's array in a `vec3` format. - * - * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to. - * @param {Number} index The offset in the attribute's TypedArray to save the result from. - * @param {Object} base THREE.Vector3 instance describing the start value. - * @param {Object} spread THREE.Vector3 instance describing the random variance to apply to the start value. - * @param {Object} spreadClamp THREE.Vector3 instance describing the multiples to clamp the randomness to. - */ - randomVector3: function( attribute, index, base, spread, spreadClamp ) { - 'use strict'; - - var x = base.x + ( Math.random() * spread.x - ( spread.x * 0.5 ) ), - y = base.y + ( Math.random() * spread.y - ( spread.y * 0.5 ) ), - z = base.z + ( Math.random() * spread.z - ( spread.z * 0.5 ) ); - - // var x = this.randomFloat( base.x, spread.x ), - // y = this.randomFloat( base.y, spread.y ), - // z = this.randomFloat( base.z, spread.z ); - - if ( spreadClamp ) { - x = -spreadClamp.x * 0.5 + this.roundToNearestMultiple( x, spreadClamp.x ); - y = -spreadClamp.y * 0.5 + this.roundToNearestMultiple( y, spreadClamp.y ); - z = -spreadClamp.z * 0.5 + this.roundToNearestMultiple( z, spreadClamp.z ); - } - - attribute.typedArray.setVec3Components( index, x, y, z ); - }, - - /** - * Given an SPE.Shader attribute instance, and various other settings, - * assign Color values to the attribute. - * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to. - * @param {Number} index The offset in the attribute's TypedArray to save the result from. - * @param {Object} base THREE.Color instance describing the start color. - * @param {Object} spread THREE.Vector3 instance describing the random variance to apply to the start color. - */ - randomColor: function( attribute, index, base, spread ) { - 'use strict'; - - var r = base.r + ( Math.random() * spread.x ), - g = base.g + ( Math.random() * spread.y ), - b = base.b + ( Math.random() * spread.z ); - - r = this.clamp( r, 0, 1 ); - g = this.clamp( g, 0, 1 ); - b = this.clamp( b, 0, 1 ); - - - attribute.typedArray.setVec3Components( index, r, g, b ); - }, - - - randomColorAsHex: ( function() { - 'use strict'; - - var workingColor = new THREE.Color(); - - /** - * Assigns a random color value, encoded as a hex value in decimal - * format, to a SPE.ShaderAttribute instance. - * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to. - * @param {Number} index The offset in the attribute's TypedArray to save the result from. - * @param {Object} base THREE.Color instance describing the start color. - * @param {Object} spread THREE.Vector3 instance describing the random variance to apply to the start color. - */ - return function( attribute, index, base, spread ) { - var numItems = base.length, - colors = []; - - for ( var i = 0; i < numItems; ++i ) { - var spreadVector = spread[ i ]; - - workingColor.copy( base[ i ] ); - - workingColor.r += ( Math.random() * spreadVector.x ) - ( spreadVector.x * 0.5 ); - workingColor.g += ( Math.random() * spreadVector.y ) - ( spreadVector.y * 0.5 ); - workingColor.b += ( Math.random() * spreadVector.z ) - ( spreadVector.z * 0.5 ); - - workingColor.r = this.clamp( workingColor.r, 0, 1 ); - workingColor.g = this.clamp( workingColor.g, 0, 1 ); - workingColor.b = this.clamp( workingColor.b, 0, 1 ); - - colors.push( workingColor.getHex() ); - } - - attribute.typedArray.setVec4Components( index, colors[ 0 ], colors[ 1 ], colors[ 2 ], colors[ 3 ] ); - }; - }() ), - - /** - * Given an SPE.ShaderAttribute instance, and various other settings, - * assign values to the attribute's array in a `vec3` format. - * - * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to. - * @param {Number} index The offset in the attribute's TypedArray to save the result from. - * @param {Object} start THREE.Vector3 instance describing the start line position. - * @param {Object} end THREE.Vector3 instance describing the end line position. - */ - randomVector3OnLine: function( attribute, index, start, end ) { - 'use strict'; - var pos = start.clone(); - - pos.lerp( end, Math.random() ); - - attribute.typedArray.setVec3Components( index, pos.x, pos.y, pos.z ); - }, - - /** - * Given an SPE.Shader attribute instance, and various other settings, - * assign Color values to the attribute. - * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to. - * @param {Number} index The offset in the attribute's TypedArray to save the result from. - * @param {Object} base THREE.Color instance describing the start color. - * @param {Object} spread THREE.Vector3 instance describing the random variance to apply to the start color. - */ - - /** - * Assigns a random vector 3 value to an SPE.ShaderAttribute instance, projecting the - * given values onto a sphere. - * - * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to. - * @param {Number} index The offset in the attribute's TypedArray to save the result from. - * @param {Object} base THREE.Vector3 instance describing the origin of the transform. - * @param {Number} radius The radius of the sphere to project onto. - * @param {Number} radiusSpread The amount of randomness to apply to the projection result - * @param {Object} radiusScale THREE.Vector3 instance describing the scale of each axis of the sphere. - * @param {Number} radiusSpreadClamp What numeric multiple the projected value should be clamped to. - */ - randomVector3OnSphere: function( - attribute, index, base, radius, radiusSpread, radiusScale, radiusSpreadClamp, distributionClamp - ) { - 'use strict'; - - var depth = 2 * Math.random() - 1, - t = 6.2832 * Math.random(), - r = Math.sqrt( 1 - depth * depth ), - rand = this.randomFloat( radius, radiusSpread ), - x = 0, - y = 0, - z = 0; - - - if ( radiusSpreadClamp ) { - rand = Math.round( rand / radiusSpreadClamp ) * radiusSpreadClamp; - } - - - - // Set position on sphere - x = r * Math.cos( t ) * rand; - y = r * Math.sin( t ) * rand; - z = depth * rand; - - // Apply radius scale to this position - x *= radiusScale.x; - y *= radiusScale.y; - z *= radiusScale.z; - - // Translate to the base position. - x += base.x; - y += base.y; - z += base.z; - - // Set the values in the typed array. - attribute.typedArray.setVec3Components( index, x, y, z ); - }, - - seededRandom: function( seed ) { - var x = Math.sin( seed ) * 10000; - return x - ( x | 0 ); - }, - - - - /** - * Assigns a random vector 3 value to an SPE.ShaderAttribute instance, projecting the - * given values onto a 2d-disc. - * - * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to. - * @param {Number} index The offset in the attribute's TypedArray to save the result from. - * @param {Object} base THREE.Vector3 instance describing the origin of the transform. - * @param {Number} radius The radius of the sphere to project onto. - * @param {Number} radiusSpread The amount of randomness to apply to the projection result - * @param {Object} radiusScale THREE.Vector3 instance describing the scale of each axis of the disc. The z-component is ignored. - * @param {Number} radiusSpreadClamp What numeric multiple the projected value should be clamped to. - */ - randomVector3OnDisc: function( attribute, index, base, radius, radiusSpread, radiusScale, radiusSpreadClamp ) { - 'use strict'; - - var t = 6.2832 * Math.random(), - rand = Math.abs( this.randomFloat( radius, radiusSpread ) ), - x = 0, - y = 0, - z = 0; - - if ( radiusSpreadClamp ) { - rand = Math.round( rand / radiusSpreadClamp ) * radiusSpreadClamp; - } - - // Set position on sphere - x = Math.cos( t ) * rand; - y = Math.sin( t ) * rand; - - // Apply radius scale to this position - x *= radiusScale.x; - y *= radiusScale.y; - - // Translate to the base position. - x += base.x; - y += base.y; - z += base.z; - - // Set the values in the typed array. - attribute.typedArray.setVec3Components( index, x, y, z ); - }, - - randomDirectionVector3OnSphere: ( function() { - 'use strict'; - - var v = new THREE.Vector3(); - - /** - * Given an SPE.ShaderAttribute instance, create a direction vector from the given - * position, using `speed` as the magnitude. Values are saved to the attribute. - * - * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to. - * @param {Number} index The offset in the attribute's TypedArray to save the result from. - * @param {Number} posX The particle's x coordinate. - * @param {Number} posY The particle's y coordinate. - * @param {Number} posZ The particle's z coordinate. - * @param {Object} emitterPosition THREE.Vector3 instance describing the emitter's base position. - * @param {Number} speed The magnitude to apply to the vector. - * @param {Number} speedSpread The amount of randomness to apply to the magnitude. - */ - return function( attribute, index, posX, posY, posZ, emitterPosition, speed, speedSpread ) { - v.copy( emitterPosition ); - - v.x -= posX; - v.y -= posY; - v.z -= posZ; - - v.normalize().multiplyScalar( -this.randomFloat( speed, speedSpread ) ); - - attribute.typedArray.setVec3Components( index, v.x, v.y, v.z ); - }; - }() ), - - - randomDirectionVector3OnDisc: ( function() { - 'use strict'; - - var v = new THREE.Vector3(); - - /** - * Given an SPE.ShaderAttribute instance, create a direction vector from the given - * position, using `speed` as the magnitude. Values are saved to the attribute. - * - * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to. - * @param {Number} index The offset in the attribute's TypedArray to save the result from. - * @param {Number} posX The particle's x coordinate. - * @param {Number} posY The particle's y coordinate. - * @param {Number} posZ The particle's z coordinate. - * @param {Object} emitterPosition THREE.Vector3 instance describing the emitter's base position. - * @param {Number} speed The magnitude to apply to the vector. - * @param {Number} speedSpread The amount of randomness to apply to the magnitude. - */ - return function( attribute, index, posX, posY, posZ, emitterPosition, speed, speedSpread ) { - v.copy( emitterPosition ); - - v.x -= posX; - v.y -= posY; - v.z -= posZ; - - v.normalize().multiplyScalar( -this.randomFloat( speed, speedSpread ) ); - - attribute.typedArray.setVec3Components( index, v.x, v.y, 0 ); - }; - }() ), - - getPackedRotationAxis: ( function() { - 'use strict'; - - var v = new THREE.Vector3(), - vSpread = new THREE.Vector3(), - c = new THREE.Color(), - addOne = new THREE.Vector3( 1, 1, 1 ); - - /** - * Given a rotation axis, and a rotation axis spread vector, - * calculate a randomised rotation axis, and pack it into - * a hexadecimal value represented in decimal form. - * @param {Object} axis THREE.Vector3 instance describing the rotation axis. - * @param {Object} axisSpread THREE.Vector3 instance describing the amount of randomness to apply to the rotation axis. - * @return {Number} The packed rotation axis, with randomness. - */ - return function( axis, axisSpread ) { - v.copy( axis ).normalize(); - vSpread.copy( axisSpread ).normalize(); - - v.x += ( -axisSpread.x * 0.5 ) + ( Math.random() * axisSpread.x ); - v.y += ( -axisSpread.y * 0.5 ) + ( Math.random() * axisSpread.y ); - v.z += ( -axisSpread.z * 0.5 ) + ( Math.random() * axisSpread.z ); - - // v.x = Math.abs( v.x ); - // v.y = Math.abs( v.y ); - // v.z = Math.abs( v.z ); - - v.normalize().add( addOne ).multiplyScalar( 0.5 ); - - c.setRGB( v.x, v.y, v.z ); - - return c.getHex(); - }; - }() ) -}; - - -/** - * An SPE.Group instance. - * @typedef {Object} Group - * @see SPE.Group - */ - -/** - * A map of options to configure an SPE.Group instance. - * @typedef {Object} GroupOptions - * - * @property {Object} texture An object describing the texture used by the group. - * - * @property {Object} texture.value An instance of THREE.Texture. - * - * @property {Object=} texture.frames A THREE.Vector2 instance describing the number - * of frames on the x- and y-axis of the given texture. - * If not provided, the texture will NOT be treated as - * a sprite-sheet and as such will NOT be animated. - * - * @property {Number} [texture.frameCount=texture.frames.x * texture.frames.y] The total number of frames in the sprite-sheet. - * Allows for sprite-sheets that don't fill the entire - * texture. - * - * @property {Number} texture.loop The number of loops through the sprite-sheet that should - * be performed over the course of a single particle's lifetime. - * - * @property {Number} fixedTimeStep If no `dt` (or `deltaTime`) value is passed to this group's - * `tick()` function, this number will be used to move the particle - * simulation forward. Value in SECONDS. - * - * @property {Boolean} hasPerspective Whether the distance a particle is from the camera should affect - * the particle's size. - * - * @property {Boolean} colorize Whether the particles in this group should be rendered with color, or - * whether the only color of particles will come from the provided texture. - * - * @property {Number} blending One of Three.js's blending modes to apply to this group's `ShaderMaterial`. - * - * @property {Boolean} transparent Whether these particle's should be rendered with transparency. - * - * @property {Number} alphaTest Sets the alpha value to be used when running an alpha test on the `texture.value` property. Value between 0 and 1. - * - * @property {Boolean} depthWrite Whether rendering the group has any effect on the depth buffer. - * - * @property {Boolean} depthTest Whether to have depth test enabled when rendering this group. - * - * @property {Boolean} fog Whether this group's particles should be affected by their scene's fog. - * - * @property {Number} scale The scale factor to apply to this group's particle sizes. Useful for - * setting particle sizes to be relative to renderer size. - */ - - -/** - * The SPE.Group class. Creates a new group, containing a material, geometry, and mesh. - * - * @constructor - * @param {GroupOptions} options A map of options to configure the group instance. - */ -SPE.Group = function( options ) { - 'use strict'; - - var utils = SPE.utils, - types = utils.types; - - // Ensure we have a map of options to play with - options = utils.ensureTypedArg( options, types.OBJECT, {} ); - options.texture = utils.ensureTypedArg( options.texture, types.OBJECT, {} ); - - // Assign a UUID to this instance - this.uuid = THREE.Math.generateUUID(); - - // If no `deltaTime` value is passed to the `SPE.Group.tick` function, - // the value of this property will be used to advance the simulation. - this.fixedTimeStep = utils.ensureTypedArg( options.fixedTimeStep, types.NUMBER, 0.016 ); - - // Set properties used in the uniforms map, starting with the - // texture stuff. - this.texture = utils.ensureInstanceOf( options.texture.value, THREE.Texture, null ); - this.textureFrames = utils.ensureInstanceOf( options.texture.frames, THREE.Vector2, new THREE.Vector2( 1, 1 ) ); - this.textureFrameCount = utils.ensureTypedArg( options.texture.frameCount, types.NUMBER, this.textureFrames.x * this.textureFrames.y ); - this.textureLoop = utils.ensureTypedArg( options.texture.loop, types.NUMBER, 1 ); - this.textureFrames.max( new THREE.Vector2( 1, 1 ) ); - - this.hasPerspective = utils.ensureTypedArg( options.hasPerspective, types.BOOLEAN, true ); - this.colorize = utils.ensureTypedArg( options.colorize, types.BOOLEAN, true ); - - this.maxParticleCount = utils.ensureTypedArg( options.maxParticleCount, types.NUMBER, null ); - - - // Set properties used to define the ShaderMaterial's appearance. - this.blending = utils.ensureTypedArg( options.blending, types.NUMBER, THREE.AdditiveBlending ); - this.transparent = utils.ensureTypedArg( options.transparent, types.BOOLEAN, true ); - this.alphaTest = parseFloat( utils.ensureTypedArg( options.alphaTest, types.NUMBER, 0.0 ) ); - this.depthWrite = utils.ensureTypedArg( options.depthWrite, types.BOOLEAN, false ); - this.depthTest = utils.ensureTypedArg( options.depthTest, types.BOOLEAN, true ); - this.fog = utils.ensureTypedArg( options.fog, types.BOOLEAN, true ); - this.scale = utils.ensureTypedArg( options.scale, types.NUMBER, 300 ); - - // Where emitter's go to curl up in a warm blanket and live - // out their days. - this.emitters = []; - this.emitterIDs = []; - - // Create properties for use by the emitter pooling functions. - this._pool = []; - this._poolCreationSettings = null; - this._createNewWhenPoolEmpty = 0; - - // Whether all attributes should be forced to updated - // their entire buffer contents on the next tick. - // - // Used when an emitter is removed. - this._attributesNeedRefresh = false; - this._attributesNeedDynamicReset = false; - - this.particleCount = 0; - - - // Map of uniforms to be applied to the ShaderMaterial instance. - this.uniforms = { - tex: { - type: 't', - value: this.texture - }, - textureAnimation: { - type: 'v4', - value: new THREE.Vector4( - this.textureFrames.x, - this.textureFrames.y, - this.textureFrameCount, - Math.max( Math.abs( this.textureLoop ), 1.0 ) - ) - }, - fogColor: { - type: 'c', - value: this.fog ? new THREE.Color() : null - }, - fogNear: { - type: 'f', - value: 10 - }, - fogFar: { - type: 'f', - value: 200 - }, - fogDensity: { - type: 'f', - value: 0.5 - }, - deltaTime: { - type: 'f', - value: 0 - }, - runTime: { - type: 'f', - value: 0 - }, - scale: { - type: 'f', - value: this.scale - } - }; - - // Add some defines into the mix... - this.defines = { - HAS_PERSPECTIVE: this.hasPerspective, - COLORIZE: this.colorize, - VALUE_OVER_LIFETIME_LENGTH: SPE.valueOverLifetimeLength, - - SHOULD_ROTATE_TEXTURE: false, - SHOULD_ROTATE_PARTICLES: false, - SHOULD_WIGGLE_PARTICLES: false, - - SHOULD_CALCULATE_SPRITE: this.textureFrames.x > 1 || this.textureFrames.y > 1 - }; - - // Map of all attributes to be applied to the particles. - // - // See SPE.ShaderAttribute for a bit more info on this bit. - this.attributes = { - position: new SPE.ShaderAttribute( 'v3', true ), - acceleration: new SPE.ShaderAttribute( 'v4', true ), // w component is drag - velocity: new SPE.ShaderAttribute( 'v3', true ), - rotation: new SPE.ShaderAttribute( 'v4', true ), - rotationCenter: new SPE.ShaderAttribute( 'v3', true ), - params: new SPE.ShaderAttribute( 'v4', true ), // Holds (alive, age, delay, wiggle) - size: new SPE.ShaderAttribute( 'v4', true ), - angle: new SPE.ShaderAttribute( 'v4', true ), - color: new SPE.ShaderAttribute( 'v4', true ), - opacity: new SPE.ShaderAttribute( 'v4', true ) - }; - - this.attributeKeys = Object.keys( this.attributes ); - this.attributeCount = this.attributeKeys.length; - - // Create the ShaderMaterial instance that'll help render the - // particles. - this.material = new THREE.ShaderMaterial( { - uniforms: this.uniforms, - vertexShader: SPE.shaders.vertex, - fragmentShader: SPE.shaders.fragment, - blending: this.blending, - transparent: this.transparent, - alphaTest: this.alphaTest, - depthWrite: this.depthWrite, - depthTest: this.depthTest, - defines: this.defines, - fog: this.fog - } ); - - // Create the BufferGeometry and Points instances, ensuring - // the geometry and material are given to the latter. - this.geometry = new THREE.BufferGeometry(); - this.mesh = new THREE.Points( this.geometry, this.material ); - - if ( this.maxParticleCount === null ) { - console.warn( 'SPE.Group: No maxParticleCount specified. Adding emitters after rendering will probably cause errors.' ); - } -}; - -SPE.Group.constructor = SPE.Group; - - -SPE.Group.prototype._updateDefines = function() { - 'use strict'; - - var emitters = this.emitters, - i = emitters.length - 1, - emitter, - defines = this.defines; - - for ( i; i >= 0; --i ) { - emitter = emitters[ i ]; - - // Only do angle calculation if there's no spritesheet defined. - // - // Saves calculations being done and then overwritten in the shaders. - if ( !defines.SHOULD_CALCULATE_SPRITE ) { - defines.SHOULD_ROTATE_TEXTURE = defines.SHOULD_ROTATE_TEXTURE || !!Math.max( - Math.max.apply( null, emitter.angle.value ), - Math.max.apply( null, emitter.angle.spread ) - ); - } - - defines.SHOULD_ROTATE_PARTICLES = defines.SHOULD_ROTATE_PARTICLES || !!Math.max( - emitter.rotation.angle, - emitter.rotation.angleSpread - ); - - defines.SHOULD_WIGGLE_PARTICLES = defines.SHOULD_WIGGLE_PARTICLES || !!Math.max( - emitter.wiggle.value, - emitter.wiggle.spread - ); - } - - this.material.needsUpdate = true; -}; - -SPE.Group.prototype._applyAttributesToGeometry = function() { - 'use strict'; - - var attributes = this.attributes, - geometry = this.geometry, - geometryAttributes = geometry.attributes, - attribute, - geometryAttribute; - - // Loop through all the shader attributes and assign (or re-assign) - // typed array buffers to each one. - for ( var attr in attributes ) { - if ( attributes.hasOwnProperty( attr ) ) { - attribute = attributes[ attr ]; - geometryAttribute = geometryAttributes[ attr ]; - - // Update the array if this attribute exists on the geometry. - // - // This needs to be done because the attribute's typed array might have - // been resized and reinstantiated, and might now be looking at a - // different ArrayBuffer, so reference needs updating. - if ( geometryAttribute ) { - geometryAttribute.array = attribute.typedArray.array; - } - - // // Add the attribute to the geometry if it doesn't already exist. - else { - geometry.addAttribute( attr, attribute.bufferAttribute ); - } - - // Mark the attribute as needing an update the next time a frame is rendered. - attribute.bufferAttribute.needsUpdate = true; - } - } - - // Mark the draw range on the geometry. This will ensure - // only the values in the attribute buffers that are - // associated with a particle will be used in THREE's - // render cycle. - this.geometry.setDrawRange( 0, this.particleCount ); -}; - -/** - * Adds an SPE.Emitter instance to this group, creating particle values and - * assigning them to this group's shader attributes. - * - * @param {Emitter} emitter The emitter to add to this group. - */ -SPE.Group.prototype.addEmitter = function( emitter ) { - 'use strict'; - - // Ensure an actual emitter instance is passed here. - // - // Decided not to throw here, just in case a scene's - // rendering would be paused. Logging an error instead - // of stopping execution if exceptions aren't caught. - if ( emitter instanceof SPE.Emitter === false ) { - console.error( '`emitter` argument must be instance of SPE.Emitter. Was provided with:', emitter ); - return; - } - - // If the emitter already exists as a member of this group, then - // stop here, we don't want to add it again. - else if ( this.emitterIDs.indexOf( emitter.uuid ) > -1 ) { - console.error( 'Emitter already exists in this group. Will not add again.' ); - return; - } - - // And finally, if the emitter is a member of another group, - // don't add it to this group. - else if ( emitter.group !== null ) { - console.error( 'Emitter already belongs to another group. Will not add to requested group.' ); - return; - } - - var attributes = this.attributes, - start = this.particleCount, - end = start + emitter.particleCount; - - // Update this group's particle count. - this.particleCount = end; - - // Emit a warning if the emitter being added will exceed the buffer sizes specified. - if ( this.maxParticleCount !== null && this.particleCount > this.maxParticleCount ) { - console.warn( 'SPE.Group: maxParticleCount exceeded. Requesting', this.particleCount, 'particles, can support only', this.maxParticleCount ); - } - - - // Set the `particlesPerSecond` value (PPS) on the emitter. - // It's used to determine how many particles to release - // on a per-frame basis. - emitter._calculatePPSValue( emitter.maxAge._value + emitter.maxAge._spread ); - emitter._setBufferUpdateRanges( this.attributeKeys ); - - // Store the offset value in the TypedArray attributes for this emitter. - emitter._setAttributeOffset( start ); - - // Save a reference to this group on the emitter so it knows - // where it belongs. - emitter.group = this; - - // Store reference to the attributes on the emitter for - // easier access during the emitter's tick function. - emitter.attributes = this.attributes; - - - - // Ensure the attributes and their BufferAttributes exist, and their - // TypedArrays are of the correct size. - for ( var attr in attributes ) { - if ( attributes.hasOwnProperty( attr ) ) { - // When creating a buffer, pass through the maxParticle count - // if one is specified. - attributes[ attr ]._createBufferAttribute( - this.maxParticleCount !== null ? - this.maxParticleCount : - this.particleCount - ); - } - } - - // Loop through each particle this emitter wants to have, and create the attributes values, - // storing them in the TypedArrays that each attribute holds. - for ( var i = start; i < end; ++i ) { - emitter._assignPositionValue( i ); - emitter._assignForceValue( i, 'velocity' ); - emitter._assignForceValue( i, 'acceleration' ); - emitter._assignAbsLifetimeValue( i, 'opacity' ); - emitter._assignAbsLifetimeValue( i, 'size' ); - emitter._assignAngleValue( i ); - emitter._assignRotationValue( i ); - emitter._assignParamsValue( i ); - emitter._assignColorValue( i ); - } - - // Update the geometry and make sure the attributes are referencing - // the typed arrays properly. - this._applyAttributesToGeometry(); - - // Store this emitter in this group's emitter's store. - this.emitters.push( emitter ); - this.emitterIDs.push( emitter.uuid ); - - // Update certain flags to enable shader calculations only if they're necessary. - this._updateDefines( emitter ); - - // Update the material since defines might have changed - this.material.needsUpdate = true; - this.geometry.needsUpdate = true; - this._attributesNeedRefresh = true; - - // Return the group to enable chaining. - return this; -}; - -/** - * Removes an SPE.Emitter instance from this group. When called, - * all particle's belonging to the given emitter will be instantly - * removed from the scene. - * - * @param {Emitter} emitter The emitter to add to this group. - */ -SPE.Group.prototype.removeEmitter = function( emitter ) { - 'use strict'; - - var emitterIndex = this.emitterIDs.indexOf( emitter.uuid ); - - // Ensure an actual emitter instance is passed here. - // - // Decided not to throw here, just in case a scene's - // rendering would be paused. Logging an error instead - // of stopping execution if exceptions aren't caught. - if ( emitter instanceof SPE.Emitter === false ) { - console.error( '`emitter` argument must be instance of SPE.Emitter. Was provided with:', emitter ); - return; - } - - // Issue an error if the emitter isn't a member of this group. - else if ( emitterIndex === -1 ) { - console.error( 'Emitter does not exist in this group. Will not remove.' ); - return; - } - - // Kill all particles by marking them as dead - // and their age as 0. - var start = emitter.attributeOffset, - end = start + emitter.particleCount, - params = this.attributes.params.typedArray; - - // Set alive and age to zero. - for ( var i = start; i < end; ++i ) { - params.array[ i * 4 ] = 0.0; - params.array[ i * 4 + 1 ] = 0.0; - } - - // Remove the emitter from this group's "store". - this.emitters.splice( emitterIndex, 1 ); - this.emitterIDs.splice( emitterIndex, 1 ); - - // Remove this emitter's attribute values from all shader attributes. - // The `.splice()` call here also marks each attribute's buffer - // as needing to update it's entire contents. - for ( var attr in this.attributes ) { - if ( this.attributes.hasOwnProperty( attr ) ) { - this.attributes[ attr ].splice( start, end ); - } - } - - // Ensure this group's particle count is correct. - this.particleCount -= emitter.particleCount; - - // Call the emitter's remove method. - emitter._onRemove(); - - // Set a flag to indicate that the attribute buffers should - // be updated in their entirety on the next frame. - this._attributesNeedRefresh = true; -}; - - -/** - * Fetch a single emitter instance from the pool. - * If there are no objects in the pool, a new emitter will be - * created if specified. - * - * @return {Emitter|null} - */ -SPE.Group.prototype.getFromPool = function() { - 'use strict'; - - var pool = this._pool, - createNew = this._createNewWhenPoolEmpty; - - if ( pool.length ) { - return pool.pop(); - } - else if ( createNew ) { - var emitter = new SPE.Emitter( this._poolCreationSettings ); - - this.addEmitter( emitter ); - - return emitter; - } - - return null; -}; - - -/** - * Release an emitter into the pool. - * - * @param {ShaderParticleEmitter} emitter - * @return {Group} This group instance. - */ -SPE.Group.prototype.releaseIntoPool = function( emitter ) { - 'use strict'; - - if ( emitter instanceof SPE.Emitter === false ) { - console.error( 'Argument is not instanceof SPE.Emitter:', emitter ); - return; - } - - emitter.reset(); - this._pool.unshift( emitter ); - - return this; -}; - - -/** - * Get the pool array - * - * @return {Array} - */ -SPE.Group.prototype.getPool = function() { - 'use strict'; - return this._pool; -}; - - -/** - * Add a pool of emitters to this particle group - * - * @param {Number} numEmitters The number of emitters to add to the pool. - * @param {EmitterOptions|Array} emitterOptions An object, or array of objects, describing the options to pass to each emitter. - * @param {Boolean} createNew Should a new emitter be created if the pool runs out? - * @return {Group} This group instance. - */ -SPE.Group.prototype.addPool = function( numEmitters, emitterOptions, createNew ) { - 'use strict'; - - var emitter; - - // Save relevant settings and flags. - this._poolCreationSettings = emitterOptions; - this._createNewWhenPoolEmpty = !!createNew; - - // Create the emitters, add them to this group and the pool. - for ( var i = 0; i < numEmitters; ++i ) { - if ( Array.isArray( emitterOptions ) ) { - emitter = new SPE.Emitter( emitterOptions[ i ] ); - } - else { - emitter = new SPE.Emitter( emitterOptions ); - } - this.addEmitter( emitter ); - this.releaseIntoPool( emitter ); - } - - return this; -}; - - - -SPE.Group.prototype._triggerSingleEmitter = function( pos ) { - 'use strict'; - - var emitter = this.getFromPool(), - self = this; - - if ( emitter === null ) { - console.log( 'SPE.Group pool ran out.' ); - return; - } - - // TODO: - // - Make sure buffers are update with thus new position. - if ( pos instanceof THREE.Vector3 ) { - emitter.position.value.copy( pos ); - - // Trigger the setter for this property to force an - // update to the emitter's position attribute. - emitter.position.value = emitter.position.value; - } - - emitter.enable(); - - setTimeout( function() { - emitter.disable(); - self.releaseIntoPool( emitter ); - }, ( Math.max( emitter.duration, ( emitter.maxAge.value + emitter.maxAge.spread ) ) ) * 1000 ); - - return this; -}; - - -/** - * Set a given number of emitters as alive, with an optional position - * vector3 to move them to. - * - * @param {Number} numEmitters The number of emitters to activate - * @param {Object} [position=undefined] A THREE.Vector3 instance describing the position to activate the emitter(s) at. - * @return {Group} This group instance. - */ -SPE.Group.prototype.triggerPoolEmitter = function( numEmitters, position ) { - 'use strict'; - - if ( typeof numEmitters === 'number' && numEmitters > 1 ) { - for ( var i = 0; i < numEmitters; ++i ) { - this._triggerSingleEmitter( position ); - } - } - else { - this._triggerSingleEmitter( position ); - } - - return this; -}; - - - -SPE.Group.prototype._updateUniforms = function( dt ) { - 'use strict'; - - this.uniforms.runTime.value += dt; - this.uniforms.deltaTime.value = dt; -}; - -SPE.Group.prototype._resetBufferRanges = function() { - 'use strict'; - - var keys = this.attributeKeys, - i = this.attributeCount - 1, - attrs = this.attributes; - - for ( i; i >= 0; --i ) { - attrs[ keys[ i ] ].resetUpdateRange(); - } -}; - - -SPE.Group.prototype._updateBuffers = function( emitter ) { - 'use strict'; - - var keys = this.attributeKeys, - i = this.attributeCount - 1, - attrs = this.attributes, - emitterRanges = emitter.bufferUpdateRanges, - key, - emitterAttr, - attr; - - for ( i; i >= 0; --i ) { - key = keys[ i ]; - emitterAttr = emitterRanges[ key ]; - attr = attrs[ key ]; - attr.setUpdateRange( emitterAttr.min, emitterAttr.max ); - attr.flagUpdate(); - } -}; - - -/** - * Simulate all the emitter's belonging to this group, updating - * attribute values along the way. - * @param {Number} [dt=Group's `fixedTimeStep` value] The number of seconds to simulate the group's emitters for (deltaTime) - */ -SPE.Group.prototype.tick = function( dt ) { - 'use strict'; - - var emitters = this.emitters, - numEmitters = emitters.length, - deltaTime = dt || this.fixedTimeStep, - keys = this.attributeKeys, - i, - attrs = this.attributes; - - // Update uniform values. - this._updateUniforms( deltaTime ); - - // Reset buffer update ranges on the shader attributes. - this._resetBufferRanges(); - - - // If nothing needs updating, then stop here. - if ( - numEmitters === 0 && - this._attributesNeedRefresh === false && - this._attributesNeedDynamicReset === false - ) { - return; - } - - // Loop through each emitter in this group and - // simulate it, then update the shader attribute - // buffers. - for ( var i = 0, emitter; i < numEmitters; ++i ) { - emitter = emitters[ i ]; - emitter.tick( deltaTime ); - this._updateBuffers( emitter ); - } - - // If the shader attributes have been refreshed, - // then the dynamic properties of each buffer - // attribute will need to be reset back to - // what they should be. - if ( this._attributesNeedDynamicReset === true ) { - i = this.attributeCount - 1; - - for ( i; i >= 0; --i ) { - attrs[ keys[ i ] ].resetDynamic(); - } - - this._attributesNeedDynamicReset = false; - } - - // If this group's shader attributes need a full refresh - // then mark each attribute's buffer attribute as - // needing so. - if ( this._attributesNeedRefresh === true ) { - i = this.attributeCount - 1; - - for ( i; i >= 0; --i ) { - attrs[ keys[ i ] ].forceUpdateAll(); - } - - this._attributesNeedRefresh = false; - this._attributesNeedDynamicReset = true; - } -}; - - -/** - * Dipose the geometry and material for the group. - * - * @return {Group} Group instance. - */ -SPE.Group.prototype.dispose = function() { - 'use strict'; - this.geometry.dispose(); - this.material.dispose(); - return this; -}; - - -/** - * An SPE.Emitter instance. - * @typedef {Object} Emitter - * @see SPE.Emitter - */ - -/** - * A map of options to configure an SPE.Emitter instance. - * - * @typedef {Object} EmitterOptions - * - * @property {distribution} [type=BOX] The default distribution this emitter should use to control - * its particle's spawn position and force behaviour. - * Must be an SPE.distributions.* value. - * - * - * @property {Number} [particleCount=100] The total number of particles this emitter will hold. NOTE: this is not the number - * of particles emitted in a second, or anything like that. The number of particles - * emitted per-second is calculated by particleCount / maxAge (approximately!) - * - * @property {Number|null} [duration=null] The duration in seconds that this emitter should live for. If not specified, the emitter - * will emit particles indefinitely. - * NOTE: When an emitter is older than a specified duration, the emitter is NOT removed from - * it's group, but rather is just marked as dead, allowing it to be reanimated at a later time - * using `SPE.Emitter.prototype.enable()`. - * - * @property {Boolean} [isStatic=false] Whether this emitter should be not be simulated (true). - * @property {Boolean} [activeMultiplier=1] A value between 0 and 1 describing what percentage of this emitter's particlesPerSecond should be - * emitted, where 0 is 0%, and 1 is 100%. - * For example, having an emitter with 100 particles, a maxAge of 2, yields a particlesPerSecond - * value of 50. Setting `activeMultiplier` to 0.5, then, will only emit 25 particles per second (0.5 = 50%). - * Values greater than 1 will emulate a burst of particles, causing the emitter to run out of particles - * before it's next activation cycle. - * - * @property {Boolean} [direction=1] The direction of the emitter. If value is `1`, emitter will start at beginning of particle's lifecycle. - * If value is `-1`, emitter will start at end of particle's lifecycle and work it's way backwards. - * - * @property {Object} [maxAge={}] An object describing the particle's maximum age in seconds. - * @property {Number} [maxAge.value=2] A number between 0 and 1 describing the amount of maxAge to apply to all particles. - * @property {Number} [maxAge.spread=0] A number describing the maxAge variance on a per-particle basis. - * - * - * @property {Object} [position={}] An object describing this emitter's position. - * @property {Object} [position.value=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's base position. - * @property {Object} [position.spread=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's position variance on a per-particle basis. - * Note that when using a SPHERE or DISC distribution, only the x-component - * of this vector is used. - * When using a LINE distribution, this value is the endpoint of the LINE. - * @property {Object} [position.spreadClamp=new THREE.Vector3()] A THREE.Vector3 instance describing the numeric multiples the particle's should - * be spread out over. - * Note that when using a SPHERE or DISC distribution, only the x-component - * of this vector is used. - * When using a LINE distribution, this property is ignored. - * @property {Number} [position.radius=10] This emitter's base radius. - * @property {Object} [position.radiusScale=new THREE.Vector3()] A THREE.Vector3 instance describing the radius's scale in all three axes. Allows a SPHERE or DISC to be squashed or stretched. - * @property {distribution} [position.distribution=value of the `type` option.] A specific distribution to use when radiusing particles. Overrides the `type` option. - * @property {Boolean} [position.randomise=false] When a particle is re-spawned, whether it's position should be re-randomised or not. Can incur a performance hit. - * - * - * @property {Object} [velocity={}] An object describing this particle velocity. - * @property {Object} [velocity.value=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's base velocity. - * @property {Object} [velocity.spread=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's velocity variance on a per-particle basis. - * Note that when using a SPHERE or DISC distribution, only the x-component - * of this vector is used. - * @property {distribution} [velocity.distribution=value of the `type` option.] A specific distribution to use when calculating a particle's velocity. Overrides the `type` option. - * @property {Boolean} [velocity.randomise=false] When a particle is re-spawned, whether it's velocity should be re-randomised or not. Can incur a performance hit. - * - * - * @property {Object} [acceleration={}] An object describing this particle's acceleration. - * @property {Object} [acceleration.value=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's base acceleration. - * @property {Object} [acceleration.spread=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's acceleration variance on a per-particle basis. - * Note that when using a SPHERE or DISC distribution, only the x-component - * of this vector is used. - * @property {distribution} [acceleration.distribution=value of the `type` option.] A specific distribution to use when calculating a particle's acceleration. Overrides the `type` option. - * @property {Boolean} [acceleration.randomise=false] When a particle is re-spawned, whether it's acceleration should be re-randomised or not. Can incur a performance hit. - * - * - * @property {Object} [drag={}] An object describing this particle drag. Drag is applied to both velocity and acceleration values. - * @property {Number} [drag.value=0] A number between 0 and 1 describing the amount of drag to apply to all particles. - * @property {Number} [drag.spread=0] A number describing the drag variance on a per-particle basis. - * @property {Boolean} [drag.randomise=false] When a particle is re-spawned, whether it's drag should be re-randomised or not. Can incur a performance hit. - * - * - * @property {Object} [wiggle={}] This is quite a fun one! The values of this object will determine whether a particle will wiggle, or jiggle, or wave, - * or shimmy, or waggle, or... Well you get the idea. The wiggle is calculated over-time, meaning that a particle will - * start off with no wiggle, and end up wiggling about with the distance of the `value` specified by the time it dies. - * It's quite handy to simulate fire embers, or similar effects where the particle's position should slightly change over - * time, and such change isn't easily controlled by rotation, velocity, or acceleration. The wiggle is a combination of sin and cos calculations, so is circular in nature. - * @property {Number} [wiggle.value=0] A number describing the amount of wiggle to apply to all particles. It's measured in distance. - * @property {Number} [wiggle.spread=0] A number describing the wiggle variance on a per-particle basis. - * - * - * @property {Object} [rotation={}] An object describing this emitter's rotation. It can either be static, or set to rotate from 0radians to the value of `rotation.value` - * over a particle's lifetime. Rotation values affect both a particle's position and the forces applied to it. - * @property {Object} [rotation.axis=new THREE.Vector3(0, 1, 0)] A THREE.Vector3 instance describing this emitter's axis of rotation. - * @property {Object} [rotation.axisSpread=new THREE.Vector3()] A THREE.Vector3 instance describing the amount of variance to apply to the axis of rotation on - * a per-particle basis. - * @property {Number} [rotation.angle=0] The angle of rotation, given in radians. If `rotation.static` is true, the emitter will start off rotated at this angle, and stay as such. - * Otherwise, the particles will rotate from 0radians to this value over their lifetimes. - * @property {Number} [rotation.angleSpread=0] The amount of variance in each particle's rotation angle. - * @property {Boolean} [rotation.static=false] Whether the rotation should be static or not. - * @property {Object} [rotation.center=The value of `position.value`] A THREE.Vector3 instance describing the center point of rotation. - * @property {Boolean} [rotation.randomise=false] When a particle is re-spawned, whether it's rotation should be re-randomised or not. Can incur a performance hit. - * - * - * @property {Object} [color={}] An object describing a particle's color. This property is a "value-over-lifetime" property, meaning an array of values and spreads can be - * given to describe specific value changes over a particle's lifetime. - * Depending on the value of SPE.valueOverLifetimeLength, if arrays of THREE.Color instances are given, then the array will be interpolated to - * have a length matching the value of SPE.valueOverLifetimeLength. - * @property {Object} [color.value=new THREE.Color()] Either a single THREE.Color instance, or an array of THREE.Color instances to describe the color of a particle over it's lifetime. - * @property {Object} [color.spread=new THREE.Vector3()] Either a single THREE.Vector3 instance, or an array of THREE.Vector3 instances to describe the color variance of a particle over it's lifetime. - * @property {Boolean} [color.randomise=false] When a particle is re-spawned, whether it's color should be re-randomised or not. Can incur a performance hit. - * - * - * @property {Object} [opacity={}] An object describing a particle's opacity. This property is a "value-over-lifetime" property, meaning an array of values and spreads can be - * given to describe specific value changes over a particle's lifetime. - * Depending on the value of SPE.valueOverLifetimeLength, if arrays of numbers are given, then the array will be interpolated to - * have a length matching the value of SPE.valueOverLifetimeLength. - * @property {Number} [opacity.value=1] Either a single number, or an array of numbers to describe the opacity of a particle over it's lifetime. - * @property {Number} [opacity.spread=0] Either a single number, or an array of numbers to describe the opacity variance of a particle over it's lifetime. - * @property {Boolean} [opacity.randomise=false] When a particle is re-spawned, whether it's opacity should be re-randomised or not. Can incur a performance hit. - * - * - * @property {Object} [size={}] An object describing a particle's size. This property is a "value-over-lifetime" property, meaning an array of values and spreads can be - * given to describe specific value changes over a particle's lifetime. - * Depending on the value of SPE.valueOverLifetimeLength, if arrays of numbers are given, then the array will be interpolated to - * have a length matching the value of SPE.valueOverLifetimeLength. - * @property {Number} [size.value=1] Either a single number, or an array of numbers to describe the size of a particle over it's lifetime. - * @property {Number} [size.spread=0] Either a single number, or an array of numbers to describe the size variance of a particle over it's lifetime. - * @property {Boolean} [size.randomise=false] When a particle is re-spawned, whether it's size should be re-randomised or not. Can incur a performance hit. - * - * - * @property {Object} [angle={}] An object describing a particle's angle. The angle is a 2d-rotation, measured in radians, applied to the particle's texture. - * NOTE: if a particle's texture is a sprite-sheet, this value IS IGNORED. - * This property is a "value-over-lifetime" property, meaning an array of values and spreads can be - * given to describe specific value changes over a particle's lifetime. - * Depending on the value of SPE.valueOverLifetimeLength, if arrays of numbers are given, then the array will be interpolated to - * have a length matching the value of SPE.valueOverLifetimeLength. - * @property {Number} [angle.value=0] Either a single number, or an array of numbers to describe the angle of a particle over it's lifetime. - * @property {Number} [angle.spread=0] Either a single number, or an array of numbers to describe the angle variance of a particle over it's lifetime. - * @property {Boolean} [angle.randomise=false] When a particle is re-spawned, whether it's angle should be re-randomised or not. Can incur a performance hit. - * - */ - -/** - * The SPE.Emitter class. - * - * @constructor - * - * @param {EmitterOptions} options A map of options to configure the emitter. - */ -SPE.Emitter = function( options ) { - 'use strict'; - - var utils = SPE.utils, - types = utils.types, - lifetimeLength = SPE.valueOverLifetimeLength; - - // Ensure we have a map of options to play with, - // and that each option is in the correct format. - options = utils.ensureTypedArg( options, types.OBJECT, {} ); - options.position = utils.ensureTypedArg( options.position, types.OBJECT, {} ); - options.velocity = utils.ensureTypedArg( options.velocity, types.OBJECT, {} ); - options.acceleration = utils.ensureTypedArg( options.acceleration, types.OBJECT, {} ); - options.radius = utils.ensureTypedArg( options.radius, types.OBJECT, {} ); - options.drag = utils.ensureTypedArg( options.drag, types.OBJECT, {} ); - options.rotation = utils.ensureTypedArg( options.rotation, types.OBJECT, {} ); - options.color = utils.ensureTypedArg( options.color, types.OBJECT, {} ); - options.opacity = utils.ensureTypedArg( options.opacity, types.OBJECT, {} ); - options.size = utils.ensureTypedArg( options.size, types.OBJECT, {} ); - options.angle = utils.ensureTypedArg( options.angle, types.OBJECT, {} ); - options.wiggle = utils.ensureTypedArg( options.wiggle, types.OBJECT, {} ); - options.maxAge = utils.ensureTypedArg( options.maxAge, types.OBJECT, {} ); - - if ( options.onParticleSpawn ) { - console.warn( 'onParticleSpawn has been removed. Please set properties directly to alter values at runtime.' ); - } - - this.uuid = THREE.Math.generateUUID(); - - this.type = utils.ensureTypedArg( options.type, types.NUMBER, SPE.distributions.BOX ); - - // Start assigning properties...kicking it off with props that DON'T support values over - // lifetimes. - // - // Btw, values over lifetimes are just the new way of referring to *Start, *Middle, and *End. - this.position = { - _value: utils.ensureInstanceOf( options.position.value, THREE.Vector3, new THREE.Vector3() ), - _spread: utils.ensureInstanceOf( options.position.spread, THREE.Vector3, new THREE.Vector3() ), - _spreadClamp: utils.ensureInstanceOf( options.position.spreadClamp, THREE.Vector3, new THREE.Vector3() ), - _distribution: utils.ensureTypedArg( options.position.distribution, types.NUMBER, this.type ), - _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false ), - _radius: utils.ensureTypedArg( options.position.radius, types.NUMBER, 10 ), - _radiusScale: utils.ensureInstanceOf( options.position.radiusScale, THREE.Vector3, new THREE.Vector3( 1, 1, 1 ) ), - _distributionClamp: utils.ensureTypedArg( options.position.distributionClamp, types.NUMBER, 0 ), - }; - - this.velocity = { - _value: utils.ensureInstanceOf( options.velocity.value, THREE.Vector3, new THREE.Vector3() ), - _spread: utils.ensureInstanceOf( options.velocity.spread, THREE.Vector3, new THREE.Vector3() ), - _distribution: utils.ensureTypedArg( options.velocity.distribution, types.NUMBER, this.type ), - _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false ) - }; - - this.acceleration = { - _value: utils.ensureInstanceOf( options.acceleration.value, THREE.Vector3, new THREE.Vector3() ), - _spread: utils.ensureInstanceOf( options.acceleration.spread, THREE.Vector3, new THREE.Vector3() ), - _distribution: utils.ensureTypedArg( options.acceleration.distribution, types.NUMBER, this.type ), - _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false ) - }; - - this.drag = { - _value: utils.ensureTypedArg( options.drag.value, types.NUMBER, 0 ), - _spread: utils.ensureTypedArg( options.drag.spread, types.NUMBER, 0 ), - _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false ) - }; - - this.wiggle = { - _value: utils.ensureTypedArg( options.wiggle.value, types.NUMBER, 0 ), - _spread: utils.ensureTypedArg( options.wiggle.spread, types.NUMBER, 0 ) - }; - - this.rotation = { - _axis: utils.ensureInstanceOf( options.rotation.axis, THREE.Vector3, new THREE.Vector3( 0.0, 1.0, 0.0 ) ), - _axisSpread: utils.ensureInstanceOf( options.rotation.axisSpread, THREE.Vector3, new THREE.Vector3() ), - _angle: utils.ensureTypedArg( options.rotation.angle, types.NUMBER, 0 ), - _angleSpread: utils.ensureTypedArg( options.rotation.angleSpread, types.NUMBER, 0 ), - _static: utils.ensureTypedArg( options.rotation.static, types.BOOLEAN, false ), - _center: utils.ensureInstanceOf( options.rotation.center, THREE.Vector3, this.position._value.clone() ), - _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false ) - }; - - - this.maxAge = { - _value: utils.ensureTypedArg( options.maxAge.value, types.NUMBER, 2 ), - _spread: utils.ensureTypedArg( options.maxAge.spread, types.NUMBER, 0 ) - }; - - - - // The following properties can support either single values, or an array of values that change - // the property over a particle's lifetime (value over lifetime). - this.color = { - _value: utils.ensureArrayInstanceOf( options.color.value, THREE.Color, new THREE.Color() ), - _spread: utils.ensureArrayInstanceOf( options.color.spread, THREE.Vector3, new THREE.Vector3() ), - _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false ) - }; - - this.opacity = { - _value: utils.ensureArrayTypedArg( options.opacity.value, types.NUMBER, 1 ), - _spread: utils.ensureArrayTypedArg( options.opacity.spread, types.NUMBER, 0 ), - _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false ) - }; - - this.size = { - _value: utils.ensureArrayTypedArg( options.size.value, types.NUMBER, 1 ), - _spread: utils.ensureArrayTypedArg( options.size.spread, types.NUMBER, 0 ), - _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false ) - }; - - this.angle = { - _value: utils.ensureArrayTypedArg( options.angle.value, types.NUMBER, 0 ), - _spread: utils.ensureArrayTypedArg( options.angle.spread, types.NUMBER, 0 ), - _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false ) - }; - - - // Assign renaining option values. - this.particleCount = utils.ensureTypedArg( options.particleCount, types.NUMBER, 100 ); - this.duration = utils.ensureTypedArg( options.duration, types.NUMBER, null ); - this.isStatic = utils.ensureTypedArg( options.isStatic, types.BOOLEAN, false ); - this.activeMultiplier = utils.ensureTypedArg( options.activeMultiplier, types.NUMBER, 1 ); - this.direction = utils.ensureTypedArg( options.direction, types.NUMBER, 1 ); - - // Whether this emitter is alive or not. - this.alive = utils.ensureTypedArg( options.alive, types.BOOLEAN, true ); - - - // The following properties are set internally and are not - // user-controllable. - this.particlesPerSecond = 0; - - // The current particle index for which particles should - // be marked as active on the next update cycle. - this.activationIndex = 0; - - // The offset in the typed arrays this emitter's - // particle's values will start at - this.attributeOffset = 0; - - // The end of the range in the attribute buffers - this.attributeEnd = 0; - - - - // Holds the time the emitter has been alive for. - this.age = 0.0; - - // Holds the number of currently-alive particles - this.activeParticleCount = 0.0; - - // Holds a reference to this emitter's group once - // it's added to one. - this.group = null; - - // Holds a reference to this emitter's group's attributes object - // for easier access. - this.attributes = null; - - // Holds a reference to the params attribute's typed array - // for quicker access. - this.paramsArray = null; - - // A set of flags to determine whether particular properties - // should be re-randomised when a particle is reset. - // - // If a `randomise` property is given, this is preferred. - // Otherwise, it looks at whether a spread value has been - // given. - // - // It allows randomization to be turned off as desired. If - // all randomization is turned off, then I'd expect a performance - // boost as no attribute buffers (excluding the `params`) - // would have to be re-passed to the GPU each frame (since nothing - // except the `params` attribute would have changed). - this.resetFlags = { - // params: utils.ensureTypedArg( options.maxAge.randomise, types.BOOLEAN, !!options.maxAge.spread ) || - // utils.ensureTypedArg( options.wiggle.randomise, types.BOOLEAN, !!options.wiggle.spread ), - position: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false ) || - utils.ensureTypedArg( options.radius.randomise, types.BOOLEAN, false ), - velocity: utils.ensureTypedArg( options.velocity.randomise, types.BOOLEAN, false ), - acceleration: utils.ensureTypedArg( options.acceleration.randomise, types.BOOLEAN, false ) || - utils.ensureTypedArg( options.drag.randomise, types.BOOLEAN, false ), - rotation: utils.ensureTypedArg( options.rotation.randomise, types.BOOLEAN, false ), - rotationCenter: utils.ensureTypedArg( options.rotation.randomise, types.BOOLEAN, false ), - size: utils.ensureTypedArg( options.size.randomise, types.BOOLEAN, false ), - color: utils.ensureTypedArg( options.color.randomise, types.BOOLEAN, false ), - opacity: utils.ensureTypedArg( options.opacity.randomise, types.BOOLEAN, false ), - angle: utils.ensureTypedArg( options.angle.randomise, types.BOOLEAN, false ) - }; - - this.updateFlags = {}; - this.updateCounts = {}; - - // A map to indicate which emitter parameters should update - // which attribute. - this.updateMap = { - maxAge: 'params', - position: 'position', - velocity: 'velocity', - acceleration: 'acceleration', - drag: 'acceleration', - wiggle: 'params', - rotation: 'rotation', - size: 'size', - color: 'color', - opacity: 'opacity', - angle: 'angle' - }; - - for ( var i in this.updateMap ) { - if ( this.updateMap.hasOwnProperty( i ) ) { - this.updateCounts[ this.updateMap[ i ] ] = 0.0; - this.updateFlags[ this.updateMap[ i ] ] = false; - this._createGetterSetters( this[ i ], i ); - } - } - - this.bufferUpdateRanges = {}; - this.attributeKeys = null; - this.attributeCount = 0; - - - // Ensure that the value-over-lifetime property objects above - // have value and spread properties that are of the same length. - // - // Also, for now, make sure they have a length of 3 (min/max arguments here). - utils.ensureValueOverLifetimeCompliance( this.color, lifetimeLength, lifetimeLength ); - utils.ensureValueOverLifetimeCompliance( this.opacity, lifetimeLength, lifetimeLength ); - utils.ensureValueOverLifetimeCompliance( this.size, lifetimeLength, lifetimeLength ); - utils.ensureValueOverLifetimeCompliance( this.angle, lifetimeLength, lifetimeLength ); -}; - -SPE.Emitter.constructor = SPE.Emitter; - -SPE.Emitter.prototype._createGetterSetters = function( propObj, propName ) { - 'use strict'; - - var self = this; - - for ( var i in propObj ) { - if ( propObj.hasOwnProperty( i ) ) { - - var name = i.replace( '_', '' ); - - Object.defineProperty( propObj, name, { - get: ( function( prop ) { - return function() { - return this[ prop ]; - }; - }( i ) ), - - set: ( function( prop ) { - return function( value ) { - var mapName = self.updateMap[ propName ], - prevValue = this[ prop ], - length = SPE.valueOverLifetimeLength; - - if ( prop === '_rotationCenter' ) { - self.updateFlags.rotationCenter = true; - self.updateCounts.rotationCenter = 0.0; - } - else if ( prop === '_randomise' ) { - self.resetFlags[ mapName ] = value; - } - else { - self.updateFlags[ mapName ] = true; - self.updateCounts[ mapName ] = 0.0; - } - - self.group._updateDefines(); - - this[ prop ] = value; - - // If the previous value was an array, then make - // sure the provided value is interpolated correctly. - if ( Array.isArray( prevValue ) ) { - SPE.utils.ensureValueOverLifetimeCompliance( self[ propName ], length, length ); - } - }; - }( i ) ) - } ); - } - } -}; - -SPE.Emitter.prototype._setBufferUpdateRanges = function( keys ) { - 'use strict'; - - this.attributeKeys = keys; - this.attributeCount = keys.length; - - for ( var i = this.attributeCount - 1; i >= 0; --i ) { - this.bufferUpdateRanges[ keys[ i ] ] = { - min: Number.POSITIVE_INFINITY, - max: Number.NEGATIVE_INFINITY - }; - } -}; - -SPE.Emitter.prototype._calculatePPSValue = function( groupMaxAge ) { - 'use strict'; - - var particleCount = this.particleCount; - - - // Calculate the `particlesPerSecond` value for this emitter. It's used - // when determining which particles should die and which should live to - // see another day. Or be born, for that matter. The "God" property. - if ( this.duration ) { - this.particlesPerSecond = particleCount / ( groupMaxAge < this.duration ? groupMaxAge : this.duration ); - } - else { - this.particlesPerSecond = particleCount / groupMaxAge; - } -}; - -SPE.Emitter.prototype._setAttributeOffset = function( startIndex ) { - this.attributeOffset = startIndex; - this.activationIndex = startIndex; - this.activationEnd = startIndex + this.particleCount; -}; - - -SPE.Emitter.prototype._assignValue = function( prop, index ) { - 'use strict'; - - switch ( prop ) { - case 'position': - this._assignPositionValue( index ); - break; - - case 'velocity': - case 'acceleration': - this._assignForceValue( index, prop ); - break; - - case 'size': - case 'opacity': - this._assignAbsLifetimeValue( index, prop ); - break; - - case 'angle': - this._assignAngleValue( index ); - break; - - case 'params': - this._assignParamsValue( index ); - break; - - case 'rotation': - this._assignRotationValue( index ); - break; - - case 'color': - this._assignColorValue( index ); - break; - } -}; - -SPE.Emitter.prototype._assignPositionValue = function( index ) { - 'use strict'; - - var distributions = SPE.distributions, - utils = SPE.utils, - prop = this.position, - attr = this.attributes.position, - value = prop._value, - spread = prop._spread, - distribution = prop._distribution; - - switch ( distribution ) { - case distributions.BOX: - utils.randomVector3( attr, index, value, spread, prop._spreadClamp ); - break; - - case distributions.SPHERE: - utils.randomVector3OnSphere( attr, index, value, prop._radius, prop._spread.x, prop._radiusScale, prop._spreadClamp.x, prop._distributionClamp || this.particleCount ); - break; - - case distributions.DISC: - utils.randomVector3OnDisc( attr, index, value, prop._radius, prop._spread.x, prop._radiusScale, prop._spreadClamp.x ); - break; - - case distributions.LINE: - utils.randomVector3OnLine( attr, index, value, spread ); - break; - } -}; - -SPE.Emitter.prototype._assignForceValue = function( index, attrName ) { - 'use strict'; - - var distributions = SPE.distributions, - utils = SPE.utils, - prop = this[ attrName ], - value = prop._value, - spread = prop._spread, - distribution = prop._distribution, - pos, - positionX, - positionY, - positionZ, - i; - - switch ( distribution ) { - case distributions.BOX: - utils.randomVector3( this.attributes[ attrName ], index, value, spread ); - break; - - case distributions.SPHERE: - pos = this.attributes.position.typedArray.array; - i = index * 3; - - // Ensure position values aren't zero, otherwise no force will be - // applied. - // positionX = utils.zeroToEpsilon( pos[ i ], true ); - // positionY = utils.zeroToEpsilon( pos[ i + 1 ], true ); - // positionZ = utils.zeroToEpsilon( pos[ i + 2 ], true ); - positionX = pos[ i ]; - positionY = pos[ i + 1 ]; - positionZ = pos[ i + 2 ]; - - utils.randomDirectionVector3OnSphere( - this.attributes[ attrName ], index, - positionX, positionY, positionZ, - this.position._value, - prop._value.x, - prop._spread.x - ); - break; - - case distributions.DISC: - pos = this.attributes.position.typedArray.array; - i = index * 3; - - // Ensure position values aren't zero, otherwise no force will be - // applied. - // positionX = utils.zeroToEpsilon( pos[ i ], true ); - // positionY = utils.zeroToEpsilon( pos[ i + 1 ], true ); - // positionZ = utils.zeroToEpsilon( pos[ i + 2 ], true ); - positionX = pos[ i ]; - positionY = pos[ i + 1 ]; - positionZ = pos[ i + 2 ]; - - utils.randomDirectionVector3OnDisc( - this.attributes[ attrName ], index, - positionX, positionY, positionZ, - this.position._value, - prop._value.x, - prop._spread.x - ); - break; - - case distributions.LINE: - utils.randomVector3OnLine( this.attributes[ attrName ], index, value, spread ); - break; - } - - if ( attrName === 'acceleration' ) { - var drag = utils.clamp( utils.randomFloat( this.drag._value, this.drag._spread ), 0, 1 ); - this.attributes.acceleration.typedArray.array[ index * 4 + 3 ] = drag; - } -}; - -SPE.Emitter.prototype._assignAbsLifetimeValue = function( index, propName ) { - 'use strict'; - - var array = this.attributes[ propName ].typedArray, - prop = this[ propName ], - utils = SPE.utils, - value; - - if ( utils.arrayValuesAreEqual( prop._value ) && utils.arrayValuesAreEqual( prop._spread ) ) { - value = Math.abs( utils.randomFloat( prop._value[ 0 ], prop._spread[ 0 ] ) ); - array.setVec4Components( index, value, value, value, value ); - } - else { - array.setVec4Components( index, - Math.abs( utils.randomFloat( prop._value[ 0 ], prop._spread[ 0 ] ) ), - Math.abs( utils.randomFloat( prop._value[ 1 ], prop._spread[ 1 ] ) ), - Math.abs( utils.randomFloat( prop._value[ 2 ], prop._spread[ 2 ] ) ), - Math.abs( utils.randomFloat( prop._value[ 3 ], prop._spread[ 3 ] ) ) - ); - } -}; - -SPE.Emitter.prototype._assignAngleValue = function( index ) { - 'use strict'; - - var array = this.attributes.angle.typedArray, - prop = this.angle, - utils = SPE.utils, - value; - - if ( utils.arrayValuesAreEqual( prop._value ) && utils.arrayValuesAreEqual( prop._spread ) ) { - value = utils.randomFloat( prop._value[ 0 ], prop._spread[ 0 ] ); - array.setVec4Components( index, value, value, value, value ); - } - else { - array.setVec4Components( index, - utils.randomFloat( prop._value[ 0 ], prop._spread[ 0 ] ), - utils.randomFloat( prop._value[ 1 ], prop._spread[ 1 ] ), - utils.randomFloat( prop._value[ 2 ], prop._spread[ 2 ] ), - utils.randomFloat( prop._value[ 3 ], prop._spread[ 3 ] ) - ); - } -}; - -SPE.Emitter.prototype._assignParamsValue = function( index ) { - 'use strict'; - - this.attributes.params.typedArray.setVec4Components( index, - this.isStatic ? 1 : 0, - 0.0, - Math.abs( SPE.utils.randomFloat( this.maxAge._value, this.maxAge._spread ) ), - SPE.utils.randomFloat( this.wiggle._value, this.wiggle._spread ) - ); -}; - -SPE.Emitter.prototype._assignRotationValue = function( index ) { - 'use strict'; - - this.attributes.rotation.typedArray.setVec3Components( index, - SPE.utils.getPackedRotationAxis( this.rotation._axis, this.rotation._axisSpread ), - SPE.utils.randomFloat( this.rotation._angle, this.rotation._angleSpread ), - this.rotation._static ? 0 : 1 - ); - - this.attributes.rotationCenter.typedArray.setVec3( index, this.rotation._center ); -}; - -SPE.Emitter.prototype._assignColorValue = function( index ) { - 'use strict'; - SPE.utils.randomColorAsHex( this.attributes.color, index, this.color._value, this.color._spread ); -}; - -SPE.Emitter.prototype._resetParticle = function( index ) { - 'use strict'; - - var resetFlags = this.resetFlags, - updateFlags = this.updateFlags, - updateCounts = this.updateCounts, - keys = this.attributeKeys, - key, - updateFlag; - - for ( var i = this.attributeCount - 1; i >= 0; --i ) { - key = keys[ i ]; - updateFlag = updateFlags[ key ]; - - if ( resetFlags[ key ] === true || updateFlag === true ) { - this._assignValue( key, index ); - this._updateAttributeUpdateRange( key, index ); - - if ( updateFlag === true && updateCounts[ key ] === this.particleCount ) { - updateFlags[ key ] = false; - updateCounts[ key ] = 0.0; - } - else if ( updateFlag == true ) { - ++updateCounts[ key ]; - } - } - } -}; - -SPE.Emitter.prototype._updateAttributeUpdateRange = function( attr, i ) { - 'use strict'; - - var ranges = this.bufferUpdateRanges[ attr ]; - - ranges.min = Math.min( i, ranges.min ); - ranges.max = Math.max( i, ranges.max ); -}; - -SPE.Emitter.prototype._resetBufferRanges = function() { - 'use strict'; - - var ranges = this.bufferUpdateRanges, - keys = this.bufferUpdateKeys, - i = this.bufferUpdateCount - 1, - key; - - for ( i; i >= 0; --i ) { - key = keys[ i ]; - ranges[ key ].min = Number.POSITIVE_INFINITY; - ranges[ key ].max = Number.NEGATIVE_INFINITY; - } -}; - -SPE.Emitter.prototype._onRemove = function() { - 'use strict'; - // Reset any properties of the emitter that were set by - // a group when it was added. - this.particlesPerSecond = 0; - this.attributeOffset = 0; - this.activationIndex = 0; - this.activeParticleCount = 0; - this.group = null; - this.attributes = null; - this.paramsArray = null; - this.age = 0.0; -}; - -SPE.Emitter.prototype._decrementParticleCount = function() { - 'use strict'; - --this.activeParticleCount; - - // TODO: - // - Trigger event if count === 0. -}; - -SPE.Emitter.prototype._incrementParticleCount = function() { - 'use strict'; - ++this.activeParticleCount; - - // TODO: - // - Trigger event if count === this.particleCount. -}; - -SPE.Emitter.prototype._checkParticleAges = function( start, end, params, dt ) { - 'use strict'; - for ( var i = end - 1, index, maxAge, age, alive; i >= start; --i ) { - index = i * 4; - - alive = params[ index ]; - - if ( alive === 0.0 ) { - continue; - } - - // Increment age - age = params[ index + 1 ]; - maxAge = params[ index + 2 ]; - - if ( this.direction === 1 ) { - age += dt; - - if ( age >= maxAge ) { - age = 0.0; - alive = 0.0; - this._decrementParticleCount(); - } - } - else { - age -= dt; - - if ( age <= 0.0 ) { - age = maxAge; - alive = 0.0; - this._decrementParticleCount(); - } - } - - params[ index ] = alive; - params[ index + 1 ] = age; - - this._updateAttributeUpdateRange( 'params', i ); - } -}; - -SPE.Emitter.prototype._activateParticles = function( activationStart, activationEnd, params, dtPerParticle ) { - 'use strict'; - var direction = this.direction; - - for ( var i = activationStart, index, dtValue; i < activationEnd; ++i ) { - index = i * 4; - - // Don't re-activate particles that aren't dead yet. - // if ( params[ index ] !== 0.0 && ( this.particleCount !== 1 || this.activeMultiplier !== 1 ) ) { - // continue; - // } - - if ( params[ index ] != 0.0 && this.particleCount !== 1 ) { - continue; - } - - // Increment the active particle count. - this._incrementParticleCount(); - - // Mark the particle as alive. - params[ index ] = 1.0; - - // Reset the particle - this._resetParticle( i ); - - // Move each particle being activated to - // it's actual position in time. - // - // This stops particles being 'clumped' together - // when frame rates are on the lower side of 60fps - // or not constant (a very real possibility!) - dtValue = dtPerParticle * ( i - activationStart ) - params[ index + 1 ] = direction === -1 ? params[ index + 2 ] - dtValue : dtValue; - - this._updateAttributeUpdateRange( 'params', i ); - } -}; - -/** - * Simulates one frame's worth of particles, updating particles - * that are already alive, and marking ones that are currently dead - * but should be alive as alive. - * - * If the emitter is marked as static, then this function will do nothing. - * - * @param {Number} dt The number of seconds to simulate (deltaTime) - */ -SPE.Emitter.prototype.tick = function( dt ) { - 'use strict'; - - if ( this.isStatic ) { - return; - } - - if ( this.paramsArray === null ) { - this.paramsArray = this.attributes.params.typedArray.array; - } - - var start = this.attributeOffset, - end = start + this.particleCount, - params = this.paramsArray, // vec3( alive, age, maxAge, wiggle ) - ppsDt = this.particlesPerSecond * this.activeMultiplier * dt, - activationIndex = this.activationIndex; - - // Reset the buffer update indices. - this._resetBufferRanges(); - - // Increment age for those particles that are alive, - // and kill off any particles whose age is over the limit. - this._checkParticleAges( start, end, params, dt ); - - // If the emitter is dead, reset the age of the emitter to zero, - // ready to go again if required - if ( this.alive === false ) { - this.age = 0.0; - return; - } - - // If the emitter has a specified lifetime and we've exceeded it, - // mark the emitter as dead. - if ( this.duration !== null && this.age > this.duration ) { - this.alive = false; - this.age = 0.0; - return; - } - - - var activationStart = this.particleCount === 1 ? activationIndex : ( activationIndex | 0 ), - activationEnd = Math.min( activationStart + ppsDt, this.activationEnd ), - activationCount = activationEnd - this.activationIndex | 0, - dtPerParticle = activationCount > 0 ? dt / activationCount : 0; - - this._activateParticles( activationStart, activationEnd, params, dtPerParticle ); - - // Move the activation window forward, soldier. - this.activationIndex += ppsDt; - - if ( this.activationIndex > end ) { - this.activationIndex = start; - } - - - // Increment the age of the emitter. - this.age += dt; -}; - -/** - * Resets all the emitter's particles to their start positions - * and marks the particles as dead if the `force` argument is - * true. - * - * @param {Boolean} [force=undefined] If true, all particles will be marked as dead instantly. - * @return {Emitter} This emitter instance. - */ -SPE.Emitter.prototype.reset = function( force ) { - 'use strict'; - - this.age = 0.0; - this.alive = false; - - if ( force === true ) { - var start = this.attributeOffset, - end = start + this.particleCount, - array = this.paramsArray, - attr = this.attributes.params.bufferAttribute; - - for ( var i = end - 1, index; i >= start; --i ) { - index = i * 4; - - array[ index ] = 0.0; - array[ index + 1 ] = 0.0; - } - - attr.updateRange.offset = 0; - attr.updateRange.count = -1; - attr.needsUpdate = true; - } - - return this; -}; - -/** - * Enables the emitter. If not already enabled, the emitter - * will start emitting particles. - * - * @return {Emitter} This emitter instance. - */ -SPE.Emitter.prototype.enable = function() { - 'use strict'; - this.alive = true; - return this; -}; - -/** - * Disables th emitter, but does not instantly remove it's - * particles fromt the scene. When called, the emitter will be - * 'switched off' and just stop emitting. Any particle's alive will - * be allowed to finish their lifecycle. - * - * @return {Emitter} This emitter instance. - */ -SPE.Emitter.prototype.disable = function() { - 'use strict'; - - this.alive = false; - return this; -}; - -/** - * Remove this emitter from it's parent group (if it has been added to one). - * Delgates to SPE.group.prototype.removeEmitter(). - * - * When called, all particle's belonging to this emitter will be instantly - * removed from the scene. - * - * @return {Emitter} This emitter instance. - * - * @see SPE.Group.prototype.removeEmitter - */ -SPE.Emitter.prototype.remove = function() { - 'use strict'; - if ( this.group !== null ) { - this.group.removeEmitter( this ); - } - else { - console.error( 'Emitter does not belong to a group, cannot remove.' ); - } - - return this; -}; diff --git a/build/SPE.min.js b/build/SPE.min.js deleted file mode 100644 index 6cfdcc8..0000000 --- a/build/SPE.min.js +++ /dev/null @@ -1,52 +0,0 @@ -/* shader-particle-engine 1.0.6 - * - * (c) 2015 Luke Moody (http://www.github.com/squarefeet) - * Originally based on Lee Stemkoski's original work (https://github.com/stemkoski/stemkoski.github.com/blob/master/Three.js/js/ParticleEngine.js). - * - * shader-particle-engine may be freely distributed under the MIT license (See LICENSE at root of this repository.) - */ -var SPE={distributions:{BOX:1,SPHERE:2,DISC:3,LINE:4},valueOverLifetimeLength:4};"function"==typeof define&&define.amd?define("spe",SPE):"undefined"!=typeof exports&&"undefined"!=typeof module&&(module.exports=SPE),SPE.TypedArrayHelper=function(a,b,c,d){"use strict";this.componentSize=c||1,this.size=b||1,this.TypedArrayConstructor=a||Float32Array,this.array=new a(b*this.componentSize),this.indexOffset=d||0},SPE.TypedArrayHelper.constructor=SPE.TypedArrayHelper,SPE.TypedArrayHelper.prototype.setSize=function(a,b){"use strict";var c=this.array.length;return b||(a*=this.componentSize),ac?this.grow(a):void console.info("TypedArray is already of size:",a+".","Will not resize.")},SPE.TypedArrayHelper.prototype.shrink=function(a){"use strict";return this.array=this.array.subarray(0,a),this.size=a,this},SPE.TypedArrayHelper.prototype.grow=function(a){"use strict";var b=this.array,c=new this.TypedArrayConstructor(a);return c.set(b),this.array=c,this.size=a,this}, -SPE.TypedArrayHelper.prototype.splice=function(a,b){"use strict";a*=this.componentSize,b*=this.componentSize;for(var c=[],d=this.array,e=d.length,f=0;f=b)&&c.push(d[f]);return this.setFromArray(0,c),this},SPE.TypedArrayHelper.prototype.setFromArray=function(a,b){"use strict";var c=b.length,d=a+c;return d>this.array.length?this.grow(d):d=81&&(this.bufferAttribute.count=this.bufferAttribute.array.length/this.bufferAttribute.itemSize),void(this.bufferAttribute.needsUpdate=!0);this.bufferAttribute=new THREE.BufferAttribute(this.typedArray.array,this.componentSize),this.bufferAttribute.usage=this.dynamicBuffer?THREE.DynamicDrawUsage:THREE.StaticDrawUsage},SPE.ShaderAttribute.prototype.getLength=function(){"use strict";return null===this.typedArray?0:this.typedArray.array.length},SPE.shaderChunks={ -defines:["#define PACKED_COLOR_SIZE 256.0","#define PACKED_COLOR_DIVISOR 255.0"].join("\n"),uniforms:["uniform float deltaTime;","uniform float runTime;","uniform sampler2D tex;","uniform vec4 textureAnimation;","uniform float scale;"].join("\n"),attributes:["attribute vec4 acceleration;","attribute vec3 velocity;","attribute vec4 rotation;","attribute vec3 rotationCenter;","attribute vec4 params;","attribute vec4 size;","attribute vec4 angle;","attribute vec4 color;","attribute vec4 opacity;"].join("\n"),varyings:["varying vec4 vColor;","#ifdef SHOULD_ROTATE_TEXTURE"," varying float vAngle;","#endif","#ifdef SHOULD_CALCULATE_SPRITE"," varying vec4 vSpriteSheet;","#endif"].join("\n"), -branchAvoidanceFunctions:["float when_gt(float x, float y) {"," return max(sign(x - y), 0.0);","}","float when_lt(float x, float y) {"," return min( max(1.0 - sign(x - y), 0.0), 1.0 );","}","float when_eq( float x, float y ) {"," return 1.0 - abs( sign( x - y ) );","}","float when_ge(float x, float y) {"," return 1.0 - when_lt(x, y);","}","float when_le(float x, float y) {"," return 1.0 - when_gt(x, y);","}","float and(float a, float b) {"," return a * b;","}","float or(float a, float b) {"," return min(a + b, 1.0);","}"].join("\n"),unpackColor:["vec3 unpackColor( in float hex ) {"," vec3 c = vec3( 0.0 );"," float r = mod( (hex / PACKED_COLOR_SIZE / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );"," float g = mod( (hex / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );"," float b = mod( hex, PACKED_COLOR_SIZE );"," c.r = r / PACKED_COLOR_DIVISOR;"," c.g = g / PACKED_COLOR_DIVISOR;"," c.b = b / PACKED_COLOR_DIVISOR;"," return c;","}"].join("\n"), -unpackRotationAxis:["vec3 unpackRotationAxis( in float hex ) {"," vec3 c = vec3( 0.0 );"," float r = mod( (hex / PACKED_COLOR_SIZE / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );"," float g = mod( (hex / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );"," float b = mod( hex, PACKED_COLOR_SIZE );"," c.r = r / PACKED_COLOR_DIVISOR;"," c.g = g / PACKED_COLOR_DIVISOR;"," c.b = b / PACKED_COLOR_DIVISOR;"," c *= vec3( 2.0 );"," c -= vec3( 1.0 );"," return c;","}"].join("\n"), -floatOverLifetime:["float getFloatOverLifetime( in float positionInTime, in vec4 attr ) {"," highp float value = 0.0;"," float deltaAge = positionInTime * float( VALUE_OVER_LIFETIME_LENGTH - 1 );"," float fIndex = 0.0;"," float shouldApplyValue = 0.0;"," value += attr[ 0 ] * when_eq( deltaAge, 0.0 );",""," for( int i = 0; i < VALUE_OVER_LIFETIME_LENGTH - 1; ++i ) {"," fIndex = float( i );"," shouldApplyValue = and( when_gt( deltaAge, fIndex ), when_le( deltaAge, fIndex + 1.0 ) );"," value += shouldApplyValue * mix( attr[ i ], attr[ i + 1 ], deltaAge - fIndex );"," }",""," return value;","}"].join("\n"), -colorOverLifetime:["vec3 getColorOverLifetime( in float positionInTime, in vec3 color1, in vec3 color2, in vec3 color3, in vec3 color4 ) {"," vec3 value = vec3( 0.0 );"," value.x = getFloatOverLifetime( positionInTime, vec4( color1.x, color2.x, color3.x, color4.x ) );"," value.y = getFloatOverLifetime( positionInTime, vec4( color1.y, color2.y, color3.y, color4.y ) );"," value.z = getFloatOverLifetime( positionInTime, vec4( color1.z, color2.z, color3.z, color4.z ) );"," return value;","}"].join("\n"),paramFetchingFunctions:["float getAlive() {"," return params.x;","}","float getAge() {"," return params.y;","}","float getMaxAge() {"," return params.z;","}","float getWiggle() {"," return params.w;","}"].join("\n"), -forceFetchingFunctions:["vec4 getPosition( in float age ) {"," return modelViewMatrix * vec4( position, 1.0 );","}","vec3 getVelocity( in float age ) {"," return velocity * age;","}","vec3 getAcceleration( in float age ) {"," return acceleration.xyz * age;","}"].join("\n"), -rotationFunctions:["#ifdef SHOULD_ROTATE_PARTICLES"," mat4 getRotationMatrix( in vec3 axis, in float angle) {"," axis = normalize(axis);"," float s = sin(angle);"," float c = cos(angle);"," float oc = 1.0 - c;",""," return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0,"," oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0,"," oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0,"," 0.0, 0.0, 0.0, 1.0);"," }",""," vec3 getRotation( in vec3 pos, in float positionInTime ) {"," if( rotation.y == 0.0 ) {"," return pos;"," }",""," vec3 axis = unpackRotationAxis( rotation.x );"," vec3 center = rotationCenter;"," vec3 translated;"," mat4 rotationMatrix;"," float angle = 0.0;"," angle += when_eq( rotation.z, 0.0 ) * rotation.y;"," angle += when_gt( rotation.z, 0.0 ) * mix( 0.0, rotation.y, positionInTime );"," translated = rotationCenter - pos;"," rotationMatrix = getRotationMatrix( axis, angle );"," return center - vec3( rotationMatrix * vec4( translated, 0.0 ) );"," }","#endif"].join("\n"), -rotateTexture:[" vec2 vUv = vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y );",""," #ifdef SHOULD_ROTATE_TEXTURE"," float x = gl_PointCoord.x - 0.5;"," float y = 1.0 - gl_PointCoord.y - 0.5;"," float c = cos( -vAngle );"," float s = sin( -vAngle );"," vUv = vec2( c * x + s * y + 0.5, c * y - s * x + 0.5 );"," #endif",""," #ifdef SHOULD_CALCULATE_SPRITE"," float framesX = vSpriteSheet.x;"," float framesY = vSpriteSheet.y;"," float columnNorm = vSpriteSheet.z;"," float rowNorm = vSpriteSheet.w;"," vUv.x = gl_PointCoord.x * framesX + columnNorm;"," vUv.y = 1.0 - (gl_PointCoord.y * framesY + rowNorm);"," #endif",""," vec4 rotatedTexture = texture2D( tex, vUv );"].join("\n")},SPE.shaders={ -vertex:[SPE.shaderChunks.defines,SPE.shaderChunks.uniforms,SPE.shaderChunks.attributes,SPE.shaderChunks.varyings,THREE.ShaderChunk.common,THREE.ShaderChunk.logdepthbuf_pars_vertex,THREE.ShaderChunk.fog_pars_vertex,SPE.shaderChunks.branchAvoidanceFunctions,SPE.shaderChunks.unpackColor,SPE.shaderChunks.unpackRotationAxis,SPE.shaderChunks.floatOverLifetime,SPE.shaderChunks.colorOverLifetime,SPE.shaderChunks.paramFetchingFunctions,SPE.shaderChunks.forceFetchingFunctions,SPE.shaderChunks.rotationFunctions,"void main() {"," highp float age = getAge();"," highp float alive = getAlive();"," highp float maxAge = getMaxAge();"," highp float positionInTime = (age / maxAge);"," highp float isAlive = when_gt( alive, 0.0 );"," #ifdef SHOULD_WIGGLE_PARTICLES"," float wiggleAmount = positionInTime * getWiggle();"," float wiggleSin = isAlive * sin( wiggleAmount );"," float wiggleCos = isAlive * cos( wiggleAmount );"," #endif"," vec3 vel = getVelocity( age );"," vec3 accel = getAcceleration( age );"," vec3 force = vec3( 0.0 );"," vec3 pos = vec3( position );"," float drag = 1.0 - (positionInTime * 0.5) * acceleration.w;"," force += vel;"," force *= drag;"," force += accel * age;"," pos += force;"," #ifdef SHOULD_WIGGLE_PARTICLES"," pos.x += wiggleSin;"," pos.y += wiggleCos;"," pos.z += wiggleSin;"," #endif"," #ifdef SHOULD_ROTATE_PARTICLES"," pos = getRotation( pos, positionInTime );"," #endif"," vec4 mvPosition = modelViewMatrix * vec4( pos, 1.0 );"," highp float pointSize = getFloatOverLifetime( positionInTime, size ) * isAlive;"," #ifdef HAS_PERSPECTIVE"," float perspective = scale / length( mvPosition.xyz );"," #else"," float perspective = 1.0;"," #endif"," float pointSizePerspective = pointSize * perspective;"," #ifdef COLORIZE"," vec3 c = isAlive * getColorOverLifetime("," positionInTime,"," unpackColor( color.x ),"," unpackColor( color.y ),"," unpackColor( color.z ),"," unpackColor( color.w )"," );"," #else"," vec3 c = vec3(1.0);"," #endif"," float o = isAlive * getFloatOverLifetime( positionInTime, opacity );"," vColor = vec4( c, o );"," #ifdef SHOULD_ROTATE_TEXTURE"," vAngle = isAlive * getFloatOverLifetime( positionInTime, angle );"," #endif"," #ifdef SHOULD_CALCULATE_SPRITE"," float framesX = textureAnimation.x;"," float framesY = textureAnimation.y;"," float loopCount = textureAnimation.w;"," float totalFrames = textureAnimation.z;"," float frameNumber = mod( (positionInTime * loopCount) * totalFrames, totalFrames );"," float column = floor(mod( frameNumber, framesX ));"," float row = floor( (frameNumber - column) / framesX );"," float columnNorm = column / framesX;"," float rowNorm = row / framesY;"," vSpriteSheet.x = 1.0 / framesX;"," vSpriteSheet.y = 1.0 / framesY;"," vSpriteSheet.z = columnNorm;"," vSpriteSheet.w = rowNorm;"," #endif"," gl_PointSize = pointSizePerspective;"," gl_Position = projectionMatrix * mvPosition;",THREE.ShaderChunk.logdepthbuf_vertex,THREE.ShaderChunk.fog_vertex,"}"].join("\n"), -fragment:[SPE.shaderChunks.uniforms,THREE.ShaderChunk.common,THREE.ShaderChunk.fog_pars_fragment,THREE.ShaderChunk.logdepthbuf_pars_fragment,SPE.shaderChunks.varyings,SPE.shaderChunks.branchAvoidanceFunctions,"void main() {"," vec3 outgoingLight = vColor.xyz;"," "," #ifdef ALPHATEST"," if ( vColor.w < float(ALPHATEST) ) discard;"," #endif",SPE.shaderChunks.rotateTexture,THREE.ShaderChunk.logdepthbuf_fragment," outgoingLight = vColor.xyz * rotatedTexture.xyz;"," gl_FragColor = vec4( outgoingLight.xyz, rotatedTexture.w * vColor.w );",THREE.ShaderChunk.fog_fragment,"}"].join("\n")},SPE.utils={types:{BOOLEAN:"boolean",STRING:"string",NUMBER:"number",OBJECT:"object"},ensureTypedArg:function(a,b,c){"use strict";return typeof a===b?a:c},ensureArrayTypedArg:function(a,b,c){"use strict";if(Array.isArray(a)){for(var d=a.length-1;d>=0;--d)if(typeof a[d]!==b)return c;return a}return this.ensureTypedArg(a,b,c)},ensureInstanceOf:function(a,b,c){"use strict" -;return void 0!==b&&a instanceof b?a:c},ensureArrayInstanceOf:function(a,b,c){"use strict";if(Array.isArray(a)){for(var d=a.length-1;d>=0;--d)if(void 0!==b&&a[d]instanceof b==!1)return c;return a}return this.ensureInstanceOf(a,b,c)},ensureValueOverLifetimeCompliance:function(a,b,c){"use strict";b=b||3,c=c||3,!1===Array.isArray(a._value)&&(a._value=[a._value]),!1===Array.isArray(a._spread)&&(a._spread=[a._spread]);var d=this.clamp(a._value.length,b,c),e=this.clamp(a._spread.length,b,c),f=Math.max(d,e);a._value.length!==f&&(a._value=this.interpolateArray(a._value,f)),a._spread.length!==f&&(a._spread=this.interpolateArray(a._spread,f))},interpolateArray:function(a,b){"use strict";for(var c=a.length,d=["function"==typeof a[0].clone?a[0].clone():a[0]],e=(c-1)/(b-1),f=1;f-c&&(d=-d),d},lerpTypeAgnostic:function(a,b,c){"use strict";var d,e=this.types;return typeof a===e.NUMBER&&typeof b===e.NUMBER?a+(b-a)*c:a instanceof THREE.Vector2&&b instanceof THREE.Vector2?(d=a.clone(),d.x=this.lerp(a.x,b.x,c),d.y=this.lerp(a.y,b.y,c),d):a instanceof THREE.Vector3&&b instanceof THREE.Vector3?(d=a.clone(),d.x=this.lerp(a.x,b.x,c),d.y=this.lerp(a.y,b.y,c),d.z=this.lerp(a.z,b.z,c),d):a instanceof THREE.Vector4&&b instanceof THREE.Vector4?(d=a.clone(),d.x=this.lerp(a.x,b.x,c),d.y=this.lerp(a.y,b.y,c),d.z=this.lerp(a.z,b.z,c),d.w=this.lerp(a.w,b.w,c),d):a instanceof THREE.Color&&b instanceof THREE.Color?(d=a.clone(),d.r=this.lerp(a.r,b.r,c),d.g=this.lerp(a.g,b.g,c),d.b=this.lerp(a.b,b.b,c),d):void console.warn("Invalid argument types, or argument types do not match:",a,b)},lerp:function(a,b,c){"use strict";return a+(b-a)*c}, -roundToNearestMultiple:function(a,b){"use strict";var c=0;return 0===b?a:(c=Math.abs(a)%b,0===c?a:a<0?-(Math.abs(a)-c):a+b-c)},arrayValuesAreEqual:function(a){"use strict";for(var b=0;b1||this.textureFrames.y>1},this.attributes={position:new SPE.ShaderAttribute("v3",!0),acceleration:new SPE.ShaderAttribute("v4",!0),velocity:new SPE.ShaderAttribute("v3",!0),rotation:new SPE.ShaderAttribute("v4",!0),rotationCenter:new SPE.ShaderAttribute("v3",!0),params:new SPE.ShaderAttribute("v4",!0),size:new SPE.ShaderAttribute("v4",!0),angle:new SPE.ShaderAttribute("v4",!0),color:new SPE.ShaderAttribute("v4",!0),opacity:new SPE.ShaderAttribute("v4",!0)},this.attributeKeys=Object.keys(this.attributes),this.attributeCount=this.attributeKeys.length,this.material=new THREE.ShaderMaterial({uniforms:this.uniforms,vertexShader:SPE.shaders.vertex,fragmentShader:SPE.shaders.fragment,blending:this.blending,transparent:this.transparent,alphaTest:this.alphaTest,depthWrite:this.depthWrite,depthTest:this.depthTest,defines:this.defines,fog:this.fog}), -this.geometry=new THREE.BufferGeometry,this.mesh=new THREE.Points(this.geometry,this.material),null===this.maxParticleCount&&console.warn("SPE.Group: No maxParticleCount specified. Adding emitters after rendering will probably cause errors.")},SPE.Group.constructor=SPE.Group,SPE.Group.prototype._updateDefines=function(){"use strict";var a,b=this.emitters,c=b.length-1,d=this.defines;for(c;c>=0;--c)a=b[c],d.SHOULD_CALCULATE_SPRITE||(d.SHOULD_ROTATE_TEXTURE=d.SHOULD_ROTATE_TEXTURE||!!Math.max(Math.max.apply(null,a.angle.value),Math.max.apply(null,a.angle.spread))),d.SHOULD_ROTATE_PARTICLES=d.SHOULD_ROTATE_PARTICLES||!!Math.max(a.rotation.angle,a.rotation.angleSpread),d.SHOULD_WIGGLE_PARTICLES=d.SHOULD_WIGGLE_PARTICLES||!!Math.max(a.wiggle.value,a.wiggle.spread);this.material.needsUpdate=!0},SPE.Group.prototype._applyAttributesToGeometry=function(){"use strict";var a,b,c=this.attributes,d=this.geometry,e=d.attributes;for(var f in c)c.hasOwnProperty(f)&&(a=c[f],b=e[f], -b?b.array=a.typedArray.array:d.addAttribute(f,a.bufferAttribute),a.bufferAttribute.needsUpdate=!0);this.geometry.setDrawRange(0,this.particleCount)},SPE.Group.prototype.addEmitter=function(a){"use strict";if(a instanceof SPE.Emitter==!1)return void console.error("`emitter` argument must be instance of SPE.Emitter. Was provided with:",a);if(this.emitterIDs.indexOf(a.uuid)>-1)return void console.error("Emitter already exists in this group. Will not add again.");if(null!==a.group)return void console.error("Emitter already belongs to another group. Will not add to requested group.");var b=this.attributes,c=this.particleCount,d=c+a.particleCount;this.particleCount=d,null!==this.maxParticleCount&&this.particleCount>this.maxParticleCount&&console.warn("SPE.Group: maxParticleCount exceeded. Requesting",this.particleCount,"particles, can support only",this.maxParticleCount),a._calculatePPSValue(a.maxAge._value+a.maxAge._spread),a._setBufferUpdateRanges(this.attributeKeys), -a._setAttributeOffset(c),a.group=this,a.attributes=this.attributes;for(var e in b)b.hasOwnProperty(e)&&b[e]._createBufferAttribute(null!==this.maxParticleCount?this.maxParticleCount:this.particleCount);for(var f=c;f1)for(var c=0;c=0;--b)c[a[b]].resetUpdateRange()},SPE.Group.prototype._updateBuffers=function(a){"use strict";var b,c,d,e=this.attributeKeys,f=this.attributeCount-1,g=this.attributes,h=a.bufferUpdateRanges;for(f;f>=0;--f)b=e[f],c=h[b],d=g[b],d.setUpdateRange(c.min,c.max),d.flagUpdate()},SPE.Group.prototype.tick=function(a){"use strict";var b,c=this.emitters,d=c.length,e=a||this.fixedTimeStep,f=this.attributeKeys,g=this.attributes;if(this._updateUniforms(e),this._resetBufferRanges(),0!==d||!1!==this._attributesNeedRefresh||!1!==this._attributesNeedDynamicReset){for(var h,b=0;b=0;--b)g[f[b]].resetDynamic();this._attributesNeedDynamicReset=!1}if(!0===this._attributesNeedRefresh){for(b=this.attributeCount-1;b>=0;--b)g[f[b]].forceUpdateAll();this._attributesNeedRefresh=!1,this._attributesNeedDynamicReset=!0}}}, -SPE.Group.prototype.dispose=function(){"use strict";return this.geometry.dispose(),this.material.dispose(),this},SPE.Emitter=function(a){"use strict";var b=SPE.utils,c=b.types,d=SPE.valueOverLifetimeLength;a=b.ensureTypedArg(a,c.OBJECT,{}),a.position=b.ensureTypedArg(a.position,c.OBJECT,{}),a.velocity=b.ensureTypedArg(a.velocity,c.OBJECT,{}),a.acceleration=b.ensureTypedArg(a.acceleration,c.OBJECT,{}),a.radius=b.ensureTypedArg(a.radius,c.OBJECT,{}),a.drag=b.ensureTypedArg(a.drag,c.OBJECT,{}),a.rotation=b.ensureTypedArg(a.rotation,c.OBJECT,{}),a.color=b.ensureTypedArg(a.color,c.OBJECT,{}),a.opacity=b.ensureTypedArg(a.opacity,c.OBJECT,{}),a.size=b.ensureTypedArg(a.size,c.OBJECT,{}),a.angle=b.ensureTypedArg(a.angle,c.OBJECT,{}),a.wiggle=b.ensureTypedArg(a.wiggle,c.OBJECT,{}),a.maxAge=b.ensureTypedArg(a.maxAge,c.OBJECT,{}),a.onParticleSpawn&&console.warn("onParticleSpawn has been removed. Please set properties directly to alter values at runtime."),this.uuid=THREE.Math.generateUUID(), -this.type=b.ensureTypedArg(a.type,c.NUMBER,SPE.distributions.BOX),this.position={_value:b.ensureInstanceOf(a.position.value,THREE.Vector3,new THREE.Vector3),_spread:b.ensureInstanceOf(a.position.spread,THREE.Vector3,new THREE.Vector3),_spreadClamp:b.ensureInstanceOf(a.position.spreadClamp,THREE.Vector3,new THREE.Vector3),_distribution:b.ensureTypedArg(a.position.distribution,c.NUMBER,this.type),_randomise:b.ensureTypedArg(a.position.randomise,c.BOOLEAN,!1),_radius:b.ensureTypedArg(a.position.radius,c.NUMBER,10),_radiusScale:b.ensureInstanceOf(a.position.radiusScale,THREE.Vector3,new THREE.Vector3(1,1,1)),_distributionClamp:b.ensureTypedArg(a.position.distributionClamp,c.NUMBER,0)},this.velocity={_value:b.ensureInstanceOf(a.velocity.value,THREE.Vector3,new THREE.Vector3),_spread:b.ensureInstanceOf(a.velocity.spread,THREE.Vector3,new THREE.Vector3),_distribution:b.ensureTypedArg(a.velocity.distribution,c.NUMBER,this.type),_randomise:b.ensureTypedArg(a.position.randomise,c.BOOLEAN,!1)}, -this.acceleration={_value:b.ensureInstanceOf(a.acceleration.value,THREE.Vector3,new THREE.Vector3),_spread:b.ensureInstanceOf(a.acceleration.spread,THREE.Vector3,new THREE.Vector3),_distribution:b.ensureTypedArg(a.acceleration.distribution,c.NUMBER,this.type),_randomise:b.ensureTypedArg(a.position.randomise,c.BOOLEAN,!1)},this.drag={_value:b.ensureTypedArg(a.drag.value,c.NUMBER,0),_spread:b.ensureTypedArg(a.drag.spread,c.NUMBER,0),_randomise:b.ensureTypedArg(a.position.randomise,c.BOOLEAN,!1)},this.wiggle={_value:b.ensureTypedArg(a.wiggle.value,c.NUMBER,0),_spread:b.ensureTypedArg(a.wiggle.spread,c.NUMBER,0)},this.rotation={_axis:b.ensureInstanceOf(a.rotation.axis,THREE.Vector3,new THREE.Vector3(0,1,0)),_axisSpread:b.ensureInstanceOf(a.rotation.axisSpread,THREE.Vector3,new THREE.Vector3),_angle:b.ensureTypedArg(a.rotation.angle,c.NUMBER,0),_angleSpread:b.ensureTypedArg(a.rotation.angleSpread,c.NUMBER,0),_static:b.ensureTypedArg(a.rotation.static,c.BOOLEAN,!1), -_center:b.ensureInstanceOf(a.rotation.center,THREE.Vector3,this.position._value.clone()),_randomise:b.ensureTypedArg(a.position.randomise,c.BOOLEAN,!1)},this.maxAge={_value:b.ensureTypedArg(a.maxAge.value,c.NUMBER,2),_spread:b.ensureTypedArg(a.maxAge.spread,c.NUMBER,0)},this.color={_value:b.ensureArrayInstanceOf(a.color.value,THREE.Color,new THREE.Color),_spread:b.ensureArrayInstanceOf(a.color.spread,THREE.Vector3,new THREE.Vector3),_randomise:b.ensureTypedArg(a.position.randomise,c.BOOLEAN,!1)},this.opacity={_value:b.ensureArrayTypedArg(a.opacity.value,c.NUMBER,1),_spread:b.ensureArrayTypedArg(a.opacity.spread,c.NUMBER,0),_randomise:b.ensureTypedArg(a.position.randomise,c.BOOLEAN,!1)},this.size={_value:b.ensureArrayTypedArg(a.size.value,c.NUMBER,1),_spread:b.ensureArrayTypedArg(a.size.spread,c.NUMBER,0),_randomise:b.ensureTypedArg(a.position.randomise,c.BOOLEAN,!1)},this.angle={_value:b.ensureArrayTypedArg(a.angle.value,c.NUMBER,0), -_spread:b.ensureArrayTypedArg(a.angle.spread,c.NUMBER,0),_randomise:b.ensureTypedArg(a.position.randomise,c.BOOLEAN,!1)},this.particleCount=b.ensureTypedArg(a.particleCount,c.NUMBER,100),this.duration=b.ensureTypedArg(a.duration,c.NUMBER,null),this.isStatic=b.ensureTypedArg(a.isStatic,c.BOOLEAN,!1),this.activeMultiplier=b.ensureTypedArg(a.activeMultiplier,c.NUMBER,1),this.direction=b.ensureTypedArg(a.direction,c.NUMBER,1),this.alive=b.ensureTypedArg(a.alive,c.BOOLEAN,!0),this.particlesPerSecond=0,this.activationIndex=0,this.attributeOffset=0,this.attributeEnd=0,this.age=0,this.activeParticleCount=0,this.group=null,this.attributes=null,this.paramsArray=null,this.resetFlags={position:b.ensureTypedArg(a.position.randomise,c.BOOLEAN,!1)||b.ensureTypedArg(a.radius.randomise,c.BOOLEAN,!1),velocity:b.ensureTypedArg(a.velocity.randomise,c.BOOLEAN,!1),acceleration:b.ensureTypedArg(a.acceleration.randomise,c.BOOLEAN,!1)||b.ensureTypedArg(a.drag.randomise,c.BOOLEAN,!1), -rotation:b.ensureTypedArg(a.rotation.randomise,c.BOOLEAN,!1),rotationCenter:b.ensureTypedArg(a.rotation.randomise,c.BOOLEAN,!1),size:b.ensureTypedArg(a.size.randomise,c.BOOLEAN,!1),color:b.ensureTypedArg(a.color.randomise,c.BOOLEAN,!1),opacity:b.ensureTypedArg(a.opacity.randomise,c.BOOLEAN,!1),angle:b.ensureTypedArg(a.angle.randomise,c.BOOLEAN,!1)},this.updateFlags={},this.updateCounts={},this.updateMap={maxAge:"params",position:"position",velocity:"velocity",acceleration:"acceleration",drag:"acceleration",wiggle:"params",rotation:"rotation",size:"size",color:"color",opacity:"opacity",angle:"angle"};for(var e in this.updateMap)this.updateMap.hasOwnProperty(e)&&(this.updateCounts[this.updateMap[e]]=0,this.updateFlags[this.updateMap[e]]=!1,this._createGetterSetters(this[e],e));this.bufferUpdateRanges={},this.attributeKeys=null,this.attributeCount=0,b.ensureValueOverLifetimeCompliance(this.color,d,d),b.ensureValueOverLifetimeCompliance(this.opacity,d,d), -b.ensureValueOverLifetimeCompliance(this.size,d,d),b.ensureValueOverLifetimeCompliance(this.angle,d,d)},SPE.Emitter.constructor=SPE.Emitter,SPE.Emitter.prototype._createGetterSetters=function(a,b){"use strict";var c=this;for(var d in a)if(a.hasOwnProperty(d)){var e=d.replace("_","");Object.defineProperty(a,e,{get:function(a){return function(){return this[a]}}(d),set:function(a){return function(d){var e=c.updateMap[b],f=this[a],g=SPE.valueOverLifetimeLength;"_rotationCenter"===a?(c.updateFlags.rotationCenter=!0,c.updateCounts.rotationCenter=0):"_randomise"===a?c.resetFlags[e]=d:(c.updateFlags[e]=!0,c.updateCounts[e]=0),c.group._updateDefines(),this[a]=d,Array.isArray(f)&&SPE.utils.ensureValueOverLifetimeCompliance(c[b],g,g)}}(d)})}},SPE.Emitter.prototype._setBufferUpdateRanges=function(a){"use strict";this.attributeKeys=a,this.attributeCount=a.length;for(var b=this.attributeCount-1;b>=0;--b)this.bufferUpdateRanges[a[b]]={min:Number.POSITIVE_INFINITY,max:Number.NEGATIVE_INFINITY}}, -SPE.Emitter.prototype._calculatePPSValue=function(a){"use strict";var b=this.particleCount;this.duration?this.particlesPerSecond=b/(a=0;--h)b=g[h],c=e[b],!0!==d[b]&&!0!==c||(this._assignValue(b,a),this._updateAttributeUpdateRange(b,a),!0===c&&f[b]===this.particleCount?(e[b]=!1,f[b]=0):1==c&&++f[b])},SPE.Emitter.prototype._updateAttributeUpdateRange=function(a,b){"use strict";var c=this.bufferUpdateRanges[a];c.min=Math.min(b,c.min),c.max=Math.max(b,c.max)},SPE.Emitter.prototype._resetBufferRanges=function(){"use strict";var a,b=this.bufferUpdateRanges,c=this.bufferUpdateKeys,d=this.bufferUpdateCount-1;for(d;d>=0;--d)a=c[d],b[a].min=Number.POSITIVE_INFINITY,b[a].max=Number.NEGATIVE_INFINITY},SPE.Emitter.prototype._onRemove=function(){"use strict";this.particlesPerSecond=0,this.attributeOffset=0,this.activationIndex=0,this.activeParticleCount=0, -this.group=null,this.attributes=null,this.paramsArray=null,this.age=0},SPE.Emitter.prototype._decrementParticleCount=function(){"use strict";--this.activeParticleCount},SPE.Emitter.prototype._incrementParticleCount=function(){"use strict";++this.activeParticleCount},SPE.Emitter.prototype._checkParticleAges=function(a,b,c,d){"use strict";for(var e,f,g,h,i=b-1;i>=a;--i)e=4*i,0!==(h=c[e])&&(g=c[e+1],f=c[e+2],1===this.direction?(g+=d)>=f&&(g=0,h=0,this._decrementParticleCount()):(g-=d)<=0&&(g=f,h=0,this._decrementParticleCount()),c[e]=h,c[e+1]=g,this._updateAttributeUpdateRange("params",i))},SPE.Emitter.prototype._activateParticles=function(a,b,c,d){"use strict";for(var e,f,g=this.direction,h=a;hthis.duration)return this.alive=!1,void(this.age=0);var g=1===this.particleCount?f:0|f,h=Math.min(g+e,this.activationEnd),i=h-this.activationIndex|0,j=i>0?a/i:0;this._activateParticles(g,h,d,j),this.activationIndex+=e,this.activationIndex>c&&(this.activationIndex=b),this.age+=a}},SPE.Emitter.prototype.reset=function(a){"use strict";if(this.age=0,this.alive=!1,!0===a){for(var b,c=this.attributeOffset,d=c+this.particleCount,e=this.paramsArray,f=this.attributes.params.bufferAttribute,g=d-1;g>=c;--g)b=4*g,e[b]=0,e[b+1]=0;f.updateRange.offset=0,f.updateRange.count=-1,f.needsUpdate=!0}return this}, -SPE.Emitter.prototype.enable=function(){"use strict";return this.alive=!0,this},SPE.Emitter.prototype.disable=function(){"use strict";return this.alive=!1,this},SPE.Emitter.prototype.remove=function(){"use strict";return null!==this.group?this.group.removeEmitter(this):console.error("Emitter does not belong to a group, cannot remove."),this}; \ No newline at end of file diff --git a/dist/SPE.js b/dist/SPE.js new file mode 100644 index 0000000..a9568c6 --- /dev/null +++ b/dist/SPE.js @@ -0,0 +1,12 @@ +/*! + * /* shader-particle-engine 2.0.0 + * * + * * (c) 2015 Luke Moody (http://www.github.com/squarefeet) + * * Originally based on Lee Stemkoski's original work (https://github.com/stemkoski/stemkoski.github.com/blob/master/Three.js/js/ParticleEngine.js). + * * + * * shader-particle-engine may be freely distributed under the MIT license (See LICENSE at root of this repository.) + * * / + * + */ +var SPE=function(e){var t={};function r(i){if(t[i])return t[i].exports;var a=t[i]={i:i,l:!1,exports:{}};return e[i].call(a.exports,a,a.exports,r),a.l=!0,a.exports}return r.m=e,r.c=t,r.d=function(e,t,i){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:i})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var i=Object.create(null);if(r.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var a in e)r.d(i,a,function(t){return e[t]}.bind(null,a));return i},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=1)}([function(e,t){e.exports=THREE},function(e,t,r){"use strict";r.r(t),r.d(t,"Group",(function(){return x})),r.d(t,"Emitter",(function(){return _})),r.d(t,"distributions",(function(){return y})),r.d(t,"globals",(function(){return u}));var i,a,s=r(0),o="boolean",n="number",l="object",u={valueOverLifetimeLength:4},c={ensureTypedArg:(e,t,r)=>typeof e===t?e:r,ensureArrayTypedArg(e,t,r){if(Array.isArray(e)){for(var i=e.length-1;i>=0;--i)if(typeof e[i]!==t)return r;return e}return this.ensureTypedArg(e,t,r)},ensureInstanceOf:(e,t,r)=>void 0!==t&&e instanceof t?e:r,ensureArrayInstanceOf(e,t,r){if(Array.isArray(e)){for(var i=e.length-1;i>=0;--i)if(void 0!==t&&e[i]instanceof t==!1)return r;return e}return this.ensureInstanceOf(e,t,r)},ensureValueOverLifetimeCompliance(e,t,r){t=t||3,r=r||3,!1===Array.isArray(e._value)&&(e._value=[e._value]),!1===Array.isArray(e._spread)&&(e._spread=[e._spread]);var i=this.clamp(e._value.length,t,r),a=this.clamp(e._spread.length,t,r),s=Math.max(i,a);e._value.length!==s&&(e._value=this.interpolateArray(e._value,s)),e._spread.length!==s&&(e._spread=this.interpolateArray(e._spread,s))},interpolateArray(e,t){for(var r=e.length,i=["function"==typeof e[0].clone?e[0].clone():e[0]],a=(r-1)/(t-1),s=1;sMath.max(t,Math.min(e,r)),zeroToEpsilon(e,t){var r=e;return r=t?1e-5*Math.random()*10:1e-5,e<0&&e>-1e-5&&(r=-r),r},lerpTypeAgnostic(e,t,r){var i;return typeof e===n&&typeof t===n?e+(t-e)*r:e instanceof s.Vector2&&t instanceof s.Vector2?((i=e.clone()).x=this.lerp(e.x,t.x,r),i.y=this.lerp(e.y,t.y,r),i):e instanceof s.Vector3&&t instanceof s.Vector3?((i=e.clone()).x=this.lerp(e.x,t.x,r),i.y=this.lerp(e.y,t.y,r),i.z=this.lerp(e.z,t.z,r),i):e instanceof s.Vector4&&t instanceof s.Vector4?((i=e.clone()).x=this.lerp(e.x,t.x,r),i.y=this.lerp(e.y,t.y,r),i.z=this.lerp(e.z,t.z,r),i.w=this.lerp(e.w,t.w,r),i):e instanceof s.Color&&t instanceof s.Color?((i=e.clone()).r=this.lerp(e.r,t.r,r),i.g=this.lerp(e.g,t.g,r),i.b=this.lerp(e.b,t.b,r),i):void console.warn("Invalid argument types, or argument types do not match:",e,t)},lerp:(e,t,r)=>e+(t-e)*r,roundToNearestMultiple(e,t){var r;return 0===t||0===(r=Math.abs(e)%t)?e:e<0?-(Math.abs(e)-r):e+t-r},arrayValuesAreEqual(e){for(var t=0;te+t*(Math.random()-.5),randomVector3(e,t,r,i,a){var s=r.x+(Math.random()*i.x-.5*i.x),o=r.y+(Math.random()*i.y-.5*i.y),n=r.z+(Math.random()*i.z-.5*i.z);a&&(s=.5*-a.x+this.roundToNearestMultiple(s,a.x),o=.5*-a.y+this.roundToNearestMultiple(o,a.y),n=.5*-a.z+this.roundToNearestMultiple(n,a.z)),e.typedArray.setVec3Components(t,s,o,n)},randomColor(e,t,r,i){var a=r.r+Math.random()*i.x,s=r.g+Math.random()*i.y,o=r.b+Math.random()*i.z;a=this.clamp(a,0,1),s=this.clamp(s,0,1),o=this.clamp(o,0,1),e.typedArray.setVec3Components(t,a,s,o)},randomColorAsHex:(a=new s.Color,function(e,t,r,i){for(var s=r.length,o=[],n=0;nr?this.grow(i):void console.info("TypedArray is already of size:",i+".","Will not resize.")}shrink(e){return this.array=this.array.subarray(0,e),this.size=e,this}grow(e){const t=new this.TypedArrayConstructor(e);return t.set(this.array),this.array=t,this.size=e,this}splice(e,t){const r=e*this.componentSize,i=t*this.componentSize,a=[],s=this.array,o=s.length;for(let e=0;e=i)&&a.push(s[e]);return this.setFromArray(0,a),this}setFromArray(e,t){const r=e+t.length;return r>this.array.length?this.grow(r):r=81&&(this.bufferAttribute.count=this.bufferAttribute.array.length/this.bufferAttribute.itemSize),void(this.bufferAttribute.needsUpdate=!0);this.bufferAttribute=new s.BufferAttribute(this.typedArray.array,this.componentSize),this.bufferAttribute.usage=this.dynamicBuffer?s.DynamicDrawUsage:s.StaticDrawUsage}getLength(){return null===this.typedArray?0:this.typedArray.array.length}}var m={defines:["#define PACKED_COLOR_SIZE 256.0","#define PACKED_COLOR_DIVISOR 255.0"].join("\n"),uniforms:["uniform float deltaTime;","uniform float runTime;","uniform sampler2D tex;","uniform vec4 textureAnimation;","uniform float scale;"].join("\n"),attributes:["attribute vec4 acceleration;","attribute vec3 velocity;","attribute vec4 rotation;","attribute vec3 rotationCenter;","attribute vec4 params;","attribute vec4 size;","attribute vec4 angle;","attribute vec4 color;","attribute vec4 opacity;"].join("\n"),varyings:["varying vec4 vColor;","#ifdef SHOULD_ROTATE_TEXTURE"," varying float vAngle;","#endif","#ifdef SHOULD_CALCULATE_SPRITE"," varying vec4 vSpriteSheet;","#endif"].join("\n"),branchAvoidanceFunctions:["float when_gt(float x, float y) {"," return max(sign(x - y), 0.0);","}","float when_lt(float x, float y) {"," return min( max(1.0 - sign(x - y), 0.0), 1.0 );","}","float when_eq( float x, float y ) {"," return 1.0 - abs( sign( x - y ) );","}","float when_ge(float x, float y) {"," return 1.0 - when_lt(x, y);","}","float when_le(float x, float y) {"," return 1.0 - when_gt(x, y);","}","float and(float a, float b) {"," return a * b;","}","float or(float a, float b) {"," return min(a + b, 1.0);","}"].join("\n"),unpackColor:["vec3 unpackColor( in float hex ) {"," vec3 c = vec3( 0.0 );"," float r = mod( (hex / PACKED_COLOR_SIZE / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );"," float g = mod( (hex / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );"," float b = mod( hex, PACKED_COLOR_SIZE );"," c.r = r / PACKED_COLOR_DIVISOR;"," c.g = g / PACKED_COLOR_DIVISOR;"," c.b = b / PACKED_COLOR_DIVISOR;"," return c;","}"].join("\n"),unpackRotationAxis:["vec3 unpackRotationAxis( in float hex ) {"," vec3 c = vec3( 0.0 );"," float r = mod( (hex / PACKED_COLOR_SIZE / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );"," float g = mod( (hex / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );"," float b = mod( hex, PACKED_COLOR_SIZE );"," c.r = r / PACKED_COLOR_DIVISOR;"," c.g = g / PACKED_COLOR_DIVISOR;"," c.b = b / PACKED_COLOR_DIVISOR;"," c *= vec3( 2.0 );"," c -= vec3( 1.0 );"," return c;","}"].join("\n"),floatOverLifetime:["float getFloatOverLifetime( in float positionInTime, in vec4 attr ) {"," highp float value = 0.0;"," float deltaAge = positionInTime * float( VALUE_OVER_LIFETIME_LENGTH - 1 );"," float fIndex = 0.0;"," float shouldApplyValue = 0.0;"," value += attr[ 0 ] * when_eq( deltaAge, 0.0 );",""," for( int i = 0; i < VALUE_OVER_LIFETIME_LENGTH - 1; ++i ) {"," fIndex = float( i );"," shouldApplyValue = and( when_gt( deltaAge, fIndex ), when_le( deltaAge, fIndex + 1.0 ) );"," value += shouldApplyValue * mix( attr[ i ], attr[ i + 1 ], deltaAge - fIndex );"," }",""," return value;","}"].join("\n"),colorOverLifetime:["vec3 getColorOverLifetime( in float positionInTime, in vec3 color1, in vec3 color2, in vec3 color3, in vec3 color4 ) {"," vec3 value = vec3( 0.0 );"," value.x = getFloatOverLifetime( positionInTime, vec4( color1.x, color2.x, color3.x, color4.x ) );"," value.y = getFloatOverLifetime( positionInTime, vec4( color1.y, color2.y, color3.y, color4.y ) );"," value.z = getFloatOverLifetime( positionInTime, vec4( color1.z, color2.z, color3.z, color4.z ) );"," return value;","}"].join("\n"),paramFetchingFunctions:["float getAlive() {"," return params.x;","}","float getAge() {"," return params.y;","}","float getMaxAge() {"," return params.z;","}","float getWiggle() {"," return params.w;","}"].join("\n"),forceFetchingFunctions:["vec4 getPosition( in float age ) {"," return modelViewMatrix * vec4( position, 1.0 );","}","vec3 getVelocity( in float age ) {"," return velocity * age;","}","vec3 getAcceleration( in float age ) {"," return acceleration.xyz * age;","}"].join("\n"),rotationFunctions:["#ifdef SHOULD_ROTATE_PARTICLES"," mat4 getRotationMatrix( in vec3 axis, in float angle) {"," axis = normalize(axis);"," float s = sin(angle);"," float c = cos(angle);"," float oc = 1.0 - c;",""," return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0,"," oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0,"," oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0,"," 0.0, 0.0, 0.0, 1.0);"," }",""," vec3 getRotation( in vec3 pos, in float positionInTime ) {"," if( rotation.y == 0.0 ) {"," return pos;"," }",""," vec3 axis = unpackRotationAxis( rotation.x );"," vec3 center = rotationCenter;"," vec3 translated;"," mat4 rotationMatrix;"," float angle = 0.0;"," angle += when_eq( rotation.z, 0.0 ) * rotation.y;"," angle += when_gt( rotation.z, 0.0 ) * mix( 0.0, rotation.y, positionInTime );"," translated = rotationCenter - pos;"," rotationMatrix = getRotationMatrix( axis, angle );"," return center - vec3( rotationMatrix * vec4( translated, 0.0 ) );"," }","#endif"].join("\n"),rotateTexture:[" vec2 vUv = vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y );",""," #ifdef SHOULD_ROTATE_TEXTURE"," float x = gl_PointCoord.x - 0.5;"," float y = 1.0 - gl_PointCoord.y - 0.5;"," float c = cos( -vAngle );"," float s = sin( -vAngle );"," vUv = vec2( c * x + s * y + 0.5, c * y - s * x + 0.5 );"," #endif",""," #ifdef SHOULD_CALCULATE_SPRITE"," float framesX = vSpriteSheet.x;"," float framesY = vSpriteSheet.y;"," float columnNorm = vSpriteSheet.z;"," float rowNorm = vSpriteSheet.w;"," vUv.x = gl_PointCoord.x * framesX + columnNorm;"," vUv.y = 1.0 - (gl_PointCoord.y * framesY + rowNorm);"," #endif",""," vec4 rotatedTexture = texture2D( tex, vUv );"].join("\n")},g={vertex:[m.defines,m.uniforms,m.attributes,m.varyings,s.ShaderChunk.common,s.ShaderChunk.logdepthbuf_pars_vertex,s.ShaderChunk.fog_pars_vertex,m.branchAvoidanceFunctions,m.unpackColor,m.unpackRotationAxis,m.floatOverLifetime,m.colorOverLifetime,m.paramFetchingFunctions,m.forceFetchingFunctions,m.rotationFunctions,"void main() {"," highp float age = getAge();"," highp float alive = getAlive();"," highp float maxAge = getMaxAge();"," highp float positionInTime = (age / maxAge);"," highp float isAlive = when_gt( alive, 0.0 );"," #ifdef SHOULD_WIGGLE_PARTICLES"," float wiggleAmount = positionInTime * getWiggle();"," float wiggleSin = isAlive * sin( wiggleAmount );"," float wiggleCos = isAlive * cos( wiggleAmount );"," #endif"," vec3 vel = getVelocity( age );"," vec3 accel = getAcceleration( age );"," vec3 force = vec3( 0.0 );"," vec3 pos = vec3( position );"," float drag = 1.0 - (positionInTime * 0.5) * acceleration.w;"," force += vel;"," force *= drag;"," force += accel * age;"," pos += force;"," #ifdef SHOULD_WIGGLE_PARTICLES"," pos.x += wiggleSin;"," pos.y += wiggleCos;"," pos.z += wiggleSin;"," #endif"," #ifdef SHOULD_ROTATE_PARTICLES"," pos = getRotation( pos, positionInTime );"," #endif"," vec4 mvPosition = modelViewMatrix * vec4( pos, 1.0 );"," highp float pointSize = getFloatOverLifetime( positionInTime, size ) * isAlive;"," #ifdef HAS_PERSPECTIVE"," float perspective = scale / length( mvPosition.xyz );"," #else"," float perspective = 1.0;"," #endif"," float pointSizePerspective = pointSize * perspective;"," #ifdef COLORIZE"," vec3 c = isAlive * getColorOverLifetime("," positionInTime,"," unpackColor( color.x ),"," unpackColor( color.y ),"," unpackColor( color.z ),"," unpackColor( color.w )"," );"," #else"," vec3 c = vec3(1.0);"," #endif"," float o = isAlive * getFloatOverLifetime( positionInTime, opacity );"," vColor = vec4( c, o );"," #ifdef SHOULD_ROTATE_TEXTURE"," vAngle = isAlive * getFloatOverLifetime( positionInTime, angle );"," #endif"," #ifdef SHOULD_CALCULATE_SPRITE"," float framesX = textureAnimation.x;"," float framesY = textureAnimation.y;"," float loopCount = textureAnimation.w;"," float totalFrames = textureAnimation.z;"," float frameNumber = mod( (positionInTime * loopCount) * totalFrames, totalFrames );"," float column = floor(mod( frameNumber, framesX ));"," float row = floor( (frameNumber - column) / framesX );"," float columnNorm = column / framesX;"," float rowNorm = row / framesY;"," vSpriteSheet.x = 1.0 / framesX;"," vSpriteSheet.y = 1.0 / framesY;"," vSpriteSheet.z = columnNorm;"," vSpriteSheet.w = rowNorm;"," #endif"," gl_PointSize = pointSizePerspective;"," gl_Position = projectionMatrix * mvPosition;",s.ShaderChunk.logdepthbuf_vertex,s.ShaderChunk.fog_vertex,"}"].join("\n"),fragment:[m.uniforms,s.ShaderChunk.common,s.ShaderChunk.fog_pars_fragment,s.ShaderChunk.logdepthbuf_pars_fragment,m.varyings,m.branchAvoidanceFunctions,"void main() {"," vec3 outgoingLight = vColor.xyz;"," "," #ifdef ALPHATEST"," if ( vColor.w < float(ALPHATEST) ) discard;"," #endif",m.rotateTexture,s.ShaderChunk.logdepthbuf_fragment," outgoingLight = vColor.xyz * rotatedTexture.xyz;"," gl_FragColor = vec4( outgoingLight.xyz, rotatedTexture.w * vColor.w );",s.ShaderChunk.fog_fragment,"}"].join("\n")},y={BOX:1,SPHERE:2,DISC:3,LINE:4};const v=Object.prototype.hasOwnProperty;class _{constructor(e){const t=c.ensureTypedArg(e,l,{});t.position=c.ensureTypedArg(t.position,l,{}),t.velocity=c.ensureTypedArg(t.velocity,l,{}),t.acceleration=c.ensureTypedArg(t.acceleration,l,{}),t.radius=c.ensureTypedArg(t.radius,l,{}),t.drag=c.ensureTypedArg(t.drag,l,{}),t.rotation=c.ensureTypedArg(t.rotation,l,{}),t.color=c.ensureTypedArg(t.color,l,{}),t.opacity=c.ensureTypedArg(t.opacity,l,{}),t.size=c.ensureTypedArg(t.size,l,{}),t.angle=c.ensureTypedArg(t.angle,l,{}),t.wiggle=c.ensureTypedArg(t.wiggle,l,{}),t.maxAge=c.ensureTypedArg(t.maxAge,l,{}),t.onParticleSpawn&&console.warn("onParticleSpawn has been removed. Please set properties directly to alter values at runtime."),this.uuid=s.Math.generateUUID(),this.type=c.ensureTypedArg(t.type,n,y.BOX),this.position={_value:c.ensureInstanceOf(t.position.value,s.Vector3,new s.Vector3),_spread:c.ensureInstanceOf(t.position.spread,s.Vector3,new s.Vector3),_spreadClamp:c.ensureInstanceOf(t.position.spreadClamp,s.Vector3,new s.Vector3),_distribution:c.ensureTypedArg(t.position.distribution,n,this.type),_randomise:c.ensureTypedArg(t.position.randomise,o,!1),_radius:c.ensureTypedArg(t.position.radius,n,10),_radiusScale:c.ensureInstanceOf(t.position.radiusScale,s.Vector3,new s.Vector3(1,1,1)),_distributionClamp:c.ensureTypedArg(t.position.distributionClamp,n,0)},this.velocity={_value:c.ensureInstanceOf(t.velocity.value,s.Vector3,new s.Vector3),_spread:c.ensureInstanceOf(t.velocity.spread,s.Vector3,new s.Vector3),_distribution:c.ensureTypedArg(t.velocity.distribution,n,this.type),_randomise:c.ensureTypedArg(t.position.randomise,o,!1)},this.acceleration={_value:c.ensureInstanceOf(t.acceleration.value,s.Vector3,new s.Vector3),_spread:c.ensureInstanceOf(t.acceleration.spread,s.Vector3,new s.Vector3),_distribution:c.ensureTypedArg(t.acceleration.distribution,n,this.type),_randomise:c.ensureTypedArg(t.position.randomise,o,!1)},this.drag={_value:c.ensureTypedArg(t.drag.value,n,0),_spread:c.ensureTypedArg(t.drag.spread,n,0),_randomise:c.ensureTypedArg(t.position.randomise,o,!1)},this.wiggle={_value:c.ensureTypedArg(t.wiggle.value,n,0),_spread:c.ensureTypedArg(t.wiggle.spread,n,0)},this.rotation={_axis:c.ensureInstanceOf(t.rotation.axis,s.Vector3,new s.Vector3(0,1,0)),_axisSpread:c.ensureInstanceOf(t.rotation.axisSpread,s.Vector3,new s.Vector3),_angle:c.ensureTypedArg(t.rotation.angle,n,0),_angleSpread:c.ensureTypedArg(t.rotation.angleSpread,n,0),_static:c.ensureTypedArg(t.rotation.static,o,!1),_center:c.ensureInstanceOf(t.rotation.center,s.Vector3,this.position._value.clone()),_randomise:c.ensureTypedArg(t.position.randomise,o,!1)},this.maxAge={_value:c.ensureTypedArg(t.maxAge.value,n,2),_spread:c.ensureTypedArg(t.maxAge.spread,n,0)},this.color={_value:c.ensureArrayInstanceOf(t.color.value,s.Color,new s.Color),_spread:c.ensureArrayInstanceOf(t.color.spread,s.Vector3,new s.Vector3),_randomise:c.ensureTypedArg(t.position.randomise,o,!1)},this.opacity={_value:c.ensureArrayTypedArg(t.opacity.value,n,1),_spread:c.ensureArrayTypedArg(t.opacity.spread,n,0),_randomise:c.ensureTypedArg(t.position.randomise,o,!1)},this.size={_value:c.ensureArrayTypedArg(t.size.value,n,1),_spread:c.ensureArrayTypedArg(t.size.spread,n,0),_randomise:c.ensureTypedArg(t.position.randomise,o,!1)},this.angle={_value:c.ensureArrayTypedArg(t.angle.value,n,0),_spread:c.ensureArrayTypedArg(t.angle.spread,n,0),_randomise:c.ensureTypedArg(t.position.randomise,o,!1)},this.particleCount=c.ensureTypedArg(t.particleCount,n,100),this.duration=c.ensureTypedArg(t.duration,n,null),this.isStatic=c.ensureTypedArg(t.isStatic,o,!1),this.activeMultiplier=c.ensureTypedArg(t.activeMultiplier,n,1),this.direction=c.ensureTypedArg(t.direction,n,1),this.alive=c.ensureTypedArg(t.alive,o,!0),this.particlesPerSecond=0,this.activationIndex=0,this.attributeOffset=0,this.attributeEnd=0,this.age=0,this.activeParticleCount=0,this.group=null,this.attributes=null,this.paramsArray=null,this.resetFlags={position:c.ensureTypedArg(t.position.randomise,o,!1)||c.ensureTypedArg(t.radius.randomise,o,!1),velocity:c.ensureTypedArg(t.velocity.randomise,o,!1),acceleration:c.ensureTypedArg(t.acceleration.randomise,o,!1)||c.ensureTypedArg(t.drag.randomise,o,!1),rotation:c.ensureTypedArg(t.rotation.randomise,o,!1),rotationCenter:c.ensureTypedArg(t.rotation.randomise,o,!1),size:c.ensureTypedArg(t.size.randomise,o,!1),color:c.ensureTypedArg(t.color.randomise,o,!1),opacity:c.ensureTypedArg(t.opacity.randomise,o,!1),angle:c.ensureTypedArg(t.angle.randomise,o,!1)},this.updateFlags={},this.updateCounts={},this.updateMap={maxAge:"params",position:"position",velocity:"velocity",acceleration:"acceleration",drag:"acceleration",wiggle:"params",rotation:"rotation",size:"size",color:"color",opacity:"opacity",angle:"angle"};for(const e in this.updateMap)v.call(this.updateMap,e)&&(this.updateCounts[this.updateMap[e]]=0,this.updateFlags[this.updateMap[e]]=!1,this._createGetterSetters(this[e],e));this.bufferUpdateRanges={},this.attributeKeys=null,this.attributeCount=0,c.ensureValueOverLifetimeCompliance(this.color,u.valueOverLifetimeLength,u.valueOverLifetimeLength),c.ensureValueOverLifetimeCompliance(this.opacity,u.valueOverLifetimeLength,u.valueOverLifetimeLength),c.ensureValueOverLifetimeCompliance(this.size,u.valueOverLifetimeLength,u.valueOverLifetimeLength),c.ensureValueOverLifetimeCompliance(this.angle,u.valueOverLifetimeLength,u.valueOverLifetimeLength)}_createGetterSetters(e,t){const r=this;for(const i in e)if(v.call(e,i)){const a=i.replace("_","");Object.defineProperty(e,a,{get:function(e){return function(){return this[e]}}(i),set:function(e){return function(i){const a=r.updateMap[t],s=this[e],o=u.valueOverLifetimeLength;"_rotationCenter"===e?(r.updateFlags.rotationCenter=!0,r.updateCounts.rotationCenter=0):"_randomise"===e?r.resetFlags[a]=i:(r.updateFlags[a]=!0,r.updateCounts[a]=0),r.group._updateDefines(),this[e]=i,Array.isArray(s)&&c.ensureValueOverLifetimeCompliance(r[t],o,o)}}(i)})}}_setBufferUpdateRanges(e){this.attributeKeys=e,this.attributeCount=e.length;for(let t=this.attributeCount-1;t>=0;--t)this.bufferUpdateRanges[e[t]]={min:Number.POSITIVE_INFINITY,max:Number.NEGATIVE_INFINITY}}_calculatePPSValue(e){const t=this.particleCount;this.duration?this.particlesPerSecond=t/(e=0;--n)s=a[n],o=r[s],!0!==t[s]&&!0!==o||(this._assignValue(s,e),this._updateAttributeUpdateRange(s,e),!0===o&&i[s]===this.particleCount?(r[s]=!1,i[s]=0):1==o&&++i[s])}_updateAttributeUpdateRange(e,t){var r=this.bufferUpdateRanges[e];r.min=Math.min(t,r.min),r.max=Math.max(t,r.max)}_resetBufferRanges(){const e=this.bufferUpdateRanges,t=this.bufferUpdateKeys;let r,i=this.bufferUpdateCount-1;for(;i>=0;--i)r=t[i],e[r].min=Number.POSITIVE_INFINITY,e[r].max=Number.NEGATIVE_INFINITY}_onRemove(){this.particlesPerSecond=0,this.attributeOffset=0,this.activationIndex=0,this.activeParticleCount=0,this.group=null,this.attributes=null,this.paramsArray=null,this.age=0}_decrementParticleCount(){--this.activeParticleCount}_incrementParticleCount(){++this.activeParticleCount}_checkParticleAges(e,t,r,i){for(let a,s,o,n,l=t-1;l>=e;--l)a=4*l,n=r[a],0!==n&&(o=r[a+1],s=r[a+2],1===this.direction?(o+=i,o>=s&&(o=0,n=0,this._decrementParticleCount())):(o-=i,o<=0&&(o=s,n=0,this._decrementParticleCount())),r[a]=n,r[a+1]=o,this._updateAttributeUpdateRange("params",l))}_activateParticles(e,t,r,i){const a=this.direction;for(let s,o,n=e;nthis.duration)return this.alive=!1,void(this.age=0);const o=1===this.particleCount?s:0|s,n=Math.min(o+a,this.activationEnd),l=n-this.activationIndex|0,u=l>0?e/l:0;this._activateParticles(o,n,i,u),this.activationIndex+=a,this.activationIndex>r&&(this.activationIndex=t),this.age+=e}reset(e){if(this.age=0,this.alive=!1,!0===e){const e=this.attributeOffset,t=e+this.particleCount,r=this.paramsArray,i=this.attributes.params.bufferAttribute;for(let i,a=t-1;a>=e;--a)i=4*a,r[i]=0,r[i+1]=0;i.updateRange.offset=0,i.updateRange.count=-1,i.needsUpdate=!0}return this}enable(){return this.alive=!0,this}disable(){return this.alive=!1,this}remove(){return null!==this.group?this.group.removeEmitter(this):console.error("Emitter does not belong to a group, cannot remove."),this}}const A=Object.prototype.hasOwnProperty;class x{constructor(e){const t=c.ensureTypedArg(e,l,{});t.texture=c.ensureTypedArg(t.texture,l,{}),this.uuid=s.Math.generateUUID(),this.fixedTimeStep=c.ensureTypedArg(t.fixedTimeStep,n,.016),this.texture=c.ensureInstanceOf(t.texture.value,s.Texture,null),this.textureFrames=c.ensureInstanceOf(t.texture.frames,s.Vector2,new s.Vector2(1,1)),this.textureFrameCount=c.ensureTypedArg(t.texture.frameCount,n,this.textureFrames.x*this.textureFrames.y),this.textureLoop=c.ensureTypedArg(t.texture.loop,n,1),this.textureFrames.max(new s.Vector2(1,1)),this.hasPerspective=c.ensureTypedArg(t.hasPerspective,o,!0),this.colorize=c.ensureTypedArg(t.colorize,o,!0),this.maxParticleCount=c.ensureTypedArg(t.maxParticleCount,n,null),this.blending=c.ensureTypedArg(t.blending,n,s.AdditiveBlending),this.transparent=c.ensureTypedArg(t.transparent,o,!0),this.alphaTest=parseFloat(c.ensureTypedArg(t.alphaTest,n,0)),this.depthWrite=c.ensureTypedArg(t.depthWrite,o,!1),this.depthTest=c.ensureTypedArg(t.depthTest,o,!0),this.fog=c.ensureTypedArg(t.fog,o,!0),this.scale=c.ensureTypedArg(t.scale,n,300),this.emitters=[],this.emitterIDs=[],this._pool=[],this._poolCreationSettings=null,this._createNewWhenPoolEmpty=0,this._attributesNeedRefresh=!1,this._attributesNeedDynamicReset=!1,this.particleCount=0,this.uniforms={tex:{type:"t",value:this.texture},textureAnimation:{type:"v4",value:new s.Vector4(this.textureFrames.x,this.textureFrames.y,this.textureFrameCount,Math.max(Math.abs(this.textureLoop),1))},fogColor:{type:"c",value:this.fog?new s.Color:null},fogNear:{type:"f",value:10},fogFar:{type:"f",value:200},fogDensity:{type:"f",value:.5},deltaTime:{type:"f",value:0},runTime:{type:"f",value:0},scale:{type:"f",value:this.scale}},this.defines={HAS_PERSPECTIVE:this.hasPerspective,COLORIZE:this.colorize,VALUE_OVER_LIFETIME_LENGTH:u.valueOverLifetimeLength,SHOULD_ROTATE_TEXTURE:!1,SHOULD_ROTATE_PARTICLES:!1,SHOULD_WIGGLE_PARTICLES:!1,SHOULD_CALCULATE_SPRITE:this.textureFrames.x>1||this.textureFrames.y>1},this.attributes={position:new f("v3",!0),acceleration:new f("v4",!0),velocity:new f("v3",!0),rotation:new f("v4",!0),rotationCenter:new f("v3",!0),params:new f("v4",!0),size:new f("v4",!0),angle:new f("v4",!0),color:new f("v4",!0),opacity:new f("v4",!0)},this.attributeKeys=Object.keys(this.attributes),this.attributeCount=this.attributeKeys.length,this.material=new s.ShaderMaterial({uniforms:this.uniforms,vertexShader:g.vertex,fragmentShader:g.fragment,blending:this.blending,transparent:this.transparent,alphaTest:this.alphaTest,depthWrite:this.depthWrite,depthTest:this.depthTest,defines:this.defines,fog:this.fog}),this.geometry=new s.BufferGeometry,this.mesh=new s.Points(this.geometry,this.material),null===this.maxParticleCount&&console.warn("SPE.Group: No maxParticleCount specified. Adding emitters after rendering will probably cause errors.")}_updateDefines(){const e=this.emitters,t=this.defines;let r,i=e.length-1;for(;i>=0;--i)r=e[i],t.SHOULD_CALCULATE_SPRITE||(t.SHOULD_ROTATE_TEXTURE=t.SHOULD_ROTATE_TEXTURE||!!Math.max(Math.max.apply(null,r.angle.value),Math.max.apply(null,r.angle.spread))),t.SHOULD_ROTATE_PARTICLES=t.SHOULD_ROTATE_PARTICLES||!!Math.max(r.rotation.angle,r.rotation.angleSpread),t.SHOULD_WIGGLE_PARTICLES=t.SHOULD_WIGGLE_PARTICLES||!!Math.max(r.wiggle.value,r.wiggle.spread);this.material.needsUpdate=!0}_applyAttributesToGeometry(){const e=this.attributes,t=this.geometry,r=t.attributes;let i,a;for(const s in e)A.call(e,s)&&(i=e[s],a=r[s],a?a.array=i.typedArray.array:t.addAttribute(s,i.bufferAttribute),i.bufferAttribute.needsUpdate=!0);this.geometry.setDrawRange(0,this.particleCount)}addEmitter(e){if(e instanceof _==!1)return void console.error("`emitter` argument must be instance of Emitter. Was provided with:",e);if(this.emitterIDs.indexOf(e.uuid)>-1)return void console.error("Emitter already exists in this group. Will not add again.");if(null!==e.group)return void console.error("Emitter already belongs to another group. Will not add to requested group.");const t=this.attributes,r=this.particleCount,i=r+e.particleCount;this.particleCount=i,null!==this.maxParticleCount&&this.particleCount>this.maxParticleCount&&console.warn("SPE.Group: maxParticleCount exceeded. Requesting",this.particleCount,"particles, can support only",this.maxParticleCount),e._calculatePPSValue(e.maxAge._value+e.maxAge._spread),e._setBufferUpdateRanges(this.attributeKeys),e._setAttributeOffset(r),e.group=this,e.attributes=this.attributes;for(const e in t)A.call(t,e)&&t[e]._createBufferAttribute(null!==this.maxParticleCount?this.maxParticleCount:this.particleCount);for(let t=r;t{t.disable(),this.releaseIntoPool(t)},1e3*Math.max(t.duration,t.maxAge.value+t.maxAge.spread)),this;console.log("Group pool ran out.")}triggerPoolEmitter(e,t){if("number"==typeof e&&e>1)for(let r=0;r=0;--r)t[e[r]].resetUpdateRange()}_updateBuffers(e){const t=this.attributeKeys,r=this.attributes,i=e.bufferUpdateRanges;let a,s,o;for(let e=this.attributeCount-1;e>=0;--e)a=t[e],s=i[a],o=r[a],o.setUpdateRange(s.min,s.max),o.flagUpdate()}tick(e){const t=this.emitters,r=t.length,i=e||this.fixedTimeStep,a=this.attributeKeys,s=this.attributes;if(this._updateUniforms(i),this._resetBufferRanges(),0!==r||!1!==this._attributesNeedRefresh||!1!==this._attributesNeedDynamicReset){for(let e,a=0;a=0;--e)s[a[e]].resetDynamic();this._attributesNeedDynamicReset=!1}if(!0===this._attributesNeedRefresh){for(let e=this.attributeCount-1;e>=0;--e)s[a[e]].forceUpdateAll();this._attributesNeedRefresh=!1,this._attributesNeedDynamicReset=!0}}}dispose(){return this.geometry.dispose(),this.material.dispose(),this}}}]); +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9TUEUvd2VicGFjay9ib290c3RyYXAiLCJ3ZWJwYWNrOi8vU1BFL2V4dGVybmFsIFwiVEhSRUVcIiIsIndlYnBhY2s6Ly9TUEUvLi9zcmMvY29yZS91dGlscy5qcyIsIndlYnBhY2s6Ly9TUEUvLi9zcmMvY29uc3RhbnRzL3ZhbHVlVHlwZXMuanMiLCJ3ZWJwYWNrOi8vU1BFLy4vc3JjL2NvbnN0YW50cy9nbG9iYWxzLmpzIiwid2VicGFjazovL1NQRS8uL3NyYy9oZWxwZXJzL1R5cGVkQXJyYXlIZWxwZXIuanMiLCJ3ZWJwYWNrOi8vU1BFLy4vc3JjL2NvbnN0YW50cy90eXBlU2l6ZU1hcC5qcyIsIndlYnBhY2s6Ly9TUEUvLi9zcmMvaGVscGVycy9TaGFkZXJBdHRyaWJ1dGUuanMiLCJ3ZWJwYWNrOi8vU1BFLy4vc3JjL3NoYWRlcnMvc2hhZGVyQ2h1bmtzLmpzIiwid2VicGFjazovL1NQRS8uL3NyYy9zaGFkZXJzL3NoYWRlcnMuanMiLCJ3ZWJwYWNrOi8vU1BFLy4vc3JjL2NvbnN0YW50cy9kaXN0cmlidXRpb25zLmpzIiwid2VicGFjazovL1NQRS8uL3NyYy9jb3JlL0VtaXR0ZXIuanMiLCJ3ZWJwYWNrOi8vU1BFLy4vc3JjL2NvcmUvR3JvdXAuanMiXSwibmFtZXMiOlsiaW5zdGFsbGVkTW9kdWxlcyIsIl9fd2VicGFja19yZXF1aXJlX18iLCJtb2R1bGVJZCIsImV4cG9ydHMiLCJtb2R1bGUiLCJpIiwibCIsIm1vZHVsZXMiLCJjYWxsIiwibSIsImMiLCJkIiwibmFtZSIsImdldHRlciIsIm8iLCJPYmplY3QiLCJkZWZpbmVQcm9wZXJ0eSIsImVudW1lcmFibGUiLCJnZXQiLCJyIiwiU3ltYm9sIiwidG9TdHJpbmdUYWciLCJ2YWx1ZSIsInQiLCJtb2RlIiwiX19lc01vZHVsZSIsIm5zIiwiY3JlYXRlIiwia2V5IiwiYmluZCIsIm4iLCJvYmplY3QiLCJwcm9wZXJ0eSIsInByb3RvdHlwZSIsImhhc093blByb3BlcnR5IiwicCIsInMiLCJUSFJFRSIsInYiLCJ3b3JraW5nQ29sb3IiLCJ2YWx1ZU92ZXJMaWZldGltZUxlbmd0aCIsImVuc3VyZVR5cGVkQXJnIiwiYXJnIiwidHlwZSIsImRlZmF1bHRWYWx1ZSIsIkFycmF5IiwiaXNBcnJheSIsImxlbmd0aCIsInRoaXMiLCJlbnN1cmVJbnN0YW5jZU9mIiwiaW5zdGFuY2UiLCJ1bmRlZmluZWQiLCJtaW5MZW5ndGgiLCJtYXhMZW5ndGgiLCJfdmFsdWUiLCJfc3ByZWFkIiwidmFsdWVMZW5ndGgiLCJjbGFtcCIsInNwcmVhZExlbmd0aCIsImRlc2lyZWRMZW5ndGgiLCJNYXRoIiwibWF4IiwiaW50ZXJwb2xhdGVBcnJheSIsInNyY0FycmF5IiwibmV3TGVuZ3RoIiwic291cmNlTGVuZ3RoIiwibmV3QXJyYXkiLCJjbG9uZSIsImZhY3RvciIsImYiLCJiZWZvcmUiLCJmbG9vciIsImFmdGVyIiwiY2VpbCIsImRlbHRhIiwibGVycFR5cGVBZ25vc3RpYyIsInB1c2giLCJtaW4iLCJyYW5kb21pc2UiLCJyZXN1bHQiLCJyYW5kb20iLCJzdGFydCIsImVuZCIsIm91dCIsInZhbHVlVHlwZXMiLCJ4IiwibGVycCIsInkiLCJ6IiwidyIsImciLCJiIiwiY29uc29sZSIsIndhcm4iLCJtdWx0aXBsZSIsInJlbWFpbmRlciIsImFicyIsImFycmF5IiwicmFuZG9tRmxvYXQiLCJiYXNlIiwic3ByZWFkIiwiYXR0cmlidXRlIiwiaW5kZXgiLCJzcHJlYWRDbGFtcCIsInJvdW5kVG9OZWFyZXN0TXVsdGlwbGUiLCJ0eXBlZEFycmF5Iiwic2V0VmVjM0NvbXBvbmVudHMiLCJyYW5kb21Db2xvckFzSGV4IiwibnVtSXRlbXMiLCJjb2xvcnMiLCJzcHJlYWRWZWN0b3IiLCJjb3B5IiwiZ2V0SGV4Iiwic2V0VmVjNENvbXBvbmVudHMiLCJwb3MiLCJyYWRpdXMiLCJyYWRpdXNTcHJlYWQiLCJyYWRpdXNTY2FsZSIsInJhZGl1c1NwcmVhZENsYW1wIiwiZGlzdHJpYnV0aW9uQ2xhbXAiLCJkZXB0aCIsInNxcnQiLCJyYW5kIiwicm91bmQiLCJjb3MiLCJzaW4iLCJzZWVkIiwicmFuZG9tRGlyZWN0aW9uVmVjdG9yM09uU3BoZXJlIiwicG9zWCIsInBvc1kiLCJwb3NaIiwiZW1pdHRlclBvc2l0aW9uIiwic3BlZWQiLCJzcGVlZFNwcmVhZCIsIm5vcm1hbGl6ZSIsIm11bHRpcGx5U2NhbGFyIiwicmFuZG9tRGlyZWN0aW9uVmVjdG9yM09uRGlzYyIsImdldFBhY2tlZFJvdGF0aW9uQXhpcyIsInZTcHJlYWQiLCJhZGRPbmUiLCJheGlzIiwiYXhpc1NwcmVhZCIsImFkZCIsInNldFJHQiIsIlR5cGVkQXJyYXlIZWxwZXIiLCJUeXBlZEFycmF5Q29uc3RydWN0b3IiLCJzaXplIiwiY29tcG9uZW50U2l6ZSIsImluZGV4T2Zmc2V0IiwiRmxvYXQzMkFycmF5Iiwibm9Db21wb25lbnRNdWx0aXBseSIsImN1cnJlbnRBcnJheVNpemUiLCJzaHJpbmsiLCJncm93IiwiaW5mbyIsInN1YmFycmF5Iiwic2V0IiwiX3N0YXJ0IiwiX2VuZCIsImRhdGEiLCJzZXRGcm9tQXJyYXkiLCJuZXdTaXplIiwidmVjMiIsInNldFZlYzJDb21wb25lbnRzIiwidmVjMyIsInZlYzQiLCJtYXQzIiwiZWxlbWVudHMiLCJtYXQ0IiwiY29sb3IiLCJudW1lcmljVmFsdWUiLCJ2MiIsInYzIiwidjQiLCJtMyIsIm00IiwiSEFTX09XTiIsImR5bmFtaWNCdWZmZXIiLCJhcnJheVR5cGUiLCJ0eXBlU2l6ZU1hcCIsImJ1ZmZlckF0dHJpYnV0ZSIsInVwZGF0ZU1pbiIsInVwZGF0ZU1heCIsImF0dHIiLCJyYW5nZSIsInVwZGF0ZVJhbmdlIiwib2Zmc2V0IiwiY291bnQiLCJuZWVkc1VwZGF0ZSIsInVzYWdlIiwic3BsaWNlIiwiZm9yY2VVcGRhdGVBbGwiLCJzZXRTaXplIiwiX2Vuc3VyZVR5cGVkQXJyYXkiLCJwYXJzZUZsb2F0IiwiaXRlbVNpemUiLCJkZWZpbmVzIiwiam9pbiIsInVuaWZvcm1zIiwiYXR0cmlidXRlcyIsInZhcnlpbmdzIiwiYnJhbmNoQXZvaWRhbmNlRnVuY3Rpb25zIiwidW5wYWNrQ29sb3IiLCJ1bnBhY2tSb3RhdGlvbkF4aXMiLCJmbG9hdE92ZXJMaWZldGltZSIsImNvbG9yT3ZlckxpZmV0aW1lIiwicGFyYW1GZXRjaGluZ0Z1bmN0aW9ucyIsImZvcmNlRmV0Y2hpbmdGdW5jdGlvbnMiLCJyb3RhdGlvbkZ1bmN0aW9ucyIsInJvdGF0ZVRleHR1cmUiLCJ2ZXJ0ZXgiLCJzaGFkZXJDaHVua3MiLCJjb21tb24iLCJsb2dkZXB0aGJ1Zl9wYXJzX3ZlcnRleCIsImZvZ19wYXJzX3ZlcnRleCIsImxvZ2RlcHRoYnVmX3ZlcnRleCIsImZvZ192ZXJ0ZXgiLCJmcmFnbWVudCIsImZvZ19wYXJzX2ZyYWdtZW50IiwibG9nZGVwdGhidWZfcGFyc19mcmFnbWVudCIsImxvZ2RlcHRoYnVmX2ZyYWdtZW50IiwiZm9nX2ZyYWdtZW50IiwiQk9YIiwiU1BIRVJFIiwiRElTQyIsIkxJTkUiLCJvcHRzIiwib3B0aW9ucyIsInV0aWxzIiwicG9zaXRpb24iLCJ2ZWxvY2l0eSIsImFjY2VsZXJhdGlvbiIsImRyYWciLCJyb3RhdGlvbiIsIm9wYWNpdHkiLCJhbmdsZSIsIndpZ2dsZSIsIm1heEFnZSIsIm9uUGFydGljbGVTcGF3biIsInV1aWQiLCJnZW5lcmF0ZVVVSUQiLCJkaXN0cmlidXRpb25zIiwiX3NwcmVhZENsYW1wIiwiX2Rpc3RyaWJ1dGlvbiIsImRpc3RyaWJ1dGlvbiIsIl9yYW5kb21pc2UiLCJfcmFkaXVzIiwiX3JhZGl1c1NjYWxlIiwiX2Rpc3RyaWJ1dGlvbkNsYW1wIiwiX2F4aXMiLCJfYXhpc1NwcmVhZCIsIl9hbmdsZSIsIl9hbmdsZVNwcmVhZCIsImFuZ2xlU3ByZWFkIiwiX3N0YXRpYyIsInN0YXRpYyIsIl9jZW50ZXIiLCJjZW50ZXIiLCJlbnN1cmVBcnJheUluc3RhbmNlT2YiLCJlbnN1cmVBcnJheVR5cGVkQXJnIiwicGFydGljbGVDb3VudCIsImR1cmF0aW9uIiwiaXNTdGF0aWMiLCJhY3RpdmVNdWx0aXBsaWVyIiwiZGlyZWN0aW9uIiwiYWxpdmUiLCJwYXJ0aWNsZXNQZXJTZWNvbmQiLCJhY3RpdmF0aW9uSW5kZXgiLCJhdHRyaWJ1dGVPZmZzZXQiLCJhdHRyaWJ1dGVFbmQiLCJhZ2UiLCJhY3RpdmVQYXJ0aWNsZUNvdW50IiwiZ3JvdXAiLCJwYXJhbXNBcnJheSIsInJlc2V0RmxhZ3MiLCJyb3RhdGlvbkNlbnRlciIsInVwZGF0ZUZsYWdzIiwidXBkYXRlQ291bnRzIiwidXBkYXRlTWFwIiwiX2NyZWF0ZUdldHRlclNldHRlcnMiLCJidWZmZXJVcGRhdGVSYW5nZXMiLCJhdHRyaWJ1dGVLZXlzIiwiYXR0cmlidXRlQ291bnQiLCJlbnN1cmVWYWx1ZU92ZXJMaWZldGltZUNvbXBsaWFuY2UiLCJnbG9iYWxzIiwicHJvcE9iaiIsInByb3BOYW1lIiwic2VsZiIsInJlcGxhY2UiLCJwcm9wIiwibWFwTmFtZSIsInByZXZWYWx1ZSIsIl91cGRhdGVEZWZpbmVzIiwia2V5cyIsIk51bWJlciIsIlBPU0lUSVZFX0lORklOSVRZIiwiTkVHQVRJVkVfSU5GSU5JVFkiLCJncm91cE1heEFnZSIsInN0YXJ0SW5kZXgiLCJhY3RpdmF0aW9uRW5kIiwiX2Fzc2lnblBvc2l0aW9uVmFsdWUiLCJfYXNzaWduRm9yY2VWYWx1ZSIsIl9hc3NpZ25BYnNMaWZldGltZVZhbHVlIiwiX2Fzc2lnbkFuZ2xlVmFsdWUiLCJfYXNzaWduUGFyYW1zVmFsdWUiLCJfYXNzaWduUm90YXRpb25WYWx1ZSIsIl9hc3NpZ25Db2xvclZhbHVlIiwicmFuZG9tVmVjdG9yMyIsInJhbmRvbVZlY3RvcjNPblNwaGVyZSIsInJhbmRvbVZlY3RvcjNPbkRpc2MiLCJyYW5kb21WZWN0b3IzT25MaW5lIiwiYXR0ck5hbWUiLCJwb3NpdGlvblgiLCJwb3NpdGlvblkiLCJwb3NpdGlvbloiLCJhcnJheVZhbHVlc0FyZUVxdWFsIiwicGFyYW1zIiwic2V0VmVjMyIsInVwZGF0ZUZsYWciLCJfYXNzaWduVmFsdWUiLCJfdXBkYXRlQXR0cmlidXRlVXBkYXRlUmFuZ2UiLCJyYW5nZXMiLCJidWZmZXJVcGRhdGVLZXlzIiwiYnVmZmVyVXBkYXRlQ291bnQiLCJkdCIsIl9kZWNyZW1lbnRQYXJ0aWNsZUNvdW50IiwiYWN0aXZhdGlvblN0YXJ0IiwiZHRQZXJQYXJ0aWNsZSIsImR0VmFsdWUiLCJfaW5jcmVtZW50UGFydGljbGVDb3VudCIsIl9yZXNldFBhcnRpY2xlIiwicHBzRHQiLCJfcmVzZXRCdWZmZXJSYW5nZXMiLCJfY2hlY2tQYXJ0aWNsZUFnZXMiLCJhY3RpdmF0aW9uQ291bnQiLCJfYWN0aXZhdGVQYXJ0aWNsZXMiLCJmb3JjZSIsInJlbW92ZUVtaXR0ZXIiLCJlcnJvciIsInRleHR1cmUiLCJmaXhlZFRpbWVTdGVwIiwidGV4dHVyZUZyYW1lcyIsImZyYW1lcyIsInRleHR1cmVGcmFtZUNvdW50IiwiZnJhbWVDb3VudCIsInRleHR1cmVMb29wIiwibG9vcCIsImhhc1BlcnNwZWN0aXZlIiwiY29sb3JpemUiLCJtYXhQYXJ0aWNsZUNvdW50IiwiYmxlbmRpbmciLCJ0cmFuc3BhcmVudCIsImFscGhhVGVzdCIsImRlcHRoV3JpdGUiLCJkZXB0aFRlc3QiLCJmb2ciLCJzY2FsZSIsImVtaXR0ZXJzIiwiZW1pdHRlcklEcyIsIl9wb29sIiwiX3Bvb2xDcmVhdGlvblNldHRpbmdzIiwiX2NyZWF0ZU5ld1doZW5Qb29sRW1wdHkiLCJfYXR0cmlidXRlc05lZWRSZWZyZXNoIiwiX2F0dHJpYnV0ZXNOZWVkRHluYW1pY1Jlc2V0IiwidGV4IiwidGV4dHVyZUFuaW1hdGlvbiIsImZvZ0NvbG9yIiwiZm9nTmVhciIsImZvZ0ZhciIsImZvZ0RlbnNpdHkiLCJkZWx0YVRpbWUiLCJydW5UaW1lIiwiSEFTX1BFUlNQRUNUSVZFIiwiQ09MT1JJWkUiLCJWQUxVRV9PVkVSX0xJRkVUSU1FX0xFTkdUSCIsIlNIT1VMRF9ST1RBVEVfVEVYVFVSRSIsIlNIT1VMRF9ST1RBVEVfUEFSVElDTEVTIiwiU0hPVUxEX1dJR0dMRV9QQVJUSUNMRVMiLCJTSE9VTERfQ0FMQ1VMQVRFX1NQUklURSIsIm1hdGVyaWFsIiwidmVydGV4U2hhZGVyIiwic2hhZGVycyIsImZyYWdtZW50U2hhZGVyIiwiZ2VvbWV0cnkiLCJtZXNoIiwiZW1pdHRlciIsImFwcGx5IiwiZ2VvbWV0cnlBdHRyaWJ1dGVzIiwiZ2VvbWV0cnlBdHRyaWJ1dGUiLCJhZGRBdHRyaWJ1dGUiLCJzZXREcmF3UmFuZ2UiLCJpbmRleE9mIiwiX2NhbGN1bGF0ZVBQU1ZhbHVlIiwiX3NldEJ1ZmZlclVwZGF0ZVJhbmdlcyIsIl9zZXRBdHRyaWJ1dGVPZmZzZXQiLCJfY3JlYXRlQnVmZmVyQXR0cmlidXRlIiwiX2FwcGx5QXR0cmlidXRlc1RvR2VvbWV0cnkiLCJlbWl0dGVySW5kZXgiLCJfb25SZW1vdmUiLCJwb29sIiwiY3JlYXRlTmV3IiwicG9wIiwiYWRkRW1pdHRlciIsInJlc2V0IiwidW5zaGlmdCIsIm51bUVtaXR0ZXJzIiwiZW1pdHRlck9wdGlvbnMiLCJhcmdzIiwicmVsZWFzZUludG9Qb29sIiwiZ2V0RnJvbVBvb2wiLCJlbmFibGUiLCJzZXRUaW1lb3V0IiwiZGlzYWJsZSIsImxvZyIsIl90cmlnZ2VyU2luZ2xlRW1pdHRlciIsImF0dHJzIiwicmVzZXRVcGRhdGVSYW5nZSIsImVtaXR0ZXJSYW5nZXMiLCJlbWl0dGVyQXR0ciIsInNldFVwZGF0ZVJhbmdlIiwiZmxhZ1VwZGF0ZSIsIl91cGRhdGVVbmlmb3JtcyIsInRpY2siLCJfdXBkYXRlQnVmZmVycyIsInJlc2V0RHluYW1pYyIsImRpc3Bvc2UiXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7b0JBQ0UsSUFBSUEsRUFBbUIsR0FHdkIsU0FBU0MsRUFBb0JDLEdBRzVCLEdBQUdGLEVBQWlCRSxHQUNuQixPQUFPRixFQUFpQkUsR0FBVUMsUUFHbkMsSUFBSUMsRUFBU0osRUFBaUJFLEdBQVksQ0FDekNHLEVBQUdILEVBQ0hJLEdBQUcsRUFDSEgsUUFBUyxJQVVWLE9BTkFJLEVBQVFMLEdBQVVNLEtBQUtKLEVBQU9ELFFBQVNDLEVBQVFBLEVBQU9ELFFBQVNG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UHpYIiwiZmlsZSI6IlNQRS5qcyIsInNvdXJjZXNDb250ZW50IjpbIiBcdC8vIFRoZSBtb2R1bGUgY2FjaGVcbiBcdHZhciBpbnN0YWxsZWRNb2R1bGVzID0ge307XG5cbiBcdC8vIFRoZSByZXF1aXJlIGZ1bmN0aW9uXG4gXHRmdW5jdGlvbiBfX3dlYnBhY2tfcmVxdWlyZV9fKG1vZHVsZUlkKSB7XG5cbiBcdFx0Ly8gQ2hlY2sgaWYgbW9kdWxlIGlzIGluIGNhY2hlXG4gXHRcdGlmKGluc3RhbGxlZE1vZHVsZXNbbW9kdWxlSWRdKSB7XG4gXHRcdFx0cmV0dXJuIGluc3RhbGxlZE1vZHVsZXNbbW9kdWxlSWRdLmV4cG9ydHM7XG4gXHRcdH1cbiBcdFx0Ly8gQ3JlYXRlIGEgbmV3IG1vZHVsZSAoYW5kIHB1dCBpdCBpbnRvIHRoZSBjYWNoZSlcbiBcdFx0dmFyIG1vZHVsZSA9IGluc3RhbGxlZE1vZHVsZXNbbW9kdWxlSWRdID0ge1xuIFx0XHRcdGk6IG1vZHVsZUlkLFxuIFx0XHRcdGw6IGZhbHNlLFxuIFx0XHRcdGV4cG9ydHM6IHt9XG4gXHRcdH07XG5cbiBcdFx0Ly8gRXhlY3V0ZSB0aGUgbW9kdWxlIGZ1bmN0aW9uXG4gXHRcdG1vZHVsZXNbbW9kdWxlSWRdLmNhbGwobW9kdWxlLmV4cG9ydHMsIG1vZHVsZSwgbW9kdWxlLmV4cG9ydHMsIF9fd2VicGFja19yZXF1aXJlX18pO1xuXG4gXHRcdC8vIEZsYWcgdGhlIG1vZHVsZSBhcyBsb2FkZWRcbiBcdFx0bW9kdWxlLmwgPSB0cnVlO1xuXG4gXHRcdC8vIFJldHVybiB0aGUgZXhwb3J0cyBvZiB0aGUgbW9kdWxlXG4gXHRcdHJldHVybiBtb2R1bGUuZXhwb3J0cztcbiBcdH1cblxuXG4gXHQvLyBleHBvc2UgdGhlIG1vZHVsZXMgb2JqZWN0IChfX3dlYnBhY2tfbW9kdWxlc19fKVxuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5tID0gbW9kdWxlcztcblxuIFx0Ly8gZXhwb3NlIHRoZSBtb2R1bGUgY2FjaGVcbiBcdF9fd2VicGFja19yZXF1aXJlX18uYyA9IGluc3RhbGxlZE1vZHVsZXM7XG5cbiBcdC8vIGRlZmluZSBnZXR0ZXIgZnVuY3Rpb24gZm9yIGhhcm1vbnkgZXhwb3J0c1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5kID0gZnVuY3Rpb24oZXhwb3J0cywgbmFtZSwgZ2V0dGVyKSB7XG4gXHRcdGlmKCFfX3dlYnBhY2tfcmVxdWlyZV9fLm8oZXhwb3J0cywgbmFtZSkpIHtcbiBcdFx0XHRPYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgbmFtZSwgeyBlbnVtZXJhYmxlOiB0cnVlLCBnZXQ6IGdldHRlciB9KTtcbiBcdFx0fVxuIFx0fTtcblxuIFx0Ly8gZGVmaW5lIF9fZXNNb2R1bGUgb24gZXhwb3J0c1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5yID0gZnVuY3Rpb24oZXhwb3J0cykge1xuIFx0XHRpZih0eXBlb2YgU3ltYm9sICE9PSAndW5kZWZpbmVkJyAmJiBTeW1ib2wudG9TdHJpbmdUYWcpIHtcbiBcdFx0XHRPYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgU3ltYm9sLnRvU3RyaW5nVGFnLCB7IHZhbHVlOiAnTW9kdWxlJyB9KTtcbiBcdFx0fVxuIFx0XHRPYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgJ19fZXNNb2R1bGUnLCB7IHZhbHVlOiB0cnVlIH0pO1xuIFx0fTtcblxuIFx0Ly8gY3JlYXRlIGEgZmFrZSBuYW1lc3BhY2Ugb2JqZWN0XG4gXHQvLyBtb2RlICYgMTogdmFsdWUgaXMgYSBtb2R1bGUgaWQsIHJlcXVpcmUgaXRcbiBcdC8vIG1vZGUgJiAyOiBtZXJnZSBhbGwgcHJvcGVydGllcyBvZiB2YWx1ZSBpbnRvIHRoZSBuc1xuIFx0Ly8gbW9kZSAmIDQ6IHJldHVybiB2YWx1ZSB3aGVuIGFscmVhZHkgbnMgb2JqZWN0XG4gXHQvLyBtb2RlICYgOHwxOiBiZWhhdmUgbGlrZSByZXF1aXJlXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLnQgPSBmdW5jdGlvbih2YWx1ZSwgbW9kZSkge1xuIFx0XHRpZihtb2RlICYgMSkgdmFsdWUgPSBfX3dlYnBhY2tfcmVxdWlyZV9fKHZhbHVlKTtcbiBcdFx0aWYobW9kZSAmIDgpIHJldHVybiB2YWx1ZTtcbiBcdFx0aWYoKG1vZGUgJiA0KSAmJiB0eXBlb2YgdmFsdWUgPT09ICdvYmplY3QnICYmIHZhbHVlICYmIHZhbHVlLl9fZXNNb2R1bGUpIHJldHVybiB2YWx1ZTtcbiBcdFx0dmFyIG5zID0gT2JqZWN0LmNyZWF0ZShudWxsKTtcbiBcdFx0X193ZWJwYWNrX3JlcXVpcmVfXy5yKG5zKTtcbiBcdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KG5zLCAnZGVmYXVsdCcsIHsgZW51bWVyYWJsZTogdHJ1ZSwgdmFsdWU6IHZhbHVlIH0pO1xuIFx0XHRpZihtb2RlICYgMiAmJiB0eXBlb2YgdmFsdWUgIT0gJ3N0cmluZycpIGZvcih2YXIga2V5IGluIHZhbHVlKSBfX3dlYnBhY2tfcmVxdWlyZV9fLmQobnMsIGtleSwgZnVuY3Rpb24oa2V5KSB7IHJldHVybiB2YWx1ZVtrZXldOyB9LmJpbmQobnVsbCwga2V5KSk7XG4gXHRcdHJldHVybiBucztcbiBcdH07XG5cbiBcdC8vIGdldERlZmF1bHRFeHBvcnQgZnVuY3Rpb24gZm9yIGNvbXBhdGliaWxpdHkgd2l0aCBub24taGFybW9ueSBtb2R1bGVzXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLm4gPSBmdW5jdGlvbihtb2R1bGUpIHtcbiBcdFx0dmFyIGdldHRlciA9IG1vZHVsZSAmJiBtb2R1bGUuX19lc01vZHVsZSA/XG4gXHRcdFx0ZnVuY3Rpb24gZ2V0RGVmYXVsdCgpIHsgcmV0dXJuIG1vZHVsZVsnZGVmYXVsdCddOyB9IDpcbiBcdFx0XHRmdW5jdGlvbiBnZXRNb2R1bGVFeHBvcnRzKCkgeyByZXR1cm4gbW9kdWxlOyB9O1xuIFx0XHRfX3dlYnBhY2tfcmVxdWlyZV9fLmQoZ2V0dGVyLCAnYScsIGdldHRlcik7XG4gXHRcdHJldHVybiBnZXR0ZXI7XG4gXHR9O1xuXG4gXHQvLyBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGxcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubyA9IGZ1bmN0aW9uKG9iamVjdCwgcHJvcGVydHkpIHsgcmV0dXJuIE9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbChvYmplY3QsIHByb3BlcnR5KTsgfTtcblxuIFx0Ly8gX193ZWJwYWNrX3B1YmxpY19wYXRoX19cbiBcdF9fd2VicGFja19yZXF1aXJlX18ucCA9IFwiXCI7XG5cblxuIFx0Ly8gTG9hZCBlbnRyeSBtb2R1bGUgYW5kIHJldHVybiBleHBvcnRzXG4gXHRyZXR1cm4gX193ZWJwYWNrX3JlcXVpcmVfXyhfX3dlYnBhY2tfcmVxdWlyZV9fLnMgPSAxKTtcbiIsIm1vZHVsZS5leHBvcnRzID0gVEhSRUU7IiwiaW1wb3J0ICogYXMgVEhSRUUgZnJvbSAndGhyZWUnO1xuaW1wb3J0IHZhbHVlVHlwZXMgZnJvbSAnQC9jb25zdGFudHMvdmFsdWVUeXBlcyc7XG5cbi8qKlxuICogQSBidW5jaCBvZiB1dGlsaXR5IGZ1bmN0aW9ucyB1c2VkIHRocm91Z2hvdXQgdGhlIGxpYnJhcnkuXG4gKiBAbmFtZXNwYWNlXG4gKiBAdHlwZSB7T2JqZWN0fVxuICovXG5leHBvcnQgZGVmYXVsdCB7XG5cblx0LyoqXG4gICAgICogR2l2ZW4gYSB2YWx1ZSwgYSB0eXBlLCBhbmQgYSBkZWZhdWx0IHZhbHVlIHRvIGZhbGxiYWNrIHRvLFxuICAgICAqIGVuc3VyZSB0aGUgZ2l2ZW4gYXJndW1lbnQgYWRoZXJlcyB0byB0aGUgdHlwZSByZXF1ZXN0aW5nLFxuICAgICAqIHJldHVybmluZyB0aGUgZGVmYXVsdCB2YWx1ZSBpZiB0eXBlIGNoZWNrIGlzIGZhbHNlLlxuICAgICAqXG4gICAgICogQHBhcmFtICB7KGJvb2xlYW58c3RyaW5nfG51bWJlcnxvYmplY3QpfSBhcmcgICAgICAgICAgVGhlIHZhbHVlIHRvIHBlcmZvcm0gYSB0eXBlLWNoZWNrIG9uLlxuICAgICAqIEBwYXJhbSAge1N0cmluZ30gdHlwZSAgICAgICAgIFRoZSB0eXBlIHRoZSBgYXJnYCBhcmd1bWVudCBzaG91bGQgYWRoZXJlIHRvLlxuICAgICAqIEBwYXJhbSAgeyhib29sZWFufHN0cmluZ3xudW1iZXJ8b2JqZWN0KX0gZGVmYXVsdFZhbHVlIEEgZGVmYXVsdCB2YWx1ZSB0byBmYWxsYmFjayBvbiBpZiB0aGUgdHlwZSBjaGVjayBmYWlscy5cbiAgICAgKiBAcmV0dXJuIHsoYm9vbGVhbnxzdHJpbmd8bnVtYmVyfG9iamVjdCl9ICAgICAgICAgICAgICBUaGUgZ2l2ZW4gdmFsdWUgaWYgdHlwZSBjaGVjayBwYXNzZXMsIG9yIHRoZSBkZWZhdWx0IHZhbHVlIGlmIGl0IGZhaWxzLlxuICAgICAqL1xuXHRlbnN1cmVUeXBlZEFyZyggYXJnLCB0eXBlLCBkZWZhdWx0VmFsdWUgKSB7XG5cdFx0J3VzZSBzdHJpY3QnO1xuXG5cdFx0aWYgKCB0eXBlb2YgYXJnID09PSB0eXBlICkge1xuXHRcdFx0cmV0dXJuIGFyZztcblx0XHR9XG5cdFx0ZWxzZSB7XG5cdFx0XHRyZXR1cm4gZGVmYXVsdFZhbHVlO1xuXHRcdH1cblx0fSxcblxuXHQvKipcbiAgICAgKiBHaXZlbiBhbiBhcnJheSBvZiB2YWx1ZXMsIGEgdHlwZSwgYW5kIGEgZGVmYXVsdCB2YWx1ZSxcbiAgICAgKiBlbnN1cmUgdGhlIGdpdmVuIGFycmF5J3MgY29udGVudHMgQUxMIGFkaGVyZSB0byB0aGUgcHJvdmlkZWQgdHlwZSxcbiAgICAgKiByZXR1cm5pbmcgdGhlIGRlZmF1bHQgdmFsdWUgaWYgdHlwZSBjaGVjayBmYWlscy5cbiAgICAgKlxuICAgICAqIElmIHRoZSBnaXZlbiB2YWx1ZSB0byBjaGVjayBpc24ndCBhbiBBcnJheSwgZGVsZWdhdGVzIHRvIFNQRS51dGlscy5lbnN1cmVUeXBlZEFyZy5cbiAgICAgKlxuICAgICAqIEBwYXJhbSAge0FycmF5fGJvb2xlYW58c3RyaW5nfG51bWJlcnxvYmplY3R9IGFyZyAgICAgICAgICBUaGUgYXJyYXkgb2YgdmFsdWVzIHRvIGNoZWNrIHR5cGUgb2YuXG4gICAgICogQHBhcmFtICB7U3RyaW5nfSB0eXBlICAgICAgICAgVGhlIHR5cGUgdGhhdCBzaG91bGQgYmUgYWRoZXJlZCB0by5cbiAgICAgKiBAcGFyYW0gIHsoYm9vbGVhbnxzdHJpbmd8bnVtYmVyfG9iamVjdCl9IGRlZmF1bHRWYWx1ZSBBIGRlZmF1bHQgZmFsbGJhY2sgdmFsdWUuXG4gICAgICogQHJldHVybiB7KGJvb2xlYW58c3RyaW5nfG51bWJlcnxvYmplY3QpfSAgICAgICAgICAgICAgVGhlIGdpdmVuIHZhbHVlIGlmIHR5cGUgY2hlY2sgcGFzc2VzLCBvciB0aGUgZGVmYXVsdCB2YWx1ZSBpZiBpdCBmYWlscy5cbiAgICAgKi9cblx0ZW5zdXJlQXJyYXlUeXBlZEFyZyggYXJnLCB0eXBlLCBkZWZhdWx0VmFsdWUgKSB7XG5cdFx0J3VzZSBzdHJpY3QnO1xuXG5cdFx0Ly8gSWYgdGhlIGFyZ3VtZW50IGJlaW5nIGNoZWNrZWQgaXMgYW4gYXJyYXksIGxvb3AgdGhyb3VnaFxuXHRcdC8vIGl0IGFuZCBlbnN1cmUgYWxsIHRoZSB2YWx1ZXMgYXJlIG9mIHRoZSBjb3JyZWN0IHR5cGUsXG5cdFx0Ly8gZmFsbGluZyBiYWNrIHRvIHRoZSBkZWZhdWx0VmFsdWUgaWYgYW55IGFyZW4ndC5cblx0XHRpZiAoIEFycmF5LmlzQXJyYXkoIGFyZyApICkge1xuXHRcdFx0Zm9yICggdmFyIGkgPSBhcmcubGVuZ3RoIC0gMTsgaSA+PSAwOyAtLWkgKSB7XG5cdFx0XHRcdGlmICggdHlwZW9mIGFyZ1sgaSBdICE9PSB0eXBlICkge1xuXHRcdFx0XHRcdHJldHVybiBkZWZhdWx0VmFsdWU7XG5cdFx0XHRcdH1cblx0XHRcdH1cblxuXHRcdFx0cmV0dXJuIGFyZztcblx0XHR9XG5cblx0XHQvLyBJZiB0aGUgYXJnIGlzbid0IGFuIGFycmF5IHRoZW4ganVzdCBmYWxsYmFjayB0b1xuXHRcdC8vIGNoZWNraW5nIHRoZSB0eXBlLlxuXHRcdHJldHVybiB0aGlzLmVuc3VyZVR5cGVkQXJnKCBhcmcsIHR5cGUsIGRlZmF1bHRWYWx1ZSApO1xuXHR9LFxuXG5cdC8qKlxuICAgICAqIEVuc3VyZXMgdGhlIGdpdmVuIHZhbHVlIGlzIGFuIGluc3RhbmNlIG9mIGEgY29uc3RydWN0b3IgZnVuY3Rpb24uXG4gICAgICpcbiAgICAgKiBAcGFyYW0gIHtPYmplY3R9IGFyZyAgICAgICAgICBUaGUgdmFsdWUgdG8gY2hlY2sgaW5zdGFuY2Ugb2YuXG4gICAgICogQHBhcmFtICB7RnVuY3Rpb259IGluc3RhbmNlICAgICBUaGUgY29uc3RydWN0b3Igb2YgdGhlIGluc3RhbmNlIHRvIGNoZWNrIGFnYWluc3QuXG4gICAgICogQHBhcmFtICB7T2JqZWN0fSBkZWZhdWx0VmFsdWUgQSBkZWZhdWx0IGZhbGxiYWNrIHZhbHVlIGlmIGluc3RhbmNlIGNoZWNrIGZhaWxzXG4gICAgICogQHJldHVybiB7T2JqZWN0fSAgICAgICAgICAgICAgVGhlIGdpdmVuIHZhbHVlIGlmIHR5cGUgY2hlY2sgcGFzc2VzLCBvciB0aGUgZGVmYXVsdCB2YWx1ZSBpZiBpdCBmYWlscy5cbiAgICAgKi9cblx0ZW5zdXJlSW5zdGFuY2VPZiggYXJnLCBpbnN0YW5jZSwgZGVmYXVsdFZhbHVlICkge1xuXHRcdCd1c2Ugc3RyaWN0JztcblxuXHRcdGlmICggaW5zdGFuY2UgIT09IHVuZGVmaW5lZCAmJiBhcmcgaW5zdGFuY2VvZiBpbnN0YW5jZSApIHtcblx0XHRcdHJldHVybiBhcmc7XG5cdFx0fVxuXHRcdGVsc2Uge1xuXHRcdFx0cmV0dXJuIGRlZmF1bHRWYWx1ZTtcblx0XHR9XG5cdH0sXG5cblx0LyoqXG4gICAgICogR2l2ZW4gYW4gYXJyYXkgb2YgdmFsdWVzLCBlbnN1cmUgdGhlIGluc3RhbmNlcyBvZiBhbGwgaXRlbXMgaW4gdGhlIGFycmF5XG4gICAgICogbWF0Y2hlcyB0aGUgZ2l2ZW4gaW5zdGFuY2UgY29uc3RydWN0b3IgZmFsbGluZyBiYWNrIHRvIGEgZGVmYXVsdCB2YWx1ZSBpZlxuICAgICAqIHRoZSBjaGVjayBmYWlscy5cbiAgICAgKlxuICAgICAqIElmIGdpdmVuIHZhbHVlIGlzbid0IGFuIEFycmF5LCBkZWxlZ2F0ZXMgdG8gYFNQRS51dGlscy5lbnN1cmVJbnN0YW5jZU9mYC5cbiAgICAgKlxuICAgICAqIEBwYXJhbSAge0FycmF5fE9iamVjdH0gYXJnICAgICAgICAgIFRoZSB2YWx1ZSB0byBwZXJmb3JtIHRoZSBpbnN0YW5jZW9mIGNoZWNrIG9uLlxuICAgICAqIEBwYXJhbSAge0Z1bmN0aW9ufSBpbnN0YW5jZSAgICAgVGhlIGNvbnN0cnVjdG9yIG9mIHRoZSBpbnN0YW5jZSB0byBjaGVjayBhZ2FpbnN0LlxuICAgICAqIEBwYXJhbSAge09iamVjdH0gZGVmYXVsdFZhbHVlIEEgZGVmYXVsdCBmYWxsYmFjayB2YWx1ZSBpZiBpbnN0YW5jZSBjaGVjayBmYWlsc1xuICAgICAqIEByZXR1cm4ge09iamVjdH0gICAgICAgICAgICAgIFRoZSBnaXZlbiB2YWx1ZSBpZiB0eXBlIGNoZWNrIHBhc3Nlcywgb3IgdGhlIGRlZmF1bHQgdmFsdWUgaWYgaXQgZmFpbHMuXG4gICAgICovXG5cdGVuc3VyZUFycmF5SW5zdGFuY2VPZiggYXJnLCBpbnN0YW5jZSwgZGVmYXVsdFZhbHVlICkge1xuXHRcdCd1c2Ugc3RyaWN0JztcblxuXHRcdC8vIElmIHRoZSBhcmd1bWVudCBiZWluZyBjaGVja2VkIGlzIGFuIGFycmF5LCBsb29wIHRocm91Z2hcblx0XHQvLyBpdCBhbmQgZW5zdXJlIGFsbCB0aGUgdmFsdWVzIGFyZSBvZiB0aGUgY29ycmVjdCB0eXBlLFxuXHRcdC8vIGZhbGxpbmcgYmFjayB0byB0aGUgZGVmYXVsdFZhbHVlIGlmIGFueSBhcmVuJ3QuXG5cdFx0aWYgKCBBcnJheS5pc0FycmF5KCBhcmcgKSApIHtcblx0XHRcdGZvciAoIHZhciBpID0gYXJnLmxlbmd0aCAtIDE7IGkgPj0gMDsgLS1pICkge1xuXHRcdFx0XHRpZiAoIGluc3RhbmNlICE9PSB1bmRlZmluZWQgJiYgYXJnWyBpIF0gaW5zdGFuY2VvZiBpbnN0YW5jZSA9PT0gZmFsc2UgKSB7XG5cdFx0XHRcdFx0cmV0dXJuIGRlZmF1bHRWYWx1ZTtcblx0XHRcdFx0fVxuXHRcdFx0fVxuXG5cdFx0XHRyZXR1cm4gYXJnO1xuXHRcdH1cblxuXHRcdC8vIElmIHRoZSBhcmcgaXNuJ3QgYW4gYXJyYXkgdGhlbiBqdXN0IGZhbGxiYWNrIHRvXG5cdFx0Ly8gY2hlY2tpbmcgdGhlIHR5cGUuXG5cdFx0cmV0dXJuIHRoaXMuZW5zdXJlSW5zdGFuY2VPZiggYXJnLCBpbnN0YW5jZSwgZGVmYXVsdFZhbHVlICk7XG5cdH0sXG5cblx0LyoqXG4gICAgICogRW5zdXJlcyB0aGF0IGFueSBcInZhbHVlLW92ZXItbGlmZXRpbWVcIiBwcm9wZXJ0aWVzIG9mIGFuIGVtaXR0ZXIgYXJlXG4gICAgICogb2YgdGhlIGNvcnJlY3QgbGVuZ3RoIChhcyBkaWN0YXRlZCBieSBgU1BFLnZhbHVlT3ZlckxpZmV0aW1lTGVuZ3RoYCkuXG4gICAgICpcbiAgICAgKiBEZWxlZ2F0ZXMgdG8gYFNQRS51dGlscy5pbnRlcnBvbGF0ZUFycmF5YCBmb3IgYXJyYXkgcmVzaXppbmcuXG4gICAgICpcbiAgICAgKiBJZiBwcm9wZXJ0aWVzIGFyZW4ndCBhcnJheXMsIHRoZW4gcHJvcGVydHkgdmFsdWVzIGFyZSBwdXQgaW50byBvbmUuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gIHtPYmplY3R9IHByb3BlcnR5ICBUaGUgcHJvcGVydHkgb2YgYW4gU1BFLkVtaXR0ZXIgaW5zdGFuY2UgdG8gY2hlY2sgY29tcGxpYW5jZSBvZi5cbiAgICAgKiBAcGFyYW0gIHtOdW1iZXJ9IG1pbkxlbmd0aCBUaGUgbWluaW11bSBsZW5ndGggb2YgdGhlIGFycmF5IHRvIGNyZWF0ZS5cbiAgICAgKiBAcGFyYW0gIHtOdW1iZXJ9IG1heExlbmd0aCBUaGUgbWF4aW11bSBsZW5ndGggb2YgdGhlIGFycmF5IHRvIGNyZWF0ZS5cbiAgICAgKi9cblx0ZW5zdXJlVmFsdWVPdmVyTGlmZXRpbWVDb21wbGlhbmNlKCBwcm9wZXJ0eSwgbWluTGVuZ3RoLCBtYXhMZW5ndGggKSB7XG5cdFx0J3VzZSBzdHJpY3QnO1xuXG5cdFx0bWluTGVuZ3RoID0gbWluTGVuZ3RoIHx8IDM7XG5cdFx0bWF4TGVuZ3RoID0gbWF4TGVuZ3RoIHx8IDM7XG5cblx0XHQvLyBGaXJzdCwgZW5zdXJlIGJvdGggcHJvcGVydGllcyBhcmUgYXJyYXlzLlxuXHRcdGlmICggQXJyYXkuaXNBcnJheSggcHJvcGVydHkuX3ZhbHVlICkgPT09IGZhbHNlICkge1xuXHRcdFx0cHJvcGVydHkuX3ZhbHVlID0gWyBwcm9wZXJ0eS5fdmFsdWUgXTtcblx0XHR9XG5cblx0XHRpZiAoIEFycmF5LmlzQXJyYXkoIHByb3BlcnR5Ll9zcHJlYWQgKSA9PT0gZmFsc2UgKSB7XG5cdFx0XHRwcm9wZXJ0eS5fc3ByZWFkID0gWyBwcm9wZXJ0eS5fc3ByZWFkIF07XG5cdFx0fVxuXG5cdFx0dmFyIHZhbHVlTGVuZ3RoID0gdGhpcy5jbGFtcCggcHJvcGVydHkuX3ZhbHVlLmxlbmd0aCwgbWluTGVuZ3RoLCBtYXhMZW5ndGggKSxcblx0XHRcdHNwcmVhZExlbmd0aCA9IHRoaXMuY2xhbXAoIHByb3BlcnR5Ll9zcHJlYWQubGVuZ3RoLCBtaW5MZW5ndGgsIG1heExlbmd0aCApLFxuXHRcdFx0ZGVzaXJlZExlbmd0aCA9IE1hdGgubWF4KCB2YWx1ZUxlbmd0aCwgc3ByZWFkTGVuZ3RoICk7XG5cblx0XHRpZiAoIHByb3BlcnR5Ll92YWx1ZS5sZW5ndGggIT09IGRlc2lyZWRMZW5ndGggKSB7XG5cdFx0XHRwcm9wZXJ0eS5fdmFsdWUgPSB0aGlzLmludGVycG9sYXRlQXJyYXkoIHByb3BlcnR5Ll92YWx1ZSwgZGVzaXJlZExlbmd0aCApO1xuXHRcdH1cblxuXHRcdGlmICggcHJvcGVydHkuX3NwcmVhZC5sZW5ndGggIT09IGRlc2lyZWRMZW5ndGggKSB7XG5cdFx0XHRwcm9wZXJ0eS5fc3ByZWFkID0gdGhpcy5pbnRlcnBvbGF0ZUFycmF5KCBwcm9wZXJ0eS5fc3ByZWFkLCBkZXNpcmVkTGVuZ3RoICk7XG5cdFx0fVxuXHR9LFxuXG5cdC8qKlxuICAgICAqIFBlcmZvcm1zIGxpbmVhciBpbnRlcnBvbGF0aW9uIChsZXJwKSBvbiBhbiBhcnJheS5cbiAgICAgKlxuICAgICAqIEZvciBleGFtcGxlLCBsZXJwaW5nIFsxLCAxMF0sIHdpdGggYSBgbmV3TGVuZ3RoYCBvZiAxMCB3aWxsIHByb2R1Y2UgWzEsIDIsIDMsIDQsIDUsIDYsIDcsIDgsIDksIDEwXS5cbiAgICAgKlxuICAgICAqIERlbGVnYXRlcyB0byBgU1BFLnV0aWxzLmxlcnBUeXBlQWdub3N0aWNgIHRvIHBlcmZvcm0gdGhlIGFjdHVhbFxuICAgICAqIGludGVycG9sYXRpb24uXG4gICAgICpcbiAgICAgKiBAcGFyYW0gIHtBcnJheX0gc3JjQXJyYXkgIFRoZSBhcnJheSB0byBsZXJwLlxuICAgICAqIEBwYXJhbSAge051bWJlcn0gbmV3TGVuZ3RoIFRoZSBsZW5ndGggdGhlIGFycmF5IHNob3VsZCBiZSBpbnRlcnBvbGF0ZWQgdG8uXG4gICAgICogQHJldHVybiB7QXJyYXl9ICAgICAgICAgICBUaGUgaW50ZXJwb2xhdGVkIGFycmF5LlxuICAgICAqL1xuXHRpbnRlcnBvbGF0ZUFycmF5KCBzcmNBcnJheSwgbmV3TGVuZ3RoICkge1xuXHRcdCd1c2Ugc3RyaWN0JztcblxuXHRcdHZhciBzb3VyY2VMZW5ndGggPSBzcmNBcnJheS5sZW5ndGgsXG5cdFx0XHRuZXdBcnJheSA9IFsgdHlwZW9mIHNyY0FycmF5WyAwIF0uY2xvbmUgPT09ICdmdW5jdGlvbicgPyBzcmNBcnJheVsgMCBdLmNsb25lKCkgOiBzcmNBcnJheVsgMCBdIF0sXG5cdFx0XHRmYWN0b3IgPSAoIHNvdXJjZUxlbmd0aCAtIDEgKSAvICggbmV3TGVuZ3RoIC0gMSApO1xuXG5cblx0XHRmb3IgKCB2YXIgaSA9IDE7IGkgPCBuZXdMZW5ndGggLSAxOyArK2kgKSB7XG5cdFx0XHR2YXIgZiA9IGkgKiBmYWN0b3IsXG5cdFx0XHRcdGJlZm9yZSA9IE1hdGguZmxvb3IoIGYgKSxcblx0XHRcdFx0YWZ0ZXIgPSBNYXRoLmNlaWwoIGYgKSxcblx0XHRcdFx0ZGVsdGEgPSBmIC0gYmVmb3JlO1xuXG5cdFx0XHRuZXdBcnJheVsgaSBdID0gdGhpcy5sZXJwVHlwZUFnbm9zdGljKCBzcmNBcnJheVsgYmVmb3JlIF0sIHNyY0FycmF5WyBhZnRlciBdLCBkZWx0YSApO1xuXHRcdH1cblxuXHRcdG5ld0FycmF5LnB1c2goXG5cdFx0XHR0eXBlb2Ygc3JjQXJyYXlbIHNvdXJjZUxlbmd0aCAtIDEgXS5jbG9uZSA9PT0gJ2Z1bmN0aW9uJyA/XG5cdFx0XHRcdHNyY0FycmF5WyBzb3VyY2VMZW5ndGggLSAxIF0uY2xvbmUoKSA6XG5cdFx0XHRcdHNyY0FycmF5WyBzb3VyY2VMZW5ndGggLSAxIF1cblx0XHQpO1xuXG5cdFx0cmV0dXJuIG5ld0FycmF5O1xuXHR9LFxuXG5cdC8qKlxuICAgICAqIENsYW1wIGEgbnVtYmVyIHRvIGJldHdlZW4gdGhlIGdpdmVuIG1pbiBhbmQgbWF4IHZhbHVlcy5cbiAgICAgKiBAcGFyYW0gIHtOdW1iZXJ9IHZhbHVlIFRoZSBudW1iZXIgdG8gY2xhbXAuXG4gICAgICogQHBhcmFtICB7TnVtYmVyfSBtaW4gICBUaGUgbWluaW11bSB2YWx1ZS5cbiAgICAgKiBAcGFyYW0gIHtOdW1iZXJ9IG1heCAgIFRoZSBtYXhpbXVtIHZhbHVlLlxuICAgICAqIEByZXR1cm4ge051bWJlcn0gICAgICAgVGhlIGNsYW1wZWQgbnVtYmVyLlxuICAgICAqL1xuXHRjbGFtcCggdmFsdWUsIG1pbiwgbWF4ICkge1xuXHRcdCd1c2Ugc3RyaWN0JztcblxuXHRcdHJldHVybiBNYXRoLm1heCggbWluLCBNYXRoLm1pbiggdmFsdWUsIG1heCApICk7XG5cdH0sXG5cblx0LyoqXG4gICAgICogSWYgdGhlIGdpdmVuIHZhbHVlIGlzIGxlc3MgdGhhbiB0aGUgZXBzaWxvbiB2YWx1ZSwgdGhlbiByZXR1cm5cbiAgICAgKiBhIHJhbmRvbWlzZWQgZXBzaWxvbiB2YWx1ZSBpZiBzcGVjaWZpZWQsIG9yIGp1c3QgdGhlIGVwc2lsb24gdmFsdWUgaWYgbm90LlxuICAgICAqIFdvcmtzIGZvciBuZWdhdGl2ZSBudW1iZXJzIGFzIHdlbGwgYXMgcG9zaXRpdmUuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gIHtOdW1iZXJ9IHZhbHVlICAgICBUaGUgdmFsdWUgdG8gcGVyZm9ybSB0aGUgb3BlcmF0aW9uIG9uLlxuICAgICAqIEBwYXJhbSAge0Jvb2xlYW59IHJhbmRvbWlzZSBXaGV0aGVyIHRoZSB2YWx1ZSBzaG91bGQgYmUgcmFuZG9taXNlZC5cbiAgICAgKiBAcmV0dXJuIHtOdW1iZXJ9ICAgICAgICAgICBUaGUgcmVzdWx0IG9mIHRoZSBvcGVyYXRpb24uXG4gICAgICovXG5cdHplcm9Ub0Vwc2lsb24oIHZhbHVlLCByYW5kb21pc2UgKSB7XG5cdFx0J3VzZSBzdHJpY3QnO1xuXG5cdFx0dmFyIGVwc2lsb24gPSAwLjAwMDAxLFxuXHRcdFx0cmVzdWx0ID0gdmFsdWU7XG5cblx0XHRyZXN1bHQgPSByYW5kb21pc2UgPyBNYXRoLnJhbmRvbSgpICogZXBzaWxvbiAqIDEwIDogZXBzaWxvbjtcblxuXHRcdGlmICggdmFsdWUgPCAwICYmIHZhbHVlID4gLWVwc2lsb24gKSB7XG5cdFx0XHRyZXN1bHQgPSAtcmVzdWx0O1xuXHRcdH1cblxuXHRcdC8vIGlmICggdmFsdWUgPT09IDAgKSB7XG5cdFx0Ly8gICAgIHJlc3VsdCA9IHJhbmRvbWlzZSA/IE1hdGgucmFuZG9tKCkgKiBlcHNpbG9uICogMTAgOiBlcHNpbG9uO1xuXHRcdC8vIH1cblx0XHQvLyBlbHNlIGlmICggdmFsdWUgPiAwICYmIHZhbHVlIDwgZXBzaWxvbiApIHtcblx0XHQvLyAgICAgcmVzdWx0ID0gcmFuZG9taXNlID8gTWF0aC5yYW5kb20oKSAqIGVwc2lsb24gKiAxMCA6IGVwc2lsb247XG5cdFx0Ly8gfVxuXHRcdC8vIGVsc2UgaWYgKCB2YWx1ZSA8IDAgJiYgdmFsdWUgPiAtZXBzaWxvbiApIHtcblx0XHQvLyAgICAgcmVzdWx0ID0gLSggcmFuZG9taXNlID8gTWF0aC5yYW5kb20oKSAqIGVwc2lsb24gKiAxMCA6IGVwc2lsb24gKTtcblx0XHQvLyB9XG5cblx0XHRyZXR1cm4gcmVzdWx0O1xuXHR9LFxuXG5cdC8qKlxuICAgICAqIExpbmVhcmx5IGludGVycG9sYXRlcyB0d28gdmFsdWVzIG9mIHZhcmlvdXMgdmFsdWVUeXBlcy4gVGhlIGdpdmVuIHZhbHVlc1xuICAgICAqIG11c3QgYmUgb2YgdGhlIHNhbWUgdHlwZSBmb3IgdGhlIGludGVycG9sYXRpb24gdG8gd29yay5cbiAgICAgKiBAcGFyYW0gIHsobnVtYmVyfE9iamVjdCl9IHN0YXJ0IFRoZSBzdGFydCB2YWx1ZSBvZiB0aGUgbGVycC5cbiAgICAgKiBAcGFyYW0gIHsobnVtYmVyfG9iamVjdCl9IGVuZCAgIFRoZSBlbmQgdmFsdWUgb2YgdGhlIGxlcnAuXG4gICAgICogQHBhcmFtICB7TnVtYmVyfSBkZWx0YSBUaGUgZGVsdGEgcG9zaWl0b24gb2YgdGhlIGxlcnAgb3BlcmF0aW9uLiBJZGVhbGx5IGJldHdlZW4gMCBhbmQgMSAoaW5jbHVzaXZlKS5cbiAgICAgKiBAcmV0dXJuIHsobnVtYmVyfG9iamVjdHx1bmRlZmluZWQpfSAgICAgICBUaGUgcmVzdWx0IG9mIHRoZSBvcGVyYXRpb24uIFJlc3VsdCB3aWxsIGJlIHVuZGVmaW5lZCBpZlxuICAgICAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGUgc3RhcnQgYW5kIGVuZCBhcmd1bWVudHMgYXJlbid0IGEgc3VwcG9ydGVkIHR5cGUsIG9yXG4gICAgICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmIHRoZWlyIHR5cGVzIGRvIG5vdCBtYXRjaC5cbiAgICAgKi9cblx0bGVycFR5cGVBZ25vc3RpYyggc3RhcnQsIGVuZCwgZGVsdGEgKSB7XG5cdFx0J3VzZSBzdHJpY3QnO1xuXG5cdFx0dmFyIG91dDtcblxuXHRcdGlmICggdHlwZW9mIHN0YXJ0ID09PSB2YWx1ZVR5cGVzLk5VTUJFUiAmJiB0eXBlb2YgZW5kID09PSB2YWx1ZVR5cGVzLk5VTUJFUiApIHtcblx0XHRcdHJldHVybiBzdGFydCArICggKCBlbmQgLSBzdGFydCApICogZGVsdGEgKTtcblx0XHR9XG5cdFx0ZWxzZSBpZiAoIHN0YXJ0IGluc3RhbmNlb2YgVEhSRUUuVmVjdG9yMiAmJiBlbmQgaW5zdGFuY2VvZiBUSFJFRS5WZWN0b3IyICkge1xuXHRcdFx0b3V0ID0gc3RhcnQuY2xvbmUoKTtcblx0XHRcdG91dC54ID0gdGhpcy5sZXJwKCBzdGFydC54LCBlbmQueCwgZGVsdGEgKTtcblx0XHRcdG91dC55ID0gdGhpcy5sZXJwKCBzdGFydC55LCBlbmQueSwgZGVsdGEgKTtcblx0XHRcdHJldHVybiBvdXQ7XG5cdFx0fVxuXHRcdGVsc2UgaWYgKCBzdGFydCBpbnN0YW5jZW9mIFRIUkVFLlZlY3RvcjMgJiYgZW5kIGluc3RhbmNlb2YgVEhSRUUuVmVjdG9yMyApIHtcblx0XHRcdG91dCA9IHN0YXJ0LmNsb25lKCk7XG5cdFx0XHRvdXQueCA9IHRoaXMubGVycCggc3RhcnQueCwgZW5kLngsIGRlbHRhICk7XG5cdFx0XHRvdXQueSA9IHRoaXMubGVycCggc3RhcnQueSwgZW5kLnksIGRlbHRhICk7XG5cdFx0XHRvdXQueiA9IHRoaXMubGVycCggc3RhcnQueiwgZW5kLnosIGRlbHRhICk7XG5cdFx0XHRyZXR1cm4gb3V0O1xuXHRcdH1cblx0XHRlbHNlIGlmICggc3RhcnQgaW5zdGFuY2VvZiBUSFJFRS5WZWN0b3I0ICYmIGVuZCBpbnN0YW5jZW9mIFRIUkVFLlZlY3RvcjQgKSB7XG5cdFx0XHRvdXQgPSBzdGFydC5jbG9uZSgpO1xuXHRcdFx0b3V0LnggPSB0aGlzLmxlcnAoIHN0YXJ0LngsIGVuZC54LCBkZWx0YSApO1xuXHRcdFx0b3V0LnkgPSB0aGlzLmxlcnAoIHN0YXJ0LnksIGVuZC55LCBkZWx0YSApO1xuXHRcdFx0b3V0LnogPSB0aGlzLmxlcnAoIHN0YXJ0LnosIGVuZC56LCBkZWx0YSApO1xuXHRcdFx0b3V0LncgPSB0aGlzLmxlcnAoIHN0YXJ0LncsIGVuZC53LCBkZWx0YSApO1xuXHRcdFx0cmV0dXJuIG91dDtcblx0XHR9XG5cdFx0ZWxzZSBpZiAoIHN0YXJ0IGluc3RhbmNlb2YgVEhSRUUuQ29sb3IgJiYgZW5kIGluc3RhbmNlb2YgVEhSRUUuQ29sb3IgKSB7XG5cdFx0XHRvdXQgPSBzdGFydC5jbG9uZSgpO1xuXHRcdFx0b3V0LnIgPSB0aGlzLmxlcnAoIHN0YXJ0LnIsIGVuZC5yLCBkZWx0YSApO1xuXHRcdFx0b3V0LmcgPSB0aGlzLmxlcnAoIHN0YXJ0LmcsIGVuZC5nLCBkZWx0YSApO1xuXHRcdFx0b3V0LmIgPSB0aGlzLmxlcnAoIHN0YXJ0LmIsIGVuZC5iLCBkZWx0YSApO1xuXHRcdFx0cmV0dXJuIG91dDtcblx0XHR9XG5cdFx0ZWxzZSB7XG5cdFx0XHRjb25zb2xlLndhcm4oICdJbnZhbGlkIGFyZ3VtZW50IHR5cGVzLCBvciBhcmd1bWVudCB0eXBlcyBkbyBub3QgbWF0Y2g6Jywgc3RhcnQsIGVuZCApO1xuXHRcdH1cblx0fSxcblxuXHQvKipcbiAgICAgKiBQZXJmb3JtIGEgbGluZWFyIGludGVycG9sYXRpb24gb3BlcmF0aW9uIG9uIHR3byBudW1iZXJzLlxuICAgICAqIEBwYXJhbSAge051bWJlcn0gc3RhcnQgVGhlIHN0YXJ0IHZhbHVlLlxuICAgICAqIEBwYXJhbSAge051bWJlcn0gZW5kICAgVGhlIGVuZCB2YWx1ZS5cbiAgICAgKiBAcGFyYW0gIHtOdW1iZXJ9IGRlbHRhIFRoZSBwb3NpdGlvbiB0byBpbnRlcnBvbGF0ZSB0by5cbiAgICAgKiBAcmV0dXJuIHtOdW1iZXJ9ICAgICAgIFRoZSByZXN1bHQgb2YgdGhlIGxlcnAgb3BlcmF0aW9uLlxuICAgICAqL1xuXHRsZXJwKCBzdGFydCwgZW5kLCBkZWx0YSApIHtcblx0XHQndXNlIHN0cmljdCc7XG5cdFx0cmV0dXJuIHN0YXJ0ICsgKCAoIGVuZCAtIHN0YXJ0ICkgKiBkZWx0YSApO1xuXHR9LFxuXG5cdC8qKlxuICAgICAqIFJvdW5kcyBhIG51bWJlciB0byBhIG5lYXJlc3QgbXVsdGlwbGUuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gIHtOdW1iZXJ9IG4gICAgICAgIFRoZSBudW1iZXIgdG8gcm91bmQuXG4gICAgICogQHBhcmFtICB7TnVtYmVyfSBtdWx0aXBsZSBUaGUgbXVsdGlwbGUgdG8gcm91bmQgdG8uXG4gICAgICogQHJldHVybiB7TnVtYmVyfSAgICAgICAgICBUaGUgcmVzdWx0IG9mIHRoZSByb3VuZCBvcGVyYXRpb24uXG4gICAgICovXG5cdHJvdW5kVG9OZWFyZXN0TXVsdGlwbGUoIG4sIG11bHRpcGxlICkge1xuXHRcdCd1c2Ugc3RyaWN0JztcblxuXHRcdHZhciByZW1haW5kZXIgPSAwO1xuXG5cdFx0aWYgKCBtdWx0aXBsZSA9PT0gMCApIHtcblx0XHRcdHJldHVybiBuO1xuXHRcdH1cblxuXHRcdHJlbWFpbmRlciA9IE1hdGguYWJzKCBuICkgJSBtdWx0aXBsZTtcblxuXHRcdGlmICggcmVtYWluZGVyID09PSAwICkge1xuXHRcdFx0cmV0dXJuIG47XG5cdFx0fVxuXG5cdFx0aWYgKCBuIDwgMCApIHtcblx0XHRcdHJldHVybiAtKCBNYXRoLmFicyggbiApIC0gcmVtYWluZGVyICk7XG5cdFx0fVxuXG5cdFx0cmV0dXJuIG4gKyBtdWx0aXBsZSAtIHJlbWFpbmRlcjtcblx0fSxcblxuXHQvKipcbiAgICAgKiBDaGVjayBpZiBhbGwgaXRlbXMgaW4gYW4gYXJyYXkgYXJlIGVxdWFsLiBVc2VzIHN0cmljdCBlcXVhbGl0eS5cbiAgICAgKlxuICAgICAqIEBwYXJhbSAge0FycmF5fSBhcnJheSBUaGUgYXJyYXkgb2YgdmFsdWVzIHRvIGNoZWNrIGVxdWFsaXR5IG9mLlxuICAgICAqIEByZXR1cm4ge0Jvb2xlYW59ICAgICAgIFdoZXRoZXIgdGhlIGFycmF5J3MgdmFsdWVzIGFyZSBhbGwgZXF1YWwgb3Igbm90LlxuICAgICAqL1xuXHRhcnJheVZhbHVlc0FyZUVxdWFsKCBhcnJheSApIHtcblx0XHQndXNlIHN0cmljdCc7XG5cblx0XHRmb3IgKCB2YXIgaSA9IDA7IGkgPCBhcnJheS5sZW5ndGggLSAxOyArK2kgKSB7XG5cdFx0XHRpZiAoIGFycmF5WyBpIF0gIT09IGFycmF5WyBpICsgMSBdICkge1xuXHRcdFx0XHRyZXR1cm4gZmFsc2U7XG5cdFx0XHR9XG5cdFx0fVxuXG5cdFx0cmV0dXJuIHRydWU7XG5cdH0sXG5cblx0Ly8gY29sb3JzQXJlRXF1YWw6IGZ1bmN0aW9uKCkge1xuXHQvLyAgICAgdmFyIGNvbG9ycyA9IEFycmF5LnByb3RvdHlwZS5zbGljZS5jYWxsKCBhcmd1bWVudHMgKSxcblx0Ly8gICAgICAgICBudW1Db2xvcnMgPSBjb2xvcnMubGVuZ3RoO1xuXG5cdC8vICAgICBmb3IgKCB2YXIgaSA9IDAsIGNvbG9yMSwgY29sb3IyOyBpIDwgbnVtQ29sb3JzIC0gMTsgKytpICkge1xuXHQvLyAgICAgICAgIGNvbG9yMSA9IGNvbG9yc1sgaSBdO1xuXHQvLyAgICAgICAgIGNvbG9yMiA9IGNvbG9yc1sgaSArIDEgXTtcblxuXHQvLyAgICAgICAgIGlmIChcblx0Ly8gICAgICAgICAgICAgY29sb3IxLnIgIT09IGNvbG9yMi5yIHx8XG5cdC8vICAgICAgICAgICAgIGNvbG9yMS5nICE9PSBjb2xvcjIuZyB8fFxuXHQvLyAgICAgICAgICAgICBjb2xvcjEuYiAhPT0gY29sb3IyLmJcblx0Ly8gICAgICAgICApIHtcblx0Ly8gICAgICAgICAgICAgcmV0dXJuIGZhbHNlXG5cdC8vICAgICAgICAgfVxuXHQvLyAgICAgfVxuXG5cdC8vICAgICByZXR1cm4gdHJ1ZTtcblx0Ly8gfSxcblxuXG5cdC8qKlxuICAgICAqIEdpdmVuIGEgc3RhcnQgdmFsdWUgYW5kIGEgc3ByZWFkIHZhbHVlLCBjcmVhdGUgYW5kIHJldHVybiBhIHJhbmRvbVxuICAgICAqIG51bWJlci5cbiAgICAgKiBAcGFyYW0gIHtOdW1iZXJ9IGJhc2UgICBUaGUgc3RhcnQgdmFsdWUuXG4gICAgICogQHBhcmFtICB7TnVtYmVyfSBzcHJlYWQgVGhlIHNpemUgb2YgdGhlIHJhbmRvbSB2YXJpYW5jZSB0byBhcHBseS5cbiAgICAgKiBAcmV0dXJuIHtOdW1iZXJ9ICAgICAgICBBIHJhbmRvbWlzZWQgbnVtYmVyLlxuICAgICAqL1xuXHRyYW5kb21GbG9hdCggYmFzZSwgc3ByZWFkICkge1xuXHRcdCd1c2Ugc3RyaWN0Jztcblx0XHRyZXR1cm4gYmFzZSArIHNwcmVhZCAqICggTWF0aC5yYW5kb20oKSAtIDAuNSApO1xuXHR9LFxuXG5cblxuXHQvKipcbiAgICAgKiBHaXZlbiBhbiBTUEUuU2hhZGVyQXR0cmlidXRlIGluc3RhbmNlLCBhbmQgdmFyaW91cyBvdGhlciBzZXR0aW5ncyxcbiAgICAgKiBhc3NpZ24gdmFsdWVzIHRvIHRoZSBhdHRyaWJ1dGUncyBhcnJheSBpbiBhIGB2ZWMzYCBmb3JtYXQuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gIHtPYmplY3R9IGF0dHJpYnV0ZSAgIFRoZSBpbnN0YW5jZSBvZiBTUEUuU2hhZGVyQXR0cmlidXRlIHRvIHNhdmUgdGhlIHJlc3VsdCB0by5cbiAgICAgKiBAcGFyYW0gIHtOdW1iZXJ9IGluZGV4ICAgICAgIFRoZSBvZmZzZXQgaW4gdGhlIGF0dHJpYnV0ZSdzIFR5cGVkQXJyYXkgdG8gc2F2ZSB0aGUgcmVzdWx0IGZyb20uXG4gICAgICogQHBhcmFtICB7T2JqZWN0fSBiYXNlICAgICAgICBUSFJFRS5WZWN0b3IzIGluc3RhbmNlIGRlc2NyaWJpbmcgdGhlIHN0YXJ0IHZhbHVlLlxuICAgICAqIEBwYXJhbSAge09iamVjdH0gc3ByZWFkICAgICAgVEhSRUUuVmVjdG9yMyBpbnN0YW5jZSBkZXNjcmliaW5nIHRoZSByYW5kb20gdmFyaWFuY2UgdG8gYXBwbHkgdG8gdGhlIHN0YXJ0IHZhbHVlLlxuICAgICAqIEBwYXJhbSAge09iamVjdH0gc3ByZWFkQ2xhbXAgVEhSRUUuVmVjdG9yMyBpbnN0YW5jZSBkZXNjcmliaW5nIHRoZSBtdWx0aXBsZXMgdG8gY2xhbXAgdGhlIHJhbmRvbW5lc3MgdG8uXG4gICAgICovXG5cdHJhbmRvbVZlY3RvcjMoIGF0dHJpYnV0ZSwgaW5kZXgsIGJhc2UsIHNwcmVhZCwgc3ByZWFkQ2xhbXAgKSB7XG5cdFx0J3VzZSBzdHJpY3QnO1xuXG5cdFx0dmFyIHggPSBiYXNlLnggKyAoIE1hdGgucmFuZG9tKCkgKiBzcHJlYWQueCAtICggc3ByZWFkLnggKiAwLjUgKSApLFxuXHRcdFx0eSA9IGJhc2UueSArICggTWF0aC5yYW5kb20oKSAqIHNwcmVhZC55IC0gKCBzcHJlYWQueSAqIDAuNSApICksXG5cdFx0XHR6ID0gYmFzZS56ICsgKCBNYXRoLnJhbmRvbSgpICogc3ByZWFkLnogLSAoIHNwcmVhZC56ICogMC41ICkgKTtcblxuXHRcdC8vIHZhciB4ID0gdGhpcy5yYW5kb21GbG9hdCggYmFzZS54LCBzcHJlYWQueCApLFxuXHRcdC8vIHkgPSB0aGlzLnJhbmRvbUZsb2F0KCBiYXNlLnksIHNwcmVhZC55ICksXG5cdFx0Ly8geiA9IHRoaXMucmFuZG9tRmxvYXQoIGJhc2Uueiwgc3ByZWFkLnogKTtcblxuXHRcdGlmICggc3ByZWFkQ2xhbXAgKSB7XG5cdFx0XHR4ID0gLXNwcmVhZENsYW1wLnggKiAwLjUgKyB0aGlzLnJvdW5kVG9OZWFyZXN0TXVsdGlwbGUoIHgsIHNwcmVhZENsYW1wLnggKTtcblx0XHRcdHkgPSAtc3ByZWFkQ2xhbXAueSAqIDAuNSArIHRoaXMucm91bmRUb05lYXJlc3RNdWx0aXBsZSggeSwgc3ByZWFkQ2xhbXAueSApO1xuXHRcdFx0eiA9IC1zcHJlYWRDbGFtcC56ICogMC41ICsgdGhpcy5yb3VuZFRvTmVhcmVzdE11bHRpcGxlKCB6LCBzcHJlYWRDbGFtcC56ICk7XG5cdFx0fVxuXG5cdFx0YXR0cmlidXRlLnR5cGVkQXJyYXkuc2V0VmVjM0NvbXBvbmVudHMoIGluZGV4LCB4LCB5LCB6ICk7XG5cdH0sXG5cblx0LyoqXG4gICAgICogR2l2ZW4gYW4gU1BFLlNoYWRlciBhdHRyaWJ1dGUgaW5zdGFuY2UsIGFuZCB2YXJpb3VzIG90aGVyIHNldHRpbmdzLFxuICAgICAqIGFzc2lnbiBDb2xvciB2YWx1ZXMgdG8gdGhlIGF0dHJpYnV0ZS5cbiAgICAgKiBAcGFyYW0gIHtPYmplY3R9IGF0dHJpYnV0ZSBUaGUgaW5zdGFuY2Ugb2YgU1BFLlNoYWRlckF0dHJpYnV0ZSB0byBzYXZlIHRoZSByZXN1bHQgdG8uXG4gICAgICogQHBhcmFtICB7TnVtYmVyfSBpbmRleCAgICAgVGhlIG9mZnNldCBpbiB0aGUgYXR0cmlidXRlJ3MgVHlwZWRBcnJheSB0byBzYXZlIHRoZSByZXN1bHQgZnJvbS5cbiAgICAgKiBAcGFyYW0gIHtPYmplY3R9IGJhc2UgICAgICBUSFJFRS5Db2xvciBpbnN0YW5jZSBkZXNjcmliaW5nIHRoZSBzdGFydCBjb2xvci5cbiAgICAgKiBAcGFyYW0gIHtPYmplY3R9IHNwcmVhZCAgICBUSFJFRS5WZWN0b3IzIGluc3RhbmNlIGRlc2NyaWJpbmcgdGhlIHJhbmRvbSB2YXJpYW5jZSB0byBhcHBseSB0byB0aGUgc3RhcnQgY29sb3IuXG4gICAgICovXG5cdHJhbmRvbUNvbG9yKCBhdHRyaWJ1dGUsIGluZGV4LCBiYXNlLCBzcHJlYWQgKSB7XG5cdFx0J3VzZSBzdHJpY3QnO1xuXG5cdFx0dmFyIHIgPSBiYXNlLnIgKyAoIE1hdGgucmFuZG9tKCkgKiBzcHJlYWQueCApLFxuXHRcdFx0ZyA9IGJhc2UuZyArICggTWF0aC5yYW5kb20oKSAqIHNwcmVhZC55ICksXG5cdFx0XHRiID0gYmFzZS5iICsgKCBNYXRoLnJhbmRvbSgpICogc3ByZWFkLnogKTtcblxuXHRcdHIgPSB0aGlzLmNsYW1wKCByLCAwLCAxICk7XG5cdFx0ZyA9IHRoaXMuY2xhbXAoIGcsIDAsIDEgKTtcblx0XHRiID0gdGhpcy5jbGFtcCggYiwgMCwgMSApO1xuXG5cblx0XHRhdHRyaWJ1dGUudHlwZWRBcnJheS5zZXRWZWMzQ29tcG9uZW50cyggaW5kZXgsIHIsIGcsIGIgKTtcblx0fSxcblxuXG5cdHJhbmRvbUNvbG9yQXNIZXg6ICggZnVuY3Rpb24oKSB7XG5cdFx0J3VzZSBzdHJpY3QnO1xuXG5cdFx0dmFyIHdvcmtpbmdDb2xvciA9IG5ldyBUSFJFRS5Db2xvcigpO1xuXG5cdFx0LyoqXG4gICAgICAgICAqIEFzc2lnbnMgYSByYW5kb20gY29sb3IgdmFsdWUsIGVuY29kZWQgYXMgYSBoZXggdmFsdWUgaW4gZGVjaW1hbFxuICAgICAgICAgKiBmb3JtYXQsIHRvIGEgU1BFLlNoYWRlckF0dHJpYnV0ZSBpbnN0YW5jZS5cbiAgICAgICAgICogQHBhcmFtICB7T2JqZWN0fSBhdHRyaWJ1dGUgVGhlIGluc3RhbmNlIG9mIFNQRS5TaGFkZXJBdHRyaWJ1dGUgdG8gc2F2ZSB0aGUgcmVzdWx0IHRvLlxuICAgICAgICAgKiBAcGFyYW0gIHtOdW1iZXJ9IGluZGV4ICAgICBUaGUgb2Zmc2V0IGluIHRoZSBhdHRyaWJ1dGUncyBUeXBlZEFycmF5IHRvIHNhdmUgdGhlIHJlc3VsdCBmcm9tLlxuICAgICAgICAgKiBAcGFyYW0gIHtPYmplY3R9IGJhc2UgICAgICBUSFJFRS5Db2xvciBpbnN0YW5jZSBkZXNjcmliaW5nIHRoZSBzdGFydCBjb2xvci5cbiAgICAgICAgICogQHBhcmFtICB7T2JqZWN0fSBzcHJlYWQgICAgVEhSRUUuVmVjdG9yMyBpbnN0YW5jZSBkZXNjcmliaW5nIHRoZSByYW5kb20gdmFyaWFuY2UgdG8gYXBwbHkgdG8gdGhlIHN0YXJ0IGNvbG9yLlxuICAgICAgICAgKi9cblx0XHRyZXR1cm4gZnVuY3Rpb24oIGF0dHJpYnV0ZSwgaW5kZXgsIGJhc2UsIHNwcmVhZCApIHtcblx0XHRcdHZhciBudW1JdGVtcyA9IGJhc2UubGVuZ3RoLFxuXHRcdFx0XHRjb2xvcnMgPSBbXTtcblxuXHRcdFx0Zm9yICggdmFyIGkgPSAwOyBpIDwgbnVtSXRlbXM7ICsraSApIHtcblx0XHRcdFx0dmFyIHNwcmVhZFZlY3RvciA9IHNwcmVhZFsgaSBdO1xuXG5cdFx0XHRcdHdvcmtpbmdDb2xvci5jb3B5KCBiYXNlWyBpIF0gKTtcblxuXHRcdFx0XHR3b3JraW5nQ29sb3IuciArPSAoIE1hdGgucmFuZG9tKCkgKiBzcHJlYWRWZWN0b3IueCApIC0gKCBzcHJlYWRWZWN0b3IueCAqIDAuNSApO1xuXHRcdFx0XHR3b3JraW5nQ29sb3IuZyArPSAoIE1hdGgucmFuZG9tKCkgKiBzcHJlYWRWZWN0b3IueSApIC0gKCBzcHJlYWRWZWN0b3IueSAqIDAuNSApO1xuXHRcdFx0XHR3b3JraW5nQ29sb3IuYiArPSAoIE1hdGgucmFuZG9tKCkgKiBzcHJlYWRWZWN0b3IueiApIC0gKCBzcHJlYWRWZWN0b3IueiAqIDAuNSApO1xuXG5cdFx0XHRcdHdvcmtpbmdDb2xvci5yID0gdGhpcy5jbGFtcCggd29ya2luZ0NvbG9yLnIsIDAsIDEgKTtcblx0XHRcdFx0d29ya2luZ0NvbG9yLmcgPSB0aGlzLmNsYW1wKCB3b3JraW5nQ29sb3IuZywgMCwgMSApO1xuXHRcdFx0XHR3b3JraW5nQ29sb3IuYiA9IHRoaXMuY2xhbXAoIHdvcmtpbmdDb2xvci5iLCAwLCAxICk7XG5cblx0XHRcdFx0Y29sb3JzLnB1c2goIHdvcmtpbmdDb2xvci5nZXRIZXgoKSApO1xuXHRcdFx0fVxuXG5cdFx0XHRhdHRyaWJ1dGUudHlwZWRBcnJheS5zZXRWZWM0Q29tcG9uZW50cyggaW5kZXgsIGNvbG9yc1sgMCBdLCBjb2xvcnNbIDEgXSwgY29sb3JzWyAyIF0sIGNvbG9yc1sgMyBdICk7XG5cdFx0fTtcblx0fSgpICksXG5cblx0LyoqXG4gICAgICogR2l2ZW4gYW4gU1BFLlNoYWRlckF0dHJpYnV0ZSBpbnN0YW5jZSwgYW5kIHZhcmlvdXMgb3RoZXIgc2V0dGluZ3MsXG4gICAgICogYXNzaWduIHZhbHVlcyB0byB0aGUgYXR0cmlidXRlJ3MgYXJyYXkgaW4gYSBgdmVjM2AgZm9ybWF0LlxuICAgICAqXG4gICAgICogQHBhcmFtICB7T2JqZWN0fSBhdHRyaWJ1dGUgICBUaGUgaW5zdGFuY2Ugb2YgU1BFLlNoYWRlckF0dHJpYnV0ZSB0byBzYXZlIHRoZSByZXN1bHQgdG8uXG4gICAgICogQHBhcmFtICB7TnVtYmVyfSBpbmRleCAgICAgICBUaGUgb2Zmc2V0IGluIHRoZSBhdHRyaWJ1dGUncyBUeXBlZEFycmF5IHRvIHNhdmUgdGhlIHJlc3VsdCBmcm9tLlxuICAgICAqIEBwYXJhbSAge09iamVjdH0gc3RhcnQgICAgICAgVEhSRUUuVmVjdG9yMyBpbnN0YW5jZSBkZXNjcmliaW5nIHRoZSBzdGFydCBsaW5lIHBvc2l0aW9uLlxuICAgICAqIEBwYXJhbSAge09iamVjdH0gZW5kICAgICAgICAgVEhSRUUuVmVjdG9yMyBpbnN0YW5jZSBkZXNjcmliaW5nIHRoZSBlbmQgbGluZSBwb3NpdGlvbi5cbiAgICAgKi9cblx0cmFuZG9tVmVjdG9yM09uTGluZSggYXR0cmlidXRlLCBpbmRleCwgc3RhcnQsIGVuZCApIHtcblx0XHQndXNlIHN0cmljdCc7XG5cdFx0dmFyIHBvcyA9IHN0YXJ0LmNsb25lKCk7XG5cblx0XHRwb3MubGVycCggZW5kLCBNYXRoLnJhbmRvbSgpICk7XG5cblx0XHRhdHRyaWJ1dGUudHlwZWRBcnJheS5zZXRWZWMzQ29tcG9uZW50cyggaW5kZXgsIHBvcy54LCBwb3MueSwgcG9zLnogKTtcblx0fSxcblxuXHQvKipcbiAgICAgKiBHaXZlbiBhbiBTUEUuU2hhZGVyIGF0dHJpYnV0ZSBpbnN0YW5jZSwgYW5kIHZhcmlvdXMgb3RoZXIgc2V0dGluZ3MsXG4gICAgICogYXNzaWduIENvbG9yIHZhbHVlcyB0byB0aGUgYXR0cmlidXRlLlxuICAgICAqIEBwYXJhbSAge09iamVjdH0gYXR0cmlidXRlIFRoZSBpbnN0YW5jZSBvZiBTUEUuU2hhZGVyQXR0cmlidXRlIHRvIHNhdmUgdGhlIHJlc3VsdCB0by5cbiAgICAgKiBAcGFyYW0gIHtOdW1iZXJ9IGluZGV4ICAgICBUaGUgb2Zmc2V0IGluIHRoZSBhdHRyaWJ1dGUncyBUeXBlZEFycmF5IHRvIHNhdmUgdGhlIHJlc3VsdCBmcm9tLlxuICAgICAqIEBwYXJhbSAge09iamVjdH0gYmFzZSAgICAgIFRIUkVFLkNvbG9yIGluc3RhbmNlIGRlc2NyaWJpbmcgdGhlIHN0YXJ0IGNvbG9yLlxuICAgICAqIEBwYXJhbSAge09iamVjdH0gc3ByZWFkICAgIFRIUkVFLlZlY3RvcjMgaW5zdGFuY2UgZGVzY3JpYmluZyB0aGUgcmFuZG9tIHZhcmlhbmNlIHRvIGFwcGx5IHRvIHRoZSBzdGFydCBjb2xvci5cbiAgICAgKi9cblxuXHQvKipcbiAgICAgKiBBc3NpZ25zIGEgcmFuZG9tIHZlY3RvciAzIHZhbHVlIHRvIGFuIFNQRS5TaGFkZXJBdHRyaWJ1dGUgaW5zdGFuY2UsIHByb2plY3RpbmcgdGhlXG4gICAgICogZ2l2ZW4gdmFsdWVzIG9udG8gYSBzcGhlcmUuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gIHtPYmplY3R9IGF0dHJpYnV0ZSBUaGUgaW5zdGFuY2Ugb2YgU1BFLlNoYWRlckF0dHJpYnV0ZSB0byBzYXZlIHRoZSByZXN1bHQgdG8uXG4gICAgICogQHBhcmFtICB7TnVtYmVyfSBpbmRleCAgICAgVGhlIG9mZnNldCBpbiB0aGUgYXR0cmlidXRlJ3MgVHlwZWRBcnJheSB0byBzYXZlIHRoZSByZXN1bHQgZnJvbS5cbiAgICAgKiBAcGFyYW0gIHtPYmplY3R9IGJhc2UgICAgICAgICAgICAgIFRIUkVFLlZlY3RvcjMgaW5zdGFuY2UgZGVzY3JpYmluZyB0aGUgb3JpZ2luIG9mIHRoZSB0cmFuc2Zvcm0uXG4gICAgICogQHBhcmFtICB7TnVtYmVyfSByYWRpdXMgICAgICAgICAgICBUaGUgcmFkaXVzIG9mIHRoZSBzcGhlcmUgdG8gcHJvamVjdCBvbnRvLlxuICAgICAqIEBwYXJhbSAge051bWJlcn0gcmFkaXVzU3ByZWFkICAgICAgVGhlIGFtb3VudCBvZiByYW5kb21uZXNzIHRvIGFwcGx5IHRvIHRoZSBwcm9qZWN0aW9uIHJlc3VsdFxuICAgICAqIEBwYXJhbSAge09iamVjdH0gcmFkaXVzU2NhbGUgICAgICAgVEhSRUUuVmVjdG9yMyBpbnN0YW5jZSBkZXNjcmliaW5nIHRoZSBzY2FsZSBvZiBlYWNoIGF4aXMgb2YgdGhlIHNwaGVyZS5cbiAgICAgKiBAcGFyYW0gIHtOdW1iZXJ9IHJhZGl1c1NwcmVhZENsYW1wIFdoYXQgbnVtZXJpYyBtdWx0aXBsZSB0aGUgcHJvamVjdGVkIHZhbHVlIHNob3VsZCBiZSBjbGFtcGVkIHRvLlxuICAgICAqL1xuXHRyYW5kb21WZWN0b3IzT25TcGhlcmUoXG5cdFx0YXR0cmlidXRlLCBpbmRleCwgYmFzZSwgcmFkaXVzLCByYWRpdXNTcHJlYWQsIHJhZGl1c1NjYWxlLCByYWRpdXNTcHJlYWRDbGFtcCwgZGlzdHJpYnV0aW9uQ2xhbXBcblx0KSB7XG5cdFx0J3VzZSBzdHJpY3QnO1xuXG5cdFx0dmFyIGRlcHRoID0gMiAqIE1hdGgucmFuZG9tKCkgLSAxLFxuXHRcdFx0dCA9IDYuMjgzMiAqIE1hdGgucmFuZG9tKCksXG5cdFx0XHRyID0gTWF0aC5zcXJ0KCAxIC0gZGVwdGggKiBkZXB0aCApLFxuXHRcdFx0cmFuZCA9IHRoaXMucmFuZG9tRmxvYXQoIHJhZGl1cywgcmFkaXVzU3ByZWFkICksXG5cdFx0XHR4ID0gMCxcblx0XHRcdHkgPSAwLFxuXHRcdFx0eiA9IDA7XG5cblxuXHRcdGlmICggcmFkaXVzU3ByZWFkQ2xhbXAgKSB7XG5cdFx0XHRyYW5kID0gTWF0aC5yb3VuZCggcmFuZCAvIHJhZGl1c1NwcmVhZENsYW1wICkgKiByYWRpdXNTcHJlYWRDbGFtcDtcblx0XHR9XG5cblxuXG5cdFx0Ly8gU2V0IHBvc2l0aW9uIG9uIHNwaGVyZVxuXHRcdHggPSByICogTWF0aC5jb3MoIHQgKSAqIHJhbmQ7XG5cdFx0eSA9IHIgKiBNYXRoLnNpbiggdCApICogcmFuZDtcblx0XHR6ID0gZGVwdGggKiByYW5kO1xuXG5cdFx0Ly8gQXBwbHkgcmFkaXVzIHNjYWxlIHRvIHRoaXMgcG9zaXRpb25cblx0XHR4ICo9IHJhZGl1c1NjYWxlLng7XG5cdFx0eSAqPSByYWRpdXNTY2FsZS55O1xuXHRcdHogKj0gcmFkaXVzU2NhbGUuejtcblxuXHRcdC8vIFRyYW5zbGF0ZSB0byB0aGUgYmFzZSBwb3NpdGlvbi5cblx0XHR4ICs9IGJhc2UueDtcblx0XHR5ICs9IGJhc2UueTtcblx0XHR6ICs9IGJhc2UuejtcblxuXHRcdC8vIFNldCB0aGUgdmFsdWVzIGluIHRoZSB0eXBlZCBhcnJheS5cblx0XHRhdHRyaWJ1dGUudHlwZWRBcnJheS5zZXRWZWMzQ29tcG9uZW50cyggaW5kZXgsIHgsIHksIHogKTtcblx0fSxcblxuXHRzZWVkZWRSYW5kb20oIHNlZWQgKSB7XG5cdFx0dmFyIHggPSBNYXRoLnNpbiggc2VlZCApICogMTAwMDA7XG5cdFx0cmV0dXJuIHggLSAoIHggfCAwICk7XG5cdH0sXG5cblxuXG5cdC8qKlxuICAgICAqIEFzc2lnbnMgYSByYW5kb20gdmVjdG9yIDMgdmFsdWUgdG8gYW4gU1BFLlNoYWRlckF0dHJpYnV0ZSBpbnN0YW5jZSwgcHJvamVjdGluZyB0aGVcbiAgICAgKiBnaXZlbiB2YWx1ZXMgb250byBhIDJkLWRpc2MuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gIHtPYmplY3R9IGF0dHJpYnV0ZSBUaGUgaW5zdGFuY2Ugb2YgU1BFLlNoYWRlckF0dHJpYnV0ZSB0byBzYXZlIHRoZSByZXN1bHQgdG8uXG4gICAgICogQHBhcmFtICB7TnVtYmVyfSBpbmRleCAgICAgVGhlIG9mZnNldCBpbiB0aGUgYXR0cmlidXRlJ3MgVHlwZWRBcnJheSB0byBzYXZlIHRoZSByZXN1bHQgZnJvbS5cbiAgICAgKiBAcGFyYW0gIHtPYmplY3R9IGJhc2UgICAgICAgICAgICAgIFRIUkVFLlZlY3RvcjMgaW5zdGFuY2UgZGVzY3JpYmluZyB0aGUgb3JpZ2luIG9mIHRoZSB0cmFuc2Zvcm0uXG4gICAgICogQHBhcmFtICB7TnVtYmVyfSByYWRpdXMgICAgICAgICAgICBUaGUgcmFkaXVzIG9mIHRoZSBzcGhlcmUgdG8gcHJvamVjdCBvbnRvLlxuICAgICAqIEBwYXJhbSAge051bWJlcn0gcmFkaXVzU3ByZWFkICAgICAgVGhlIGFtb3VudCBvZiByYW5kb21uZXNzIHRvIGFwcGx5IHRvIHRoZSBwcm9qZWN0aW9uIHJlc3VsdFxuICAgICAqIEBwYXJhbSAge09iamVjdH0gcmFkaXVzU2NhbGUgICAgICAgVEhSRUUuVmVjdG9yMyBpbnN0YW5jZSBkZXNjcmliaW5nIHRoZSBzY2FsZSBvZiBlYWNoIGF4aXMgb2YgdGhlIGRpc2MuIFRoZSB6LWNvbXBvbmVudCBpcyBpZ25vcmVkLlxuICAgICAqIEBwYXJhbSAge051bWJlcn0gcmFkaXVzU3ByZWFkQ2xhbXAgV2hhdCBudW1lcmljIG11bHRpcGxlIHRoZSBwcm9qZWN0ZWQgdmFsdWUgc2hvdWxkIGJlIGNsYW1wZWQgdG8uXG4gICAgICovXG5cdHJhbmRvbVZlY3RvcjNPbkRpc2MoIGF0dHJpYnV0ZSwgaW5kZXgsIGJhc2UsIHJhZGl1cywgcmFkaXVzU3ByZWFkLCByYWRpdXNTY2FsZSwgcmFkaXVzU3ByZWFkQ2xhbXAgKSB7XG5cdFx0J3VzZSBzdHJpY3QnO1xuXG5cdFx0dmFyIHQgPSA2LjI4MzIgKiBNYXRoLnJhbmRvbSgpLFxuXHRcdFx0cmFuZCA9IE1hdGguYWJzKCB0aGlzLnJhbmRvbUZsb2F0KCByYWRpdXMsIHJhZGl1c1NwcmVhZCApICksXG5cdFx0XHR4ID0gMCxcblx0XHRcdHkgPSAwLFxuXHRcdFx0eiA9IDA7XG5cblx0XHRpZiAoIHJhZGl1c1NwcmVhZENsYW1wICkge1xuXHRcdFx0cmFuZCA9IE1hdGgucm91bmQoIHJhbmQgLyByYWRpdXNTcHJlYWRDbGFtcCApICogcmFkaXVzU3ByZWFkQ2xhbXA7XG5cdFx0fVxuXG5cdFx0Ly8gU2V0IHBvc2l0aW9uIG9uIHNwaGVyZVxuXHRcdHggPSBNYXRoLmNvcyggdCApICogcmFuZDtcblx0XHR5ID0gTWF0aC5zaW4oIHQgKSAqIHJhbmQ7XG5cblx0XHQvLyBBcHBseSByYWRpdXMgc2NhbGUgdG8gdGhpcyBwb3NpdGlvblxuXHRcdHggKj0gcmFkaXVzU2NhbGUueDtcblx0XHR5ICo9IHJhZGl1c1NjYWxlLnk7XG5cblx0XHQvLyBUcmFuc2xhdGUgdG8gdGhlIGJhc2UgcG9zaXRpb24uXG5cdFx0eCArPSBiYXNlLng7XG5cdFx0eSArPSBiYXNlLnk7XG5cdFx0eiArPSBiYXNlLno7XG5cblx0XHQvLyBTZXQgdGhlIHZhbHVlcyBpbiB0aGUgdHlwZWQgYXJyYXkuXG5cdFx0YXR0cmlidXRlLnR5cGVkQXJyYXkuc2V0VmVjM0NvbXBvbmVudHMoIGluZGV4LCB4LCB5LCB6ICk7XG5cdH0sXG5cblx0cmFuZG9tRGlyZWN0aW9uVmVjdG9yM09uU3BoZXJlOiAoIGZ1bmN0aW9uKCkge1xuXHRcdCd1c2Ugc3RyaWN0JztcblxuXHRcdHZhciB2ID0gbmV3IFRIUkVFLlZlY3RvcjMoKTtcblxuXHRcdC8qKlxuICAgICAgICAgKiBHaXZlbiBhbiBTUEUuU2hhZGVyQXR0cmlidXRlIGluc3RhbmNlLCBjcmVhdGUgYSBkaXJlY3Rpb24gdmVjdG9yIGZyb20gdGhlIGdpdmVuXG4gICAgICAgICAqIHBvc2l0aW9uLCB1c2luZyBgc3BlZWRgIGFzIHRoZSBtYWduaXR1ZGUuIFZhbHVlcyBhcmUgc2F2ZWQgdG8gdGhlIGF0dHJpYnV0ZS5cbiAgICAgICAgICpcbiAgICAgICAgICogQHBhcmFtICB7T2JqZWN0fSBhdHRyaWJ1dGUgICAgICAgVGhlIGluc3RhbmNlIG9mIFNQRS5TaGFkZXJBdHRyaWJ1dGUgdG8gc2F2ZSB0aGUgcmVzdWx0IHRvLlxuICAgICAgICAgKiBAcGFyYW0gIHtOdW1iZXJ9IGluZGV4ICAgICAgICAgICBUaGUgb2Zmc2V0IGluIHRoZSBhdHRyaWJ1dGUncyBUeXBlZEFycmF5IHRvIHNhdmUgdGhlIHJlc3VsdCBmcm9tLlxuICAgICAgICAgKiBAcGFyYW0gIHtOdW1iZXJ9IHBvc1ggICAgICAgICAgICBUaGUgcGFydGljbGUncyB4IGNvb3JkaW5hdGUuXG4gICAgICAgICAqIEBwYXJhbSAge051bWJlcn0gcG9zWSAgICAgICAgICAgIFRoZSBwYXJ0aWNsZSdzIHkgY29vcmRpbmF0ZS5cbiAgICAgICAgICogQHBhcmFtICB7TnVtYmVyfSBwb3NaICAgICAgICAgICAgVGhlIHBhcnRpY2xlJ3MgeiBjb29yZGluYXRlLlxuICAgICAgICAgKiBAcGFyYW0gIHtPYmplY3R9IGVtaXR0ZXJQb3NpdGlvbiBUSFJFRS5WZWN0b3IzIGluc3RhbmNlIGRlc2NyaWJpbmcgdGhlIGVtaXR0ZXIncyBiYXNlIHBvc2l0aW9uLlxuICAgICAgICAgKiBAcGFyYW0gIHtOdW1iZXJ9IHNwZWVkICAgICAgICAgICBUaGUgbWFnbml0dWRlIHRvIGFwcGx5IHRvIHRoZSB2ZWN0b3IuXG4gICAgICAgICAqIEBwYXJhbSAge051bWJlcn0gc3BlZWRTcHJlYWQgICAgIFRoZSBhbW91bnQgb2YgcmFuZG9tbmVzcyB0byBhcHBseSB0byB0aGUgbWFnbml0dWRlLlxuICAgICAgICAgKi9cblx0XHRyZXR1cm4gZnVuY3Rpb24oIGF0dHJpYnV0ZSwgaW5kZXgsIHBvc1gsIHBvc1ksIHBvc1osIGVtaXR0ZXJQb3NpdGlvbiwgc3BlZWQsIHNwZWVkU3ByZWFkICkge1xuXHRcdFx0di5jb3B5KCBlbWl0dGVyUG9zaXRpb24gKTtcblxuXHRcdFx0di54IC09IHBvc1g7XG5cdFx0XHR2LnkgLT0gcG9zWTtcblx0XHRcdHYueiAtPSBwb3NaO1xuXG5cdFx0XHR2Lm5vcm1hbGl6ZSgpLm11bHRpcGx5U2NhbGFyKCAtdGhpcy5yYW5kb21GbG9hdCggc3BlZWQsIHNwZWVkU3ByZWFkICkgKTtcblxuXHRcdFx0YXR0cmlidXRlLnR5cGVkQXJyYXkuc2V0VmVjM0NvbXBvbmVudHMoIGluZGV4LCB2LngsIHYueSwgdi56ICk7XG5cdFx0fTtcblx0fSgpICksXG5cblxuXHRyYW5kb21EaXJlY3Rpb25WZWN0b3IzT25EaXNjOiAoIGZ1bmN0aW9uKCkge1xuXHRcdCd1c2Ugc3RyaWN0JztcblxuXHRcdHZhciB2ID0gbmV3IFRIUkVFLlZlY3RvcjMoKTtcblxuXHRcdC8qKlxuICAgICAgICAgKiBHaXZlbiBhbiBTUEUuU2hhZGVyQXR0cmlidXRlIGluc3RhbmNlLCBjcmVhdGUgYSBkaXJlY3Rpb24gdmVjdG9yIGZyb20gdGhlIGdpdmVuXG4gICAgICAgICAqIHBvc2l0aW9uLCB1c2luZyBgc3BlZWRgIGFzIHRoZSBtYWduaXR1ZGUuIFZhbHVlcyBhcmUgc2F2ZWQgdG8gdGhlIGF0dHJpYnV0ZS5cbiAgICAgICAgICpcbiAgICAgICAgICogQHBhcmFtICB7T2JqZWN0fSBhdHRyaWJ1dGUgICAgICAgVGhlIGluc3RhbmNlIG9mIFNQRS5TaGFkZXJBdHRyaWJ1dGUgdG8gc2F2ZSB0aGUgcmVzdWx0IHRvLlxuICAgICAgICAgKiBAcGFyYW0gIHtOdW1iZXJ9IGluZGV4ICAgICAgICAgICBUaGUgb2Zmc2V0IGluIHRoZSBhdHRyaWJ1dGUncyBUeXBlZEFycmF5IHRvIHNhdmUgdGhlIHJlc3VsdCBmcm9tLlxuICAgICAgICAgKiBAcGFyYW0gIHtOdW1iZXJ9IHBvc1ggICAgICAgICAgICBUaGUgcGFydGljbGUncyB4IGNvb3JkaW5hdGUuXG4gICAgICAgICAqIEBwYXJhbSAge051bWJlcn0gcG9zWSAgICAgICAgICAgIFRoZSBwYXJ0aWNsZSdzIHkgY29vcmRpbmF0ZS5cbiAgICAgICAgICogQHBhcmFtICB7TnVtYmVyfSBwb3NaICAgICAgICAgICAgVGhlIHBhcnRpY2xlJ3MgeiBjb29yZGluYXRlLlxuICAgICAgICAgKiBAcGFyYW0gIHtPYmplY3R9IGVtaXR0ZXJQb3NpdGlvbiBUSFJFRS5WZWN0b3IzIGluc3RhbmNlIGRlc2NyaWJpbmcgdGhlIGVtaXR0ZXIncyBiYXNlIHBvc2l0aW9uLlxuICAgICAgICAgKiBAcGFyYW0gIHtOdW1iZXJ9IHNwZWVkICAgICAgICAgICBUaGUgbWFnbml0dWRlIHRvIGFwcGx5IHRvIHRoZSB2ZWN0b3IuXG4gICAgICAgICAqIEBwYXJhbSAge051bWJlcn0gc3BlZWRTcHJlYWQgICAgIFRoZSBhbW91bnQgb2YgcmFuZG9tbmVzcyB0byBhcHBseSB0byB0aGUgbWFnbml0dWRlLlxuICAgICAgICAgKi9cblx0XHRyZXR1cm4gZnVuY3Rpb24oIGF0dHJpYnV0ZSwgaW5kZXgsIHBvc1gsIHBvc1ksIHBvc1osIGVtaXR0ZXJQb3NpdGlvbiwgc3BlZWQsIHNwZWVkU3ByZWFkICkge1xuXHRcdFx0di5jb3B5KCBlbWl0dGVyUG9zaXRpb24gKTtcblxuXHRcdFx0di54IC09IHBvc1g7XG5cdFx0XHR2LnkgLT0gcG9zWTtcblx0XHRcdHYueiAtPSBwb3NaO1xuXG5cdFx0XHR2Lm5vcm1hbGl6ZSgpLm11bHRpcGx5U2NhbGFyKCAtdGhpcy5yYW5kb21GbG9hdCggc3BlZWQsIHNwZWVkU3ByZWFkICkgKTtcblxuXHRcdFx0YXR0cmlidXRlLnR5cGVkQXJyYXkuc2V0VmVjM0NvbXBvbmVudHMoIGluZGV4LCB2LngsIHYueSwgMCApO1xuXHRcdH07XG5cdH0oKSApLFxuXG5cdGdldFBhY2tlZFJvdGF0aW9uQXhpczogKCBmdW5jdGlvbigpIHtcblx0XHQndXNlIHN0cmljdCc7XG5cblx0XHR2YXIgdiA9IG5ldyBUSFJFRS5WZWN0b3IzKCksXG5cdFx0XHR2U3ByZWFkID0gbmV3IFRIUkVFLlZlY3RvcjMoKSxcblx0XHRcdGMgPSBuZXcgVEhSRUUuQ29sb3IoKSxcblx0XHRcdGFkZE9uZSA9IG5ldyBUSFJFRS5WZWN0b3IzKCAxLCAxLCAxICk7XG5cblx0XHQvKipcbiAgICAgICAgICogR2l2ZW4gYSByb3RhdGlvbiBheGlzLCBhbmQgYSByb3RhdGlvbiBheGlzIHNwcmVhZCB2ZWN0b3IsXG4gICAgICAgICAqIGNhbGN1bGF0ZSBhIHJhbmRvbWlzZWQgcm90YXRpb24gYXhpcywgYW5kIHBhY2sgaXQgaW50b1xuICAgICAgICAgKiBhIGhleGFkZWNpbWFsIHZhbHVlIHJlcHJlc2VudGVkIGluIGRlY2ltYWwgZm9ybS5cbiAgICAgICAgICogQHBhcmFtICB7T2JqZWN0fSBheGlzICAgICAgIFRIUkVFLlZlY3RvcjMgaW5zdGFuY2UgZGVzY3JpYmluZyB0aGUgcm90YXRpb24gYXhpcy5cbiAgICAgICAgICogQHBhcmFtICB7T2JqZWN0fSBheGlzU3ByZWFkIFRIUkVFLlZlY3RvcjMgaW5zdGFuY2UgZGVzY3JpYmluZyB0aGUgYW1vdW50IG9mIHJhbmRvbW5lc3MgdG8gYXBwbHkgdG8gdGhlIHJvdGF0aW9uIGF4aXMuXG4gICAgICAgICAqIEByZXR1cm4ge051bWJlcn0gICAgICAgICAgICBUaGUgcGFja2VkIHJvdGF0aW9uIGF4aXMsIHdpdGggcmFuZG9tbmVzcy5cbiAgICAgICAgICovXG5cdFx0cmV0dXJuIGZ1bmN0aW9uKCBheGlzLCBheGlzU3ByZWFkICkge1xuXHRcdFx0di5jb3B5KCBheGlzICkubm9ybWFsaXplKCk7XG5cdFx0XHR2U3ByZWFkLmNvcHkoIGF4aXNTcHJlYWQgKS5ub3JtYWxpemUoKTtcblxuXHRcdFx0di54ICs9ICggLWF4aXNTcHJlYWQueCAqIDAuNSApICsgKCBNYXRoLnJhbmRvbSgpICogYXhpc1NwcmVhZC54ICk7XG5cdFx0XHR2LnkgKz0gKCAtYXhpc1NwcmVhZC55ICogMC41ICkgKyAoIE1hdGgucmFuZG9tKCkgKiBheGlzU3ByZWFkLnkgKTtcblx0XHRcdHYueiArPSAoIC1heGlzU3ByZWFkLnogKiAwLjUgKSArICggTWF0aC5yYW5kb20oKSAqIGF4aXNTcHJlYWQueiApO1xuXG5cdFx0XHQvLyB2LnggPSBNYXRoLmFicyggdi54ICk7XG5cdFx0XHQvLyB2LnkgPSBNYXRoLmFicyggdi55ICk7XG5cdFx0XHQvLyB2LnogPSBNYXRoLmFicyggdi56ICk7XG5cblx0XHRcdHYubm9ybWFsaXplKCkuYWRkKCBhZGRPbmUgKVxuXHRcdFx0XHQubXVsdGlwbHlTY2FsYXIoIDAuNSApO1xuXG5cdFx0XHRjLnNldFJHQiggdi54LCB2LnksIHYueiApO1xuXG5cdFx0XHRyZXR1cm4gYy5nZXRIZXgoKTtcblx0XHR9O1xuXHR9KCkgKSxcbn07XG4iLCJleHBvcnQgZGVmYXVsdCB7XG5cblx0LyoqXG5cdCAqIEJvb2xlYW4gdHlwZS5cblx0ICogQHR5cGUge1N0cmluZ31cblx0ICovXG5cdEJPT0xFQU46ICdib29sZWFuJyxcblxuXHQvKipcblx0ICogU3RyaW5nIHR5cGUuXG5cdCAqIEB0eXBlIHtTdHJpbmd9XG5cdCAqL1xuXHRTVFJJTkc6ICdzdHJpbmcnLFxuXG5cdC8qKlxuXHQgKiBOdW1iZXIgdHlwZS5cblx0ICogQHR5cGUge1N0cmluZ31cblx0ICovXG5cdE5VTUJFUjogJ251bWJlcicsXG5cblx0LyoqXG5cdCAqIE9iamVjdCB0eXBlLlxuXHQgKiBAdHlwZSB7U3RyaW5nfVxuXHQgKi9cblx0T0JKRUNUOiAnb2JqZWN0Jyxcbn07XG4iLCJleHBvcnQgZGVmYXVsdCB7XG5cdHZhbHVlT3ZlckxpZmV0aW1lTGVuZ3RoOiA0LFxufTsiLCIvKipcbiAqIEEgaGVscGVyIGNsYXNzIGZvciBUeXBlZEFycmF5cy5cbiAqXG4gKiBBbGxvd3MgZm9yIGVhc3kgcmVzaXppbmcsIGFzc2lnbm1lbnQgb2YgdmFyaW91cyBjb21wb25lbnQtYmFzZWRcbiAqIHR5cGVzIChWZWN0b3IycywgVmVjdG9yM3MsIFZlY3RvcjRzLCBNYXQzcywgTWF0NHMpLFxuICogYXMgd2VsbCBhcyBDb2xvcnMgKHdoZXJlIGNvbXBvbmVudHMgYXJlIGByYCwgYGdgLCBgYmApLFxuICogTnVtYmVycywgYW5kIHNldHRpbmcgZnJvbSBvdGhlciBUeXBlZEFycmF5cy5cbiAqXG4gKiBAYXV0aG9yIEx1a2UgTW9vZHlcbiAqIEBjb25zdHJ1Y3RvclxuICogQHBhcmFtIHtGdW5jdGlvbn0gVHlwZWRBcnJheUNvbnN0cnVjdG9yIFRoZSBjb25zdHJ1Y3RvciB0byB1c2UgKEZsb2F0MzJBcnJheSwgVWludDhBcnJheSwgZXRjLilcbiAqIEBwYXJhbSB7TnVtYmVyfSBzaXplICAgICAgICAgICAgICAgICBUaGUgc2l6ZSBvZiB0aGUgYXJyYXkgdG8gY3JlYXRlXG4gKiBAcGFyYW0ge051bWJlcn0gY29tcG9uZW50U2l6ZSAgICAgICAgVGhlIG51bWJlciBvZiBjb21wb25lbnRzIHBlci12YWx1ZSAoaWUuIDMgZm9yIGEgdmVjMywgOSBmb3IgYSBNYXQzLCBldGMuKVxuICogQHBhcmFtIHtOdW1iZXJ9IGluZGV4T2Zmc2V0ICAgICAgICAgIFRoZSBpbmRleCBpbiB0aGUgYXJyYXkgZnJvbSB3aGljaCB0byBzdGFydCBhc3NpZ25pbmcgdmFsdWVzLiBEZWZhdWx0IGAwYCBpZiBub25lIHByb3ZpZGVkXG4gKi9cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIFR5cGVkQXJyYXlIZWxwZXIge1xuXHRjb25zdHJ1Y3RvciggVHlwZWRBcnJheUNvbnN0cnVjdG9yLCBzaXplLCBjb21wb25lbnRTaXplLCBpbmRleE9mZnNldCApIHtcblx0XHR0aGlzLmNvbXBvbmVudFNpemUgPSBjb21wb25lbnRTaXplIHx8IDE7XG5cdFx0dGhpcy5zaXplID0gKCBzaXplIHx8IDEgKTtcblx0XHR0aGlzLlR5cGVkQXJyYXlDb25zdHJ1Y3RvciA9IFR5cGVkQXJyYXlDb25zdHJ1Y3RvciB8fCBGbG9hdDMyQXJyYXk7XG5cdFx0dGhpcy5hcnJheSA9IG5ldyBUeXBlZEFycmF5Q29uc3RydWN0b3IoIHNpemUgKiB0aGlzLmNvbXBvbmVudFNpemUgKTtcblx0XHR0aGlzLmluZGV4T2Zmc2V0ID0gaW5kZXhPZmZzZXQgfHwgMDtcblx0fVxuXG5cdC8qKlxuXHQgKiBTZXRzIHRoZSBzaXplIG9mIHRoZSBpbnRlcm5hbCBhcnJheS5cblx0ICpcblx0ICogRGVsZWdhdGVzIHRvIGB0aGlzLnNocmlua2Agb3IgYHRoaXMuZ3Jvd2AgZGVwZW5kaW5nIG9uIHNpemVcblx0ICogYXJndW1lbnQncyByZWxhdGlvbiB0byB0aGUgY3VycmVudCBzaXplIG9mIHRoZSBpbnRlcm5hbCBhcnJheS5cblx0ICpcblx0ICogTm90ZSB0aGF0IGlmIHRoZSBhcnJheSBpcyB0byBiZSBzaHJ1bmssIGRhdGEgd2lsbCBiZSBsb3N0LlxuXHQgKlxuXHQgKiBAcGFyYW0ge051bWJlcn0gc2l6ZSBUaGUgbmV3IHNpemUgb2YgdGhlIGFycmF5LlxuXHQgKi9cblx0c2V0U2l6ZSggcywgbm9Db21wb25lbnRNdWx0aXBseSApIHtcblx0XHRjb25zdCBjdXJyZW50QXJyYXlTaXplID0gdGhpcy5hcnJheS5sZW5ndGg7XG5cdFx0bGV0IHNpemUgPSBzO1xuXG5cdFx0aWYgKCAhbm9Db21wb25lbnRNdWx0aXBseSApIHtcblx0XHRcdHNpemUgPSBzaXplICogdGhpcy5jb21wb25lbnRTaXplO1xuXHRcdH1cblxuXHRcdGlmICggc2l6ZSA8IGN1cnJlbnRBcnJheVNpemUgKSB7XG5cdFx0XHRyZXR1cm4gdGhpcy5zaHJpbmsoIHNpemUgKTtcblx0XHR9XG5cdFx0ZWxzZSBpZiAoIHNpemUgPiBjdXJyZW50QXJyYXlTaXplICkge1xuXHRcdFx0cmV0dXJuIHRoaXMuZ3Jvdyggc2l6ZSApO1xuXHRcdH1cblx0XHRlbHNlIHtcblx0XHRcdGNvbnNvbGUuaW5mbyggJ1R5cGVkQXJyYXkgaXMgYWxyZWFkeSBvZiBzaXplOicsIHNpemUgKyAnLicsICdXaWxsIG5vdCByZXNpemUuJyApO1xuXHRcdH1cblx0fVxuXG5cdC8qKlxuXHQgKiBTaHJpbmtzIHRoZSBpbnRlcm5hbCBhcnJheS5cblx0ICpcblx0ICogQHBhcmFtICB7TnVtYmVyfSBzaXplIFRoZSBuZXcgc2l6ZSBvZiB0aGUgdHlwZWQgYXJyYXkuIE11c3QgYmUgc21hbGxlciB0aGFuIGB0aGlzLmFycmF5Lmxlbmd0aGAuXG5cdCAqIEByZXR1cm4ge1NQRS5UeXBlZEFycmF5SGVscGVyfSAgICAgIEluc3RhbmNlIG9mIHRoaXMgY2xhc3MuXG5cdCAqL1xuXHRzaHJpbmsoIHNpemUgKSB7XG5cdFx0dGhpcy5hcnJheSA9IHRoaXMuYXJyYXkuc3ViYXJyYXkoIDAsIHNpemUgKTtcblx0XHR0aGlzLnNpemUgPSBzaXplO1xuXHRcdHJldHVybiB0aGlzO1xuXHR9XG5cblx0LyoqXG5cdCAqIEdyb3dzIHRoZSBpbnRlcm5hbCBhcnJheS5cblx0ICogQHBhcmFtICB7TnVtYmVyfSBzaXplIFRoZSBuZXcgc2l6ZSBvZiB0aGUgdHlwZWQgYXJyYXkuIE11c3QgYmUgbGFyZ2VyIHRoYW4gYHRoaXMuYXJyYXkubGVuZ3RoYC5cblx0ICogQHJldHVybiB7U1BFLlR5cGVkQXJyYXlIZWxwZXJ9ICAgICAgSW5zdGFuY2Ugb2YgdGhpcyBjbGFzcy5cblx0ICovXG5cdGdyb3coIHNpemUgKSB7XG5cdFx0Y29uc3QgbmV3QXJyYXkgPSBuZXcgdGhpcy5UeXBlZEFycmF5Q29uc3RydWN0b3IoIHNpemUgKTtcblxuXHRcdG5ld0FycmF5LnNldCggdGhpcy5hcnJheSApO1xuXHRcdHRoaXMuYXJyYXkgPSBuZXdBcnJheTtcblx0XHR0aGlzLnNpemUgPSBzaXplO1xuXG5cdFx0cmV0dXJuIHRoaXM7XG5cdH1cblxuXG5cdC8qKlxuXHQgKiBQZXJmb3JtIGEgc3BsaWNlIG9wZXJhdGlvbiBvbiB0aGlzIGFycmF5J3MgYnVmZmVyLlxuXHQgKiBAcGFyYW0gIHtOdW1iZXJ9IHN0YXJ0IFRoZSBzdGFydCBpbmRleCBvZiB0aGUgc3BsaWNlLiBXaWxsIGJlIG11bHRpcGxpZWQgYnkgdGhlIG51bWJlciBvZiBjb21wb25lbnRzIGZvciB0aGlzIGF0dHJpYnV0ZS5cblx0ICogQHBhcmFtICB7TnVtYmVyfSBlbmQgVGhlIGVuZCBpbmRleCBvZiB0aGUgc3BsaWNlLiBXaWxsIGJlIG11bHRpcGxpZWQgYnkgdGhlIG51bWJlciBvZiBjb21wb25lbnRzIGZvciB0aGlzIGF0dHJpYnV0ZS5cblx0ICogQHJldHVybnMge09iamVjdH0gVGhlIFNQRS5UeXBlZEFycmF5SGVscGVyIGluc3RhbmNlLlxuXHQgKi9cblx0c3BsaWNlKCBzdGFydCwgZW5kICkge1xuXHRcdGNvbnN0IF9zdGFydCA9IHN0YXJ0ICogdGhpcy5jb21wb25lbnRTaXplLFxuXHRcdFx0X2VuZCA9IGVuZCAqIHRoaXMuY29tcG9uZW50U2l6ZTtcblxuXHRcdGNvbnN0IGRhdGEgPSBbXSxcblx0XHRcdGFycmF5ID0gdGhpcy5hcnJheSxcblx0XHRcdHNpemUgPSBhcnJheS5sZW5ndGg7XG5cblx0XHRmb3IgKCBsZXQgaSA9IDA7IGkgPCBzaXplOyArK2kgKSB7XG5cdFx0XHRpZiAoIGkgPCBfc3RhcnQgfHwgaSA+PSBfZW5kICkge1xuXHRcdFx0XHRkYXRhLnB1c2goIGFycmF5WyBpIF0gKTtcblx0XHRcdH1cblx0XHRcdC8vIGFycmF5WyBpIF0gPSAwO1xuXHRcdH1cblxuXHRcdHRoaXMuc2V0RnJvbUFycmF5KCAwLCBkYXRhICk7XG5cblx0XHRyZXR1cm4gdGhpcztcblx0fVxuXG5cblx0LyoqXG5cdCAqIENvcGllcyBmcm9tIHRoZSBnaXZlbiBUeXBlZEFycmF5IGludG8gdGhpcyBvbmUsIHVzaW5nIHRoZSBpbmRleCBhcmd1bWVudFxuXHQgKiBhcyB0aGUgc3RhcnQgcG9zaXRpb24uIEFsaWFzIGZvciBgVHlwZWRBcnJheS5zZXRgLiBXaWxsIGF1dG9tYXRpY2FsbHkgcmVzaXplXG5cdCAqIGlmIHRoZSBnaXZlbiBzb3VyY2UgYXJyYXkgaXMgb2YgYSBsYXJnZXIgc2l6ZSB0aGFuIHRoZSBpbnRlcm5hbCBhcnJheS5cblx0ICpcblx0ICogQHBhcmFtIHtOdW1iZXJ9IGluZGV4ICAgICAgVGhlIHN0YXJ0IHBvc2l0aW9uIGZyb20gd2hpY2ggdG8gY29weSBpbnRvIHRoaXMgYXJyYXkuXG5cdCAqIEBwYXJhbSB7VHlwZWRBcnJheX0gYXJyYXkgVGhlIGFycmF5IGZyb20gd2hpY2ggdG8gY29weTsgdGhlIHNvdXJjZSBhcnJheS5cblx0ICogQHJldHVybiB7U1BFLlR5cGVkQXJyYXlIZWxwZXJ9IEluc3RhbmNlIG9mIHRoaXMgY2xhc3MuXG5cdCAqL1xuXHRzZXRGcm9tQXJyYXkoIGluZGV4LCBhcnJheSApIHtcblx0XHRjb25zdCBzb3VyY2VBcnJheVNpemUgPSBhcnJheS5sZW5ndGgsXG5cdFx0XHRuZXdTaXplID0gaW5kZXggKyBzb3VyY2VBcnJheVNpemU7XG5cblx0XHRpZiAoIG5ld1NpemUgPiB0aGlzLmFycmF5Lmxlbmd0aCApIHtcblx0XHRcdHRoaXMuZ3JvdyggbmV3U2l6ZSApO1xuXHRcdH1cblx0XHRlbHNlIGlmICggbmV3U2l6ZSA8IHRoaXMuYXJyYXkubGVuZ3RoICkge1xuXHRcdFx0dGhpcy5zaHJpbmsoIG5ld1NpemUgKTtcblx0XHR9XG5cblx0XHR0aGlzLmFycmF5LnNldCggYXJyYXksIHRoaXMuaW5kZXhPZmZzZXQgKyBpbmRleCApO1xuXG5cdFx0cmV0dXJuIHRoaXM7XG5cdH1cblxuXHQvKipcblx0ICogU2V0IGEgVmVjdG9yMiB2YWx1ZSBhdCBgaW5kZXhgLlxuXHQgKlxuXHQgKiBAcGFyYW0ge051bWJlcn0gaW5kZXggVGhlIGluZGV4IGF0IHdoaWNoIHRvIHNldCB0aGUgdmVjMiB2YWx1ZXMgZnJvbS5cblx0ICogQHBhcmFtIHtWZWN0b3IyfSB2ZWMyICBBbnkgb2JqZWN0IHRoYXQgaGFzIGB4YCBhbmQgYHlgIHByb3BlcnRpZXMuXG5cdCAqIEByZXR1cm4ge1NQRS5UeXBlZEFycmF5SGVscGVyfSBJbnN0YW5jZSBvZiB0aGlzIGNsYXNzLlxuXHQgKi9cblx0c2V0VmVjMiggaW5kZXgsIHZlYzIgKSB7XG5cdFx0cmV0dXJuIHRoaXMuc2V0VmVjMkNvbXBvbmVudHMoIGluZGV4LCB2ZWMyLngsIHZlYzIueSApO1xuXHR9XG5cblx0LyoqXG5cdCAqIFNldCBhIFZlY3RvcjIgdmFsdWUgdXNpbmcgcmF3IGNvbXBvbmVudHMuXG5cdCAqXG5cdCAqIEBwYXJhbSB7TnVtYmVyfSBpbmRleCBUaGUgaW5kZXggYXQgd2hpY2ggdG8gc2V0IHRoZSB2ZWMyIHZhbHVlcyBmcm9tLlxuXHQgKiBAcGFyYW0ge051bWJlcn0geCAgICAgVGhlIFZlYzIncyBgeGAgY29tcG9uZW50LlxuXHQgKiBAcGFyYW0ge051bWJlcn0geSAgICAgVGhlIFZlYzIncyBgeWAgY29tcG9uZW50LlxuXHQgKiBAcmV0dXJuIHtTUEUuVHlwZWRBcnJheUhlbHBlcn0gSW5zdGFuY2Ugb2YgdGhpcyBjbGFzcy5cblx0ICovXG5cdHNldFZlYzJDb21wb25lbnRzKCBpbmRleCwgeCwgeSApIHtcblx0XHRjb25zdCBhcnJheSA9IHRoaXMuYXJyYXksXG5cdFx0XHRpID0gdGhpcy5pbmRleE9mZnNldCArICggaW5kZXggKiB0aGlzLmNvbXBvbmVudFNpemUgKTtcblxuXHRcdGFycmF5WyBpIF0gPSB4O1xuXHRcdGFycmF5WyBpICsgMSBdID0geTtcblx0XHRyZXR1cm4gdGhpcztcblx0fVxuXG5cdC8qKlxuXHQgKiBTZXQgYSBWZWN0b3IzIHZhbHVlIGF0IGBpbmRleGAuXG5cdCAqXG5cdCAqIEBwYXJhbSB7TnVtYmVyfSBpbmRleCBUaGUgaW5kZXggYXQgd2hpY2ggdG8gc2V0IHRoZSB2ZWMzIHZhbHVlcyBmcm9tLlxuXHQgKiBAcGFyYW0ge1ZlY3RvcjN9IHZlYzIgIEFueSBvYmplY3QgdGhhdCBoYXMgYHhgLCBgeWAsIGFuZCBgemAgcHJvcGVydGllcy5cblx0ICogQHJldHVybiB7U1BFLlR5cGVkQXJyYXlIZWxwZXJ9IEluc3RhbmNlIG9mIHRoaXMgY2xhc3MuXG5cdCAqL1xuXHRzZXRWZWMzKCBpbmRleCwgdmVjMyApIHtcblx0XHRyZXR1cm4gdGhpcy5zZXRWZWMzQ29tcG9uZW50cyggaW5kZXgsIHZlYzMueCwgdmVjMy55LCB2ZWMzLnogKTtcblx0fVxuXG5cdC8qKlxuXHQgKiBTZXQgYSBWZWN0b3IzIHZhbHVlIHVzaW5nIHJhdyBjb21wb25lbnRzLlxuXHQgKlxuXHQgKiBAcGFyYW0ge051bWJlcn0gaW5kZXggVGhlIGluZGV4IGF0IHdoaWNoIHRvIHNldCB0aGUgdmVjMyB2YWx1ZXMgZnJvbS5cblx0ICogQHBhcmFtIHtOdW1iZXJ9IHggICAgIFRoZSBWZWMzJ3MgYHhgIGNvbXBvbmVudC5cblx0ICogQHBhcmFtIHtOdW1iZXJ9IHkgICAgIFRoZSBWZWMzJ3MgYHlgIGNvbXBvbmVudC5cblx0ICogQHBhcmFtIHtOdW1iZXJ9IHogICAgIFRoZSBWZWMzJ3MgYHpgIGNvbXBvbmVudC5cblx0ICogQHJldHVybiB7U1BFLlR5cGVkQXJyYXlIZWxwZXJ9IEluc3RhbmNlIG9mIHRoaXMgY2xhc3MuXG5cdCAqL1xuXHRzZXRWZWMzQ29tcG9uZW50cyggaW5kZXgsIHgsIHksIHogKSB7XG5cdFx0Y29uc3QgYXJyYXkgPSB0aGlzLmFycmF5LFxuXHRcdFx0aSA9IHRoaXMuaW5kZXhPZmZzZXQgKyAoIGluZGV4ICogdGhpcy5jb21wb25lbnRTaXplICk7XG5cblx0XHRhcnJheVsgaSBdID0geDtcblx0XHRhcnJheVsgaSArIDEgXSA9IHk7XG5cdFx0YXJyYXlbIGkgKyAyIF0gPSB6O1xuXHRcdHJldHVybiB0aGlzO1xuXHR9XG5cblx0LyoqXG5cdCAqIFNldCBhIFZlY3RvcjQgdmFsdWUgYXQgYGluZGV4YC5cblx0ICpcblx0ICogQHBhcmFtIHtOdW1iZXJ9IGluZGV4IFRoZSBpbmRleCBhdCB3aGljaCB0byBzZXQgdGhlIHZlYzQgdmFsdWVzIGZyb20uXG5cdCAqIEBwYXJhbSB7VmVjdG9yNH0gdmVjMiAgQW55IG9iamVjdCB0aGF0IGhhcyBgeGAsIGB5YCwgYHpgLCBhbmQgYHdgIHByb3BlcnRpZXMuXG5cdCAqIEByZXR1cm4ge1NQRS5UeXBlZEFycmF5SGVscGVyfSBJbnN0YW5jZSBvZiB0aGlzIGNsYXNzLlxuXHQgKi9cblx0c2V0VmVjNCggaW5kZXgsIHZlYzQgKSB7XG5cdFx0cmV0dXJuIHRoaXMuc2V0VmVjNENvbXBvbmVudHMoIGluZGV4LCB2ZWM0LngsIHZlYzQueSwgdmVjNC56LCB2ZWM0LncgKTtcblx0fVxuXG5cdC8qKlxuXHQgKiBTZXQgYSBWZWN0b3I0IHZhbHVlIHVzaW5nIHJhdyBjb21wb25lbnRzLlxuXHQgKlxuXHQgKiBAcGFyYW0ge051bWJlcn0gaW5kZXggVGhlIGluZGV4IGF0IHdoaWNoIHRvIHNldCB0aGUgdmVjNCB2YWx1ZXMgZnJvbS5cblx0ICogQHBhcmFtIHtOdW1iZXJ9IHggICAgIFRoZSBWZWM0J3MgYHhgIGNvbXBvbmVudC5cblx0ICogQHBhcmFtIHtOdW1iZXJ9IHkgICAgIFRoZSBWZWM0J3MgYHlgIGNvbXBvbmVudC5cblx0ICogQHBhcmFtIHtOdW1iZXJ9IHogICAgIFRoZSBWZWM0J3MgYHpgIGNvbXBvbmVudC5cblx0ICogQHBhcmFtIHtOdW1iZXJ9IHcgICAgIFRoZSBWZWM0J3MgYHdgIGNvbXBvbmVudC5cblx0ICogQHJldHVybiB7U1BFLlR5cGVkQXJyYXlIZWxwZXJ9IEluc3RhbmNlIG9mIHRoaXMgY2xhc3MuXG5cdCAqL1xuXHRzZXRWZWM0Q29tcG9uZW50cyggaW5kZXgsIHgsIHksIHosIHcgKSB7XG5cdFx0Y29uc3QgYXJyYXkgPSB0aGlzLmFycmF5LFxuXHRcdFx0aSA9IHRoaXMuaW5kZXhPZmZzZXQgKyAoIGluZGV4ICogdGhpcy5jb21wb25lbnRTaXplICk7XG5cblx0XHRhcnJheVsgaSBdID0geDtcblx0XHRhcnJheVsgaSArIDEgXSA9IHk7XG5cdFx0YXJyYXlbIGkgKyAyIF0gPSB6O1xuXHRcdGFycmF5WyBpICsgMyBdID0gdztcblx0XHRyZXR1cm4gdGhpcztcblx0fVxuXG5cdC8qKlxuXHQgKiBTZXQgYSBNYXRyaXgzIHZhbHVlIGF0IGBpbmRleGAuXG5cdCAqXG5cdCAqIEBwYXJhbSB7TnVtYmVyfSBpbmRleCBUaGUgaW5kZXggYXQgd2hpY2ggdG8gc2V0IHRoZSBtYXRyaXggdmFsdWVzIGZyb20uXG5cdCAqIEBwYXJhbSB7TWF0cml4M30gbWF0MyBUaGUgM3gzIG1hdHJpeCB0byBzZXQgZnJvbS4gTXVzdCBoYXZlIGEgVHlwZWRBcnJheSBwcm9wZXJ0eSBuYW1lZCBgZWxlbWVudHNgIHRvIGNvcHkgZnJvbS5cblx0ICogQHJldHVybiB7U1BFLlR5cGVkQXJyYXlIZWxwZXJ9IEluc3RhbmNlIG9mIHRoaXMgY2xhc3MuXG5cdCAqL1xuXHRzZXRNYXQzKCBpbmRleCwgbWF0MyApIHtcblx0XHRyZXR1cm4gdGhpcy5zZXRGcm9tQXJyYXkoIHRoaXMuaW5kZXhPZmZzZXQgKyAoIGluZGV4ICogdGhpcy5jb21wb25lbnRTaXplICksIG1hdDMuZWxlbWVudHMgKTtcblx0fVxuXG5cdC8qKlxuXHQgKiBTZXQgYSBNYXRyaXg0IHZhbHVlIGF0IGBpbmRleGAuXG5cdCAqXG5cdCAqIEBwYXJhbSB7TnVtYmVyfSBpbmRleCBUaGUgaW5kZXggYXQgd2hpY2ggdG8gc2V0IHRoZSBtYXRyaXggdmFsdWVzIGZyb20uXG5cdCAqIEBwYXJhbSB7TWF0cml4NH0gbWF0MyBUaGUgNHg0IG1hdHJpeCB0byBzZXQgZnJvbS4gTXVzdCBoYXZlIGEgVHlwZWRBcnJheSBwcm9wZXJ0eSBuYW1lZCBgZWxlbWVudHNgIHRvIGNvcHkgZnJvbS5cblx0ICogQHJldHVybiB7U1BFLlR5cGVkQXJyYXlIZWxwZXJ9IEluc3RhbmNlIG9mIHRoaXMgY2xhc3MuXG5cdCAqL1xuXHRzZXRNYXQ0KCBpbmRleCwgbWF0NCApIHtcblx0XHRyZXR1cm4gdGhpcy5zZXRGcm9tQXJyYXkoIHRoaXMuaW5kZXhPZmZzZXQgKyAoIGluZGV4ICogdGhpcy5jb21wb25lbnRTaXplICksIG1hdDQuZWxlbWVudHMgKTtcblx0fVxuXG5cdC8qKlxuXHQgKiBTZXQgYSBDb2xvciB2YWx1ZSBhdCBgaW5kZXhgLlxuXHQgKlxuXHQgKiBAcGFyYW0ge051bWJlcn0gaW5kZXggVGhlIGluZGV4IGF0IHdoaWNoIHRvIHNldCB0aGUgdmVjMyB2YWx1ZXMgZnJvbS5cblx0ICogQHBhcmFtIHtDb2xvcn0gY29sb3IgIEFueSBvYmplY3QgdGhhdCBoYXMgYHJgLCBgZ2AsIGFuZCBgYmAgcHJvcGVydGllcy5cblx0ICogQHJldHVybiB7U1BFLlR5cGVkQXJyYXlIZWxwZXJ9IEluc3RhbmNlIG9mIHRoaXMgY2xhc3MuXG5cdCAqL1xuXHRzZXRDb2xvciggaW5kZXgsIGNvbG9yICkge1xuXHRcdHJldHVybiB0aGlzLnNldFZlYzNDb21wb25lbnRzKCBpbmRleCwgY29sb3IuciwgY29sb3IuZywgY29sb3IuYiApO1xuXHR9XG5cblx0LyoqXG5cdCAqIFNldCBhIE51bWJlciB2YWx1ZSBhdCBgaW5kZXhgLlxuXHQgKlxuXHQgKiBAcGFyYW0ge051bWJlcn0gaW5kZXggVGhlIGluZGV4IGF0IHdoaWNoIHRvIHNldCB0aGUgdmVjMyB2YWx1ZXMgZnJvbS5cblx0ICogQHBhcmFtIHtOdW1iZXJ9IG51bWVyaWNWYWx1ZSAgVGhlIG51bWJlciB0byBhc3NpZ24gdG8gdGhpcyBpbmRleCBpbiB0aGUgYXJyYXkuXG5cdCAqIEByZXR1cm4ge1NQRS5UeXBlZEFycmF5SGVscGVyfSBJbnN0YW5jZSBvZiB0aGlzIGNsYXNzLlxuXHQgKi9cblx0c2V0TnVtYmVyKCBpbmRleCwgbnVtZXJpY1ZhbHVlICkge1xuXHRcdHRoaXMuYXJyYXlbIHRoaXMuaW5kZXhPZmZzZXQgKyAoIGluZGV4ICogdGhpcy5jb21wb25lbnRTaXplICkgXSA9IG51bWVyaWNWYWx1ZTtcblx0XHRyZXR1cm4gdGhpcztcblx0fVxuXG5cdC8qKlxuXHQgKiBSZXR1cm5zIHRoZSB2YWx1ZSBvZiB0aGUgYXJyYXkgYXQgdGhlIGdpdmVuIGluZGV4LCB0YWtpbmcgaW50byBhY2NvdW50XG5cdCAqIHRoZSBgaW5kZXhPZmZzZXRgIHByb3BlcnR5IG9mIHRoaXMgY2xhc3MuXG5cdCAqXG5cdCAqIE5vdGUgdGhhdCB0aGlzIGZ1bmN0aW9uIGlnbm9yZXMgdGhlIGNvbXBvbmVudCBzaXplIGFuZCB3aWxsIGp1c3QgcmV0dXJuIGFcblx0ICogc2luZ2xlIHZhbHVlLlxuXHQgKlxuXHQgKiBAcGFyYW0gIHtOdW1iZXJ9IGluZGV4IFRoZSBpbmRleCBpbiB0aGUgYXJyYXkgdG8gZmV0Y2guXG5cdCAqIEByZXR1cm4ge051bWJlcn0gICAgICAgVGhlIHZhbHVlIGF0IHRoZSBnaXZlbiBpbmRleC5cblx0ICovXG5cdGdldFZhbHVlQXRJbmRleCggaW5kZXggKSB7XG5cdFx0cmV0dXJuIHRoaXMuYXJyYXlbIHRoaXMuaW5kZXhPZmZzZXQgKyBpbmRleCBdO1xuXHR9XG5cblx0LyoqXG5cdCAqIFJldHVybnMgdGhlIGNvbXBvbmVudCB2YWx1ZSBvZiB0aGUgYXJyYXkgYXQgdGhlIGdpdmVuIGluZGV4LCB0YWtpbmcgaW50byBhY2NvdW50XG5cdCAqIHRoZSBgaW5kZXhPZmZzZXRgIHByb3BlcnR5IG9mIHRoaXMgY2xhc3MuXG5cdCAqXG5cdCAqIElmIHRoZSBjb21wb25lbnRTaXplIGlzIHNldCB0byAzLCB0aGVuIGl0IHdpbGwgcmV0dXJuIGEgbmV3IFR5cGVkQXJyYXlcblx0ICogb2YgbGVuZ3RoIDMuXG5cdCAqXG5cdCAqIEBwYXJhbSAge051bWJlcn0gaW5kZXggVGhlIGluZGV4IGluIHRoZSBhcnJheSB0byBmZXRjaC5cblx0ICogQHJldHVybiB7VHlwZWRBcnJheX0gICAgICAgVGhlIGNvbXBvbmVudCB2YWx1ZSBhdCB0aGUgZ2l2ZW4gaW5kZXguXG5cdCAqL1xuXHRnZXRDb21wb25lbnRWYWx1ZUF0SW5kZXgoIGluZGV4ICkge1xuXHRcdHJldHVybiB0aGlzLmFycmF5LnN1YmFycmF5KCB0aGlzLmluZGV4T2Zmc2V0ICsgKCBpbmRleCAqIHRoaXMuY29tcG9uZW50U2l6ZSApICk7XG5cdH1cbn0iLCIvKipcbiAqIEEgbWFwIG9mIHVuaWZvcm0gdHlwZXMgdG8gdGhlaXIgY29tcG9uZW50IHNpemUuXG4gKiBAZW51bSB7TnVtYmVyfVxuICovXG5leHBvcnQgZGVmYXVsdCB7XG5cblx0LyoqXG5cdCAqIEZsb2F0XG5cdCAqIEB0eXBlIHtOdW1iZXJ9XG5cdCAqL1xuXHRmOiAxLFxuXG5cdC8qKlxuXHQgKiBWZWMyXG5cdCAqIEB0eXBlIHtOdW1iZXJ9XG5cdCAqL1xuXHR2MjogMixcblxuXHQvKipcblx0ICogVmVjM1xuXHQgKiBAdHlwZSB7TnVtYmVyfVxuXHQgKi9cblx0djM6IDMsXG5cblx0LyoqXG5cdCAqIFZlYzRcblx0ICogQHR5cGUge051bWJlcn1cblx0ICovXG5cdHY0OiA0LFxuXG5cdC8qKlxuXHQgKiBDb2xvclxuXHQgKiBAdHlwZSB7TnVtYmVyfVxuXHQgKi9cblx0YzogMyxcblxuXHQvKipcblx0ICogTWF0M1xuXHQgKiBAdHlwZSB7TnVtYmVyfVxuXHQgKi9cblx0bTM6IDksXG5cblx0LyoqXG5cdCAqIE1hdDRcblx0ICogQHR5cGUge051bWJlcn1cblx0ICovXG5cdG00OiAxNixcbn07XG5cbiIsImltcG9ydCAqIGFzIFRIUkVFIGZyb20gJ3RocmVlJztcbmltcG9ydCBUeXBlZEFycmF5SGVscGVyIGZyb20gJy4vVHlwZWRBcnJheUhlbHBlcic7XG5pbXBvcnQgdHlwZVNpemVNYXAgZnJvbSAnQC9jb25zdGFudHMvdHlwZVNpemVNYXAnO1xuXG5jb25zdCBIQVNfT1dOID0gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eTtcblxuLyoqXG4gKiBBIGhlbHBlciB0byBoYW5kbGUgY3JlYXRpbmcgYW5kIHVwZGF0aW5nIGEgVEhSRUUuQnVmZmVyQXR0cmlidXRlIGluc3RhbmNlLlxuICpcbiAqIEBhdXRob3IgIEx1a2UgTW9vZHlcbiAqIEBjb25zdHJ1Y3RvclxuICogQHBhcmFtIHtTdHJpbmd9IHR5cGUgICAgICAgICAgVGhlIGJ1ZmZlciBhdHRyaWJ1dGUgdHlwZS4gU2VlIGB0eXBlU2l6ZU1hcGBgIGZvciB2YWxpZCB2YWx1ZXMuXG4gKiBAcGFyYW0ge0Jvb2xlYW49fSBkeW5hbWljQnVmZmVyIFdoZXRoZXIgdGhpcyBidWZmZXIgYXR0cmlidXRlIHNob3VsZCBiZSBtYXJrZWQgYXMgZHluYW1pYyBvciBub3QuXG4gKiBAcGFyYW0ge0Z1bmN0aW9uPX0gYXJyYXlUeXBlICAgICBBIHJlZmVyZW5jZSB0byBhIFR5cGVkQXJyYXkgY29uc3RydWN0b3IuIERlZmF1bHRzIHRvIEZsb2F0MzJBcnJheSBpZiBub25lIHByb3ZpZGVkLlxuICovXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBTaGFkZXJBdHRyaWJ1dGUge1xuXHRjb25zdHJ1Y3RvciggdHlwZSwgZHluYW1pY0J1ZmZlciwgYXJyYXlUeXBlICkge1xuXHRcdHRoaXMudHlwZSA9IHR5cGVvZiB0eXBlID09PSAnc3RyaW5nJyAmJiBIQVNfT1dOLmNhbGwoIHR5cGVTaXplTWFwLCB0eXBlICkgPyB0eXBlIDogJ2YnO1xuXHRcdHRoaXMuY29tcG9uZW50U2l6ZSA9IHR5cGVTaXplTWFwWyB0aGlzLnR5cGUgXTtcblx0XHR0aGlzLmFycmF5VHlwZSA9IGFycmF5VHlwZSB8fCBGbG9hdDMyQXJyYXk7XG5cdFx0dGhpcy50eXBlZEFycmF5ID0gbnVsbDtcblx0XHR0aGlzLmJ1ZmZlckF0dHJpYnV0ZSA9IG51bGw7XG5cdFx0dGhpcy5keW5hbWljQnVmZmVyID0gISFkeW5hbWljQnVmZmVyO1xuXG5cdFx0dGhpcy51cGRhdGVNaW4gPSAwO1xuXHRcdHRoaXMudXBkYXRlTWF4ID0gMDtcblx0fVxuXG5cdC8qKlxuXHQgKiBDYWxjdWxhdGUgdGhlIG1pbmltdW0gYW5kIG1heGltdW0gdXBkYXRlIHJhbmdlIGZvciB0aGlzIGJ1ZmZlciBhdHRyaWJ1dGUgdXNpbmdcblx0ICogY29tcG9uZW50IHNpemUgaW5kZXBlbmRhbnQgbWluIGFuZCBtYXggdmFsdWVzLlxuXHQgKlxuXHQgKiBAcGFyYW0ge051bWJlcn0gbWluIFRoZSBzdGFydCBvZiB0aGUgcmFuZ2UgdG8gbWFyayBhcyBuZWVkaW5nIGFuIHVwZGF0ZS5cblx0ICogQHBhcmFtIHtOdW1iZXJ9IG1heCBUaGUgZW5kIG9mIHRoZSByYW5nZSB0byBtYXJrIGFzIG5lZWRpbmcgYW4gdXBkYXRlLlxuXHQgKi9cblx0c2V0VXBkYXRlUmFuZ2UoIG1pbiwgbWF4ICkge1xuXHRcdHRoaXMudXBkYXRlTWluID0gTWF0aC5taW4oIG1pbiAqIHRoaXMuY29tcG9uZW50U2l6ZSwgdGhpcy51cGRhdGVNaW4gKiB0aGlzLmNvbXBvbmVudFNpemUgKTtcblx0XHR0aGlzLnVwZGF0ZU1heCA9IE1hdGgubWF4KCBtYXggKiB0aGlzLmNvbXBvbmVudFNpemUsIHRoaXMudXBkYXRlTWF4ICogdGhpcy5jb21wb25lbnRTaXplICk7XG5cdH1cblxuXHQvKipcblx0ICogQ2FsY3VsYXRlIHRoZSBudW1iZXIgb2YgaW5kaWNlcyB0aGF0IHRoaXMgYXR0cmlidXRlIHNob3VsZCBtYXJrIGFzIG5lZWRpbmdcblx0ICogdXBkYXRpbmcuIEFsc28gbWFya3MgdGhlIGF0dHJpYnV0ZSBhcyBuZWVkaW5nIGFuIHVwZGF0ZS5cblx0ICovXG5cdGZsYWdVcGRhdGUoKSB7XG5cdFx0Y29uc3QgYXR0ciA9IHRoaXMuYnVmZmVyQXR0cmlidXRlLFxuXHRcdFx0cmFuZ2UgPSBhdHRyLnVwZGF0ZVJhbmdlO1xuXG5cdFx0cmFuZ2Uub2Zmc2V0ID0gdGhpcy51cGRhdGVNaW47XG5cdFx0cmFuZ2UuY291bnQgPSBNYXRoLm1pbiggKCB0aGlzLnVwZGF0ZU1heCAtIHRoaXMudXBkYXRlTWluICkgKyB0aGlzLmNvbXBvbmVudFNpemUsIHRoaXMudHlwZWRBcnJheS5hcnJheS5sZW5ndGggKTtcblx0XHRhdHRyLm5lZWRzVXBkYXRlID0gdHJ1ZTtcblx0fVxuXG5cblxuXHQvKipcblx0ICogUmVzZXQgdGhlIGluZGV4IHVwZGF0ZSBjb3VudHMgZm9yIHRoaXMgYXR0cmlidXRlXG5cdCAqL1xuXHRyZXNldFVwZGF0ZVJhbmdlKCkge1xuXHRcdHRoaXMudXBkYXRlTWluID0gMDtcblx0XHR0aGlzLnVwZGF0ZU1heCA9IDA7XG5cdH1cblxuXHRyZXNldER5bmFtaWMoKSB7XG5cdFx0dGhpcy5idWZmZXJBdHRyaWJ1dGUudXNhZ2UgPSB0aGlzLmR5bmFtaWNCdWZmZXIgP1xuXHRcdFx0VEhSRUUuRHluYW1pY0RyYXdVc2FnZSA6XG5cdFx0XHRUSFJFRS5TdGF0aWNEcmF3VXNhZ2U7XG5cdH1cblxuXHQvKipcblx0ICogUGVyZm9ybSBhIHNwbGljZSBvcGVyYXRpb24gb24gdGhpcyBhdHRyaWJ1dGUncyBidWZmZXIuXG5cdCAqIEBwYXJhbSAge051bWJlcn0gc3RhcnQgVGhlIHN0YXJ0IGluZGV4IG9mIHRoZSBzcGxpY2UuIFdpbGwgYmUgbXVsdGlwbGllZCBieSB0aGUgbnVtYmVyIG9mIGNvbXBvbmVudHMgZm9yIHRoaXMgYXR0cmlidXRlLlxuXHQgKiBAcGFyYW0gIHtOdW1iZXJ9IGVuZCBUaGUgZW5kIGluZGV4IG9mIHRoZSBzcGxpY2UuIFdpbGwgYmUgbXVsdGlwbGllZCBieSB0aGUgbnVtYmVyIG9mIGNvbXBvbmVudHMgZm9yIHRoaXMgYXR0cmlidXRlLlxuXHQgKi9cblx0c3BsaWNlKCBzdGFydCwgZW5kICkge1xuXHRcdHRoaXMudHlwZWRBcnJheS5zcGxpY2UoIHN0YXJ0LCBlbmQgKTtcblxuXHRcdC8vIFJlc2V0IHRoZSByZWZlcmVuY2UgdG8gdGhlIGF0dHJpYnV0ZSdzIHR5cGVkIGFycmF5XG5cdFx0Ly8gc2luY2UgaXQgaGFzIHByb2JhYmx5IGNoYW5nZWQuXG5cdFx0dGhpcy5mb3JjZVVwZGF0ZUFsbCgpO1xuXHR9XG5cblx0Zm9yY2VVcGRhdGVBbGwoKSB7XG5cdFx0dGhpcy5idWZmZXJBdHRyaWJ1dGUuYXJyYXkgPSB0aGlzLnR5cGVkQXJyYXkuYXJyYXk7XG5cdFx0dGhpcy5idWZmZXJBdHRyaWJ1dGUudXBkYXRlUmFuZ2Uub2Zmc2V0ID0gMDtcblx0XHR0aGlzLmJ1ZmZlckF0dHJpYnV0ZS51cGRhdGVSYW5nZS5jb3VudCA9IC0xO1xuXG5cdFx0dGhpcy5idWZmZXJBdHRyaWJ1dGUudXNhZ2UgPSBUSFJFRS5TdGF0aWNEcmF3VXNhZ2U7XG5cdFx0dGhpcy5idWZmZXJBdHRyaWJ1dGUubmVlZHNVcGRhdGUgPSB0cnVlO1xuXHR9XG5cblx0LyoqXG5cdCAqIE1ha2Ugc3VyZSB0aGlzIGF0dHJpYnV0ZSBoYXMgYSB0eXBlZCBhcnJheSBhc3NvY2lhdGVkIHdpdGggaXQuXG5cdCAqXG5cdCAqIElmIGl0IGRvZXMsIHRoZW4gaXQgd2lsbCBlbnN1cmUgdGhlIHR5cGVkIGFycmF5IGlzIG9mIHRoZSBjb3JyZWN0IHNpemUuXG5cdCAqXG5cdCAqIElmIG5vdCwgYSBuZXcgYFR5cGVkQXJyYXlIZWxwZXJgIGluc3RhbmNlIHdpbGwgYmUgY3JlYXRlZC5cblx0ICpcblx0ICogQHBhcmFtICB7TnVtYmVyfSBzaXplIFRoZSBzaXplIG9mIHRoZSB0eXBlZCBhcnJheSB0byBjcmVhdGUgb3IgdXBkYXRlIHRvLlxuXHQgKi9cblx0X2Vuc3VyZVR5cGVkQXJyYXkoIHNpemUgKSB7XG5cdFx0Ly8gQ29uZGl0aW9uIHRoYXQncyBtb3N0IGxpa2VseSB0byBiZSB0cnVlIGF0IHRoZSB0b3A6IG5vIGNoYW5nZS5cblx0XHRpZiAoIHRoaXMudHlwZWRBcnJheSAhPT0gbnVsbCAmJiB0aGlzLnR5cGVkQXJyYXkuc2l6ZSA9PT0gc2l6ZSAqIHRoaXMuY29tcG9uZW50U2l6ZSApIHtcblx0XHRcdHJldHVybjtcblx0XHR9XG5cblx0XHQvLyBSZXNpemUgdGhlIGFycmF5IGlmIHdlIG5lZWQgdG8sIHRlbGxpbmcgdGhlIFR5cGVkQXJyYXlIZWxwZXIgdG9cblx0XHQvLyBpZ25vcmUgaXQncyBjb21wb25lbnQgc2l6ZSB3aGVuIGV2YWx1YXRpbmcgc2l6ZS5cblx0XHRlbHNlIGlmICggdGhpcy50eXBlZEFycmF5ICE9PSBudWxsICYmIHRoaXMudHlwZWRBcnJheS5zaXplICE9PSBzaXplICkge1xuXHRcdFx0dGhpcy50eXBlZEFycmF5LnNldFNpemUoIHNpemUgKTtcblx0XHR9XG5cblx0XHQvLyBUaGlzIGNvbmRpdGlvbiBzaG91bGQgb25seSBvY2N1ciBvbmNlIGluIGFuIGF0dHJpYnV0ZSdzIGxpZmVjeWNsZS5cblx0XHRlbHNlIGlmICggdGhpcy50eXBlZEFycmF5ID09PSBudWxsICkge1xuXHRcdFx0dGhpcy50eXBlZEFycmF5ID0gbmV3IFR5cGVkQXJyYXlIZWxwZXIoIHRoaXMuYXJyYXlUeXBlLCBzaXplLCB0aGlzLmNvbXBvbmVudFNpemUgKTtcblx0XHR9XG5cdH1cblxuXG5cdC8qKlxuXHQgKiBDcmVhdGVzIGEgVEhSRUUuQnVmZmVyQXR0cmlidXRlIGluc3RhbmNlIGlmIG9uZSBkb2Vzbid0IGV4aXN0IGFscmVhZHkuXG5cdCAqXG5cdCAqIEVuc3VyZXMgYSB0eXBlZCBhcnJheSBpcyBwcmVzZW50IGJ5IGNhbGxpbmcgX2Vuc3VyZVR5cGVkQXJyYXkoKSBmaXJzdC5cblx0ICpcblx0ICogSWYgYSBidWZmZXIgYXR0cmlidXRlIGV4aXN0cyBhbHJlYWR5LCB0aGVuIGl0IHdpbGwgYmUgbWFya2VkIGFzIG5lZWRpbmcgYW4gdXBkYXRlLlxuXHQgKlxuXHQgKiBAcGFyYW0gIHtOdW1iZXJ9IHNpemUgVGhlIHNpemUgb2YgdGhlIHR5cGVkIGFycmF5IHRvIGNyZWF0ZSBpZiBvbmUgZG9lc24ndCBleGlzdCwgb3IgcmVzaXplIGV4aXN0aW5nIGFycmF5IHRvLlxuXHQgKi9cblx0X2NyZWF0ZUJ1ZmZlckF0dHJpYnV0ZSggc2l6ZSApIHtcblx0XHQvLyBNYWtlIHN1cmUgdGhlIHR5cGVkQXJyYXkgaXMgcHJlc2VudCBhbmQgY29ycmVjdC5cblx0XHR0aGlzLl9lbnN1cmVUeXBlZEFycmF5KCBzaXplICk7XG5cblx0XHQvLyBEb24ndCBjcmVhdGUgaXQgaWYgaXQgYWxyZWFkeSBleGlzdHMsIGJ1dCBkb1xuXHRcdC8vIGZsYWcgdGhhdCBpdCBuZWVkcyB1cGRhdGluZyBvbiB0aGUgbmV4dCByZW5kZXJcblx0XHQvLyBjeWNsZS5cblx0XHRpZiAoIHRoaXMuYnVmZmVyQXR0cmlidXRlICE9PSBudWxsICkge1xuXHRcdFx0dGhpcy5idWZmZXJBdHRyaWJ1dGUuYXJyYXkgPSB0aGlzLnR5cGVkQXJyYXkuYXJyYXk7XG5cblx0XHRcdC8vIFNpbmNlIFRIUkVFLmpzIHZlcnNpb24gODEsIGR5bmFtaWMgY291bnQgY2FsY3VsYXRpb24gd2FzIHJlbW92ZWRcblx0XHRcdC8vIHNvIEkgbmVlZCB0byBkbyBpdCBtYW51YWxseSBoZXJlLlxuXHRcdFx0Ly9cblx0XHRcdC8vIEluIHRoZSBuZXh0IG1pbm9yIHJlbGVhc2UsIEkgbWF5IHdlbGwgcmVtb3ZlIHRoaXMgY2hlY2sgYW5kIGZvcmNlXG5cdFx0XHQvLyBkZXBlbmRlbmN5IG9uIFRIUkVFIHI4MSsuXG5cdFx0XHRpZiAoIHBhcnNlRmxvYXQoIFRIUkVFLlJFVklTSU9OICkgPj0gODEgKSB7XG5cdFx0XHRcdHRoaXMuYnVmZmVyQXR0cmlidXRlLmNvdW50ID0gdGhpcy5idWZmZXJBdHRyaWJ1dGUuYXJyYXkubGVuZ3RoIC8gdGhpcy5idWZmZXJBdHRyaWJ1dGUuaXRlbVNpemU7XG5cdFx0XHR9XG5cblx0XHRcdHRoaXMuYnVmZmVyQXR0cmlidXRlLm5lZWRzVXBkYXRlID0gdHJ1ZTtcblx0XHRcdHJldHVybjtcblx0XHR9XG5cblx0XHR0aGlzLmJ1ZmZlckF0dHJpYnV0ZSA9IG5ldyBUSFJFRS5CdWZmZXJBdHRyaWJ1dGUoIHRoaXMudHlwZWRBcnJheS5hcnJheSwgdGhpcy5jb21wb25lbnRTaXplICk7XG5cblx0XHR0aGlzLmJ1ZmZlckF0dHJpYnV0ZS51c2FnZSA9IHRoaXMuZHluYW1pY0J1ZmZlciA/XG5cdFx0XHRUSFJFRS5EeW5hbWljRHJhd1VzYWdlIDpcblx0XHRcdFRIUkVFLlN0YXRpY0RyYXdVc2FnZTtcblx0fVxuXG5cdC8qKlxuXHQgKiBSZXR1cm5zIHRoZSBsZW5ndGggb2YgdGhlIHR5cGVkIGFycmF5IGFzc29jaWF0ZWQgd2l0aCB0aGlzIGF0dHJpYnV0ZS5cblx0ICogQHJldHVybiB7TnVtYmVyfSBUaGUgbGVuZ3RoIG9mIHRoZSB0eXBlZCBhcnJheS4gV2lsbCBiZSAwIGlmIG5vIHR5cGVkIGFycmF5IGhhcyBiZWVuIGNyZWF0ZWQgeWV0LlxuXHQgKi9cblx0Z2V0TGVuZ3RoKCkge1xuXHRcdGlmICggdGhpcy50eXBlZEFycmF5ID09PSBudWxsICkge1xuXHRcdFx0cmV0dXJuIDA7XG5cdFx0fVxuXG5cdFx0cmV0dXJuIHRoaXMudHlwZWRBcnJheS5hcnJheS5sZW5ndGg7XG5cdH1cblxufVxuXG4iLCJleHBvcnQgZGVmYXVsdCB7XG5cdC8vIFJlZ2lzdGVyIGNvbG9yLXBhY2tpbmcgZGVmaW5lIHN0YXRlbWVudHMuXG5cdGRlZmluZXM6IFtcblx0XHQnI2RlZmluZSBQQUNLRURfQ09MT1JfU0laRSAyNTYuMCcsXG5cdFx0JyNkZWZpbmUgUEFDS0VEX0NPTE9SX0RJVklTT1IgMjU1LjAnLFxuXHRdLmpvaW4oICdcXG4nICksXG5cblx0Ly8gQWxsIHVuaWZvcm1zIHVzZWQgYnkgdmVydGV4IC8gZnJhZ21lbnQgc2hhZGVyc1xuXHR1bmlmb3JtczogW1xuXHRcdCd1bmlmb3JtIGZsb2F0IGRlbHRhVGltZTsnLFxuXHRcdCd1bmlmb3JtIGZsb2F0IHJ1blRpbWU7Jyxcblx0XHQndW5pZm9ybSBzYW1wbGVyMkQgdGV4OycsXG5cdFx0J3VuaWZvcm0gdmVjNCB0ZXh0dXJlQW5pbWF0aW9uOycsXG5cdFx0J3VuaWZvcm0gZmxvYXQgc2NhbGU7Jyxcblx0XS5qb2luKCAnXFxuJyApLFxuXG5cdC8vIEFsbCBhdHRyaWJ1dGVzIHVzZWQgYnkgdGhlIHZlcnRleCBzaGFkZXIuXG5cdC8vXG5cdC8vIE5vdGUgdGhhdCBzb21lIGF0dHJpYnV0ZXMgYXJlIHNxdWFzaGVkIGludG8gb3RoZXIgb25lczpcblx0Ly9cblx0Ly8gKiBEcmFnIGlzIGFjY2VsZXJhdGlvbi53XG5cdGF0dHJpYnV0ZXM6IFtcblx0XHQnYXR0cmlidXRlIHZlYzQgYWNjZWxlcmF0aW9uOycsXG5cdFx0J2F0dHJpYnV0ZSB2ZWMzIHZlbG9jaXR5OycsXG5cdFx0J2F0dHJpYnV0ZSB2ZWM0IHJvdGF0aW9uOycsXG5cdFx0J2F0dHJpYnV0ZSB2ZWMzIHJvdGF0aW9uQ2VudGVyOycsXG5cdFx0J2F0dHJpYnV0ZSB2ZWM0IHBhcmFtczsnLFxuXHRcdCdhdHRyaWJ1dGUgdmVjNCBzaXplOycsXG5cdFx0J2F0dHJpYnV0ZSB2ZWM0IGFuZ2xlOycsXG5cdFx0J2F0dHJpYnV0ZSB2ZWM0IGNvbG9yOycsXG5cdFx0J2F0dHJpYnV0ZSB2ZWM0IG9wYWNpdHk7Jyxcblx0XS5qb2luKCAnXFxuJyApLFxuXG5cdC8vXG5cdHZhcnlpbmdzOiBbXG5cdFx0J3ZhcnlpbmcgdmVjNCB2Q29sb3I7Jyxcblx0XHQnI2lmZGVmIFNIT1VMRF9ST1RBVEVfVEVYVFVSRScsXG5cdFx0JyAgICB2YXJ5aW5nIGZsb2F0IHZBbmdsZTsnLFxuXHRcdCcjZW5kaWYnLFxuXG5cdFx0JyNpZmRlZiBTSE9VTERfQ0FMQ1VMQVRFX1NQUklURScsXG5cdFx0JyAgICB2YXJ5aW5nIHZlYzQgdlNwcml0ZVNoZWV0OycsXG5cdFx0JyNlbmRpZicsXG5cdF0uam9pbiggJ1xcbicgKSxcblxuXG5cdC8vIEJyYW5jaC1hdm9pZGluZyBjb21wYXJpc29uIGZuc1xuXHQvLyAtIGh0dHA6Ly90aGVvcmFuZ2VkdWNrLmNvbS9wYWdlL2F2b2lkaW5nLXNoYWRlci1jb25kaXRpb25hbHNcblx0YnJhbmNoQXZvaWRhbmNlRnVuY3Rpb25zOiBbXG5cdFx0J2Zsb2F0IHdoZW5fZ3QoZmxvYXQgeCwgZmxvYXQgeSkgeycsXG5cdFx0JyAgICByZXR1cm4gbWF4KHNpZ24oeCAtIHkpLCAwLjApOycsXG5cdFx0J30nLFxuXG5cdFx0J2Zsb2F0IHdoZW5fbHQoZmxvYXQgeCwgZmxvYXQgeSkgeycsXG5cdFx0JyAgICByZXR1cm4gbWluKCBtYXgoMS4wIC0gc2lnbih4IC0geSksIDAuMCksIDEuMCApOycsXG5cdFx0J30nLFxuXG5cdFx0J2Zsb2F0IHdoZW5fZXEoIGZsb2F0IHgsIGZsb2F0IHkgKSB7Jyxcblx0XHQnICAgIHJldHVybiAxLjAgLSBhYnMoIHNpZ24oIHggLSB5ICkgKTsnLFxuXHRcdCd9JyxcblxuXHRcdCdmbG9hdCB3aGVuX2dlKGZsb2F0IHgsIGZsb2F0IHkpIHsnLFxuXHRcdCcgIHJldHVybiAxLjAgLSB3aGVuX2x0KHgsIHkpOycsXG5cdFx0J30nLFxuXG5cdFx0J2Zsb2F0IHdoZW5fbGUoZmxvYXQgeCwgZmxvYXQgeSkgeycsXG5cdFx0JyAgcmV0dXJuIDEuMCAtIHdoZW5fZ3QoeCwgeSk7Jyxcblx0XHQnfScsXG5cblx0XHQvLyBCcmFuY2gtYXZvaWRpbmcgbG9naWNhbCBvcGVyYXRvcnNcblx0XHQvLyAodG8gYmUgdXNlZCB3aXRoIGFib3ZlIGNvbXBhcmlzb24gZm5zKVxuXHRcdCdmbG9hdCBhbmQoZmxvYXQgYSwgZmxvYXQgYikgeycsXG5cdFx0JyAgICByZXR1cm4gYSAqIGI7Jyxcblx0XHQnfScsXG5cblx0XHQnZmxvYXQgb3IoZmxvYXQgYSwgZmxvYXQgYikgeycsXG5cdFx0JyAgICByZXR1cm4gbWluKGEgKyBiLCAxLjApOycsXG5cdFx0J30nLFxuXHRdLmpvaW4oICdcXG4nICksXG5cblxuXHQvLyBGcm9tOlxuXHQvLyAtIGh0dHA6Ly9zdGFja292ZXJmbG93LmNvbS9hLzEyNTUzMTQ5XG5cdC8vIC0gaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS9xdWVzdGlvbnMvMjI4OTUyMzcvaGV4YWRlY2ltYWwtdG8tcmdiLXZhbHVlcy1pbi13ZWJnbC1zaGFkZXJcblx0dW5wYWNrQ29sb3I6IFtcblx0XHQndmVjMyB1bnBhY2tDb2xvciggaW4gZmxvYXQgaGV4ICkgeycsXG5cdFx0JyAgIHZlYzMgYyA9IHZlYzMoIDAuMCApOycsXG5cblx0XHQnICAgZmxvYXQgciA9IG1vZCggKGhleCAvIFBBQ0tFRF9DT0xPUl9TSVpFIC8gUEFDS0VEX0NPTE9SX1NJWkUpLCBQQUNLRURfQ09MT1JfU0laRSApOycsXG5cdFx0JyAgIGZsb2F0IGcgPSBtb2QoIChoZXggLyBQQUNLRURfQ09MT1JfU0laRSksIFBBQ0tFRF9DT0xPUl9TSVpFICk7Jyxcblx0XHQnICAgZmxvYXQgYiA9IG1vZCggaGV4LCBQQUNLRURfQ09MT1JfU0laRSApOycsXG5cblx0XHQnICAgYy5yID0gciAvIFBBQ0tFRF9DT0xPUl9ESVZJU09SOycsXG5cdFx0JyAgIGMuZyA9IGcgLyBQQUNLRURfQ09MT1JfRElWSVNPUjsnLFxuXHRcdCcgICBjLmIgPSBiIC8gUEFDS0VEX0NPTE9SX0RJVklTT1I7JyxcblxuXHRcdCcgICByZXR1cm4gYzsnLFxuXHRcdCd9Jyxcblx0XS5qb2luKCAnXFxuJyApLFxuXG5cdHVucGFja1JvdGF0aW9uQXhpczogW1xuXHRcdCd2ZWMzIHVucGFja1JvdGF0aW9uQXhpcyggaW4gZmxvYXQgaGV4ICkgeycsXG5cdFx0JyAgIHZlYzMgYyA9IHZlYzMoIDAuMCApOycsXG5cblx0XHQnICAgZmxvYXQgciA9IG1vZCggKGhleCAvIFBBQ0tFRF9DT0xPUl9TSVpFIC8gUEFDS0VEX0NPTE9SX1NJWkUpLCBQQUNLRURfQ09MT1JfU0laRSApOycsXG5cdFx0JyAgIGZsb2F0IGcgPSBtb2QoIChoZXggLyBQQUNLRURfQ09MT1JfU0laRSksIFBBQ0tFRF9DT0xPUl9TSVpFICk7Jyxcblx0XHQnICAgZmxvYXQgYiA9IG1vZCggaGV4LCBQQUNLRURfQ09MT1JfU0laRSApOycsXG5cblx0XHQnICAgYy5yID0gciAvIFBBQ0tFRF9DT0xPUl9ESVZJU09SOycsXG5cdFx0JyAgIGMuZyA9IGcgLyBQQUNLRURfQ09MT1JfRElWSVNPUjsnLFxuXHRcdCcgICBjLmIgPSBiIC8gUEFDS0VEX0NPTE9SX0RJVklTT1I7JyxcblxuXHRcdCcgICBjICo9IHZlYzMoIDIuMCApOycsXG5cdFx0JyAgIGMgLT0gdmVjMyggMS4wICk7JyxcblxuXHRcdCcgICByZXR1cm4gYzsnLFxuXHRcdCd9Jyxcblx0XS5qb2luKCAnXFxuJyApLFxuXG5cdGZsb2F0T3ZlckxpZmV0aW1lOiBbXG5cdFx0J2Zsb2F0IGdldEZsb2F0T3ZlckxpZmV0aW1lKCBpbiBmbG9hdCBwb3NpdGlvbkluVGltZSwgaW4gdmVjNCBhdHRyICkgeycsXG5cdFx0JyAgICBoaWdocCBmbG9hdCB2YWx1ZSA9IDAuMDsnLFxuXHRcdCcgICAgZmxvYXQgZGVsdGFBZ2UgPSBwb3NpdGlvbkluVGltZSAqIGZsb2F0KCBWQUxVRV9PVkVSX0xJRkVUSU1FX0xFTkdUSCAtIDEgKTsnLFxuXHRcdCcgICAgZmxvYXQgZkluZGV4ID0gMC4wOycsXG5cdFx0JyAgICBmbG9hdCBzaG91bGRBcHBseVZhbHVlID0gMC4wOycsXG5cblx0XHQvLyBUaGlzIG1pZ2h0IGxvb2sgYSBsaXR0bGUgb2RkLCBidXQgaXQncyBmYXN0ZXIgaW4gdGhlIHRlc3RpbmcgSSd2ZSBkb25lIHRoYW4gdXNpbmcgYnJhbmNoZXMuXG5cdFx0Ly8gVXNlcyBiYXNpYyBtYXRocyB0byBhdm9pZCBicmFuY2hpbmcuXG5cdFx0Ly9cblx0XHQvLyBUYWtlIGEgbG9vayBhdCB0aGUgYnJhbmNoLWF2b2lkYW5jZSBmdW5jdGlvbnMgZGVmaW5lZCBhYm92ZSxcblx0XHQvLyBhbmQgYmUgc3VyZSB0byBjaGVjayBvdXQgVGhlIE9yYW5nZSBEdWNrIHNpdGUgd2hlcmUgSSBnb3QgdGhpc1xuXHRcdC8vIGZyb20gKGxpbmsgYWJvdmUpLlxuXG5cdFx0Ly8gRml4IGZvciBzdGF0aWMgZW1pdHRlcnMgKGFnZSBpcyBhbHdheXMgemVybykuXG5cdFx0JyAgICB2YWx1ZSArPSBhdHRyWyAwIF0gKiB3aGVuX2VxKCBkZWx0YUFnZSwgMC4wICk7Jyxcblx0XHQnJyxcblx0XHQnICAgIGZvciggaW50IGkgPSAwOyBpIDwgVkFMVUVfT1ZFUl9MSUZFVElNRV9MRU5HVEggLSAxOyArK2kgKSB7Jyxcblx0XHQnICAgICAgIGZJbmRleCA9IGZsb2F0KCBpICk7Jyxcblx0XHQnICAgICAgIHNob3VsZEFwcGx5VmFsdWUgPSBhbmQoIHdoZW5fZ3QoIGRlbHRhQWdlLCBmSW5kZXggKSwgd2hlbl9sZSggZGVsdGFBZ2UsIGZJbmRleCArIDEuMCApICk7Jyxcblx0XHQnICAgICAgIHZhbHVlICs9IHNob3VsZEFwcGx5VmFsdWUgKiBtaXgoIGF0dHJbIGkgXSwgYXR0clsgaSArIDEgXSwgZGVsdGFBZ2UgLSBmSW5kZXggKTsnLFxuXHRcdCcgICAgfScsXG5cdFx0JycsXG5cdFx0JyAgICByZXR1cm4gdmFsdWU7Jyxcblx0XHQnfScsXG5cdF0uam9pbiggJ1xcbicgKSxcblxuXHRjb2xvck92ZXJMaWZldGltZTogW1xuXHRcdCd2ZWMzIGdldENvbG9yT3ZlckxpZmV0aW1lKCBpbiBmbG9hdCBwb3NpdGlvbkluVGltZSwgaW4gdmVjMyBjb2xvcjEsIGluIHZlYzMgY29sb3IyLCBpbiB2ZWMzIGNvbG9yMywgaW4gdmVjMyBjb2xvcjQgKSB7Jyxcblx0XHQnICAgIHZlYzMgdmFsdWUgPSB2ZWMzKCAwLjAgKTsnLFxuXHRcdCcgICAgdmFsdWUueCA9IGdldEZsb2F0T3ZlckxpZmV0aW1lKCBwb3NpdGlvbkluVGltZSwgdmVjNCggY29sb3IxLngsIGNvbG9yMi54LCBjb2xvcjMueCwgY29sb3I0LnggKSApOycsXG5cdFx0JyAgICB2YWx1ZS55ID0gZ2V0RmxvYXRPdmVyTGlmZXRpbWUoIHBvc2l0aW9uSW5UaW1lLCB2ZWM0KCBjb2xvcjEueSwgY29sb3IyLnksIGNvbG9yMy55LCBjb2xvcjQueSApICk7Jyxcblx0XHQnICAgIHZhbHVlLnogPSBnZXRGbG9hdE92ZXJMaWZldGltZSggcG9zaXRpb25JblRpbWUsIHZlYzQoIGNvbG9yMS56LCBjb2xvcjIueiwgY29sb3IzLnosIGNvbG9yNC56ICkgKTsnLFxuXHRcdCcgICAgcmV0dXJuIHZhbHVlOycsXG5cdFx0J30nLFxuXHRdLmpvaW4oICdcXG4nICksXG5cblx0cGFyYW1GZXRjaGluZ0Z1bmN0aW9uczogW1xuXHRcdCdmbG9hdCBnZXRBbGl2ZSgpIHsnLFxuXHRcdCcgICByZXR1cm4gcGFyYW1zLng7Jyxcblx0XHQnfScsXG5cblx0XHQnZmxvYXQgZ2V0QWdlKCkgeycsXG5cdFx0JyAgIHJldHVybiBwYXJhbXMueTsnLFxuXHRcdCd9JyxcblxuXHRcdCdmbG9hdCBnZXRNYXhBZ2UoKSB7Jyxcblx0XHQnICAgcmV0dXJuIHBhcmFtcy56OycsXG5cdFx0J30nLFxuXG5cdFx0J2Zsb2F0IGdldFdpZ2dsZSgpIHsnLFxuXHRcdCcgICByZXR1cm4gcGFyYW1zLnc7Jyxcblx0XHQnfScsXG5cdF0uam9pbiggJ1xcbicgKSxcblxuXHRmb3JjZUZldGNoaW5nRnVuY3Rpb25zOiBbXG5cdFx0J3ZlYzQgZ2V0UG9zaXRpb24oIGluIGZsb2F0IGFnZSApIHsnLFxuXHRcdCcgICByZXR1cm4gbW9kZWxWaWV3TWF0cml4ICogdmVjNCggcG9zaXRpb24sIDEuMCApOycsXG5cdFx0J30nLFxuXG5cdFx0J3ZlYzMgZ2V0VmVsb2NpdHkoIGluIGZsb2F0IGFnZSApIHsnLFxuXHRcdCcgICByZXR1cm4gdmVsb2NpdHkgKiBhZ2U7Jyxcblx0XHQnfScsXG5cblx0XHQndmVjMyBnZXRBY2NlbGVyYXRpb24oIGluIGZsb2F0IGFnZSApIHsnLFxuXHRcdCcgICByZXR1cm4gYWNjZWxlcmF0aW9uLnh5eiAqIGFnZTsnLFxuXHRcdCd9Jyxcblx0XS5qb2luKCAnXFxuJyApLFxuXG5cblx0cm90YXRpb25GdW5jdGlvbnM6IFtcblx0XHQvLyBIdWdlIHRoYW5rcyB0bzpcblx0XHQvLyAtIGh0dHA6Ly93d3cubmVpbG1lbmRvemEuY29tL2dsc2wtcm90YXRpb24tYWJvdXQtYW4tYXJiaXRyYXJ5LWF4aXMvXG5cdFx0JyNpZmRlZiBTSE9VTERfUk9UQVRFX1BBUlRJQ0xFUycsXG5cdFx0JyAgIG1hdDQgZ2V0Um90YXRpb25NYXRyaXgoIGluIHZlYzMgYXhpcywgaW4gZmxvYXQgYW5nbGUpIHsnLFxuXHRcdCcgICAgICAgYXhpcyA9IG5vcm1hbGl6ZShheGlzKTsnLFxuXHRcdCcgICAgICAgZmxvYXQgcyA9IHNpbihhbmdsZSk7Jyxcblx0XHQnICAgICAgIGZsb2F0IGMgPSBjb3MoYW5nbGUpOycsXG5cdFx0JyAgICAgICBmbG9hdCBvYyA9IDEuMCAtIGM7Jyxcblx0XHQnJyxcblx0XHQnICAgICAgIHJldHVybiBtYXQ0KG9jICogYXhpcy54ICogYXhpcy54ICsgYywgICAgICAgICAgIG9jICogYXhpcy54ICogYXhpcy55IC0gYXhpcy56ICogcywgIG9jICogYXhpcy56ICogYXhpcy54ICsgYXhpcy55ICogcywgIDAuMCwnLFxuXHRcdCcgICAgICAgICAgICAgICAgICAgb2MgKiBheGlzLnggKiBheGlzLnkgKyBheGlzLnogKiBzLCAgb2MgKiBheGlzLnkgKiBheGlzLnkgKyBjLCAgICAgICAgICAgb2MgKiBheGlzLnkgKiBheGlzLnogLSBheGlzLnggKiBzLCAgMC4wLCcsXG5cdFx0JyAgICAgICAgICAgICAgICAgICBvYyAqIGF4aXMueiAqIGF4aXMueCAtIGF4aXMueSAqIHMsICBvYyAqIGF4aXMueSAqIGF4aXMueiArIGF4aXMueCAqIHMsICBvYyAqIGF4aXMueiAqIGF4aXMueiArIGMsICAgICAgICAgICAwLjAsJyxcblx0XHQnICAgICAgICAgICAgICAgICAgIDAuMCwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDAuMCwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDAuMCwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDEuMCk7Jyxcblx0XHQnICAgfScsXG5cdFx0JycsXG5cdFx0JyAgIHZlYzMgZ2V0Um90YXRpb24oIGluIHZlYzMgcG9zLCBpbiBmbG9hdCBwb3NpdGlvbkluVGltZSApIHsnLFxuXHRcdCcgICAgICBpZiggcm90YXRpb24ueSA9PSAwLjAgKSB7Jyxcblx0XHQnICAgICAgICAgICByZXR1cm4gcG9zOycsXG5cdFx0JyAgICAgIH0nLFxuXHRcdCcnLFxuXHRcdCcgICAgICB2ZWMzIGF4aXMgPSB1bnBhY2tSb3RhdGlvbkF4aXMoIHJvdGF0aW9uLnggKTsnLFxuXHRcdCcgICAgICB2ZWMzIGNlbnRlciA9IHJvdGF0aW9uQ2VudGVyOycsXG5cdFx0JyAgICAgIHZlYzMgdHJhbnNsYXRlZDsnLFxuXHRcdCcgICAgICBtYXQ0IHJvdGF0aW9uTWF0cml4OycsXG5cblx0XHQnICAgICAgZmxvYXQgYW5nbGUgPSAwLjA7Jyxcblx0XHQnICAgICAgYW5nbGUgKz0gd2hlbl9lcSggcm90YXRpb24ueiwgMC4wICkgKiByb3RhdGlvbi55OycsXG5cdFx0JyAgICAgIGFuZ2xlICs9IHdoZW5fZ3QoIHJvdGF0aW9uLnosIDAuMCApICogbWl4KCAwLjAsIHJvdGF0aW9uLnksIHBvc2l0aW9uSW5UaW1lICk7Jyxcblx0XHQnICAgICAgdHJhbnNsYXRlZCA9IHJvdGF0aW9uQ2VudGVyIC0gcG9zOycsXG5cdFx0JyAgICAgIHJvdGF0aW9uTWF0cml4ID0gZ2V0Um90YXRpb25NYXRyaXgoIGF4aXMsIGFuZ2xlICk7Jyxcblx0XHQnICAgICAgcmV0dXJuIGNlbnRlciAtIHZlYzMoIHJvdGF0aW9uTWF0cml4ICogdmVjNCggdHJhbnNsYXRlZCwgMC4wICkgKTsnLFxuXHRcdCcgICB9Jyxcblx0XHQnI2VuZGlmJyxcblx0XS5qb2luKCAnXFxuJyApLFxuXG5cblx0Ly8gRnJhZ21lbnQgY2h1bmtzXG5cdHJvdGF0ZVRleHR1cmU6IFtcblx0XHQnICAgIHZlYzIgdlV2ID0gdmVjMiggZ2xfUG9pbnRDb29yZC54LCAxLjAgLSBnbF9Qb2ludENvb3JkLnkgKTsnLFxuXHRcdCcnLFxuXHRcdCcgICAgI2lmZGVmIFNIT1VMRF9ST1RBVEVfVEVYVFVSRScsXG5cdFx0JyAgICAgICBmbG9hdCB4ID0gZ2xfUG9pbnRDb29yZC54IC0gMC41OycsXG5cdFx0JyAgICAgICBmbG9hdCB5ID0gMS4wIC0gZ2xfUG9pbnRDb29yZC55IC0gMC41OycsXG5cdFx0JyAgICAgICBmbG9hdCBjID0gY29zKCAtdkFuZ2xlICk7Jyxcblx0XHQnICAgICAgIGZsb2F0IHMgPSBzaW4oIC12QW5nbGUgKTsnLFxuXG5cdFx0JyAgICAgICB2VXYgPSB2ZWMyKCBjICogeCArIHMgKiB5ICsgMC41LCBjICogeSAtIHMgKiB4ICsgMC41ICk7Jyxcblx0XHQnICAgICNlbmRpZicsXG5cdFx0JycsXG5cblx0XHQvLyBTcHJpdGVzaGVldHMgb3ZlcndyaXRlIGFuZ2xlIGNhbGN1bGF0aW9ucy5cblx0XHQnICAgICNpZmRlZiBTSE9VTERfQ0FMQ1VMQVRFX1NQUklURScsXG5cdFx0JyAgICAgICAgZmxvYXQgZnJhbWVzWCA9IHZTcHJpdGVTaGVldC54OycsXG5cdFx0JyAgICAgICAgZmxvYXQgZnJhbWVzWSA9IHZTcHJpdGVTaGVldC55OycsXG5cdFx0JyAgICAgICAgZmxvYXQgY29sdW1uTm9ybSA9IHZTcHJpdGVTaGVldC56OycsXG5cdFx0JyAgICAgICAgZmxvYXQgcm93Tm9ybSA9IHZTcHJpdGVTaGVldC53OycsXG5cblx0XHQnICAgICAgICB2VXYueCA9IGdsX1BvaW50Q29vcmQueCAqIGZyYW1lc1ggKyBjb2x1bW5Ob3JtOycsXG5cdFx0JyAgICAgICAgdlV2LnkgPSAxLjAgLSAoZ2xfUG9pbnRDb29yZC55ICogZnJhbWVzWSArIHJvd05vcm0pOycsXG5cdFx0JyAgICAjZW5kaWYnLFxuXG5cdFx0JycsXG5cdFx0JyAgICB2ZWM0IHJvdGF0ZWRUZXh0dXJlID0gdGV4dHVyZTJEKCB0ZXgsIHZVdiApOycsXG5cdF0uam9pbiggJ1xcbicgKSxcbn07IiwiaW1wb3J0ICogYXMgVEhSRUUgZnJvbSAndGhyZWUnO1xuaW1wb3J0IHNoYWRlckNodW5rcyBmcm9tICcuL3NoYWRlckNodW5rcyc7XG5cbmV4cG9ydCBkZWZhdWx0IHtcblx0dmVydGV4OiBbXG5cdFx0c2hhZGVyQ2h1bmtzLmRlZmluZXMsXG5cdFx0c2hhZGVyQ2h1bmtzLnVuaWZvcm1zLFxuXHRcdHNoYWRlckNodW5rcy5hdHRyaWJ1dGVzLFxuXHRcdHNoYWRlckNodW5rcy52YXJ5aW5ncyxcblxuXHRcdFRIUkVFLlNoYWRlckNodW5rLmNvbW1vbixcblx0XHRUSFJFRS5TaGFkZXJDaHVuay5sb2dkZXB0aGJ1Zl9wYXJzX3ZlcnRleCxcblx0XHRUSFJFRS5TaGFkZXJDaHVuay5mb2dfcGFyc192ZXJ0ZXgsXG5cblx0XHRzaGFkZXJDaHVua3MuYnJhbmNoQXZvaWRhbmNlRnVuY3Rpb25zLFxuXHRcdHNoYWRlckNodW5rcy51bnBhY2tDb2xvcixcblx0XHRzaGFkZXJDaHVua3MudW5wYWNrUm90YXRpb25BeGlzLFxuXHRcdHNoYWRlckNodW5rcy5mbG9hdE92ZXJMaWZldGltZSxcblx0XHRzaGFkZXJDaHVua3MuY29sb3JPdmVyTGlmZXRpbWUsXG5cdFx0c2hhZGVyQ2h1bmtzLnBhcmFtRmV0Y2hpbmdGdW5jdGlvbnMsXG5cdFx0c2hhZGVyQ2h1bmtzLmZvcmNlRmV0Y2hpbmdGdW5jdGlvbnMsXG5cdFx0c2hhZGVyQ2h1bmtzLnJvdGF0aW9uRnVuY3Rpb25zLFxuXG5cdFx0J3ZvaWQgbWFpbigpIHsnLFxuXG5cdFx0Ly9cblx0XHQvLyBTZXR1cC4uLlxuXHRcdC8vXG5cdFx0JyAgICBoaWdocCBmbG9hdCBhZ2UgPSBnZXRBZ2UoKTsnLFxuXHRcdCcgICAgaGlnaHAgZmxvYXQgYWxpdmUgPSBnZXRBbGl2ZSgpOycsXG5cdFx0JyAgICBoaWdocCBmbG9hdCBtYXhBZ2UgPSBnZXRNYXhBZ2UoKTsnLFxuXHRcdCcgICAgaGlnaHAgZmxvYXQgcG9zaXRpb25JblRpbWUgPSAoYWdlIC8gbWF4QWdlKTsnLFxuXHRcdCcgICAgaGlnaHAgZmxvYXQgaXNBbGl2ZSA9IHdoZW5fZ3QoIGFsaXZlLCAwLjAgKTsnLFxuXG5cdFx0JyAgICAjaWZkZWYgU0hPVUxEX1dJR0dMRV9QQVJUSUNMRVMnLFxuXHRcdCcgICAgICAgIGZsb2F0IHdpZ2dsZUFtb3VudCA9IHBvc2l0aW9uSW5UaW1lICogZ2V0V2lnZ2xlKCk7Jyxcblx0XHQnICAgICAgICBmbG9hdCB3aWdnbGVTaW4gPSBpc0FsaXZlICogc2luKCB3aWdnbGVBbW91bnQgKTsnLFxuXHRcdCcgICAgICAgIGZsb2F0IHdpZ2dsZUNvcyA9IGlzQWxpdmUgKiBjb3MoIHdpZ2dsZUFtb3VudCApOycsXG5cdFx0JyAgICAjZW5kaWYnLFxuXG5cdFx0Ly9cblx0XHQvLyBGb3JjZXNcblx0XHQvL1xuXG5cdFx0Ly8gR2V0IGZvcmNlcyAmIHBvc2l0aW9uXG5cdFx0JyAgICB2ZWMzIHZlbCA9IGdldFZlbG9jaXR5KCBhZ2UgKTsnLFxuXHRcdCcgICAgdmVjMyBhY2NlbCA9IGdldEFjY2VsZXJhdGlvbiggYWdlICk7Jyxcblx0XHQnICAgIHZlYzMgZm9yY2UgPSB2ZWMzKCAwLjAgKTsnLFxuXHRcdCcgICAgdmVjMyBwb3MgPSB2ZWMzKCBwb3NpdGlvbiApOycsXG5cblx0XHQvLyBDYWxjdWxhdGUgdGhlIHJlcXVpcmVkIGRyYWcgdG8gYXBwbHkgdG8gdGhlIGZvcmNlcy5cblx0XHQnICAgIGZsb2F0IGRyYWcgPSAxLjAgLSAocG9zaXRpb25JblRpbWUgKiAwLjUpICogYWNjZWxlcmF0aW9uLnc7JyxcblxuXHRcdC8vIEludGVncmF0ZSBmb3JjZXMuLi5cblx0XHQnICAgIGZvcmNlICs9IHZlbDsnLFxuXHRcdCcgICAgZm9yY2UgKj0gZHJhZzsnLFxuXHRcdCcgICAgZm9yY2UgKz0gYWNjZWwgKiBhZ2U7Jyxcblx0XHQnICAgIHBvcyArPSBmb3JjZTsnLFxuXG5cblx0XHQvLyBXaWdnbHkgd2lnZ2x5IHdpZ2dsZSFcblx0XHQnICAgICNpZmRlZiBTSE9VTERfV0lHR0xFX1BBUlRJQ0xFUycsXG5cdFx0JyAgICAgICAgcG9zLnggKz0gd2lnZ2xlU2luOycsXG5cdFx0JyAgICAgICAgcG9zLnkgKz0gd2lnZ2xlQ29zOycsXG5cdFx0JyAgICAgICAgcG9zLnogKz0gd2lnZ2xlU2luOycsXG5cdFx0JyAgICAjZW5kaWYnLFxuXG5cblx0XHQvLyBSb3RhdGUgdGhlIGVtaXR0ZXIgYXJvdW5kIGl0J3MgY2VudHJhbCBwb2ludFxuXHRcdCcgICAgI2lmZGVmIFNIT1VMRF9ST1RBVEVfUEFSVElDTEVTJyxcblx0XHQnICAgICAgICBwb3MgPSBnZXRSb3RhdGlvbiggcG9zLCBwb3NpdGlvbkluVGltZSApOycsXG5cdFx0JyAgICAjZW5kaWYnLFxuXG5cdFx0Ly8gQ29udmVydCBwb3MgdG8gYSB3b3JsZC1zcGFjZSB2YWx1ZVxuXHRcdCcgICAgdmVjNCBtdlBvc2l0aW9uID0gbW9kZWxWaWV3TWF0cml4ICogdmVjNCggcG9zLCAxLjAgKTsnLFxuXG5cdFx0Ly8gRGV0ZXJtaW5lIHBvaW50IHNpemUuXG5cdFx0JyAgICBoaWdocCBmbG9hdCBwb2ludFNpemUgPSBnZXRGbG9hdE92ZXJMaWZldGltZSggcG9zaXRpb25JblRpbWUsIHNpemUgKSAqIGlzQWxpdmU7JyxcblxuXHRcdC8vIERldGVybWluZSBwZXJzcGVjdGl2ZVxuXHRcdCcgICAgI2lmZGVmIEhBU19QRVJTUEVDVElWRScsXG5cdFx0JyAgICAgICAgZmxvYXQgcGVyc3BlY3RpdmUgPSBzY2FsZSAvIGxlbmd0aCggbXZQb3NpdGlvbi54eXogKTsnLFxuXHRcdCcgICAgI2Vsc2UnLFxuXHRcdCcgICAgICAgIGZsb2F0IHBlcnNwZWN0aXZlID0gMS4wOycsXG5cdFx0JyAgICAjZW5kaWYnLFxuXG5cdFx0Ly8gQXBwbHkgcGVycGVjdGl2ZSB0byBwb2ludFNpemUgdmFsdWVcblx0XHQnICAgIGZsb2F0IHBvaW50U2l6ZVBlcnNwZWN0aXZlID0gcG9pbnRTaXplICogcGVyc3BlY3RpdmU7JyxcblxuXG5cdFx0Ly9cblx0XHQvLyBBcHBlYXJhbmNlXG5cdFx0Ly9cblxuXHRcdC8vIERldGVybWluZSBjb2xvciBhbmQgb3BhY2l0eSBmb3IgdGhpcyBwYXJ0aWNsZVxuXHRcdCcgICAgI2lmZGVmIENPTE9SSVpFJyxcblx0XHQnICAgICAgIHZlYzMgYyA9IGlzQWxpdmUgKiBnZXRDb2xvck92ZXJMaWZldGltZSgnLFxuXHRcdCcgICAgICAgICAgIHBvc2l0aW9uSW5UaW1lLCcsXG5cdFx0JyAgICAgICAgICAgdW5wYWNrQ29sb3IoIGNvbG9yLnggKSwnLFxuXHRcdCcgICAgICAgICAgIHVucGFja0NvbG9yKCBjb2xvci55ICksJyxcblx0XHQnICAgICAgICAgICB1bnBhY2tDb2xvciggY29sb3IueiApLCcsXG5cdFx0JyAgICAgICAgICAgdW5wYWNrQ29sb3IoIGNvbG9yLncgKScsXG5cdFx0JyAgICAgICApOycsXG5cdFx0JyAgICAjZWxzZScsXG5cdFx0JyAgICAgICB2ZWMzIGMgPSB2ZWMzKDEuMCk7Jyxcblx0XHQnICAgICNlbmRpZicsXG5cblx0XHQnICAgIGZsb2F0IG8gPSBpc0FsaXZlICogZ2V0RmxvYXRPdmVyTGlmZXRpbWUoIHBvc2l0aW9uSW5UaW1lLCBvcGFjaXR5ICk7JyxcblxuXHRcdC8vIEFzc2lnbiBjb2xvciB0byB2Q29sb3IgdmFyeWluZy5cblx0XHQnICAgIHZDb2xvciA9IHZlYzQoIGMsIG8gKTsnLFxuXG5cdFx0Ly8gRGV0ZXJtaW5lIGFuZ2xlXG5cdFx0JyAgICAjaWZkZWYgU0hPVUxEX1JPVEFURV9URVhUVVJFJyxcblx0XHQnICAgICAgICB2QW5nbGUgPSBpc0FsaXZlICogZ2V0RmxvYXRPdmVyTGlmZXRpbWUoIHBvc2l0aW9uSW5UaW1lLCBhbmdsZSApOycsXG5cdFx0JyAgICAjZW5kaWYnLFxuXG5cdFx0Ly8gSWYgdGhpcyBwYXJ0aWNsZSBpcyB1c2luZyBhIHNwcml0ZS1zaGVldCBhcyBhIHRleHR1cmUsIHdlJ2xsIGhhdmUgdG8gZmlndXJlIG91dFxuXHRcdC8vIHdoYXQgZnJhbWUgb2YgdGhlIHRleHR1cmUgdGhlIHBhcnRpY2xlIGlzIHVzaW5nIGF0IGl0J3MgY3VycmVudCBwb3NpdGlvbiBpbiB0aW1lLlxuXHRcdCcgICAgI2lmZGVmIFNIT1VMRF9DQUxDVUxBVEVfU1BSSVRFJyxcblx0XHQnICAgICAgICBmbG9hdCBmcmFtZXNYID0gdGV4dHVyZUFuaW1hdGlvbi54OycsXG5cdFx0JyAgICAgICAgZmxvYXQgZnJhbWVzWSA9IHRleHR1cmVBbmltYXRpb24ueTsnLFxuXHRcdCcgICAgICAgIGZsb2F0IGxvb3BDb3VudCA9IHRleHR1cmVBbmltYXRpb24udzsnLFxuXHRcdCcgICAgICAgIGZsb2F0IHRvdGFsRnJhbWVzID0gdGV4dHVyZUFuaW1hdGlvbi56OycsXG5cdFx0JyAgICAgICAgZmxvYXQgZnJhbWVOdW1iZXIgPSBtb2QoIChwb3NpdGlvbkluVGltZSAqIGxvb3BDb3VudCkgKiB0b3RhbEZyYW1lcywgdG90YWxGcmFtZXMgKTsnLFxuXG5cdFx0JyAgICAgICAgZmxvYXQgY29sdW1uID0gZmxvb3IobW9kKCBmcmFtZU51bWJlciwgZnJhbWVzWCApKTsnLFxuXHRcdCcgICAgICAgIGZsb2F0IHJvdyA9IGZsb29yKCAoZnJhbWVOdW1iZXIgLSBjb2x1bW4pIC8gZnJhbWVzWCApOycsXG5cblx0XHQnICAgICAgICBmbG9hdCBjb2x1bW5Ob3JtID0gY29sdW1uIC8gZnJhbWVzWDsnLFxuXHRcdCcgICAgICAgIGZsb2F0IHJvd05vcm0gPSByb3cgLyBmcmFtZXNZOycsXG5cblx0XHQnICAgICAgICB2U3ByaXRlU2hlZXQueCA9IDEuMCAvIGZyYW1lc1g7Jyxcblx0XHQnICAgICAgICB2U3ByaXRlU2hlZXQueSA9IDEuMCAvIGZyYW1lc1k7Jyxcblx0XHQnICAgICAgICB2U3ByaXRlU2hlZXQueiA9IGNvbHVtbk5vcm07Jyxcblx0XHQnICAgICAgICB2U3ByaXRlU2hlZXQudyA9IHJvd05vcm07Jyxcblx0XHQnICAgICNlbmRpZicsXG5cblx0XHQvL1xuXHRcdC8vIFdyaXRlIHZhbHVlc1xuXHRcdC8vXG5cblx0XHQvLyBTZXQgUG9pbnRTaXplIGFjY29yZGluZyB0byBzaXplIGF0IGN1cnJlbnQgcG9pbnQgaW4gdGltZS5cblx0XHQnICAgIGdsX1BvaW50U2l6ZSA9IHBvaW50U2l6ZVBlcnNwZWN0aXZlOycsXG5cdFx0JyAgICBnbF9Qb3NpdGlvbiA9IHByb2plY3Rpb25NYXRyaXggKiBtdlBvc2l0aW9uOycsXG5cblx0XHRUSFJFRS5TaGFkZXJDaHVuay5sb2dkZXB0aGJ1Zl92ZXJ0ZXgsXG5cdFx0VEhSRUUuU2hhZGVyQ2h1bmsuZm9nX3ZlcnRleCxcblxuXHRcdCd9Jyxcblx0XS5qb2luKCAnXFxuJyApLFxuXG5cdGZyYWdtZW50OiBbXG5cdFx0c2hhZGVyQ2h1bmtzLnVuaWZvcm1zLFxuXG5cdFx0VEhSRUUuU2hhZGVyQ2h1bmsuY29tbW9uLFxuXHRcdFRIUkVFLlNoYWRlckNodW5rLmZvZ19wYXJzX2ZyYWdtZW50LFxuXHRcdFRIUkVFLlNoYWRlckNodW5rLmxvZ2RlcHRoYnVmX3BhcnNfZnJhZ21lbnQsXG5cblx0XHRzaGFkZXJDaHVua3MudmFyeWluZ3MsXG5cblx0XHRzaGFkZXJDaHVua3MuYnJhbmNoQXZvaWRhbmNlRnVuY3Rpb25zLFxuXG5cdFx0J3ZvaWQgbWFpbigpIHsnLFxuXHRcdCcgICAgdmVjMyBvdXRnb2luZ0xpZ2h0ID0gdkNvbG9yLnh5ejsnLFxuXHRcdCcgICAgJyxcblx0XHQnICAgICNpZmRlZiBBTFBIQVRFU1QnLFxuXHRcdCcgICAgICAgaWYgKCB2Q29sb3IudyA8IGZsb2F0KEFMUEhBVEVTVCkgKSBkaXNjYXJkOycsXG5cdFx0JyAgICAjZW5kaWYnLFxuXG5cdFx0c2hhZGVyQ2h1bmtzLnJvdGF0ZVRleHR1cmUsXG5cblx0XHRUSFJFRS5TaGFkZXJDaHVuay5sb2dkZXB0aGJ1Zl9mcmFnbWVudCxcblxuXHRcdCcgICAgb3V0Z29pbmdMaWdodCA9IHZDb2xvci54eXogKiByb3RhdGVkVGV4dHVyZS54eXo7Jyxcblx0XHQnICAgIGdsX0ZyYWdDb2xvciA9IHZlYzQoIG91dGdvaW5nTGlnaHQueHl6LCByb3RhdGVkVGV4dHVyZS53ICogdkNvbG9yLncgKTsnLFxuXG5cdFx0VEhSRUUuU2hhZGVyQ2h1bmsuZm9nX2ZyYWdtZW50LFxuXG5cdFx0J30nLFxuXHRdLmpvaW4oICdcXG4nICksXG59O1xuIiwiLyoqXG4gKiBBIG1hcCBvZiBzdXBwb3J0ZWQgZGlzdHJpYnV0aW9uIHR5cGVzIHVzZWRcbiAqIGJ5IFNQRS5FbWl0dGVyIGluc3RhbmNlcy5cbiAqXG4gKiBUaGVzZSBkaXN0cmlidXRpb24gdHlwZXMgY2FuIGJlIGFwcGxpZWQgdG9cbiAqIGFuIGVtaXR0ZXIgZ2xvYmFsbHksIHdoaWNoIHdpbGwgYWZmZWN0IHRoZVxuICogYHBvc2l0aW9uYCwgYHZlbG9jaXR5YCwgYW5kIGBhY2NlbGVyYXRpb25gXG4gKiB2YWx1ZSBjYWxjdWxhdGlvbnMgZm9yIGFuIGVtaXR0ZXIsIG9yIHRoZXlcbiAqIGNhbiBiZSBhcHBsaWVkIG9uIGEgcGVyLXByb3BlcnR5IGJhc2lzLlxuICpcbiAqIEBlbnVtIHtOdW1iZXJ9XG4gKi9cbmV4cG9ydCBkZWZhdWx0IHtcblxuXHQvKipcblx0ICogVmFsdWVzIHdpbGwgYmUgZGlzdHJpYnV0ZWQgd2l0aGluIGEgYm94LlxuXHQgKiBAdHlwZSB7TnVtYmVyfVxuXHQgKi9cblx0Qk9YOiAxLFxuXG5cdC8qKlxuXHQgKiBWYWx1ZXMgd2lsbCBiZSBkaXN0cmlidXRlZCBvbiBhIHNwaGVyZS5cblx0ICogQHR5cGUge051bWJlcn1cblx0ICovXG5cdFNQSEVSRTogMixcblxuXHQvKipcblx0ICogVmFsdWVzIHdpbGwgYmUgZGlzdHJpYnV0ZWQgb24gYSAyZC1kaXNjIHNoYXBlLlxuXHQgKiBAdHlwZSB7TnVtYmVyfVxuXHQgKi9cblx0RElTQzogMyxcblxuXHQvKipcblx0ICogVmFsdWVzIHdpbGwgYmUgZGlzdHJpYnV0ZWQgYWxvbmcgYSBsaW5lLlxuXHQgKiBAdHlwZSB7TnVtYmVyfVxuXHQgKi9cblx0TElORTogNCxcbn07IiwiaW1wb3J0ICogYXMgVEhSRUUgZnJvbSAndGhyZWUnO1xuaW1wb3J0IHZhbHVlVHlwZXMgZnJvbSAnQC9jb25zdGFudHMvdmFsdWVUeXBlcyc7XG5pbXBvcnQgZGlzdHJpYnV0aW9ucyBmcm9tICdAL2NvbnN0YW50cy9kaXN0cmlidXRpb25zJztcbmltcG9ydCBnbG9iYWxzIGZyb20gJ0AvY29uc3RhbnRzL2dsb2JhbHMnO1xuaW1wb3J0IHV0aWxzIGZyb20gJy4vdXRpbHMnO1xuXG5jb25zdCBIQVNfT1dOID0gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eTtcblxuLyoqXG4gKiBBbiBTUEUuRW1pdHRlciBpbnN0YW5jZS5cbiAqIEB0eXBlZGVmIHtPYmplY3R9IEVtaXR0ZXJcbiAqIEBzZWUgU1BFLkVtaXR0ZXJcbiAqL1xuXG4vKipcbiAqIEEgbWFwIG9mIG9wdGlvbnMgdG8gY29uZmlndXJlIGFuIFNQRS5FbWl0dGVyIGluc3RhbmNlLlxuICpcbiAqIEB0eXBlZGVmIHtPYmplY3R9IEVtaXR0ZXJPcHRpb25zXG4gKlxuICogQHByb3BlcnR5IHtkaXN0cmlidXRpb259IFt0eXBlPUJPWF0gVGhlIGRlZmF1bHQgZGlzdHJpYnV0aW9uIHRoaXMgZW1pdHRlciBzaG91bGQgdXNlIHRvIGNvbnRyb2xcbiAqICAgICAgICAgICAgICAgICAgICAgICAgIGl0cyBwYXJ0aWNsZSdzIHNwYXduIHBvc2l0aW9uIGFuZCBmb3JjZSBiZWhhdmlvdXIuXG4gKiAgICAgICAgICAgICAgICAgICAgICAgICBNdXN0IGJlIGFuIGRpc3RyaWJ1dGlvbnMuKiB2YWx1ZS5cbiAqXG4gKlxuICogQHByb3BlcnR5IHtOdW1iZXJ9IFtwYXJ0aWNsZUNvdW50PTEwMF0gVGhlIHRvdGFsIG51bWJlciBvZiBwYXJ0aWNsZXMgdGhpcyBlbWl0dGVyIHdpbGwgaG9sZC4gTk9URTogdGhpcyBpcyBub3QgdGhlIG51bWJlclxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb2YgcGFydGljbGVzIGVtaXR0ZWQgaW4gYSBzZWNvbmQsIG9yIGFueXRoaW5nIGxpa2UgdGhhdC4gVGhlIG51bWJlciBvZiBwYXJ0aWNsZXNcbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVtaXR0ZWQgcGVyLXNlY29uZCBpcyBjYWxjdWxhdGVkIGJ5IHBhcnRpY2xlQ291bnQgLyBtYXhBZ2UgKGFwcHJveGltYXRlbHkhKVxuICpcbiAqIEBwcm9wZXJ0eSB7TnVtYmVyfG51bGx9IFtkdXJhdGlvbj1udWxsXSBUaGUgZHVyYXRpb24gaW4gc2Vjb25kcyB0aGF0IHRoaXMgZW1pdHRlciBzaG91bGQgbGl2ZSBmb3IuIElmIG5vdCBzcGVjaWZpZWQsIHRoZSBlbWl0dGVyXG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2lsbCBlbWl0IHBhcnRpY2xlcyBpbmRlZmluaXRlbHkuXG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTk9URTogV2hlbiBhbiBlbWl0dGVyIGlzIG9sZGVyIHRoYW4gYSBzcGVjaWZpZWQgZHVyYXRpb24sIHRoZSBlbWl0dGVyIGlzIE5PVCByZW1vdmVkIGZyb21cbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpdCdzIGdyb3VwLCBidXQgcmF0aGVyIGlzIGp1c3QgbWFya2VkIGFzIGRlYWQsIGFsbG93aW5nIGl0IHRvIGJlIHJlYW5pbWF0ZWQgYXQgYSBsYXRlciB0aW1lXG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXNpbmcgYFNQRS5FbWl0dGVyLnByb3RvdHlwZS5lbmFibGUoKWAuXG4gKlxuICogQHByb3BlcnR5IHtCb29sZWFufSBbaXNTdGF0aWM9ZmFsc2VdIFdoZXRoZXIgdGhpcyBlbWl0dGVyIHNob3VsZCBiZSBub3QgYmUgc2ltdWxhdGVkICh0cnVlKS5cbiAqIEBwcm9wZXJ0eSB7Qm9vbGVhbn0gW2FjdGl2ZU11bHRpcGxpZXI9MV0gQSB2YWx1ZSBiZXR3ZWVuIDAgYW5kIDEgZGVzY3JpYmluZyB3aGF0IHBlcmNlbnRhZ2Ugb2YgdGhpcyBlbWl0dGVyJ3MgcGFydGljbGVzUGVyU2Vjb25kIHNob3VsZCBiZVxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlbWl0dGVkLCB3aGVyZSAwIGlzIDAlLCBhbmQgMSBpcyAxMDAlLlxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBGb3IgZXhhbXBsZSwgaGF2aW5nIGFuIGVtaXR0ZXIgd2l0aCAxMDAgcGFydGljbGVzLCBhIG1heEFnZSBvZiAyLCB5aWVsZHMgYSBwYXJ0aWNsZXNQZXJTZWNvbmRcbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWUgb2YgNTAuIFNldHRpbmcgYGFjdGl2ZU11bHRpcGxpZXJgIHRvIDAuNSwgdGhlbiwgd2lsbCBvbmx5IGVtaXQgMjUgcGFydGljbGVzIHBlciBzZWNvbmQgKDAuNSA9IDUwJSkuXG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFZhbHVlcyBncmVhdGVyIHRoYW4gMSB3aWxsIGVtdWxhdGUgYSBidXJzdCBvZiBwYXJ0aWNsZXMsIGNhdXNpbmcgdGhlIGVtaXR0ZXIgdG8gcnVuIG91dCBvZiBwYXJ0aWNsZXNcbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmVmb3JlIGl0J3MgbmV4dCBhY3RpdmF0aW9uIGN5Y2xlLlxuICpcbiAqIEBwcm9wZXJ0eSB7Qm9vbGVhbn0gW2RpcmVjdGlvbj0xXSBUaGUgZGlyZWN0aW9uIG9mIHRoZSBlbWl0dGVyLiBJZiB2YWx1ZSBpcyBgMWAsIGVtaXR0ZXIgd2lsbCBzdGFydCBhdCBiZWdpbm5pbmcgb2YgcGFydGljbGUncyBsaWZlY3ljbGUuXG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgdmFsdWUgaXMgYC0xYCwgZW1pdHRlciB3aWxsIHN0YXJ0IGF0IGVuZCBvZiBwYXJ0aWNsZSdzIGxpZmVjeWNsZSBhbmQgd29yayBpdCdzIHdheSBiYWNrd2FyZHMuXG4gKlxuICogQHByb3BlcnR5IHtPYmplY3R9IFttYXhBZ2U9e31dIEFuIG9iamVjdCBkZXNjcmliaW5nIHRoZSBwYXJ0aWNsZSdzIG1heGltdW0gYWdlIGluIHNlY29uZHMuXG4gKiBAcHJvcGVydHkge051bWJlcn0gW21heEFnZS52YWx1ZT0yXSBBIG51bWJlciBiZXR3ZWVuIDAgYW5kIDEgZGVzY3JpYmluZyB0aGUgYW1vdW50IG9mIG1heEFnZSB0byBhcHBseSB0byBhbGwgcGFydGljbGVzLlxuICogQHByb3BlcnR5IHtOdW1iZXJ9IFttYXhBZ2Uuc3ByZWFkPTBdIEEgbnVtYmVyIGRlc2NyaWJpbmcgdGhlIG1heEFnZSB2YXJpYW5jZSBvbiBhIHBlci1wYXJ0aWNsZSBiYXNpcy5cbiAqXG4gKlxuICogQHByb3BlcnR5IHtPYmplY3R9IFtwb3NpdGlvbj17fV0gQW4gb2JqZWN0IGRlc2NyaWJpbmcgdGhpcyBlbWl0dGVyJ3MgcG9zaXRpb24uXG4gKiBAcHJvcGVydHkge09iamVjdH0gW3Bvc2l0aW9uLnZhbHVlPW5ldyBUSFJFRS5WZWN0b3IzKCldIEEgVEhSRUUuVmVjdG9yMyBpbnN0YW5jZSBkZXNjcmliaW5nIHRoaXMgZW1pdHRlcidzIGJhc2UgcG9zaXRpb24uXG4gKiBAcHJvcGVydHkge09iamVjdH0gW3Bvc2l0aW9uLnNwcmVhZD1uZXcgVEhSRUUuVmVjdG9yMygpXSBBIFRIUkVFLlZlY3RvcjMgaW5zdGFuY2UgZGVzY3JpYmluZyB0aGlzIGVtaXR0ZXIncyBwb3NpdGlvbiB2YXJpYW5jZSBvbiBhIHBlci1wYXJ0aWNsZSBiYXNpcy5cbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5vdGUgdGhhdCB3aGVuIHVzaW5nIGEgU1BIRVJFIG9yIERJU0MgZGlzdHJpYnV0aW9uLCBvbmx5IHRoZSB4LWNvbXBvbmVudFxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb2YgdGhpcyB2ZWN0b3IgaXMgdXNlZC5cbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFdoZW4gdXNpbmcgYSBMSU5FIGRpc3RyaWJ1dGlvbiwgdGhpcyB2YWx1ZSBpcyB0aGUgZW5kcG9pbnQgb2YgdGhlIExJTkUuXG4gKiBAcHJvcGVydHkge09iamVjdH0gW3Bvc2l0aW9uLnNwcmVhZENsYW1wPW5ldyBUSFJFRS5WZWN0b3IzKCldIEEgVEhSRUUuVmVjdG9yMyBpbnN0YW5jZSBkZXNjcmliaW5nIHRoZSBudW1lcmljIG11bHRpcGxlcyB0aGUgcGFydGljbGUncyBzaG91bGRcbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmUgc3ByZWFkIG91dCBvdmVyLlxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBOb3RlIHRoYXQgd2hlbiB1c2luZyBhIFNQSEVSRSBvciBESVNDIGRpc3RyaWJ1dGlvbiwgb25seSB0aGUgeC1jb21wb25lbnRcbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb2YgdGhpcyB2ZWN0b3IgaXMgdXNlZC5cbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgV2hlbiB1c2luZyBhIExJTkUgZGlzdHJpYnV0aW9uLCB0aGlzIHByb3BlcnR5IGlzIGlnbm9yZWQuXG4gKiBAcHJvcGVydHkge051bWJlcn0gW3Bvc2l0aW9uLnJhZGl1cz0xMF0gVGhpcyBlbWl0dGVyJ3MgYmFzZSByYWRpdXMuXG4gKiBAcHJvcGVydHkge09iamVjdH0gW3Bvc2l0aW9uLnJhZGl1c1NjYWxlPW5ldyBUSFJFRS5WZWN0b3IzKCldIEEgVEhSRUUuVmVjdG9yMyBpbnN0YW5jZSBkZXNjcmliaW5nIHRoZSByYWRpdXMncyBzY2FsZSBpbiBhbGwgdGhyZWUgYXhlcy4gQWxsb3dzIGEgU1BIRVJFIG9yIERJU0MgdG8gYmUgc3F1YXNoZWQgb3Igc3RyZXRjaGVkLlxuICogQHByb3BlcnR5IHtkaXN0cmlidXRpb259IFtwb3NpdGlvbi5kaXN0cmlidXRpb249dmFsdWUgb2YgdGhlIGB0eXBlYCBvcHRpb24uXSBBIHNwZWNpZmljIGRpc3RyaWJ1dGlvbiB0byB1c2Ugd2hlbiByYWRpdXNpbmcgcGFydGljbGVzLiBPdmVycmlkZXMgdGhlIGB0eXBlYCBvcHRpb24uXG4gKiBAcHJvcGVydHkge0Jvb2xlYW59IFtwb3NpdGlvbi5yYW5kb21pc2U9ZmFsc2VdIFdoZW4gYSBwYXJ0aWNsZSBpcyByZS1zcGF3bmVkLCB3aGV0aGVyIGl0J3MgcG9zaXRpb24gc2hvdWxkIGJlIHJlLXJhbmRvbWlzZWQgb3Igbm90LiBDYW4gaW5jdXIgYSBwZXJmb3JtYW5jZSBoaXQuXG4gKlxuICpcbiAqIEBwcm9wZXJ0eSB7T2JqZWN0fSBbdmVsb2NpdHk9e31dIEFuIG9iamVjdCBkZXNjcmliaW5nIHRoaXMgcGFydGljbGUgdmVsb2NpdHkuXG4gKiBAcHJvcGVydHkge09iamVjdH0gW3ZlbG9jaXR5LnZhbHVlPW5ldyBUSFJFRS5WZWN0b3IzKCldIEEgVEhSRUUuVmVjdG9yMyBpbnN0YW5jZSBkZXNjcmliaW5nIHRoaXMgZW1pdHRlcidzIGJhc2UgdmVsb2NpdHkuXG4gKiBAcHJvcGVydHkge09iamVjdH0gW3ZlbG9jaXR5LnNwcmVhZD1uZXcgVEhSRUUuVmVjdG9yMygpXSBBIFRIUkVFLlZlY3RvcjMgaW5zdGFuY2UgZGVzY3JpYmluZyB0aGlzIGVtaXR0ZXIncyB2ZWxvY2l0eSB2YXJpYW5jZSBvbiBhIHBlci1wYXJ0aWNsZSBiYXNpcy5cbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5vdGUgdGhhdCB3aGVuIHVzaW5nIGEgU1BIRVJFIG9yIERJU0MgZGlzdHJpYnV0aW9uLCBvbmx5IHRoZSB4LWNvbXBvbmVudFxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb2YgdGhpcyB2ZWN0b3IgaXMgdXNlZC5cbiAqIEBwcm9wZXJ0eSB7ZGlzdHJpYnV0aW9ufSBbdmVsb2NpdHkuZGlzdHJpYnV0aW9uPXZhbHVlIG9mIHRoZSBgdHlwZWAgb3B0aW9uLl0gQSBzcGVjaWZpYyBkaXN0cmlidXRpb24gdG8gdXNlIHdoZW4gY2FsY3VsYXRpbmcgYSBwYXJ0aWNsZSdzIHZlbG9jaXR5LiBPdmVycmlkZXMgdGhlIGB0eXBlYCBvcHRpb24uXG4gKiBAcHJvcGVydHkge0Jvb2xlYW59IFt2ZWxvY2l0eS5yYW5kb21pc2U9ZmFsc2VdIFdoZW4gYSBwYXJ0aWNsZSBpcyByZS1zcGF3bmVkLCB3aGV0aGVyIGl0J3MgdmVsb2NpdHkgc2hvdWxkIGJlIHJlLXJhbmRvbWlzZWQgb3Igbm90LiBDYW4gaW5jdXIgYSBwZXJmb3JtYW5jZSBoaXQuXG4gKlxuICpcbiAqIEBwcm9wZXJ0eSB7T2JqZWN0fSBbYWNjZWxlcmF0aW9uPXt9XSBBbiBvYmplY3QgZGVzY3JpYmluZyB0aGlzIHBhcnRpY2xlJ3MgYWNjZWxlcmF0aW9uLlxuICogQHByb3BlcnR5IHtPYmplY3R9IFthY2NlbGVyYXRpb24udmFsdWU9bmV3IFRIUkVFLlZlY3RvcjMoKV0gQSBUSFJFRS5WZWN0b3IzIGluc3RhbmNlIGRlc2NyaWJpbmcgdGhpcyBlbWl0dGVyJ3MgYmFzZSBhY2NlbGVyYXRpb24uXG4gKiBAcHJvcGVydHkge09iamVjdH0gW2FjY2VsZXJhdGlvbi5zcHJlYWQ9bmV3IFRIUkVFLlZlY3RvcjMoKV0gQSBUSFJFRS5WZWN0b3IzIGluc3RhbmNlIGRlc2NyaWJpbmcgdGhpcyBlbWl0dGVyJ3MgYWNjZWxlcmF0aW9uIHZhcmlhbmNlIG9uIGEgcGVyLXBhcnRpY2xlIGJhc2lzLlxuICogICAgICAgICAgICAgICAgICAgICAgICAgICBOb3RlIHRoYXQgd2hlbiB1c2luZyBhIFNQSEVSRSBvciBESVNDIGRpc3RyaWJ1dGlvbiwgb25seSB0aGUgeC1jb21wb25lbnRcbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgb2YgdGhpcyB2ZWN0b3IgaXMgdXNlZC5cbiAqIEBwcm9wZXJ0eSB7ZGlzdHJpYnV0aW9ufSBbYWNjZWxlcmF0aW9uLmRpc3RyaWJ1dGlvbj12YWx1ZSBvZiB0aGUgYHR5cGVgIG9wdGlvbi5dIEEgc3BlY2lmaWMgZGlzdHJpYnV0aW9uIHRvIHVzZSB3aGVuIGNhbGN1bGF0aW5nIGEgcGFydGljbGUncyBhY2NlbGVyYXRpb24uIE92ZXJyaWRlcyB0aGUgYHR5cGVgIG9wdGlvbi5cbiAqIEBwcm9wZXJ0eSB7Qm9vbGVhbn0gW2FjY2VsZXJhdGlvbi5yYW5kb21pc2U9ZmFsc2VdIFdoZW4gYSBwYXJ0aWNsZSBpcyByZS1zcGF3bmVkLCB3aGV0aGVyIGl0J3MgYWNjZWxlcmF0aW9uIHNob3VsZCBiZSByZS1yYW5kb21pc2VkIG9yIG5vdC4gQ2FuIGluY3VyIGEgcGVyZm9ybWFuY2UgaGl0LlxuICpcbiAqXG4gKiBAcHJvcGVydHkge09iamVjdH0gW2RyYWc9e31dIEFuIG9iamVjdCBkZXNjcmliaW5nIHRoaXMgcGFydGljbGUgZHJhZy4gRHJhZyBpcyBhcHBsaWVkIHRvIGJvdGggdmVsb2NpdHkgYW5kIGFjY2VsZXJhdGlvbiB2YWx1ZXMuXG4gKiBAcHJvcGVydHkge051bWJlcn0gW2RyYWcudmFsdWU9MF0gQSBudW1iZXIgYmV0d2VlbiAwIGFuZCAxIGRlc2NyaWJpbmcgdGhlIGFtb3VudCBvZiBkcmFnIHRvIGFwcGx5IHRvIGFsbCBwYXJ0aWNsZXMuXG4gKiBAcHJvcGVydHkge051bWJlcn0gW2RyYWcuc3ByZWFkPTBdIEEgbnVtYmVyIGRlc2NyaWJpbmcgdGhlIGRyYWcgdmFyaWFuY2Ugb24gYSBwZXItcGFydGljbGUgYmFzaXMuXG4gKiBAcHJvcGVydHkge0Jvb2xlYW59IFtkcmFnLnJhbmRvbWlzZT1mYWxzZV0gV2hlbiBhIHBhcnRpY2xlIGlzIHJlLXNwYXduZWQsIHdoZXRoZXIgaXQncyBkcmFnIHNob3VsZCBiZSByZS1yYW5kb21pc2VkIG9yIG5vdC4gQ2FuIGluY3VyIGEgcGVyZm9ybWFuY2UgaGl0LlxuICpcbiAqXG4gKiBAcHJvcGVydHkge09iamVjdH0gW3dpZ2dsZT17fV0gVGhpcyBpcyBxdWl0ZSBhIGZ1biBvbmUhIFRoZSB2YWx1ZXMgb2YgdGhpcyBvYmplY3Qgd2lsbCBkZXRlcm1pbmUgd2hldGhlciBhIHBhcnRpY2xlIHdpbGwgd2lnZ2xlLCBvciBqaWdnbGUsIG9yIHdhdmUsXG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3Igc2hpbW15LCBvciB3YWdnbGUsIG9yLi4uIFdlbGwgeW91IGdldCB0aGUgaWRlYS4gVGhlIHdpZ2dsZSBpcyBjYWxjdWxhdGVkIG92ZXItdGltZSwgbWVhbmluZyB0aGF0IGEgcGFydGljbGUgd2lsbFxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0YXJ0IG9mZiB3aXRoIG5vIHdpZ2dsZSwgYW5kIGVuZCB1cCB3aWdnbGluZyBhYm91dCB3aXRoIHRoZSBkaXN0YW5jZSBvZiB0aGUgYHZhbHVlYCBzcGVjaWZpZWQgYnkgdGhlIHRpbWUgaXQgZGllcy5cbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJdCdzIHF1aXRlIGhhbmR5IHRvIHNpbXVsYXRlIGZpcmUgZW1iZXJzLCBvciBzaW1pbGFyIGVmZmVjdHMgd2hlcmUgdGhlIHBhcnRpY2xlJ3MgcG9zaXRpb24gc2hvdWxkIHNsaWdodGx5IGNoYW5nZSBvdmVyXG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGltZSwgYW5kIHN1Y2ggY2hhbmdlIGlzbid0IGVhc2lseSBjb250cm9sbGVkIGJ5IHJvdGF0aW9uLCB2ZWxvY2l0eSwgb3IgYWNjZWxlcmF0aW9uLiBUaGUgd2lnZ2xlIGlzIGEgY29tYmluYXRpb24gb2Ygc2luIGFuZCBjb3MgY2FsY3VsYXRpb25zLCBzbyBpcyBjaXJjdWxhciBpbiBuYXR1cmUuXG4gKiBAcHJvcGVydHkge051bWJlcn0gW3dpZ2dsZS52YWx1ZT0wXSBBIG51bWJlciBkZXNjcmliaW5nIHRoZSBhbW91bnQgb2Ygd2lnZ2xlIHRvIGFwcGx5IHRvIGFsbCBwYXJ0aWNsZXMuIEl0J3MgbWVhc3VyZWQgaW4gZGlzdGFuY2UuXG4gKiBAcHJvcGVydHkge051bWJlcn0gW3dpZ2dsZS5zcHJlYWQ9MF0gQSBudW1iZXIgZGVzY3JpYmluZyB0aGUgd2lnZ2xlIHZhcmlhbmNlIG9uIGEgcGVyLXBhcnRpY2xlIGJhc2lzLlxuICpcbiAqXG4gKiBAcHJvcGVydHkge09iamVjdH0gW3JvdGF0aW9uPXt9XSBBbiBvYmplY3QgZGVzY3JpYmluZyB0aGlzIGVtaXR0ZXIncyByb3RhdGlvbi4gSXQgY2FuIGVpdGhlciBiZSBzdGF0aWMsIG9yIHNldCB0byByb3RhdGUgZnJvbSAwcmFkaWFucyB0byB0aGUgdmFsdWUgb2YgYHJvdGF0aW9uLnZhbHVlYFxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3ZlciBhIHBhcnRpY2xlJ3MgbGlmZXRpbWUuIFJvdGF0aW9uIHZhbHVlcyBhZmZlY3QgYm90aCBhIHBhcnRpY2xlJ3MgcG9zaXRpb24gYW5kIHRoZSBmb3JjZXMgYXBwbGllZCB0byBpdC5cbiAqIEBwcm9wZXJ0eSB7T2JqZWN0fSBbcm90YXRpb24uYXhpcz1uZXcgVEhSRUUuVmVjdG9yMygwLCAxLCAwKV0gQSBUSFJFRS5WZWN0b3IzIGluc3RhbmNlIGRlc2NyaWJpbmcgdGhpcyBlbWl0dGVyJ3MgYXhpcyBvZiByb3RhdGlvbi5cbiAqIEBwcm9wZXJ0eSB7T2JqZWN0fSBbcm90YXRpb24uYXhpc1NwcmVhZD1uZXcgVEhSRUUuVmVjdG9yMygpXSBBIFRIUkVFLlZlY3RvcjMgaW5zdGFuY2UgZGVzY3JpYmluZyB0aGUgYW1vdW50IG9mIHZhcmlhbmNlIHRvIGFwcGx5IHRvIHRoZSBheGlzIG9mIHJvdGF0aW9uIG9uXG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYSBwZXItcGFydGljbGUgYmFzaXMuXG4gKiBAcHJvcGVydHkge051bWJlcn0gW3JvdGF0aW9uLmFuZ2xlPTBdIFRoZSBhbmdsZSBvZiByb3RhdGlvbiwgZ2l2ZW4gaW4gcmFkaWFucy4gSWYgYHJvdGF0aW9uLnN0YXRpY2AgaXMgdHJ1ZSwgdGhlIGVtaXR0ZXIgd2lsbCBzdGFydCBvZmYgcm90YXRlZCBhdCB0aGlzIGFuZ2xlLCBhbmQgc3RheSBhcyBzdWNoLlxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBPdGhlcndpc2UsIHRoZSBwYXJ0aWNsZXMgd2lsbCByb3RhdGUgZnJvbSAwcmFkaWFucyB0byB0aGlzIHZhbHVlIG92ZXIgdGhlaXIgbGlmZXRpbWVzLlxuICogQHByb3BlcnR5IHtOdW1iZXJ9IFtyb3RhdGlvbi5hbmdsZVNwcmVhZD0wXSBUaGUgYW1vdW50IG9mIHZhcmlhbmNlIGluIGVhY2ggcGFydGljbGUncyByb3RhdGlvbiBhbmdsZS5cbiAqIEBwcm9wZXJ0eSB7Qm9vbGVhbn0gW3JvdGF0aW9uLnN0YXRpYz1mYWxzZV0gV2hldGhlciB0aGUgcm90YXRpb24gc2hvdWxkIGJlIHN0YXRpYyBvciBub3QuXG4gKiBAcHJvcGVydHkge09iamVjdH0gW3JvdGF0aW9uLmNlbnRlcj1UaGUgdmFsdWUgb2YgYHBvc2l0aW9uLnZhbHVlYF0gQSBUSFJFRS5WZWN0b3IzIGluc3RhbmNlIGRlc2NyaWJpbmcgdGhlIGNlbnRlciBwb2ludCBvZiByb3RhdGlvbi5cbiAqIEBwcm9wZXJ0eSB7Qm9vbGVhbn0gW3JvdGF0aW9uLnJhbmRvbWlzZT1mYWxzZV0gV2hlbiBhIHBhcnRpY2xlIGlzIHJlLXNwYXduZWQsIHdoZXRoZXIgaXQncyByb3RhdGlvbiBzaG91bGQgYmUgcmUtcmFuZG9taXNlZCBvciBub3QuIENhbiBpbmN1ciBhIHBlcmZvcm1hbmNlIGhpdC5cbiAqXG4gKlxuICogQHByb3BlcnR5IHtPYmplY3R9IFtjb2xvcj17fV0gQW4gb2JqZWN0IGRlc2NyaWJpbmcgYSBwYXJ0aWNsZSdzIGNvbG9yLiBUaGlzIHByb3BlcnR5IGlzIGEgXCJ2YWx1ZS1vdmVyLWxpZmV0aW1lXCIgcHJvcGVydHksIG1lYW5pbmcgYW4gYXJyYXkgb2YgdmFsdWVzIGFuZCBzcHJlYWRzIGNhbiBiZVxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2l2ZW4gdG8gZGVzY3JpYmUgc3BlY2lmaWMgdmFsdWUgY2hhbmdlcyBvdmVyIGEgcGFydGljbGUncyBsaWZldGltZS5cbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIERlcGVuZGluZyBvbiB0aGUgdmFsdWUgb2YgU1BFLnZhbHVlT3ZlckxpZmV0aW1lTGVuZ3RoLCBpZiBhcnJheXMgb2YgVEhSRUUuQ29sb3IgaW5zdGFuY2VzIGFyZSBnaXZlbiwgdGhlbiB0aGUgYXJyYXkgd2lsbCBiZSBpbnRlcnBvbGF0ZWQgdG9cbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhhdmUgYSBsZW5ndGggbWF0Y2hpbmcgdGhlIHZhbHVlIG9mIFNQRS52YWx1ZU92ZXJMaWZldGltZUxlbmd0aC5cbiAqIEBwcm9wZXJ0eSB7T2JqZWN0fSBbY29sb3IudmFsdWU9bmV3IFRIUkVFLkNvbG9yKCldIEVpdGhlciBhIHNpbmdsZSBUSFJFRS5Db2xvciBpbnN0YW5jZSwgb3IgYW4gYXJyYXkgb2YgVEhSRUUuQ29sb3IgaW5zdGFuY2VzIHRvIGRlc2NyaWJlIHRoZSBjb2xvciBvZiBhIHBhcnRpY2xlIG92ZXIgaXQncyBsaWZldGltZS5cbiAqIEBwcm9wZXJ0eSB7T2JqZWN0fSBbY29sb3Iuc3ByZWFkPW5ldyBUSFJFRS5WZWN0b3IzKCldIEVpdGhlciBhIHNpbmdsZSBUSFJFRS5WZWN0b3IzIGluc3RhbmNlLCBvciBhbiBhcnJheSBvZiBUSFJFRS5WZWN0b3IzIGluc3RhbmNlcyB0byBkZXNjcmliZSB0aGUgY29sb3IgdmFyaWFuY2Ugb2YgYSBwYXJ0aWNsZSBvdmVyIGl0J3MgbGlmZXRpbWUuXG4gKiBAcHJvcGVydHkge0Jvb2xlYW59IFtjb2xvci5yYW5kb21pc2U9ZmFsc2VdIFdoZW4gYSBwYXJ0aWNsZSBpcyByZS1zcGF3bmVkLCB3aGV0aGVyIGl0J3MgY29sb3Igc2hvdWxkIGJlIHJlLXJhbmRvbWlzZWQgb3Igbm90LiBDYW4gaW5jdXIgYSBwZXJmb3JtYW5jZSBoaXQuXG4gKlxuICpcbiAqIEBwcm9wZXJ0eSB7T2JqZWN0fSBbb3BhY2l0eT17fV0gQW4gb2JqZWN0IGRlc2NyaWJpbmcgYSBwYXJ0aWNsZSdzIG9wYWNpdHkuIFRoaXMgcHJvcGVydHkgaXMgYSBcInZhbHVlLW92ZXItbGlmZXRpbWVcIiBwcm9wZXJ0eSwgbWVhbmluZyBhbiBhcnJheSBvZiB2YWx1ZXMgYW5kIHNwcmVhZHMgY2FuIGJlXG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnaXZlbiB0byBkZXNjcmliZSBzcGVjaWZpYyB2YWx1ZSBjaGFuZ2VzIG92ZXIgYSBwYXJ0aWNsZSdzIGxpZmV0aW1lLlxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRGVwZW5kaW5nIG9uIHRoZSB2YWx1ZSBvZiBTUEUudmFsdWVPdmVyTGlmZXRpbWVMZW5ndGgsIGlmIGFycmF5cyBvZiBudW1iZXJzIGFyZSBnaXZlbiwgdGhlbiB0aGUgYXJyYXkgd2lsbCBiZSBpbnRlcnBvbGF0ZWQgdG9cbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhhdmUgYSBsZW5ndGggbWF0Y2hpbmcgdGhlIHZhbHVlIG9mIFNQRS52YWx1ZU92ZXJMaWZldGltZUxlbmd0aC5cbiAqIEBwcm9wZXJ0eSB7TnVtYmVyfSBbb3BhY2l0eS52YWx1ZT0xXSBFaXRoZXIgYSBzaW5nbGUgbnVtYmVyLCBvciBhbiBhcnJheSBvZiBudW1iZXJzIHRvIGRlc2NyaWJlIHRoZSBvcGFjaXR5IG9mIGEgcGFydGljbGUgb3ZlciBpdCdzIGxpZmV0aW1lLlxuICogQHByb3BlcnR5IHtOdW1iZXJ9IFtvcGFjaXR5LnNwcmVhZD0wXSBFaXRoZXIgYSBzaW5nbGUgbnVtYmVyLCBvciBhbiBhcnJheSBvZiBudW1iZXJzIHRvIGRlc2NyaWJlIHRoZSBvcGFjaXR5IHZhcmlhbmNlIG9mIGEgcGFydGljbGUgb3ZlciBpdCdzIGxpZmV0aW1lLlxuICogQHByb3BlcnR5IHtCb29sZWFufSBbb3BhY2l0eS5yYW5kb21pc2U9ZmFsc2VdIFdoZW4gYSBwYXJ0aWNsZSBpcyByZS1zcGF3bmVkLCB3aGV0aGVyIGl0J3Mgb3BhY2l0eSBzaG91bGQgYmUgcmUtcmFuZG9taXNlZCBvciBub3QuIENhbiBpbmN1ciBhIHBlcmZvcm1hbmNlIGhpdC5cbiAqXG4gKlxuICogQHByb3BlcnR5IHtPYmplY3R9IFtzaXplPXt9XSBBbiBvYmplY3QgZGVzY3JpYmluZyBhIHBhcnRpY2xlJ3Mgc2l6ZS4gVGhpcyBwcm9wZXJ0eSBpcyBhIFwidmFsdWUtb3Zlci1saWZldGltZVwiIHByb3BlcnR5LCBtZWFuaW5nIGFuIGFycmF5IG9mIHZhbHVlcyBhbmQgc3ByZWFkcyBjYW4gYmVcbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdpdmVuIHRvIGRlc2NyaWJlIHNwZWNpZmljIHZhbHVlIGNoYW5nZXMgb3ZlciBhIHBhcnRpY2xlJ3MgbGlmZXRpbWUuXG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEZXBlbmRpbmcgb24gdGhlIHZhbHVlIG9mIFNQRS52YWx1ZU92ZXJMaWZldGltZUxlbmd0aCwgaWYgYXJyYXlzIG9mIG51bWJlcnMgYXJlIGdpdmVuLCB0aGVuIHRoZSBhcnJheSB3aWxsIGJlIGludGVycG9sYXRlZCB0b1xuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGF2ZSBhIGxlbmd0aCBtYXRjaGluZyB0aGUgdmFsdWUgb2YgU1BFLnZhbHVlT3ZlckxpZmV0aW1lTGVuZ3RoLlxuICogQHByb3BlcnR5IHtOdW1iZXJ9IFtzaXplLnZhbHVlPTFdIEVpdGhlciBhIHNpbmdsZSBudW1iZXIsIG9yIGFuIGFycmF5IG9mIG51bWJlcnMgdG8gZGVzY3JpYmUgdGhlIHNpemUgb2YgYSBwYXJ0aWNsZSBvdmVyIGl0J3MgbGlmZXRpbWUuXG4gKiBAcHJvcGVydHkge051bWJlcn0gW3NpemUuc3ByZWFkPTBdIEVpdGhlciBhIHNpbmdsZSBudW1iZXIsIG9yIGFuIGFycmF5IG9mIG51bWJlcnMgdG8gZGVzY3JpYmUgdGhlIHNpemUgdmFyaWFuY2Ugb2YgYSBwYXJ0aWNsZSBvdmVyIGl0J3MgbGlmZXRpbWUuXG4gKiBAcHJvcGVydHkge0Jvb2xlYW59IFtzaXplLnJhbmRvbWlzZT1mYWxzZV0gV2hlbiBhIHBhcnRpY2xlIGlzIHJlLXNwYXduZWQsIHdoZXRoZXIgaXQncyBzaXplIHNob3VsZCBiZSByZS1yYW5kb21pc2VkIG9yIG5vdC4gQ2FuIGluY3VyIGEgcGVyZm9ybWFuY2UgaGl0LlxuICpcbiAqXG4gKiBAcHJvcGVydHkge09iamVjdH0gW2FuZ2xlPXt9XSBBbiBvYmplY3QgZGVzY3JpYmluZyBhIHBhcnRpY2xlJ3MgYW5nbGUuIFRoZSBhbmdsZSBpcyBhIDJkLXJvdGF0aW9uLCBtZWFzdXJlZCBpbiByYWRpYW5zLCBhcHBsaWVkIHRvIHRoZSBwYXJ0aWNsZSdzIHRleHR1cmUuXG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBOT1RFOiBpZiBhIHBhcnRpY2xlJ3MgdGV4dHVyZSBpcyBhIHNwcml0ZS1zaGVldCwgdGhpcyB2YWx1ZSBJUyBJR05PUkVELlxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVGhpcyBwcm9wZXJ0eSBpcyBhIFwidmFsdWUtb3Zlci1saWZldGltZVwiIHByb3BlcnR5LCBtZWFuaW5nIGFuIGFycmF5IG9mIHZhbHVlcyBhbmQgc3ByZWFkcyBjYW4gYmVcbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdpdmVuIHRvIGRlc2NyaWJlIHNwZWNpZmljIHZhbHVlIGNoYW5nZXMgb3ZlciBhIHBhcnRpY2xlJ3MgbGlmZXRpbWUuXG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEZXBlbmRpbmcgb24gdGhlIHZhbHVlIG9mIFNQRS52YWx1ZU92ZXJMaWZldGltZUxlbmd0aCwgaWYgYXJyYXlzIG9mIG51bWJlcnMgYXJlIGdpdmVuLCB0aGVuIHRoZSBhcnJheSB3aWxsIGJlIGludGVycG9sYXRlZCB0b1xuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGF2ZSBhIGxlbmd0aCBtYXRjaGluZyB0aGUgdmFsdWUgb2YgU1BFLnZhbHVlT3ZlckxpZmV0aW1lTGVuZ3RoLlxuICogQHByb3BlcnR5IHtOdW1iZXJ9IFthbmdsZS52YWx1ZT0wXSBFaXRoZXIgYSBzaW5nbGUgbnVtYmVyLCBvciBhbiBhcnJheSBvZiBudW1iZXJzIHRvIGRlc2NyaWJlIHRoZSBhbmdsZSBvZiBhIHBhcnRpY2xlIG92ZXIgaXQncyBsaWZldGltZS5cbiAqIEBwcm9wZXJ0eSB7TnVtYmVyfSBbYW5nbGUuc3ByZWFkPTBdIEVpdGhlciBhIHNpbmdsZSBudW1iZXIsIG9yIGFuIGFycmF5IG9mIG51bWJlcnMgdG8gZGVzY3JpYmUgdGhlIGFuZ2xlIHZhcmlhbmNlIG9mIGEgcGFydGljbGUgb3ZlciBpdCdzIGxpZmV0aW1lLlxuICogQHByb3BlcnR5IHtCb29sZWFufSBbYW5nbGUucmFuZG9taXNlPWZhbHNlXSBXaGVuIGEgcGFydGljbGUgaXMgcmUtc3Bhd25lZCwgd2hldGhlciBpdCdzIGFuZ2xlIHNob3VsZCBiZSByZS1yYW5kb21pc2VkIG9yIG5vdC4gQ2FuIGluY3VyIGEgcGVyZm9ybWFuY2UgaGl0LlxuICpcbiAqL1xuXG4vKipcbiAqIFRoZSBTUEUuRW1pdHRlciBjbGFzcy5cbiAqXG4gKiBAY29uc3RydWN0b3JcbiAqXG4gKiBAcGFyYW0ge0VtaXR0ZXJPcHRpb25zfSBvcHRpb25zIEEgbWFwIG9mIG9wdGlvbnMgdG8gY29uZmlndXJlIHRoZSBlbWl0dGVyLlxuICovXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBFbWl0dGVyIHtcblx0Y29uc3RydWN0b3IoIG9wdHMgKSB7XG5cdFx0Ly8gRW5zdXJlIHdlIGhhdmUgYSBtYXAgb2Ygb3B0aW9ucyB0byBwbGF5IHdpdGgsXG5cdFx0Ly8gYW5kIHRoYXQgZWFjaCBvcHRpb24gaXMgaW4gdGhlIGNvcnJlY3QgZm9ybWF0LlxuXHRcdGNvbnN0IG9wdGlvbnMgPSB1dGlscy5lbnN1cmVUeXBlZEFyZyggb3B0cywgdmFsdWVUeXBlcy5PQkpFQ1QsIHt9ICk7XG5cdFx0b3B0aW9ucy5wb3NpdGlvbiA9IHV0aWxzLmVuc3VyZVR5cGVkQXJnKCBvcHRpb25zLnBvc2l0aW9uLCB2YWx1ZVR5cGVzLk9CSkVDVCwge30gKTtcblx0XHRvcHRpb25zLnZlbG9jaXR5ID0gdXRpbHMuZW5zdXJlVHlwZWRBcmcoIG9wdGlvbnMudmVsb2NpdHksIHZhbHVlVHlwZXMuT0JKRUNULCB7fSApO1xuXHRcdG9wdGlvbnMuYWNjZWxlcmF0aW9uID0gdXRpbHMuZW5zdXJlVHlwZWRBcmcoIG9wdGlvbnMuYWNjZWxlcmF0aW9uLCB2YWx1ZVR5cGVzLk9CSkVDVCwge30gKTtcblx0XHRvcHRpb25zLnJhZGl1cyA9IHV0aWxzLmVuc3VyZVR5cGVkQXJnKCBvcHRpb25zLnJhZGl1cywgdmFsdWVUeXBlcy5PQkpFQ1QsIHt9ICk7XG5cdFx0b3B0aW9ucy5kcmFnID0gdXRpbHMuZW5zdXJlVHlwZWRBcmcoIG9wdGlvbnMuZHJhZywgdmFsdWVUeXBlcy5PQkpFQ1QsIHt9ICk7XG5cdFx0b3B0aW9ucy5yb3RhdGlvbiA9IHV0aWxzLmVuc3VyZVR5cGVkQXJnKCBvcHRpb25zLnJvdGF0aW9uLCB2YWx1ZVR5cGVzLk9CSkVDVCwge30gKTtcblx0XHRvcHRpb25zLmNvbG9yID0gdXRpbHMuZW5zdXJlVHlwZWRBcmcoIG9wdGlvbnMuY29sb3IsIHZhbHVlVHlwZXMuT0JKRUNULCB7fSApO1xuXHRcdG9wdGlvbnMub3BhY2l0eSA9IHV0aWxzLmVuc3VyZVR5cGVkQXJnKCBvcHRpb25zLm9wYWNpdHksIHZhbHVlVHlwZXMuT0JKRUNULCB7fSApO1xuXHRcdG9wdGlvbnMuc2l6ZSA9IHV0aWxzLmVuc3VyZVR5cGVkQXJnKCBvcHRpb25zLnNpemUsIHZhbHVlVHlwZXMuT0JKRUNULCB7fSApO1xuXHRcdG9wdGlvbnMuYW5nbGUgPSB1dGlscy5lbnN1cmVUeXBlZEFyZyggb3B0aW9ucy5hbmdsZSwgdmFsdWVUeXBlcy5PQkpFQ1QsIHt9ICk7XG5cdFx0b3B0aW9ucy53aWdnbGUgPSB1dGlscy5lbnN1cmVUeXBlZEFyZyggb3B0aW9ucy53aWdnbGUsIHZhbHVlVHlwZXMuT0JKRUNULCB7fSApO1xuXHRcdG9wdGlvbnMubWF4QWdlID0gdXRpbHMuZW5zdXJlVHlwZWRBcmcoIG9wdGlvbnMubWF4QWdlLCB2YWx1ZVR5cGVzLk9CSkVDVCwge30gKTtcblxuXHRcdGlmICggb3B0aW9ucy5vblBhcnRpY2xlU3Bhd24gKSB7XG5cdFx0XHRjb25zb2xlLndhcm4oICdvblBhcnRpY2xlU3Bhd24gaGFzIGJlZW4gcmVtb3ZlZC4gUGxlYXNlIHNldCBwcm9wZXJ0aWVzIGRpcmVjdGx5IHRvIGFsdGVyIHZhbHVlcyBhdCBydW50aW1lLicgKTtcblx0XHR9XG5cblx0XHR0aGlzLnV1aWQgPSBUSFJFRS5NYXRoLmdlbmVyYXRlVVVJRCgpO1xuXG5cdFx0dGhpcy50eXBlID0gdXRpbHMuZW5zdXJlVHlwZWRBcmcoIG9wdGlvbnMudHlwZSwgdmFsdWVUeXBlcy5OVU1CRVIsIGRpc3RyaWJ1dGlvbnMuQk9YICk7XG5cblx0XHQvLyBTdGFydCBhc3NpZ25pbmcgcHJvcGVydGllcy4uLmtpY2tpbmcgaXQgb2ZmIHdpdGggcHJvcHMgdGhhdCBET04nVCBzdXBwb3J0IHZhbHVlcyBvdmVyXG5cdFx0Ly8gbGlmZXRpbWVzLlxuXHRcdC8vXG5cdFx0Ly8gQnR3LCB2YWx1ZXMgb3ZlciBsaWZldGltZXMgYXJlIGp1c3QgdGhlIG5ldyB3YXkgb2YgcmVmZXJyaW5nIHRvICpTdGFydCwgKk1pZGRsZSwgYW5kICpFbmQuXG5cdFx0dGhpcy5wb3NpdGlvbiA9IHtcblx0XHRcdF92YWx1ZTogdXRpbHMuZW5zdXJlSW5zdGFuY2VPZiggb3B0aW9ucy5wb3NpdGlvbi52YWx1ZSwgVEhSRUUuVmVjdG9yMywgbmV3IFRIUkVFLlZlY3RvcjMoKSApLFxuXHRcdFx0X3NwcmVhZDogdXRpbHMuZW5zdXJlSW5zdGFuY2VPZiggb3B0aW9ucy5wb3NpdGlvbi5zcHJlYWQsIFRIUkVFLlZlY3RvcjMsIG5ldyBUSFJFRS5WZWN0b3IzKCkgKSxcblx0XHRcdF9zcHJlYWRDbGFtcDogdXRpbHMuZW5zdXJlSW5zdGFuY2VPZiggb3B0aW9ucy5wb3NpdGlvbi5zcHJlYWRDbGFtcCwgVEhSRUUuVmVjdG9yMywgbmV3IFRIUkVFLlZlY3RvcjMoKSApLFxuXHRcdFx0X2Rpc3RyaWJ1dGlvbjogdXRpbHMuZW5zdXJlVHlwZWRBcmcoIG9wdGlvbnMucG9zaXRpb24uZGlzdHJpYnV0aW9uLCB2YWx1ZVR5cGVzLk5VTUJFUiwgdGhpcy50eXBlICksXG5cdFx0XHRfcmFuZG9taXNlOiB1dGlscy5lbnN1cmVUeXBlZEFyZyggb3B0aW9ucy5wb3NpdGlvbi5yYW5kb21pc2UsIHZhbHVlVHlwZXMuQk9PTEVBTiwgZmFsc2UgKSxcblx0XHRcdF9yYWRpdXM6IHV0aWxzLmVuc3VyZVR5cGVkQXJnKCBvcHRpb25zLnBvc2l0aW9uLnJhZGl1cywgdmFsdWVUeXBlcy5OVU1CRVIsIDEwICksXG5cdFx0XHRfcmFkaXVzU2NhbGU6IHV0aWxzLmVuc3VyZUluc3RhbmNlT2YoIG9wdGlvbnMucG9zaXRpb24ucmFkaXVzU2NhbGUsIFRIUkVFLlZlY3RvcjMsIG5ldyBUSFJFRS5WZWN0b3IzKCAxLCAxLCAxICkgKSxcblx0XHRcdF9kaXN0cmlidXRpb25DbGFtcDogdXRpbHMuZW5zdXJlVHlwZWRBcmcoIG9wdGlvbnMucG9zaXRpb24uZGlzdHJpYnV0aW9uQ2xhbXAsIHZhbHVlVHlwZXMuTlVNQkVSLCAwICksXG5cdFx0fTtcblxuXHRcdHRoaXMudmVsb2NpdHkgPSB7XG5cdFx0XHRfdmFsdWU6IHV0aWxzLmVuc3VyZUluc3RhbmNlT2YoIG9wdGlvbnMudmVsb2NpdHkudmFsdWUsIFRIUkVFLlZlY3RvcjMsIG5ldyBUSFJFRS5WZWN0b3IzKCkgKSxcblx0XHRcdF9zcHJlYWQ6IHV0aWxzLmVuc3VyZUluc3RhbmNlT2YoIG9wdGlvbnMudmVsb2NpdHkuc3ByZWFkLCBUSFJFRS5WZWN0b3IzLCBuZXcgVEhSRUUuVmVjdG9yMygpICksXG5cdFx0XHRfZGlzdHJpYnV0aW9uOiB1dGlscy5lbnN1cmVUeXBlZEFyZyggb3B0aW9ucy52ZWxvY2l0eS5kaXN0cmlidXRpb24sIHZhbHVlVHlwZXMuTlVNQkVSLCB0aGlzLnR5cGUgKSxcblx0XHRcdF9yYW5kb21pc2U6IHV0aWxzLmVuc3VyZVR5cGVkQXJnKCBvcHRpb25zLnBvc2l0aW9uLnJhbmRvbWlzZSwgdmFsdWVUeXBlcy5CT09MRUFOLCBmYWxzZSApLFxuXHRcdH07XG5cblx0XHR0aGlzLmFjY2VsZXJhdGlvbiA9IHtcblx0XHRcdF92YWx1ZTogdXRpbHMuZW5zdXJlSW5zdGFuY2VPZiggb3B0aW9ucy5hY2NlbGVyYXRpb24udmFsdWUsIFRIUkVFLlZlY3RvcjMsIG5ldyBUSFJFRS5WZWN0b3IzKCkgKSxcblx0XHRcdF9zcHJlYWQ6IHV0aWxzLmVuc3VyZUluc3RhbmNlT2YoIG9wdGlvbnMuYWNjZWxlcmF0aW9uLnNwcmVhZCwgVEhSRUUuVmVjdG9yMywgbmV3IFRIUkVFLlZlY3RvcjMoKSApLFxuXHRcdFx0X2Rpc3RyaWJ1dGlvbjogdXRpbHMuZW5zdXJlVHlwZWRBcmcoIG9wdGlvbnMuYWNjZWxlcmF0aW9uLmRpc3RyaWJ1dGlvbiwgdmFsdWVUeXBlcy5OVU1CRVIsIHRoaXMudHlwZSApLFxuXHRcdFx0X3JhbmRvbWlzZTogdXRpbHMuZW5zdXJlVHlwZWRBcmcoIG9wdGlvbnMucG9zaXRpb24ucmFuZG9taXNlLCB2YWx1ZVR5cGVzLkJPT0xFQU4sIGZhbHNlICksXG5cdFx0fTtcblxuXHRcdHRoaXMuZHJhZyA9IHtcblx0XHRcdF92YWx1ZTogdXRpbHMuZW5zdXJlVHlwZWRBcmcoIG9wdGlvbnMuZHJhZy52YWx1ZSwgdmFsdWVUeXBlcy5OVU1CRVIsIDAgKSxcblx0XHRcdF9zcHJlYWQ6IHV0aWxzLmVuc3VyZVR5cGVkQXJnKCBvcHRpb25zLmRyYWcuc3ByZWFkLCB2YWx1ZVR5cGVzLk5VTUJFUiwgMCApLFxuXHRcdFx0X3JhbmRvbWlzZTogdXRpbHMuZW5zdXJlVHlwZWRBcmcoIG9wdGlvbnMucG9zaXRpb24ucmFuZG9taXNlLCB2YWx1ZVR5cGVzLkJPT0xFQU4sIGZhbHNlICksXG5cdFx0fTtcblxuXHRcdHRoaXMud2lnZ2xlID0ge1xuXHRcdFx0X3ZhbHVlOiB1dGlscy5lbnN1cmVUeXBlZEFyZyggb3B0aW9ucy53aWdnbGUudmFsdWUsIHZhbHVlVHlwZXMuTlVNQkVSLCAwICksXG5cdFx0XHRfc3ByZWFkOiB1dGlscy5lbnN1cmVUeXBlZEFyZyggb3B0aW9ucy53aWdnbGUuc3ByZWFkLCB2YWx1ZVR5cGVzLk5VTUJFUiwgMCApLFxuXHRcdH07XG5cblx0XHR0aGlzLnJvdGF0aW9uID0ge1xuXHRcdFx0X2F4aXM6IHV0aWxzLmVuc3VyZUluc3RhbmNlT2YoIG9wdGlvbnMucm90YXRpb24uYXhpcywgVEhSRUUuVmVjdG9yMywgbmV3IFRIUkVFLlZlY3RvcjMoIDAuMCwgMS4wLCAwLjAgKSApLFxuXHRcdFx0X2F4aXNTcHJlYWQ6IHV0aWxzLmVuc3VyZUluc3RhbmNlT2YoIG9wdGlvbnMucm90YXRpb24uYXhpc1NwcmVhZCwgVEhSRUUuVmVjdG9yMywgbmV3IFRIUkVFLlZlY3RvcjMoKSApLFxuXHRcdFx0X2FuZ2xlOiB1dGlscy5lbnN1cmVUeXBlZEFyZyggb3B0aW9ucy5yb3RhdGlvbi5hbmdsZSwgdmFsdWVUeXBlcy5OVU1CRVIsIDAgKSxcblx0XHRcdF9hbmdsZVNwcmVhZDogdXRpbHMuZW5zdXJlVHlwZWRBcmcoIG9wdGlvbnMucm90YXRpb24uYW5nbGVTcHJlYWQsIHZhbHVlVHlwZXMuTlVNQkVSLCAwICksXG5cdFx0XHRfc3RhdGljOiB1dGlscy5lbnN1cmVUeXBlZEFyZyggb3B0aW9ucy5yb3RhdGlvbi5zdGF0aWMsIHZhbHVlVHlwZXMuQk9PTEVBTiwgZmFsc2UgKSxcblx0XHRcdF9jZW50ZXI6IHV0aWxzLmVuc3VyZUluc3RhbmNlT2YoIG9wdGlvbnMucm90YXRpb24uY2VudGVyLCBUSFJFRS5WZWN0b3IzLCB0aGlzLnBvc2l0aW9uLl92YWx1ZS5jbG9uZSgpICksXG5cdFx0XHRfcmFuZG9taXNlOiB1dGlscy5lbnN1cmVUeXBlZEFyZyggb3B0aW9ucy5wb3NpdGlvbi5yYW5kb21pc2UsIHZhbHVlVHlwZXMuQk9PTEVBTiwgZmFsc2UgKSxcblx0XHR9O1xuXG5cblx0XHR0aGlzLm1heEFnZSA9IHtcblx0XHRcdF92YWx1ZTogdXRpbHMuZW5zdXJlVHlwZWRBcmcoIG9wdGlvbnMubWF4QWdlLnZhbHVlLCB2YWx1ZVR5cGVzLk5VTUJFUiwgMiApLFxuXHRcdFx0X3NwcmVhZDogdXRpbHMuZW5zdXJlVHlwZWRBcmcoIG9wdGlvbnMubWF4QWdlLnNwcmVhZCwgdmFsdWVUeXBlcy5OVU1CRVIsIDAgKSxcblx0XHR9O1xuXG5cblxuXHRcdC8vIFRoZSBmb2xsb3dpbmcgcHJvcGVydGllcyBjYW4gc3VwcG9ydCBlaXRoZXIgc2luZ2xlIHZhbHVlcywgb3IgYW4gYXJyYXkgb2YgdmFsdWVzIHRoYXQgY2hhbmdlXG5cdFx0Ly8gdGhlIHByb3BlcnR5IG92ZXIgYSBwYXJ0aWNsZSdzIGxpZmV0aW1lICh2YWx1ZSBvdmVyIGxpZmV0aW1lKS5cblx0XHR0aGlzLmNvbG9yID0ge1xuXHRcdFx0X3ZhbHVlOiB1dGlscy5lbnN1cmVBcnJheUluc3RhbmNlT2YoIG9wdGlvbnMuY29sb3IudmFsdWUsIFRIUkVFLkNvbG9yLCBuZXcgVEhSRUUuQ29sb3IoKSApLFxuXHRcdFx0X3NwcmVhZDogdXRpbHMuZW5zdXJlQXJyYXlJbnN0YW5jZU9mKCBvcHRpb25zLmNvbG9yLnNwcmVhZCwgVEhSRUUuVmVjdG9yMywgbmV3IFRIUkVFLlZlY3RvcjMoKSApLFxuXHRcdFx0X3JhbmRvbWlzZTogdXRpbHMuZW5zdXJlVHlwZWRBcmcoIG9wdGlvbnMucG9zaXRpb24ucmFuZG9taXNlLCB2YWx1ZVR5cGVzLkJPT0xFQU4sIGZhbHNlICksXG5cdFx0fTtcblxuXHRcdHRoaXMub3BhY2l0eSA9IHtcblx0XHRcdF92YWx1ZTogdXRpbHMuZW5zdXJlQXJyYXlUeXBlZEFyZyggb3B0aW9ucy5vcGFjaXR5LnZhbHVlLCB2YWx1ZVR5cGVzLk5VTUJFUiwgMSApLFxuXHRcdFx0X3NwcmVhZDogdXRpbHMuZW5zdXJlQXJyYXlUeXBlZEFyZyggb3B0aW9ucy5vcGFjaXR5LnNwcmVhZCwgdmFsdWVUeXBlcy5OVU1CRVIsIDAgKSxcblx0XHRcdF9yYW5kb21pc2U6IHV0aWxzLmVuc3VyZVR5cGVkQXJnKCBvcHRpb25zLnBvc2l0aW9uLnJhbmRvbWlzZSwgdmFsdWVUeXBlcy5CT09MRUFOLCBmYWxzZSApLFxuXHRcdH07XG5cblx0XHR0aGlzLnNpemUgPSB7XG5cdFx0XHRfdmFsdWU6IHV0aWxzLmVuc3VyZUFycmF5VHlwZWRBcmcoIG9wdGlvbnMuc2l6ZS52YWx1ZSwgdmFsdWVUeXBlcy5OVU1CRVIsIDEgKSxcblx0XHRcdF9zcHJlYWQ6IHV0aWxzLmVuc3VyZUFycmF5VHlwZWRBcmcoIG9wdGlvbnMuc2l6ZS5zcHJlYWQsIHZhbHVlVHlwZXMuTlVNQkVSLCAwICksXG5cdFx0XHRfcmFuZG9taXNlOiB1dGlscy5lbnN1cmVUeXBlZEFyZyggb3B0aW9ucy5wb3NpdGlvbi5yYW5kb21pc2UsIHZhbHVlVHlwZXMuQk9PTEVBTiwgZmFsc2UgKSxcblx0XHR9O1xuXG5cdFx0dGhpcy5hbmdsZSA9IHtcblx0XHRcdF92YWx1ZTogdXRpbHMuZW5zdXJlQXJyYXlUeXBlZEFyZyggb3B0aW9ucy5hbmdsZS52YWx1ZSwgdmFsdWVUeXBlcy5OVU1CRVIsIDAgKSxcblx0XHRcdF9zcHJlYWQ6IHV0aWxzLmVuc3VyZUFycmF5VHlwZWRBcmcoIG9wdGlvbnMuYW5nbGUuc3ByZWFkLCB2YWx1ZVR5cGVzLk5VTUJFUiwgMCApLFxuXHRcdFx0X3JhbmRvbWlzZTogdXRpbHMuZW5zdXJlVHlwZWRBcmcoIG9wdGlvbnMucG9zaXRpb24ucmFuZG9taXNlLCB2YWx1ZVR5cGVzLkJPT0xFQU4sIGZhbHNlICksXG5cdFx0fTtcblxuXG5cdFx0Ly8gQXNzaWduIHJlbmFpbmluZyBvcHRpb24gdmFsdWVzLlxuXHRcdHRoaXMucGFydGljbGVDb3VudCA9IHV0aWxzLmVuc3VyZVR5cGVkQXJnKCBvcHRpb25zLnBhcnRpY2xlQ291bnQsIHZhbHVlVHlwZXMuTlVNQkVSLCAxMDAgKTtcblx0XHR0aGlzLmR1cmF0aW9uID0gdXRpbHMuZW5zdXJlVHlwZWRBcmcoIG9wdGlvbnMuZHVyYXRpb24sIHZhbHVlVHlwZXMuTlVNQkVSLCBudWxsICk7XG5cdFx0dGhpcy5pc1N0YXRpYyA9IHV0aWxzLmVuc3VyZVR5cGVkQXJnKCBvcHRpb25zLmlzU3RhdGljLCB2YWx1ZVR5cGVzLkJPT0xFQU4sIGZhbHNlICk7XG5cdFx0dGhpcy5hY3RpdmVNdWx0aXBsaWVyID0gdXRpbHMuZW5zdXJlVHlwZWRBcmcoIG9wdGlvbnMuYWN0aXZlTXVsdGlwbGllciwgdmFsdWVUeXBlcy5OVU1CRVIsIDEgKTtcblx0XHR0aGlzLmRpcmVjdGlvbiA9IHV0aWxzLmVuc3VyZVR5cGVkQXJnKCBvcHRpb25zLmRpcmVjdGlvbiwgdmFsdWVUeXBlcy5OVU1CRVIsIDEgKTtcblxuXHRcdC8vIFdoZXRoZXIgdGhpcyBlbWl0dGVyIGlzIGFsaXZlIG9yIG5vdC5cblx0XHR0aGlzLmFsaXZlID0gdXRpbHMuZW5zdXJlVHlwZWRBcmcoIG9wdGlvbnMuYWxpdmUsIHZhbHVlVHlwZXMuQk9PTEVBTiwgdHJ1ZSApO1xuXG5cblx0XHQvLyBUaGUgZm9sbG93aW5nIHByb3BlcnRpZXMgYXJlIHNldCBpbnRlcm5hbGx5IGFuZCBhcmUgbm90XG5cdFx0Ly8gdXNlci1jb250cm9sbGFibGUuXG5cdFx0dGhpcy5wYXJ0aWNsZXNQZXJTZWNvbmQgPSAwO1xuXG5cdFx0Ly8gVGhlIGN1cnJlbnQgcGFydGljbGUgaW5kZXggZm9yIHdoaWNoIHBhcnRpY2xlcyBzaG91bGRcblx0XHQvLyBiZSBtYXJrZWQgYXMgYWN0aXZlIG9uIHRoZSBuZXh0IHVwZGF0ZSBjeWNsZS5cblx0XHR0aGlzLmFjdGl2YXRpb25JbmRleCA9IDA7XG5cblx0XHQvLyBUaGUgb2Zmc2V0IGluIHRoZSB0eXBlZCBhcnJheXMgdGhpcyBlbWl0dGVyJ3Ncblx0XHQvLyBwYXJ0aWNsZSdzIHZhbHVlcyB3aWxsIHN0YXJ0IGF0XG5cdFx0dGhpcy5hdHRyaWJ1dGVPZmZzZXQgPSAwO1xuXG5cdFx0Ly8gVGhlIGVuZCBvZiB0aGUgcmFuZ2UgaW4gdGhlIGF0dHJpYnV0ZSBidWZmZXJzXG5cdFx0dGhpcy5hdHRyaWJ1dGVFbmQgPSAwO1xuXG5cblxuXHRcdC8vIEhvbGRzIHRoZSB0aW1lIHRoZSBlbWl0dGVyIGhhcyBiZWVuIGFsaXZlIGZvci5cblx0XHR0aGlzLmFnZSA9IDAuMDtcblxuXHRcdC8vIEhvbGRzIHRoZSBudW1iZXIgb2YgY3VycmVudGx5LWFsaXZlIHBhcnRpY2xlc1xuXHRcdHRoaXMuYWN0aXZlUGFydGljbGVDb3VudCA9IDAuMDtcblxuXHRcdC8vIEhvbGRzIGEgcmVmZXJlbmNlIHRvIHRoaXMgZW1pdHRlcidzIGdyb3VwIG9uY2Vcblx0XHQvLyBpdCdzIGFkZGVkIHRvIG9uZS5cblx0XHR0aGlzLmdyb3VwID0gbnVsbDtcblxuXHRcdC8vIEhvbGRzIGEgcmVmZXJlbmNlIHRvIHRoaXMgZW1pdHRlcidzIGdyb3VwJ3MgYXR0cmlidXRlcyBvYmplY3Rcblx0XHQvLyBmb3IgZWFzaWVyIGFjY2Vzcy5cblx0XHR0aGlzLmF0dHJpYnV0ZXMgPSBudWxsO1xuXG5cdFx0Ly8gSG9sZHMgYSByZWZlcmVuY2UgdG8gdGhlIHBhcmFtcyBhdHRyaWJ1dGUncyB0eXBlZCBhcnJheVxuXHRcdC8vIGZvciBxdWlja2VyIGFjY2Vzcy5cblx0XHR0aGlzLnBhcmFtc0FycmF5ID0gbnVsbDtcblxuXHRcdC8vIEEgc2V0IG9mIGZsYWdzIHRvIGRldGVybWluZSB3aGV0aGVyIHBhcnRpY3VsYXIgcHJvcGVydGllc1xuXHRcdC8vIHNob3VsZCBiZSByZS1yYW5kb21pc2VkIHdoZW4gYSBwYXJ0aWNsZSBpcyByZXNldC5cblx0XHQvL1xuXHRcdC8vIElmIGEgYHJhbmRvbWlzZWAgcHJvcGVydHkgaXMgZ2l2ZW4sIHRoaXMgaXMgcHJlZmVycmVkLlxuXHRcdC8vIE90aGVyd2lzZSwgaXQgbG9va3MgYXQgd2hldGhlciBhIHNwcmVhZCB2YWx1ZSBoYXMgYmVlblxuXHRcdC8vIGdpdmVuLlxuXHRcdC8vXG5cdFx0Ly8gSXQgYWxsb3dzIHJhbmRvbWl6YXRpb24gdG8gYmUgdHVybmVkIG9mZiBhcyBkZXNpcmVkLiBJZlxuXHRcdC8vIGFsbCByYW5kb21pemF0aW9uIGlzIHR1cm5lZCBvZmYsIHRoZW4gSSdkIGV4cGVjdCBhIHBlcmZvcm1hbmNlXG5cdFx0Ly8gYm9vc3QgYXMgbm8gYXR0cmlidXRlIGJ1ZmZlcnMgKGV4Y2x1ZGluZyB0aGUgYHBhcmFtc2ApXG5cdFx0Ly8gd291bGQgaGF2ZSB0byBiZSByZS1wYXNzZWQgdG8gdGhlIEdQVSBlYWNoIGZyYW1lIChzaW5jZSBub3RoaW5nXG5cdFx0Ly8gZXhjZXB0IHRoZSBgcGFyYW1zYCBhdHRyaWJ1dGUgd291bGQgaGF2ZSBjaGFuZ2VkKS5cblx0XHR0aGlzLnJlc2V0RmxhZ3MgPSB7XG5cdFx0XHQvLyBwYXJhbXM6IHV0aWxzLmVuc3VyZVR5cGVkQXJnKCBvcHRpb25zLm1heEFnZS5yYW5kb21pc2UsIHZhbHVlVHlwZXMuQk9PTEVBTiwgISFvcHRpb25zLm1heEFnZS5zcHJlYWQgKSB8fFxuXHRcdFx0Ly8gICAgIHV0aWxzLmVuc3VyZVR5cGVkQXJnKCBvcHRpb25zLndpZ2dsZS5yYW5kb21pc2UsIHZhbHVlVHlwZXMuQk9PTEVBTiwgISFvcHRpb25zLndpZ2dsZS5zcHJlYWQgKSxcblx0XHRcdHBvc2l0aW9uOiB1dGlscy5lbnN1cmVUeXBlZEFyZyggb3B0aW9ucy5wb3NpdGlvbi5yYW5kb21pc2UsIHZhbHVlVHlwZXMuQk9PTEVBTiwgZmFsc2UgKSB8fFxuXHRcdFx0XHR1dGlscy5lbnN1cmVUeXBlZEFyZyggb3B0aW9ucy5yYWRpdXMucmFuZG9taXNlLCB2YWx1ZVR5cGVzLkJPT0xFQU4sIGZhbHNlICksXG5cdFx0XHR2ZWxvY2l0eTogdXRpbHMuZW5zdXJlVHlwZWRBcmcoIG9wdGlvbnMudmVsb2NpdHkucmFuZG9taXNlLCB2YWx1ZVR5cGVzLkJPT0xFQU4sIGZhbHNlICksXG5cdFx0XHRhY2NlbGVyYXRpb246IHV0aWxzLmVuc3VyZVR5cGVkQXJnKCBvcHRpb25zLmFjY2VsZXJhdGlvbi5yYW5kb21pc2UsIHZhbHVlVHlwZXMuQk9PTEVBTiwgZmFsc2UgKSB8fFxuXHRcdFx0XHR1dGlscy5lbnN1cmVUeXBlZEFyZyggb3B0aW9ucy5kcmFnLnJhbmRvbWlzZSwgdmFsdWVUeXBlcy5CT09MRUFOLCBmYWxzZSApLFxuXHRcdFx0cm90YXRpb246IHV0aWxzLmVuc3VyZVR5cGVkQXJnKCBvcHRpb25zLnJvdGF0aW9uLnJhbmRvbWlzZSwgdmFsdWVUeXBlcy5CT09MRUFOLCBmYWxzZSApLFxuXHRcdFx0cm90YXRpb25DZW50ZXI6IHV0aWxzLmVuc3VyZVR5cGVkQXJnKCBvcHRpb25zLnJvdGF0aW9uLnJhbmRvbWlzZSwgdmFsdWVUeXBlcy5CT09MRUFOLCBmYWxzZSApLFxuXHRcdFx0c2l6ZTogdXRpbHMuZW5zdXJlVHlwZWRBcmcoIG9wdGlvbnMuc2l6ZS5yYW5kb21pc2UsIHZhbHVlVHlwZXMuQk9PTEVBTiwgZmFsc2UgKSxcblx0XHRcdGNvbG9yOiB1dGlscy5lbnN1cmVUeXBlZEFyZyggb3B0aW9ucy5jb2xvci5yYW5kb21pc2UsIHZhbHVlVHlwZXMuQk9PTEVBTiwgZmFsc2UgKSxcblx0XHRcdG9wYWNpdHk6IHV0aWxzLmVuc3VyZVR5cGVkQXJnKCBvcHRpb25zLm9wYWNpdHkucmFuZG9taXNlLCB2YWx1ZVR5cGVzLkJPT0xFQU4sIGZhbHNlICksXG5cdFx0XHRhbmdsZTogdXRpbHMuZW5zdXJlVHlwZWRBcmcoIG9wdGlvbnMuYW5nbGUucmFuZG9taXNlLCB2YWx1ZVR5cGVzLkJPT0xFQU4sIGZhbHNlICksXG5cdFx0fTtcblxuXHRcdHRoaXMudXBkYXRlRmxhZ3MgPSB7fTtcblx0XHR0aGlzLnVwZGF0ZUNvdW50cyA9IHt9O1xuXG5cdFx0Ly8gQSBtYXAgdG8gaW5kaWNhdGUgd2hpY2ggZW1pdHRlciBwYXJhbWV0ZXJzIHNob3VsZCB1cGRhdGVcblx0XHQvLyB3aGljaCBhdHRyaWJ1dGUuXG5cdFx0dGhpcy51cGRhdGVNYXAgPSB7XG5cdFx0XHRtYXhBZ2U6ICdwYXJhbXMnLFxuXHRcdFx0cG9zaXRpb246ICdwb3NpdGlvbicsXG5cdFx0XHR2ZWxvY2l0eTogJ3ZlbG9jaXR5Jyxcblx0XHRcdGFjY2VsZXJhdGlvbjogJ2FjY2VsZXJhdGlvbicsXG5cdFx0XHRkcmFnOiAnYWNjZWxlcmF0aW9uJyxcblx0XHRcdHdpZ2dsZTogJ3BhcmFtcycsXG5cdFx0XHRyb3RhdGlvbjogJ3JvdGF0aW9uJyxcblx0XHRcdHNpemU6ICdzaXplJyxcblx0XHRcdGNvbG9yOiAnY29sb3InLFxuXHRcdFx0b3BhY2l0eTogJ29wYWNpdHknLFxuXHRcdFx0YW5nbGU6ICdhbmdsZScsXG5cdFx0fTtcblxuXHRcdGZvciAoIGNvbnN0IGkgaW4gdGhpcy51cGRhdGVNYXAgKSB7XG5cdFx0XHRpZiAoIEhBU19PV04uY2FsbCggdGhpcy51cGRhdGVNYXAsIGkgKSApIHtcblx0XHRcdFx0dGhpcy51cGRhdGVDb3VudHNbIHRoaXMudXBkYXRlTWFwWyBpIF0gXSA9IDAuMDtcblx0XHRcdFx0dGhpcy51cGRhdGVGbGFnc1sgdGhpcy51cGRhdGVNYXBbIGkgXSBdID0gZmFsc2U7XG5cdFx0XHRcdHRoaXMuX2NyZWF0ZUdldHRlclNldHRlcnMoIHRoaXNbIGkgXSwgaSApO1xuXHRcdFx0fVxuXHRcdH1cblxuXHRcdHRoaXMuYnVmZmVyVXBkYXRlUmFuZ2VzID0ge307XG5cdFx0dGhpcy5hdHRyaWJ1dGVLZXlzID0gbnVsbDtcblx0XHR0aGlzLmF0dHJpYnV0ZUNvdW50ID0gMDtcblxuXG5cdFx0Ly8gRW5zdXJlIHRoYXQgdGhlIHZhbHVlLW92ZXItbGlmZXRpbWUgcHJvcGVydHkgb2JqZWN0cyBhYm92ZVxuXHRcdC8vIGhhdmUgdmFsdWUgYW5kIHNwcmVhZCBwcm9wZXJ0aWVzIHRoYXQgYXJlIG9mIHRoZSBzYW1lIGxlbmd0aC5cblx0XHQvL1xuXHRcdC8vIEFsc28sIGZvciBub3csIG1ha2Ugc3VyZSB0aGV5IGhhdmUgYSBsZW5ndGggb2YgMyAobWluL21heCBhcmd1bWVudHMgaGVyZSkuXG5cdFx0dXRpbHMuZW5zdXJlVmFsdWVPdmVyTGlmZXRpbWVDb21wbGlhbmNlKCB0aGlzLmNvbG9yLCBnbG9iYWxzLnZhbHVlT3ZlckxpZmV0aW1lTGVuZ3RoLCBnbG9iYWxzLnZhbHVlT3ZlckxpZmV0aW1lTGVuZ3RoICk7XG5cdFx0dXRpbHMuZW5zdXJlVmFsdWVPdmVyTGlmZXRpbWVDb21wbGlhbmNlKCB0aGlzLm9wYWNpdHksIGdsb2JhbHMudmFsdWVPdmVyTGlmZXRpbWVMZW5ndGgsIGdsb2JhbHMudmFsdWVPdmVyTGlmZXRpbWVMZW5ndGggKTtcblx0XHR1dGlscy5lbnN1cmVWYWx1ZU92ZXJMaWZldGltZUNvbXBsaWFuY2UoIHRoaXMuc2l6ZSwgZ2xvYmFscy52YWx1ZU92ZXJMaWZldGltZUxlbmd0aCwgZ2xvYmFscy52YWx1ZU92ZXJMaWZldGltZUxlbmd0aCApO1xuXHRcdHV0aWxzLmVuc3VyZVZhbHVlT3ZlckxpZmV0aW1lQ29tcGxpYW5jZSggdGhpcy5hbmdsZSwgZ2xvYmFscy52YWx1ZU92ZXJMaWZldGltZUxlbmd0aCwgZ2xvYmFscy52YWx1ZU92ZXJMaWZldGltZUxlbmd0aCApO1xuXHR9XG5cblx0X2NyZWF0ZUdldHRlclNldHRlcnMoIHByb3BPYmosIHByb3BOYW1lICkge1xuXHRcdGNvbnN0IHNlbGYgPSB0aGlzO1xuXG5cdFx0Zm9yICggY29uc3QgaSBpbiBwcm9wT2JqICkge1xuXHRcdFx0aWYgKCBIQVNfT1dOLmNhbGwoIHByb3BPYmosIGkgKSApIHtcblxuXHRcdFx0XHRjb25zdCBuYW1lID0gaS5yZXBsYWNlKCAnXycsICcnICk7XG5cblx0XHRcdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KCBwcm9wT2JqLCBuYW1lLCB7XG5cdFx0XHRcdFx0Z2V0OiAoIGZ1bmN0aW9uKCBwcm9wICkge1xuXHRcdFx0XHRcdFx0cmV0dXJuIGZ1bmN0aW9uKCkge1xuXHRcdFx0XHRcdFx0XHRyZXR1cm4gdGhpc1sgcHJvcCBdO1xuXHRcdFx0XHRcdFx0fTtcblx0XHRcdFx0XHR9KCBpICkgKSxcblxuXHRcdFx0XHRcdHNldDogKCBmdW5jdGlvbiggcHJvcCApIHtcblx0XHRcdFx0XHRcdHJldHVybiBmdW5jdGlvbiggdmFsdWUgKSB7XG5cdFx0XHRcdFx0XHRcdGNvbnN0IG1hcE5hbWUgPSBzZWxmLnVwZGF0ZU1hcFsgcHJvcE5hbWUgXSxcblx0XHRcdFx0XHRcdFx0XHRwcmV2VmFsdWUgPSB0aGlzWyBwcm9wIF0sXG5cdFx0XHRcdFx0XHRcdFx0bGVuZ3RoID0gZ2xvYmFscy52YWx1ZU92ZXJMaWZldGltZUxlbmd0aDtcblxuXHRcdFx0XHRcdFx0XHRpZiAoIHByb3AgPT09ICdfcm90YXRpb25DZW50ZXInICkge1xuXHRcdFx0XHRcdFx0XHRcdHNlbGYudXBkYXRlRmxhZ3Mucm90YXRpb25DZW50ZXIgPSB0cnVlO1xuXHRcdFx0XHRcdFx0XHRcdHNlbGYudXBkYXRlQ291bnRzLnJvdGF0aW9uQ2VudGVyID0gMC4wO1xuXHRcdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0XHRcdGVsc2UgaWYgKCBwcm9wID09PSAnX3JhbmRvbWlzZScgKSB7XG5cdFx0XHRcdFx0XHRcdFx0c2VsZi5yZXNldEZsYWdzWyBtYXBOYW1lIF0gPSB2YWx1ZTtcblx0XHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdFx0XHRlbHNlIHtcblx0XHRcdFx0XHRcdFx0XHRzZWxmLnVwZGF0ZUZsYWdzWyBtYXBOYW1lIF0gPSB0cnVlO1xuXHRcdFx0XHRcdFx0XHRcdHNlbGYudXBkYXRlQ291bnRzWyBtYXBOYW1lIF0gPSAwLjA7XG5cdFx0XHRcdFx0XHRcdH1cblxuXHRcdFx0XHRcdFx0XHRzZWxmLmdyb3VwLl91cGRhdGVEZWZpbmVzKCk7XG5cblx0XHRcdFx0XHRcdFx0dGhpc1sgcHJvcCBdID0gdmFsdWU7XG5cblx0XHRcdFx0XHRcdFx0Ly8gSWYgdGhlIHByZXZpb3VzIHZhbHVlIHdhcyBhbiBhcnJheSwgdGhlbiBtYWtlXG5cdFx0XHRcdFx0XHRcdC8vIHN1cmUgdGhlIHByb3ZpZGVkIHZhbHVlIGlzIGludGVycG9sYXRlZCBjb3JyZWN0bHkuXG5cdFx0XHRcdFx0XHRcdGlmICggQXJyYXkuaXNBcnJheSggcHJldlZhbHVlICkgKSB7XG5cdFx0XHRcdFx0XHRcdFx0dXRpbHMuZW5zdXJlVmFsdWVPdmVyTGlmZXRpbWVDb21wbGlhbmNlKCBzZWxmWyBwcm9wTmFtZSBdLCBsZW5ndGgsIGxlbmd0aCApO1xuXHRcdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0XHR9O1xuXHRcdFx0XHRcdH0oIGkgKSApLFxuXHRcdFx0XHR9ICk7XG5cdFx0XHR9XG5cdFx0fVxuXHR9XG5cblx0X3NldEJ1ZmZlclVwZGF0ZVJhbmdlcygga2V5cyApIHtcblx0XHR0aGlzLmF0dHJpYnV0ZUtleXMgPSBrZXlzO1xuXHRcdHRoaXMuYXR0cmlidXRlQ291bnQgPSBrZXlzLmxlbmd0aDtcblxuXHRcdGZvciAoIGxldCBpID0gdGhpcy5hdHRyaWJ1dGVDb3VudCAtIDE7IGkgPj0gMDsgLS1pICkge1xuXHRcdFx0dGhpcy5idWZmZXJVcGRhdGVSYW5nZXNbIGtleXNbIGkgXSBdID0ge1xuXHRcdFx0XHRtaW46IE51bWJlci5QT1NJVElWRV9JTkZJTklUWSxcblx0XHRcdFx0bWF4OiBOdW1iZXIuTkVHQVRJVkVfSU5GSU5JVFksXG5cdFx0XHR9O1xuXHRcdH1cblx0fVxuXG5cdF9jYWxjdWxhdGVQUFNWYWx1ZSggZ3JvdXBNYXhBZ2UgKSB7XG5cdFx0Y29uc3QgcGFydGljbGVDb3VudCA9IHRoaXMucGFydGljbGVDb3VudDtcblxuXHRcdC8vIENhbGN1bGF0ZSB0aGUgYHBhcnRpY2xlc1BlclNlY29uZGAgdmFsdWUgZm9yIHRoaXMgZW1pdHRlci4gSXQncyB1c2VkXG5cdFx0Ly8gd2hlbiBkZXRlcm1pbmluZyB3aGljaCBwYXJ0aWNsZXMgc2hvdWxkIGRpZSBhbmQgd2hpY2ggc2hvdWxkIGxpdmUgdG9cblx0XHQvLyBzZWUgYW5vdGhlciBkYXkuIE9yIGJlIGJvcm4sIGZvciB0aGF0IG1hdHRlci4gVGhlIFwiR29kXCIgcHJvcGVydHkuXG5cdFx0aWYgKCB0aGlzLmR1cmF0aW9uICkge1xuXHRcdFx0dGhpcy5wYXJ0aWNsZXNQZXJTZWNvbmQgPSBwYXJ0aWNsZUNvdW50IC8gKCBncm91cE1heEFnZSA8IHRoaXMuZHVyYXRpb24gPyBncm91cE1heEFnZSA6IHRoaXMuZHVyYXRpb24gKTtcblx0XHR9XG5cdFx0ZWxzZSB7XG5cdFx0XHR0aGlzLnBhcnRpY2xlc1BlclNlY29uZCA9IHBhcnRpY2xlQ291bnQgLyBncm91cE1heEFnZTtcblx0XHR9XG5cdH1cblxuXHRfc2V0QXR0cmlidXRlT2Zmc2V0KCBzdGFydEluZGV4ICkge1xuXHRcdHRoaXMuYXR0cmlidXRlT2Zmc2V0ID0gc3RhcnRJbmRleDtcblx0XHR0aGlzLmFjdGl2YXRpb25JbmRleCA9IHN0YXJ0SW5kZXg7XG5cdFx0dGhpcy5hY3RpdmF0aW9uRW5kID0gc3RhcnRJbmRleCArIHRoaXMucGFydGljbGVDb3VudDtcblx0fVxuXG5cblx0X2Fzc2lnblZhbHVlKCBwcm9wLCBpbmRleCApIHtcblx0XHRzd2l0Y2ggKCBwcm9wICkge1xuXHRcdFx0Y2FzZSAncG9zaXRpb24nOlxuXHRcdFx0XHR0aGlzLl9hc3NpZ25Qb3NpdGlvblZhbHVlKCBpbmRleCApO1xuXHRcdFx0XHRicmVhaztcblxuXHRcdFx0Y2FzZSAndmVsb2NpdHknOlxuXHRcdFx0Y2FzZSAnYWNjZWxlcmF0aW9uJzpcblx0XHRcdFx0dGhpcy5fYXNzaWduRm9yY2VWYWx1ZSggaW5kZXgsIHByb3AgKTtcblx0XHRcdFx0YnJlYWs7XG5cblx0XHRcdGNhc2UgJ3NpemUnOlxuXHRcdFx0Y2FzZSAnb3BhY2l0eSc6XG5cdFx0XHRcdHRoaXMuX2Fzc2lnbkFic0xpZmV0aW1lVmFsdWUoIGluZGV4LCBwcm9wICk7XG5cdFx0XHRcdGJyZWFrO1xuXG5cdFx0XHRjYXNlICdhbmdsZSc6XG5cdFx0XHRcdHRoaXMuX2Fzc2lnbkFuZ2xlVmFsdWUoIGluZGV4ICk7XG5cdFx0XHRcdGJyZWFrO1xuXG5cdFx0XHRjYXNlICdwYXJhbXMnOlxuXHRcdFx0XHR0aGlzLl9hc3NpZ25QYXJhbXNWYWx1ZSggaW5kZXggKTtcblx0XHRcdFx0YnJlYWs7XG5cblx0XHRcdGNhc2UgJ3JvdGF0aW9uJzpcblx0XHRcdFx0dGhpcy5fYXNzaWduUm90YXRpb25WYWx1ZSggaW5kZXggKTtcblx0XHRcdFx0YnJlYWs7XG5cblx0XHRcdGNhc2UgJ2NvbG9yJzpcblx0XHRcdFx0dGhpcy5fYXNzaWduQ29sb3JWYWx1ZSggaW5kZXggKTtcblx0XHRcdFx0YnJlYWs7XG5cdFx0fVxuXHR9XG5cblx0X2Fzc2lnblBvc2l0aW9uVmFsdWUoIGluZGV4ICkge1xuXHRcdGNvbnN0IHByb3AgPSB0aGlzLnBvc2l0aW9uLFxuXHRcdFx0YXR0ciA9IHRoaXMuYXR0cmlidXRlcy5wb3NpdGlvbixcblx0XHRcdHZhbHVlID0gcHJvcC5fdmFsdWUsXG5cdFx0XHRzcHJlYWQgPSBwcm9wLl9zcHJlYWQsXG5cdFx0XHRkaXN0cmlidXRpb24gPSBwcm9wLl9kaXN0cmlidXRpb247XG5cblx0XHRzd2l0Y2ggKCBkaXN0cmlidXRpb24gKSB7XG5cdFx0XHRjYXNlIGRpc3RyaWJ1dGlvbnMuQk9YOlxuXHRcdFx0XHR1dGlscy5yYW5kb21WZWN0b3IzKCBhdHRyLCBpbmRleCwgdmFsdWUsIHNwcmVhZCwgcHJvcC5fc3ByZWFkQ2xhbXAgKTtcblx0XHRcdFx0YnJlYWs7XG5cblx0XHRcdGNhc2UgZGlzdHJpYnV0aW9ucy5TUEhFUkU6XG5cdFx0XHRcdHV0aWxzLnJhbmRvbVZlY3RvcjNPblNwaGVyZSggYXR0ciwgaW5kZXgsIHZhbHVlLCBwcm9wLl9yYWRpdXMsIHByb3AuX3NwcmVhZC54LCBwcm9wLl9yYWRpdXNTY2FsZSwgcHJvcC5fc3ByZWFkQ2xhbXAueCwgcHJvcC5fZGlzdHJpYnV0aW9uQ2xhbXAgfHwgdGhpcy5wYXJ0aWNsZUNvdW50ICk7XG5cdFx0XHRcdGJyZWFrO1xuXG5cdFx0XHRjYXNlIGRpc3RyaWJ1dGlvbnMuRElTQzpcblx0XHRcdFx0dXRpbHMucmFuZG9tVmVjdG9yM09uRGlzYyggYXR0ciwgaW5kZXgsIHZhbHVlLCBwcm9wLl9yYWRpdXMsIHByb3AuX3NwcmVhZC54LCBwcm9wLl9yYWRpdXNTY2FsZSwgcHJvcC5fc3ByZWFkQ2xhbXAueCApO1xuXHRcdFx0XHRicmVhaztcblxuXHRcdFx0Y2FzZSBkaXN0cmlidXRpb25zLkxJTkU6XG5cdFx0XHRcdHV0aWxzLnJhbmRvbVZlY3RvcjNPbkxpbmUoIGF0dHIsIGluZGV4LCB2YWx1ZSwgc3ByZWFkICk7XG5cdFx0XHRcdGJyZWFrO1xuXHRcdH1cblx0fVxuXG5cdF9hc3NpZ25Gb3JjZVZhbHVlKCBpbmRleCwgYXR0ck5hbWUgKSB7XG5cdFx0Y29uc3QgcHJvcCA9IHRoaXNbIGF0dHJOYW1lIF0sXG5cdFx0XHR2YWx1ZSA9IHByb3AuX3ZhbHVlLFxuXHRcdFx0c3ByZWFkID0gcHJvcC5fc3ByZWFkLFxuXHRcdFx0ZGlzdHJpYnV0aW9uID0gcHJvcC5fZGlzdHJpYnV0aW9uO1xuXHRcdGxldCBwb3MsXG5cdFx0XHRwb3NpdGlvblgsXG5cdFx0XHRwb3NpdGlvblksXG5cdFx0XHRwb3NpdGlvblosXG5cdFx0XHRpO1xuXG5cdFx0c3dpdGNoICggZGlzdHJpYnV0aW9uICkge1xuXHRcdFx0Y2FzZSBkaXN0cmlidXRpb25zLkJPWDpcblx0XHRcdFx0dXRpbHMucmFuZG9tVmVjdG9yMyggdGhpcy5hdHRyaWJ1dGVzWyBhdHRyTmFtZSBdLCBpbmRleCwgdmFsdWUsIHNwcmVhZCApO1xuXHRcdFx0XHRicmVhaztcblxuXHRcdFx0Y2FzZSBkaXN0cmlidXRpb25zLlNQSEVSRTpcblx0XHRcdFx0cG9zID0gdGhpcy5hdHRyaWJ1dGVzLnBvc2l0aW9uLnR5cGVkQXJyYXkuYXJyYXk7XG5cdFx0XHRcdGkgPSBpbmRleCAqIDM7XG5cblx0XHRcdFx0Ly8gRW5zdXJlIHBvc2l0aW9uIHZhbHVlcyBhcmVuJ3QgemVybywgb3RoZXJ3aXNlIG5vIGZvcmNlIHdpbGwgYmVcblx0XHRcdFx0Ly8gYXBwbGllZC5cblx0XHRcdFx0Ly8gcG9zaXRpb25YID0gdXRpbHMuemVyb1RvRXBzaWxvbiggcG9zWyBpIF0sIHRydWUgKTtcblx0XHRcdFx0Ly8gcG9zaXRpb25ZID0gdXRpbHMuemVyb1RvRXBzaWxvbiggcG9zWyBpICsgMSBdLCB0cnVlICk7XG5cdFx0XHRcdC8vIHBvc2l0aW9uWiA9IHV0aWxzLnplcm9Ub0Vwc2lsb24oIHBvc1sgaSArIDIgXSwgdHJ1ZSApO1xuXHRcdFx0XHRwb3NpdGlvblggPSBwb3NbIGkgXTtcblx0XHRcdFx0cG9zaXRpb25ZID0gcG9zWyBpICsgMSBdO1xuXHRcdFx0XHRwb3NpdGlvblogPSBwb3NbIGkgKyAyIF07XG5cblx0XHRcdFx0dXRpbHMucmFuZG9tRGlyZWN0aW9uVmVjdG9yM09uU3BoZXJlKFxuXHRcdFx0XHRcdHRoaXMuYXR0cmlidXRlc1sgYXR0ck5hbWUgXSwgaW5kZXgsXG5cdFx0XHRcdFx0cG9zaXRpb25YLCBwb3NpdGlvblksIHBvc2l0aW9uWixcblx0XHRcdFx0XHR0aGlzLnBvc2l0aW9uLl92YWx1ZSxcblx0XHRcdFx0XHRwcm9wLl92YWx1ZS54LFxuXHRcdFx0XHRcdHByb3AuX3NwcmVhZC54XG5cdFx0XHRcdCk7XG5cdFx0XHRcdGJyZWFrO1xuXG5cdFx0XHRjYXNlIGRpc3RyaWJ1dGlvbnMuRElTQzpcblx0XHRcdFx0cG9zID0gdGhpcy5hdHRyaWJ1dGVzLnBvc2l0aW9uLnR5cGVkQXJyYXkuYXJyYXk7XG5cdFx0XHRcdGkgPSBpbmRleCAqIDM7XG5cblx0XHRcdFx0Ly8gRW5zdXJlIHBvc2l0aW9uIHZhbHVlcyBhcmVuJ3QgemVybywgb3RoZXJ3aXNlIG5vIGZvcmNlIHdpbGwgYmVcblx0XHRcdFx0Ly8gYXBwbGllZC5cblx0XHRcdFx0Ly8gcG9zaXRpb25YID0gdXRpbHMuemVyb1RvRXBzaWxvbiggcG9zWyBpIF0sIHRydWUgKTtcblx0XHRcdFx0Ly8gcG9zaXRpb25ZID0gdXRpbHMuemVyb1RvRXBzaWxvbiggcG9zWyBpICsgMSBdLCB0cnVlICk7XG5cdFx0XHRcdC8vIHBvc2l0aW9uWiA9IHV0aWxzLnplcm9Ub0Vwc2lsb24oIHBvc1sgaSArIDIgXSwgdHJ1ZSApO1xuXHRcdFx0XHRwb3NpdGlvblggPSBwb3NbIGkgXTtcblx0XHRcdFx0cG9zaXRpb25ZID0gcG9zWyBpICsgMSBdO1xuXHRcdFx0XHRwb3NpdGlvblogPSBwb3NbIGkgKyAyIF07XG5cblx0XHRcdFx0dXRpbHMucmFuZG9tRGlyZWN0aW9uVmVjdG9yM09uRGlzYyhcblx0XHRcdFx0XHR0aGlzLmF0dHJpYnV0ZXNbIGF0dHJOYW1lIF0sIGluZGV4LFxuXHRcdFx0XHRcdHBvc2l0aW9uWCwgcG9zaXRpb25ZLCBwb3NpdGlvblosXG5cdFx0XHRcdFx0dGhpcy5wb3NpdGlvbi5fdmFsdWUsXG5cdFx0XHRcdFx0cHJvcC5fdmFsdWUueCxcblx0XHRcdFx0XHRwcm9wLl9zcHJlYWQueFxuXHRcdFx0XHQpO1xuXHRcdFx0XHRicmVhaztcblxuXHRcdFx0Y2FzZSBkaXN0cmlidXRpb25zLkxJTkU6XG5cdFx0XHRcdHV0aWxzLnJhbmRvbVZlY3RvcjNPbkxpbmUoIHRoaXMuYXR0cmlidXRlc1sgYXR0ck5hbWUgXSwgaW5kZXgsIHZhbHVlLCBzcHJlYWQgKTtcblx0XHRcdFx0YnJlYWs7XG5cdFx0fVxuXG5cdFx0aWYgKCBhdHRyTmFtZSA9PT0gJ2FjY2VsZXJhdGlvbicgKSB7XG5cdFx0XHRjb25zdCBkcmFnID0gdXRpbHMuY2xhbXAoIHV0aWxzLnJhbmRvbUZsb2F0KCB0aGlzLmRyYWcuX3ZhbHVlLCB0aGlzLmRyYWcuX3NwcmVhZCApLCAwLCAxICk7XG5cdFx0XHR0aGlzLmF0dHJpYnV0ZXMuYWNjZWxlcmF0aW9uLnR5cGVkQXJyYXkuYXJyYXlbIGluZGV4ICogNCArIDMgXSA9IGRyYWc7XG5cdFx0fVxuXHR9XG5cblx0X2Fzc2lnbkFic0xpZmV0aW1lVmFsdWUoIGluZGV4LCBwcm9wTmFtZSApIHtcblx0XHRjb25zdCBhcnJheSA9IHRoaXMuYXR0cmlidXRlc1sgcHJvcE5hbWUgXS50eXBlZEFycmF5LFxuXHRcdFx0cHJvcCA9IHRoaXNbIHByb3BOYW1lIF07XG5cblx0XHRpZiAoIHV0aWxzLmFycmF5VmFsdWVzQXJlRXF1YWwoIHByb3AuX3ZhbHVlICkgJiYgdXRpbHMuYXJyYXlWYWx1ZXNBcmVFcXVhbCggcHJvcC5fc3ByZWFkICkgKSB7XG5cdFx0XHRjb25zdCB2YWx1ZSA9IE1hdGguYWJzKCB1dGlscy5yYW5kb21GbG9hdCggcHJvcC5fdmFsdWVbIDAgXSwgcHJvcC5fc3ByZWFkWyAwIF0gKSApO1xuXHRcdFx0YXJyYXkuc2V0VmVjNENvbXBvbmVudHMoIGluZGV4LCB2YWx1ZSwgdmFsdWUsIHZhbHVlLCB2YWx1ZSApO1xuXHRcdH1cblx0XHRlbHNlIHtcblx0XHRcdGFycmF5LnNldFZlYzRDb21wb25lbnRzKCBpbmRleCxcblx0XHRcdFx0TWF0aC5hYnMoIHV0aWxzLnJhbmRvbUZsb2F0KCBwcm9wLl92YWx1ZVsgMCBdLCBwcm9wLl9zcHJlYWRbIDAgXSApICksXG5cdFx0XHRcdE1hdGguYWJzKCB1dGlscy5yYW5kb21GbG9hdCggcHJvcC5fdmFsdWVbIDEgXSwgcHJvcC5fc3ByZWFkWyAxIF0gKSApLFxuXHRcdFx0XHRNYXRoLmFicyggdXRpbHMucmFuZG9tRmxvYXQoIHByb3AuX3ZhbHVlWyAyIF0sIHByb3AuX3NwcmVhZFsgMiBdICkgKSxcblx0XHRcdFx0TWF0aC5hYnMoIHV0aWxzLnJhbmRvbUZsb2F0KCBwcm9wLl92YWx1ZVsgMyBdLCBwcm9wLl9zcHJlYWRbIDMgXSApIClcblx0XHRcdCk7XG5cdFx0fVxuXHR9XG5cblx0X2Fzc2lnbkFuZ2xlVmFsdWUoIGluZGV4ICkge1xuXHRcdGNvbnN0IGFycmF5ID0gdGhpcy5hdHRyaWJ1dGVzLmFuZ2xlLnR5cGVkQXJyYXksXG5cdFx0XHRwcm9wID0gdGhpcy5hbmdsZTtcblxuXHRcdGlmICggdXRpbHMuYXJyYXlWYWx1ZXNBcmVFcXVhbCggcHJvcC5fdmFsdWUgKSAmJiB1dGlscy5hcnJheVZhbHVlc0FyZUVxdWFsKCBwcm9wLl9zcHJlYWQgKSApIHtcblx0XHRcdGNvbnN0IHZhbHVlID0gdXRpbHMucmFuZG9tRmxvYXQoIHByb3AuX3ZhbHVlWyAwIF0sIHByb3AuX3NwcmVhZFsgMCBdICk7XG5cdFx0XHRhcnJheS5zZXRWZWM0Q29tcG9uZW50cyggaW5kZXgsIHZhbHVlLCB2YWx1ZSwgdmFsdWUsIHZhbHVlICk7XG5cdFx0fVxuXHRcdGVsc2Uge1xuXHRcdFx0YXJyYXkuc2V0VmVjNENvbXBvbmVudHMoIGluZGV4LFxuXHRcdFx0XHR1dGlscy5yYW5kb21GbG9hdCggcHJvcC5fdmFsdWVbIDAgXSwgcHJvcC5fc3ByZWFkWyAwIF0gKSxcblx0XHRcdFx0dXRpbHMucmFuZG9tRmxvYXQoIHByb3AuX3ZhbHVlWyAxIF0sIHByb3AuX3NwcmVhZFsgMSBdICksXG5cdFx0XHRcdHV0aWxzLnJhbmRvbUZsb2F0KCBwcm9wLl92YWx1ZVsgMiBdLCBwcm9wLl9zcHJlYWRbIDIgXSApLFxuXHRcdFx0XHR1dGlscy5yYW5kb21GbG9hdCggcHJvcC5fdmFsdWVbIDMgXSwgcHJvcC5fc3ByZWFkWyAzIF0gKVxuXHRcdFx0KTtcblx0XHR9XG5cdH1cblxuXHRfYXNzaWduUGFyYW1zVmFsdWUoIGluZGV4ICkge1xuXHRcdHRoaXMuYXR0cmlidXRlcy5wYXJhbXMudHlwZWRBcnJheS5zZXRWZWM0Q29tcG9uZW50cyggaW5kZXgsXG5cdFx0XHR0aGlzLmlzU3RhdGljID8gMSA6IDAsXG5cdFx0XHQwLjAsXG5cdFx0XHRNYXRoLmFicyggdXRpbHMucmFuZG9tRmxvYXQoIHRoaXMubWF4QWdlLl92YWx1ZSwgdGhpcy5tYXhBZ2UuX3NwcmVhZCApICksXG5cdFx0XHR1dGlscy5yYW5kb21GbG9hdCggdGhpcy53aWdnbGUuX3ZhbHVlLCB0aGlzLndpZ2dsZS5fc3ByZWFkIClcblx0XHQpO1xuXHR9XG5cblx0X2Fzc2lnblJvdGF0aW9uVmFsdWUoIGluZGV4ICkge1xuXHRcdHRoaXMuYXR0cmlidXRlcy5yb3RhdGlvbi50eXBlZEFycmF5LnNldFZlYzNDb21wb25lbnRzKCBpbmRleCxcblx0XHRcdHV0aWxzLmdldFBhY2tlZFJvdGF0aW9uQXhpcyggdGhpcy5yb3RhdGlvbi5fYXhpcywgdGhpcy5yb3RhdGlvbi5fYXhpc1NwcmVhZCApLFxuXHRcdFx0dXRpbHMucmFuZG9tRmxvYXQoIHRoaXMucm90YXRpb24uX2FuZ2xlLCB0aGlzLnJvdGF0aW9uLl9hbmdsZVNwcmVhZCApLFxuXHRcdFx0dGhpcy5yb3RhdGlvbi5fc3RhdGljID8gMCA6IDFcblx0XHQpO1xuXG5cdFx0dGhpcy5hdHRyaWJ1dGVzLnJvdGF0aW9uQ2VudGVyLnR5cGVkQXJyYXkuc2V0VmVjMyggaW5kZXgsIHRoaXMucm90YXRpb24uX2NlbnRlciApO1xuXHR9XG5cblx0X2Fzc2lnbkNvbG9yVmFsdWUoIGluZGV4ICkge1xuXHRcdHV0aWxzLnJhbmRvbUNvbG9yQXNIZXgoIHRoaXMuYXR0cmlidXRlcy5jb2xvciwgaW5kZXgsIHRoaXMuY29sb3IuX3ZhbHVlLCB0aGlzLmNvbG9yLl9zcHJlYWQgKTtcblx0fVxuXG5cdF9yZXNldFBhcnRpY2xlKCBpbmRleCApIHtcblx0XHRjb25zdCByZXNldEZsYWdzID0gdGhpcy5yZXNldEZsYWdzLFxuXHRcdFx0dXBkYXRlRmxhZ3MgPSB0aGlzLnVwZGF0ZUZsYWdzLFxuXHRcdFx0dXBkYXRlQ291bnRzID0gdGhpcy51cGRhdGVDb3VudHMsXG5cdFx0XHRrZXlzID0gdGhpcy5hdHRyaWJ1dGVLZXlzO1xuXHRcdGxldCBrZXksXG5cdFx0XHR1cGRhdGVGbGFnO1xuXG5cdFx0Zm9yICggbGV0IGkgPSB0aGlzLmF0dHJpYnV0ZUNvdW50IC0gMTsgaSA+PSAwOyAtLWkgKSB7XG5cdFx0XHRrZXkgPSBrZXlzWyBpIF07XG5cdFx0XHR1cGRhdGVGbGFnID0gdXBkYXRlRmxhZ3NbIGtleSBdO1xuXG5cdFx0XHRpZiAoIHJlc2V0RmxhZ3NbIGtleSBdID09PSB0cnVlIHx8IHVwZGF0ZUZsYWcgPT09IHRydWUgKSB7XG5cdFx0XHRcdHRoaXMuX2Fzc2lnblZhbHVlKCBrZXksIGluZGV4ICk7XG5cdFx0XHRcdHRoaXMuX3VwZGF0ZUF0dHJpYnV0ZVVwZGF0ZVJhbmdlKCBrZXksIGluZGV4ICk7XG5cblx0XHRcdFx0aWYgKCB1cGRhdGVGbGFnID09PSB0cnVlICYmIHVwZGF0ZUNvdW50c1sga2V5IF0gPT09IHRoaXMucGFydGljbGVDb3VudCApIHtcblx0XHRcdFx0XHR1cGRhdGVGbGFnc1sga2V5IF0gPSBmYWxzZTtcblx0XHRcdFx0XHR1cGRhdGVDb3VudHNbIGtleSBdID0gMC4wO1xuXHRcdFx0XHR9XG5cdFx0XHRcdGVsc2UgaWYgKCB1cGRhdGVGbGFnID09IHRydWUgKSB7XG5cdFx0XHRcdFx0Kyt1cGRhdGVDb3VudHNbIGtleSBdO1xuXHRcdFx0XHR9XG5cdFx0XHR9XG5cdFx0fVxuXHR9XG5cblx0X3VwZGF0ZUF0dHJpYnV0ZVVwZGF0ZVJhbmdlKCBhdHRyLCBpICkge1xuXHRcdHZhciByYW5nZXMgPSB0aGlzLmJ1ZmZlclVwZGF0ZVJhbmdlc1sgYXR0ciBdO1xuXG5cdFx0cmFuZ2VzLm1pbiA9IE1hdGgubWluKCBpLCByYW5nZXMubWluICk7XG5cdFx0cmFuZ2VzLm1heCA9IE1hdGgubWF4KCBpLCByYW5nZXMubWF4ICk7XG5cdH1cblxuXHRfcmVzZXRCdWZmZXJSYW5nZXMoKSB7XG5cdFx0Y29uc3QgcmFuZ2VzID0gdGhpcy5idWZmZXJVcGRhdGVSYW5nZXMsXG5cdFx0XHRrZXlzID0gdGhpcy5idWZmZXJVcGRhdGVLZXlzO1xuXHRcdGxldCBpID0gdGhpcy5idWZmZXJVcGRhdGVDb3VudCAtIDEsXG5cdFx0XHRrZXk7XG5cblx0XHRmb3IgKCBpOyBpID49IDA7IC0taSApIHtcblx0XHRcdGtleSA9IGtleXNbIGkgXTtcblx0XHRcdHJhbmdlc1sga2V5IF0ubWluID0gTnVtYmVyLlBPU0lUSVZFX0lORklOSVRZO1xuXHRcdFx0cmFuZ2VzWyBrZXkgXS5tYXggPSBOdW1iZXIuTkVHQVRJVkVfSU5GSU5JVFk7XG5cdFx0fVxuXHR9XG5cblx0X29uUmVtb3ZlKCkge1xuXHRcdC8vIFJlc2V0IGFueSBwcm9wZXJ0aWVzIG9mIHRoZSBlbWl0dGVyIHRoYXQgd2VyZSBzZXQgYnlcblx0XHQvLyBhIGdyb3VwIHdoZW4gaXQgd2FzIGFkZGVkLlxuXHRcdHRoaXMucGFydGljbGVzUGVyU2Vjb25kID0gMDtcblx0XHR0aGlzLmF0dHJpYnV0ZU9mZnNldCA9IDA7XG5cdFx0dGhpcy5hY3RpdmF0aW9uSW5kZXggPSAwO1xuXHRcdHRoaXMuYWN0aXZlUGFydGljbGVDb3VudCA9IDA7XG5cdFx0dGhpcy5ncm91cCA9IG51bGw7XG5cdFx0dGhpcy5hdHRyaWJ1dGVzID0gbnVsbDtcblx0XHR0aGlzLnBhcmFtc0FycmF5ID0gbnVsbDtcblx0XHR0aGlzLmFnZSA9IDAuMDtcblx0fVxuXG5cdF9kZWNyZW1lbnRQYXJ0aWNsZUNvdW50KCkge1xuXHRcdC0tdGhpcy5hY3RpdmVQYXJ0aWNsZUNvdW50O1xuXG5cdFx0Ly8gVE9ETzpcblx0XHQvLyAgLSBUcmlnZ2VyIGV2ZW50IGlmIGNvdW50ID09PSAwLlxuXHR9XG5cblx0X2luY3JlbWVudFBhcnRpY2xlQ291bnQoKSB7XG5cdFx0J3VzZSBzdHJpY3QnO1xuXHRcdCsrdGhpcy5hY3RpdmVQYXJ0aWNsZUNvdW50O1xuXG5cdFx0Ly8gVE9ETzpcblx0XHQvLyAgLSBUcmlnZ2VyIGV2ZW50IGlmIGNvdW50ID09PSB0aGlzLnBhcnRpY2xlQ291bnQuXG5cdH1cblxuXHRfY2hlY2tQYXJ0aWNsZUFnZXMoIHN0YXJ0LCBlbmQsIHBhcmFtcywgZHQgKSB7XG5cdFx0Zm9yICggbGV0IGkgPSBlbmQgLSAxLCBpbmRleCwgbWF4QWdlLCBhZ2UsIGFsaXZlOyBpID49IHN0YXJ0OyAtLWkgKSB7XG5cdFx0XHRpbmRleCA9IGkgKiA0O1xuXG5cdFx0XHRhbGl2ZSA9IHBhcmFtc1sgaW5kZXggXTtcblxuXHRcdFx0aWYgKCBhbGl2ZSA9PT0gMC4wICkge1xuXHRcdFx0XHRjb250aW51ZTtcblx0XHRcdH1cblxuXHRcdFx0Ly8gSW5jcmVtZW50IGFnZVxuXHRcdFx0YWdlID0gcGFyYW1zWyBpbmRleCArIDEgXTtcblx0XHRcdG1heEFnZSA9IHBhcmFtc1sgaW5kZXggKyAyIF07XG5cblx0XHRcdGlmICggdGhpcy5kaXJlY3Rpb24gPT09IDEgKSB7XG5cdFx0XHRcdGFnZSArPSBkdDtcblxuXHRcdFx0XHRpZiAoIGFnZSA+PSBtYXhBZ2UgKSB7XG5cdFx0XHRcdFx0YWdlID0gMC4wO1xuXHRcdFx0XHRcdGFsaXZlID0gMC4wO1xuXHRcdFx0XHRcdHRoaXMuX2RlY3JlbWVudFBhcnRpY2xlQ291bnQoKTtcblx0XHRcdFx0fVxuXHRcdFx0fVxuXHRcdFx0ZWxzZSB7XG5cdFx0XHRcdGFnZSAtPSBkdDtcblxuXHRcdFx0XHRpZiAoIGFnZSA8PSAwLjAgKSB7XG5cdFx0XHRcdFx0YWdlID0gbWF4QWdlO1xuXHRcdFx0XHRcdGFsaXZlID0gMC4wO1xuXHRcdFx0XHRcdHRoaXMuX2RlY3JlbWVudFBhcnRpY2xlQ291bnQoKTtcblx0XHRcdFx0fVxuXHRcdFx0fVxuXG5cdFx0XHRwYXJhbXNbIGluZGV4IF0gPSBhbGl2ZTtcblx0XHRcdHBhcmFtc1sgaW5kZXggKyAxIF0gPSBhZ2U7XG5cblx0XHRcdHRoaXMuX3VwZGF0ZUF0dHJpYnV0ZVVwZGF0ZVJhbmdlKCAncGFyYW1zJywgaSApO1xuXHRcdH1cblx0fVxuXG5cdF9hY3RpdmF0ZVBhcnRpY2xlcyggYWN0aXZhdGlvblN0YXJ0LCBhY3RpdmF0aW9uRW5kLCBwYXJhbXMsIGR0UGVyUGFydGljbGUgKSB7XG5cdFx0Y29uc3QgZGlyZWN0aW9uID0gdGhpcy5kaXJlY3Rpb247XG5cblx0XHRmb3IgKCBsZXQgaSA9IGFjdGl2YXRpb25TdGFydCwgaW5kZXgsIGR0VmFsdWU7IGkgPCBhY3RpdmF0aW9uRW5kOyArK2kgKSB7XG5cdFx0XHRpbmRleCA9IGkgKiA0O1xuXG5cdFx0XHQvLyBEb24ndCByZS1hY3RpdmF0ZSBwYXJ0aWNsZXMgdGhhdCBhcmVuJ3QgZGVhZCB5ZXQuXG5cdFx0XHQvLyBpZiAoIHBhcmFtc1sgaW5kZXggXSAhPT0gMC4wICYmICggdGhpcy5wYXJ0aWNsZUNvdW50ICE9PSAxIHx8IHRoaXMuYWN0aXZlTXVsdGlwbGllciAhPT0gMSApICkge1xuXHRcdFx0Ly8gICAgIGNvbnRpbnVlO1xuXHRcdFx0Ly8gfVxuXG5cdFx0XHRpZiAoIHBhcmFtc1sgaW5kZXggXSAhPSAwLjAgJiYgdGhpcy5wYXJ0aWNsZUNvdW50ICE9PSAxICkge1xuXHRcdFx0XHRjb250aW51ZTtcblx0XHRcdH1cblxuXHRcdFx0Ly8gSW5jcmVtZW50IHRoZSBhY3RpdmUgcGFydGljbGUgY291bnQuXG5cdFx0XHR0aGlzLl9pbmNyZW1lbnRQYXJ0aWNsZUNvdW50KCk7XG5cblx0XHRcdC8vIE1hcmsgdGhlIHBhcnRpY2xlIGFzIGFsaXZlLlxuXHRcdFx0cGFyYW1zWyBpbmRleCBdID0gMS4wO1xuXG5cdFx0XHQvLyBSZXNldCB0aGUgcGFydGljbGVcblx0XHRcdHRoaXMuX3Jlc2V0UGFydGljbGUoIGkgKTtcblxuXHRcdFx0Ly8gTW92ZSBlYWNoIHBhcnRpY2xlIGJlaW5nIGFjdGl2YXRlZCB0b1xuXHRcdFx0Ly8gaXQncyBhY3R1YWwgcG9zaXRpb24gaW4gdGltZS5cblx0XHRcdC8vXG5cdFx0XHQvLyBUaGlzIHN0b3BzIHBhcnRpY2xlcyBiZWluZyAnY2x1bXBlZCcgdG9nZXRoZXJcblx0XHRcdC8vIHdoZW4gZnJhbWUgcmF0ZXMgYXJlIG9uIHRoZSBsb3dlciBzaWRlIG9mIDYwZnBzXG5cdFx0XHQvLyBvciBub3QgY29uc3RhbnQgKGEgdmVyeSByZWFsIHBvc3NpYmlsaXR5ISlcblx0XHRcdGR0VmFsdWUgPSBkdFBlclBhcnRpY2xlICogKCBpIC0gYWN0aXZhdGlvblN0YXJ0ICk7XG5cdFx0XHRwYXJhbXNbIGluZGV4ICsgMSBdID0gZGlyZWN0aW9uID09PSAtMSA/IHBhcmFtc1sgaW5kZXggKyAyIF0gLSBkdFZhbHVlIDogZHRWYWx1ZTtcblxuXHRcdFx0dGhpcy5fdXBkYXRlQXR0cmlidXRlVXBkYXRlUmFuZ2UoICdwYXJhbXMnLCBpICk7XG5cdFx0fVxuXHR9XG5cblx0LyoqXG5cdCAqIFNpbXVsYXRlcyBvbmUgZnJhbWUncyB3b3J0aCBvZiBwYXJ0aWNsZXMsIHVwZGF0aW5nIHBhcnRpY2xlc1xuXHQgKiB0aGF0IGFyZSBhbHJlYWR5IGFsaXZlLCBhbmQgbWFya2luZyBvbmVzIHRoYXQgYXJlIGN1cnJlbnRseSBkZWFkXG5cdCAqIGJ1dCBzaG91bGQgYmUgYWxpdmUgYXMgYWxpdmUuXG5cdCAqXG5cdCAqIElmIHRoZSBlbWl0dGVyIGlzIG1hcmtlZCBhcyBzdGF0aWMsIHRoZW4gdGhpcyBmdW5jdGlvbiB3aWxsIGRvIG5vdGhpbmcuXG5cdCAqXG5cdCAqIEBwYXJhbSAge051bWJlcn0gZHQgVGhlIG51bWJlciBvZiBzZWNvbmRzIHRvIHNpbXVsYXRlIChkZWx0YVRpbWUpXG5cdCAqL1xuXHR0aWNrKCBkdCApIHtcblx0XHRpZiAoIHRoaXMuaXNTdGF0aWMgKSB7XG5cdFx0XHRyZXR1cm47XG5cdFx0fVxuXG5cdFx0aWYgKCB0aGlzLnBhcmFtc0FycmF5ID09PSBudWxsICkge1xuXHRcdFx0dGhpcy5wYXJhbXNBcnJheSA9IHRoaXMuYXR0cmlidXRlcy5wYXJhbXMudHlwZWRBcnJheS5hcnJheTtcblx0XHR9XG5cblx0XHRjb25zdCBzdGFydCA9IHRoaXMuYXR0cmlidXRlT2Zmc2V0LFxuXHRcdFx0ZW5kID0gc3RhcnQgKyB0aGlzLnBhcnRpY2xlQ291bnQsXG5cdFx0XHRwYXJhbXMgPSB0aGlzLnBhcmFtc0FycmF5LCAvLyB2ZWMzKCBhbGl2ZSwgYWdlLCBtYXhBZ2UsIHdpZ2dsZSApXG5cdFx0XHRwcHNEdCA9IHRoaXMucGFydGljbGVzUGVyU2Vjb25kICogdGhpcy5hY3RpdmVNdWx0aXBsaWVyICogZHQsXG5cdFx0XHRhY3RpdmF0aW9uSW5kZXggPSB0aGlzLmFjdGl2YXRpb25JbmRleDtcblxuXHRcdC8vIFJlc2V0IHRoZSBidWZmZXIgdXBkYXRlIGluZGljZXMuXG5cdFx0dGhpcy5fcmVzZXRCdWZmZXJSYW5nZXMoKTtcblxuXHRcdC8vIEluY3JlbWVudCBhZ2UgZm9yIHRob3NlIHBhcnRpY2xlcyB0aGF0IGFyZSBhbGl2ZSxcblx0XHQvLyBhbmQga2lsbCBvZmYgYW55IHBhcnRpY2xlcyB3aG9zZSBhZ2UgaXMgb3ZlciB0aGUgbGltaXQuXG5cdFx0dGhpcy5fY2hlY2tQYXJ0aWNsZUFnZXMoIHN0YXJ0LCBlbmQsIHBhcmFtcywgZHQgKTtcblxuXHRcdC8vIElmIHRoZSBlbWl0dGVyIGlzIGRlYWQsIHJlc2V0IHRoZSBhZ2Ugb2YgdGhlIGVtaXR0ZXIgdG8gemVybyxcblx0XHQvLyByZWFkeSB0byBnbyBhZ2FpbiBpZiByZXF1aXJlZFxuXHRcdGlmICggdGhpcy5hbGl2ZSA9PT0gZmFsc2UgKSB7XG5cdFx0XHR0aGlzLmFnZSA9IDAuMDtcblx0XHRcdHJldHVybjtcblx0XHR9XG5cblx0XHQvLyBJZiB0aGUgZW1pdHRlciBoYXMgYSBzcGVjaWZpZWQgbGlmZXRpbWUgYW5kIHdlJ3ZlIGV4Y2VlZGVkIGl0LFxuXHRcdC8vIG1hcmsgdGhlIGVtaXR0ZXIgYXMgZGVhZC5cblx0XHRpZiAoIHRoaXMuZHVyYXRpb24gIT09IG51bGwgJiYgdGhpcy5hZ2UgPiB0aGlzLmR1cmF0aW9uICkge1xuXHRcdFx0dGhpcy5hbGl2ZSA9IGZhbHNlO1xuXHRcdFx0dGhpcy5hZ2UgPSAwLjA7XG5cdFx0XHRyZXR1cm47XG5cdFx0fVxuXG5cblx0XHRjb25zdCBhY3RpdmF0aW9uU3RhcnQgPSB0aGlzLnBhcnRpY2xlQ291bnQgPT09IDEgPyBhY3RpdmF0aW9uSW5kZXggOiAoIGFjdGl2YXRpb25JbmRleCB8IDAgKSxcblx0XHRcdGFjdGl2YXRpb25FbmQgPSBNYXRoLm1pbiggYWN0aXZhdGlvblN0YXJ0ICsgcHBzRHQsIHRoaXMuYWN0aXZhdGlvbkVuZCApLFxuXHRcdFx0YWN0aXZhdGlvbkNvdW50ID0gYWN0aXZhdGlvbkVuZCAtIHRoaXMuYWN0aXZhdGlvbkluZGV4IHwgMCxcblx0XHRcdGR0UGVyUGFydGljbGUgPSBhY3RpdmF0aW9uQ291bnQgPiAwID8gZHQgLyBhY3RpdmF0aW9uQ291bnQgOiAwO1xuXG5cdFx0dGhpcy5fYWN0aXZhdGVQYXJ0aWNsZXMoIGFjdGl2YXRpb25TdGFydCwgYWN0aXZhdGlvbkVuZCwgcGFyYW1zLCBkdFBlclBhcnRpY2xlICk7XG5cblx0XHQvLyBNb3ZlIHRoZSBhY3RpdmF0aW9uIHdpbmRvdyBmb3J3YXJkLCBzb2xkaWVyLlxuXHRcdHRoaXMuYWN0aXZhdGlvbkluZGV4ICs9IHBwc0R0O1xuXG5cdFx0aWYgKCB0aGlzLmFjdGl2YXRpb25JbmRleCA+IGVuZCApIHtcblx0XHRcdHRoaXMuYWN0aXZhdGlvbkluZGV4ID0gc3RhcnQ7XG5cdFx0fVxuXG5cblx0XHQvLyBJbmNyZW1lbnQgdGhlIGFnZSBvZiB0aGUgZW1pdHRlci5cblx0XHR0aGlzLmFnZSArPSBkdDtcblx0fVxuXG5cdC8qKlxuXHQgKiBSZXNldHMgYWxsIHRoZSBlbWl0dGVyJ3MgcGFydGljbGVzIHRvIHRoZWlyIHN0YXJ0IHBvc2l0aW9uc1xuXHQgKiBhbmQgbWFya3MgdGhlIHBhcnRpY2xlcyBhcyBkZWFkIGlmIHRoZSBgZm9yY2VgIGFyZ3VtZW50IGlzXG5cdCAqIHRydWUuXG5cdCAqXG5cdCAqIEBwYXJhbSAge0Jvb2xlYW59IFtmb3JjZT11bmRlZmluZWRdIElmIHRydWUsIGFsbCBwYXJ0aWNsZXMgd2lsbCBiZSBtYXJrZWQgYXMgZGVhZCBpbnN0YW50bHkuXG5cdCAqIEByZXR1cm4ge0VtaXR0ZXJ9ICAgICAgIFRoaXMgZW1pdHRlciBpbnN0YW5jZS5cblx0ICovXG5cdHJlc2V0KCBmb3JjZSApIHtcblx0XHR0aGlzLmFnZSA9IDAuMDtcblx0XHR0aGlzLmFsaXZlID0gZmFsc2U7XG5cblx0XHRpZiAoIGZvcmNlID09PSB0cnVlICkge1xuXHRcdFx0Y29uc3Qgc3RhcnQgPSB0aGlzLmF0dHJpYnV0ZU9mZnNldCxcblx0XHRcdFx0ZW5kID0gc3RhcnQgKyB0aGlzLnBhcnRpY2xlQ291bnQsXG5cdFx0XHRcdGFycmF5ID0gdGhpcy5wYXJhbXNBcnJheSxcblx0XHRcdFx0YXR0ciA9IHRoaXMuYXR0cmlidXRlcy5wYXJhbXMuYnVmZmVyQXR0cmlidXRlO1xuXG5cdFx0XHRmb3IgKCBsZXQgaSA9IGVuZCAtIDEsIGluZGV4OyBpID49IHN0YXJ0OyAtLWkgKSB7XG5cdFx0XHRcdGluZGV4ID0gaSAqIDQ7XG5cblx0XHRcdFx0YXJyYXlbIGluZGV4IF0gPSAwLjA7XG5cdFx0XHRcdGFycmF5WyBpbmRleCArIDEgXSA9IDAuMDtcblx0XHRcdH1cblxuXHRcdFx0YXR0ci51cGRhdGVSYW5nZS5vZmZzZXQgPSAwO1xuXHRcdFx0YXR0ci51cGRhdGVSYW5nZS5jb3VudCA9IC0xO1xuXHRcdFx0YXR0ci5uZWVkc1VwZGF0ZSA9IHRydWU7XG5cdFx0fVxuXG5cdFx0cmV0dXJuIHRoaXM7XG5cdH1cblxuXHQvKipcblx0ICogRW5hYmxlcyB0aGUgZW1pdHRlci4gSWYgbm90IGFscmVhZHkgZW5hYmxlZCwgdGhlIGVtaXR0ZXJcblx0ICogd2lsbCBzdGFydCBlbWl0dGluZyBwYXJ0aWNsZXMuXG5cdCAqXG5cdCAqIEByZXR1cm4ge0VtaXR0ZXJ9IFRoaXMgZW1pdHRlciBpbnN0YW5jZS5cblx0ICovXG5cdGVuYWJsZSgpIHtcblx0XHR0aGlzLmFsaXZlID0gdHJ1ZTtcblx0XHRyZXR1cm4gdGhpcztcblx0fVxuXG5cdC8qKlxuXHQgKiBEaXNhYmxlcyB0aCBlbWl0dGVyLCBidXQgZG9lcyBub3QgaW5zdGFudGx5IHJlbW92ZSBpdCdzXG5cdCAqIHBhcnRpY2xlcyBmcm9tdCB0aGUgc2NlbmUuIFdoZW4gY2FsbGVkLCB0aGUgZW1pdHRlciB3aWxsIGJlXG5cdCAqICdzd2l0Y2hlZCBvZmYnIGFuZCBqdXN0IHN0b3AgZW1pdHRpbmcuIEFueSBwYXJ0aWNsZSdzIGFsaXZlIHdpbGxcblx0ICogYmUgYWxsb3dlZCB0byBmaW5pc2ggdGhlaXIgbGlmZWN5Y2xlLlxuXHQgKlxuXHQgKiBAcmV0dXJuIHtFbWl0dGVyfSBUaGlzIGVtaXR0ZXIgaW5zdGFuY2UuXG5cdCAqL1xuXHRkaXNhYmxlKCkge1xuXHRcdHRoaXMuYWxpdmUgPSBmYWxzZTtcblx0XHRyZXR1cm4gdGhpcztcblx0fVxuXG5cdC8qKlxuXHQgKiBSZW1vdmUgdGhpcyBlbWl0dGVyIGZyb20gaXQncyBwYXJlbnQgZ3JvdXAgKGlmIGl0IGhhcyBiZWVuIGFkZGVkIHRvIG9uZSkuXG5cdCAqIERlbGdhdGVzIHRvIEdyb3VwLnByb3RvdHlwZS5yZW1vdmVFbWl0dGVyKCkuXG5cdCAqXG5cdCAqIFdoZW4gY2FsbGVkLCBhbGwgcGFydGljbGUncyBiZWxvbmdpbmcgdG8gdGhpcyBlbWl0dGVyIHdpbGwgYmUgaW5zdGFudGx5XG5cdCAqIHJlbW92ZWQgZnJvbSB0aGUgc2NlbmUuXG5cdCAqXG5cdCAqIEByZXR1cm4ge0VtaXR0ZXJ9IFRoaXMgZW1pdHRlciBpbnN0YW5jZS5cblx0ICpcblx0ICogQHNlZSBHcm91cC5wcm90b3R5cGUucmVtb3ZlRW1pdHRlclxuXHQgKi9cblx0cmVtb3ZlKCkge1xuXHRcdGlmICggdGhpcy5ncm91cCAhPT0gbnVsbCApIHtcblx0XHRcdHRoaXMuZ3JvdXAucmVtb3ZlRW1pdHRlciggdGhpcyApO1xuXHRcdH1cblx0XHRlbHNlIHtcblx0XHRcdGNvbnNvbGUuZXJyb3IoICdFbWl0dGVyIGRvZXMgbm90IGJlbG9uZyB0byBhIGdyb3VwLCBjYW5ub3QgcmVtb3ZlLicgKTtcblx0XHR9XG5cblx0XHRyZXR1cm4gdGhpcztcblx0fVxufSIsImltcG9ydCAqIGFzIFRIUkVFIGZyb20gJ3RocmVlJztcbmltcG9ydCB2YWx1ZVR5cGVzIGZyb20gJ0AvY29uc3RhbnRzL3ZhbHVlVHlwZXMnO1xuaW1wb3J0IGdsb2JhbHMgZnJvbSAnQC9jb25zdGFudHMvZ2xvYmFscyc7XG5pbXBvcnQgdXRpbHMgZnJvbSAnLi91dGlscyc7XG5pbXBvcnQgU2hhZGVyQXR0cmlidXRlIGZyb20gJ0AvaGVscGVycy9TaGFkZXJBdHRyaWJ1dGUnO1xuaW1wb3J0IHNoYWRlcnMgZnJvbSAnQC9zaGFkZXJzL3NoYWRlcnMnO1xuaW1wb3J0IEVtaXR0ZXIgZnJvbSAnLi9FbWl0dGVyJztcblxuY29uc3QgSEFTX09XTiA9IE9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHk7XG5cbi8qKlxuICogQW4gU1BFLkdyb3VwIGluc3RhbmNlLlxuICogQHR5cGVkZWYge09iamVjdH0gR3JvdXBcbiAqIEBzZWUgU1BFLkdyb3VwXG4gKi9cblxuLyoqXG4gKiBBIG1hcCBvZiBvcHRpb25zIHRvIGNvbmZpZ3VyZSBhbiBTUEUuR3JvdXAgaW5zdGFuY2UuXG4gKiBAdHlwZWRlZiB7T2JqZWN0fSBHcm91cE9wdGlvbnNcbiAqXG4gKiBAcHJvcGVydHkge09iamVjdH0gdGV4dHVyZSBBbiBvYmplY3QgZGVzY3JpYmluZyB0aGUgdGV4dHVyZSB1c2VkIGJ5IHRoZSBncm91cC5cbiAqXG4gKiBAcHJvcGVydHkge09iamVjdH0gdGV4dHVyZS52YWx1ZSBBbiBpbnN0YW5jZSBvZiBUSFJFRS5UZXh0dXJlLlxuICpcbiAqIEBwcm9wZXJ0eSB7T2JqZWN0PX0gdGV4dHVyZS5mcmFtZXMgQSBUSFJFRS5WZWN0b3IyIGluc3RhbmNlIGRlc2NyaWJpbmcgdGhlIG51bWJlclxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvZiBmcmFtZXMgb24gdGhlIHgtIGFuZCB5LWF4aXMgb2YgdGhlIGdpdmVuIHRleHR1cmUuXG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElmIG5vdCBwcm92aWRlZCwgdGhlIHRleHR1cmUgd2lsbCBOT1QgYmUgdHJlYXRlZCBhc1xuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhIHNwcml0ZS1zaGVldCBhbmQgYXMgc3VjaCB3aWxsIE5PVCBiZSBhbmltYXRlZC5cbiAqXG4gKiBAcHJvcGVydHkge051bWJlcn0gW3RleHR1cmUuZnJhbWVDb3VudD10ZXh0dXJlLmZyYW1lcy54ICogdGV4dHVyZS5mcmFtZXMueV0gVGhlIHRvdGFsIG51bWJlciBvZiBmcmFtZXMgaW4gdGhlIHNwcml0ZS1zaGVldC5cbiAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEFsbG93cyBmb3Igc3ByaXRlLXNoZWV0cyB0aGF0IGRvbid0IGZpbGwgdGhlIGVudGlyZVxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGV4dHVyZS5cbiAqXG4gKiBAcHJvcGVydHkge051bWJlcn0gdGV4dHVyZS5sb29wIFRoZSBudW1iZXIgb2YgbG9vcHMgdGhyb3VnaCB0aGUgc3ByaXRlLXNoZWV0IHRoYXQgc2hvdWxkXG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJlIHBlcmZvcm1lZCBvdmVyIHRoZSBjb3Vyc2Ugb2YgYSBzaW5nbGUgcGFydGljbGUncyBsaWZldGltZS5cbiAqXG4gKiBAcHJvcGVydHkge051bWJlcn0gZml4ZWRUaW1lU3RlcCBJZiBubyBgZHRgIChvciBgZGVsdGFUaW1lYCkgdmFsdWUgaXMgcGFzc2VkIHRvIHRoaXMgZ3JvdXAnc1xuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYHRpY2soKWAgZnVuY3Rpb24sIHRoaXMgbnVtYmVyIHdpbGwgYmUgdXNlZCB0byBtb3ZlIHRoZSBwYXJ0aWNsZVxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2ltdWxhdGlvbiBmb3J3YXJkLiBWYWx1ZSBpbiBTRUNPTkRTLlxuICpcbiAqIEBwcm9wZXJ0eSB7Qm9vbGVhbn0gaGFzUGVyc3BlY3RpdmUgV2hldGhlciB0aGUgZGlzdGFuY2UgYSBwYXJ0aWNsZSBpcyBmcm9tIHRoZSBjYW1lcmEgc2hvdWxkIGFmZmVjdFxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGUgcGFydGljbGUncyBzaXplLlxuICpcbiAqIEBwcm9wZXJ0eSB7Qm9vbGVhbn0gY29sb3JpemUgV2hldGhlciB0aGUgcGFydGljbGVzIGluIHRoaXMgZ3JvdXAgc2hvdWxkIGJlIHJlbmRlcmVkIHdpdGggY29sb3IsIG9yXG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdoZXRoZXIgdGhlIG9ubHkgY29sb3Igb2YgcGFydGljbGVzIHdpbGwgY29tZSBmcm9tIHRoZSBwcm92aWRlZCB0ZXh0dXJlLlxuICpcbiAqIEBwcm9wZXJ0eSB7TnVtYmVyfSBibGVuZGluZyBPbmUgb2YgVGhyZWUuanMncyBibGVuZGluZyBtb2RlcyB0byBhcHBseSB0byB0aGlzIGdyb3VwJ3MgYFNoYWRlck1hdGVyaWFsYC5cbiAqXG4gKiBAcHJvcGVydHkge0Jvb2xlYW59IHRyYW5zcGFyZW50IFdoZXRoZXIgdGhlc2UgcGFydGljbGUncyBzaG91bGQgYmUgcmVuZGVyZWQgd2l0aCB0cmFuc3BhcmVuY3kuXG4gKlxuICogQHByb3BlcnR5IHtOdW1iZXJ9IGFscGhhVGVzdCBTZXRzIHRoZSBhbHBoYSB2YWx1ZSB0byBiZSB1c2VkIHdoZW4gcnVubmluZyBhbiBhbHBoYSB0ZXN0IG9uIHRoZSBgdGV4dHVyZS52YWx1ZWAgcHJvcGVydHkuIFZhbHVlIGJldHdlZW4gMCBhbmQgMS5cbiAqXG4gKiBAcHJvcGVydHkge0Jvb2xlYW59IGRlcHRoV3JpdGUgV2hldGhlciByZW5kZXJpbmcgdGhlIGdyb3VwIGhhcyBhbnkgZWZmZWN0IG9uIHRoZSBkZXB0aCBidWZmZXIuXG4gKlxuICogQHByb3BlcnR5IHtCb29sZWFufSBkZXB0aFRlc3QgV2hldGhlciB0byBoYXZlIGRlcHRoIHRlc3QgZW5hYmxlZCB3aGVuIHJlbmRlcmluZyB0aGlzIGdyb3VwLlxuICpcbiAqIEBwcm9wZXJ0eSB7Qm9vbGVhbn0gZm9nIFdoZXRoZXIgdGhpcyBncm91cCdzIHBhcnRpY2xlcyBzaG91bGQgYmUgYWZmZWN0ZWQgYnkgdGhlaXIgc2NlbmUncyBmb2cuXG4gKlxuICogQHByb3BlcnR5IHtOdW1iZXJ9IHNjYWxlIFRoZSBzY2FsZSBmYWN0b3IgdG8gYXBwbHkgdG8gdGhpcyBncm91cCdzIHBhcnRpY2xlIHNpemVzLiBVc2VmdWwgZm9yXG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgc2V0dGluZyBwYXJ0aWNsZSBzaXplcyB0byBiZSByZWxhdGl2ZSB0byByZW5kZXJlciBzaXplLlxuICovXG5cblxuLyoqXG4gKiBUaGUgU1BFLkdyb3VwIGNsYXNzLiBDcmVhdGVzIGEgbmV3IGdyb3VwLCBjb250YWluaW5nIGEgbWF0ZXJpYWwsIGdlb21ldHJ5LCBhbmQgbWVzaC5cbiAqXG4gKiBAY29uc3RydWN0b3JcbiAqIEBwYXJhbSB7R3JvdXBPcHRpb25zfSBvcHRpb25zIEEgbWFwIG9mIG9wdGlvbnMgdG8gY29uZmlndXJlIHRoZSBncm91cCBpbnN0YW5jZS5cbiAqL1xuZXhwb3J0IGRlZmF1bHQgY2xhc3MgR3JvdXAge1xuXHRjb25zdHJ1Y3Rvciggb3B0cyApIHtcblx0XHQvLyBFbnN1cmUgd2UgaGF2ZSBhIG1hcCBvZiBvcHRpb25zIHRvIHBsYXkgd2l0aFxuXHRcdGNvbnN0IG9wdGlvbnMgPSB1dGlscy5lbnN1cmVUeXBlZEFyZyggb3B0cywgdmFsdWVUeXBlcy5PQkpFQ1QsIHt9ICk7XG5cdFx0b3B0aW9ucy50ZXh0dXJlID0gdXRpbHMuZW5zdXJlVHlwZWRBcmcoIG9wdGlvbnMudGV4dHVyZSwgdmFsdWVUeXBlcy5PQkpFQ1QsIHt9ICk7XG5cblx0XHQvLyBBc3NpZ24gYSBVVUlEIHRvIHRoaXMgaW5zdGFuY2Vcblx0XHR0aGlzLnV1aWQgPSBUSFJFRS5NYXRoLmdlbmVyYXRlVVVJRCgpO1xuXG5cdFx0Ly8gSWYgbm8gYGRlbHRhVGltZWAgdmFsdWUgaXMgcGFzc2VkIHRvIHRoZSBgU1BFLkdyb3VwLnRpY2tgIGZ1bmN0aW9uLFxuXHRcdC8vIHRoZSB2YWx1ZSBvZiB0aGlzIHByb3BlcnR5IHdpbGwgYmUgdXNlZCB0byBhZHZhbmNlIHRoZSBzaW11bGF0aW9uLlxuXHRcdHRoaXMuZml4ZWRUaW1lU3RlcCA9IHV0aWxzLmVuc3VyZVR5cGVkQXJnKCBvcHRpb25zLmZpeGVkVGltZVN0ZXAsIHZhbHVlVHlwZXMuTlVNQkVSLCAwLjAxNiApO1xuXG5cdFx0Ly8gU2V0IHByb3BlcnRpZXMgdXNlZCBpbiB0aGUgdW5pZm9ybXMgbWFwLCBzdGFydGluZyB3aXRoIHRoZVxuXHRcdC8vIHRleHR1cmUgc3R1ZmYuXG5cdFx0dGhpcy50ZXh0dXJlID0gdXRpbHMuZW5zdXJlSW5zdGFuY2VPZiggb3B0aW9ucy50ZXh0dXJlLnZhbHVlLCBUSFJFRS5UZXh0dXJlLCBudWxsICk7XG5cdFx0dGhpcy50ZXh0dXJlRnJhbWVzID0gdXRpbHMuZW5zdXJlSW5zdGFuY2VPZiggb3B0aW9ucy50ZXh0dXJlLmZyYW1lcywgVEhSRUUuVmVjdG9yMiwgbmV3IFRIUkVFLlZlY3RvcjIoIDEsIDEgKSApO1xuXHRcdHRoaXMudGV4dHVyZUZyYW1lQ291bnQgPSB1dGlscy5lbnN1cmVUeXBlZEFyZyggb3B0aW9ucy50ZXh0dXJlLmZyYW1lQ291bnQsIHZhbHVlVHlwZXMuTlVNQkVSLCB0aGlzLnRleHR1cmVGcmFtZXMueCAqIHRoaXMudGV4dHVyZUZyYW1lcy55ICk7XG5cdFx0dGhpcy50ZXh0dXJlTG9vcCA9IHV0aWxzLmVuc3VyZVR5cGVkQXJnKCBvcHRpb25zLnRleHR1cmUubG9vcCwgdmFsdWVUeXBlcy5OVU1CRVIsIDEgKTtcblx0XHR0aGlzLnRleHR1cmVGcmFtZXMubWF4KCBuZXcgVEhSRUUuVmVjdG9yMiggMSwgMSApICk7XG5cblx0XHR0aGlzLmhhc1BlcnNwZWN0aXZlID0gdXRpbHMuZW5zdXJlVHlwZWRBcmcoIG9wdGlvbnMuaGFzUGVyc3BlY3RpdmUsIHZhbHVlVHlwZXMuQk9PTEVBTiwgdHJ1ZSApO1xuXHRcdHRoaXMuY29sb3JpemUgPSB1dGlscy5lbnN1cmVUeXBlZEFyZyggb3B0aW9ucy5jb2xvcml6ZSwgdmFsdWVUeXBlcy5CT09MRUFOLCB0cnVlICk7XG5cblx0XHR0aGlzLm1heFBhcnRpY2xlQ291bnQgPSB1dGlscy5lbnN1cmVUeXBlZEFyZyggb3B0aW9ucy5tYXhQYXJ0aWNsZUNvdW50LCB2YWx1ZVR5cGVzLk5VTUJFUiwgbnVsbCApO1xuXG5cblx0XHQvLyBTZXQgcHJvcGVydGllcyB1c2VkIHRvIGRlZmluZSB0aGUgU2hhZGVyTWF0ZXJpYWwncyBhcHBlYXJhbmNlLlxuXHRcdHRoaXMuYmxlbmRpbmcgPSB1dGlscy5lbnN1cmVUeXBlZEFyZyggb3B0aW9ucy5ibGVuZGluZywgdmFsdWVUeXBlcy5OVU1CRVIsIFRIUkVFLkFkZGl0aXZlQmxlbmRpbmcgKTtcblx0XHR0aGlzLnRyYW5zcGFyZW50ID0gdXRpbHMuZW5zdXJlVHlwZWRBcmcoIG9wdGlvbnMudHJhbnNwYXJlbnQsIHZhbHVlVHlwZXMuQk9PTEVBTiwgdHJ1ZSApO1xuXHRcdHRoaXMuYWxwaGFUZXN0ID0gcGFyc2VGbG9hdCggdXRpbHMuZW5zdXJlVHlwZWRBcmcoIG9wdGlvbnMuYWxwaGFUZXN0LCB2YWx1ZVR5cGVzLk5VTUJFUiwgMC4wICkgKTtcblx0XHR0aGlzLmRlcHRoV3JpdGUgPSB1dGlscy5lbnN1cmVUeXBlZEFyZyggb3B0aW9ucy5kZXB0aFdyaXRlLCB2YWx1ZVR5cGVzLkJPT0xFQU4sIGZhbHNlICk7XG5cdFx0dGhpcy5kZXB0aFRlc3QgPSB1dGlscy5lbnN1cmVUeXBlZEFyZyggb3B0aW9ucy5kZXB0aFRlc3QsIHZhbHVlVHlwZXMuQk9PTEVBTiwgdHJ1ZSApO1xuXHRcdHRoaXMuZm9nID0gdXRpbHMuZW5zdXJlVHlwZWRBcmcoIG9wdGlvbnMuZm9nLCB2YWx1ZVR5cGVzLkJPT0xFQU4sIHRydWUgKTtcblx0XHR0aGlzLnNjYWxlID0gdXRpbHMuZW5zdXJlVHlwZWRBcmcoIG9wdGlvbnMuc2NhbGUsIHZhbHVlVHlwZXMuTlVNQkVSLCAzMDAgKTtcblxuXHRcdC8vIFdoZXJlIGVtaXR0ZXIncyBnbyB0byBjdXJsIHVwIGluIGEgd2FybSBibGFua2V0IGFuZCBsaXZlXG5cdFx0Ly8gb3V0IHRoZWlyIGRheXMuXG5cdFx0dGhpcy5lbWl0dGVycyA9IFtdO1xuXHRcdHRoaXMuZW1pdHRlcklEcyA9IFtdO1xuXG5cdFx0Ly8gQ3JlYXRlIHByb3BlcnRpZXMgZm9yIHVzZSBieSB0aGUgZW1pdHRlciBwb29saW5nIGZ1bmN0aW9ucy5cblx0XHR0aGlzLl9wb29sID0gW107XG5cdFx0dGhpcy5fcG9vbENyZWF0aW9uU2V0dGluZ3MgPSBudWxsO1xuXHRcdHRoaXMuX2NyZWF0ZU5ld1doZW5Qb29sRW1wdHkgPSAwO1xuXG5cdFx0Ly8gV2hldGhlciBhbGwgYXR0cmlidXRlcyBzaG91bGQgYmUgZm9yY2VkIHRvIHVwZGF0ZWRcblx0XHQvLyB0aGVpciBlbnRpcmUgYnVmZmVyIGNvbnRlbnRzIG9uIHRoZSBuZXh0IHRpY2suXG5cdFx0Ly9cblx0XHQvLyBVc2VkIHdoZW4gYW4gZW1pdHRlciBpcyByZW1vdmVkLlxuXHRcdHRoaXMuX2F0dHJpYnV0ZXNOZWVkUmVmcmVzaCA9IGZhbHNlO1xuXHRcdHRoaXMuX2F0dHJpYnV0ZXNOZWVkRHluYW1pY1Jlc2V0ID0gZmFsc2U7XG5cblx0XHR0aGlzLnBhcnRpY2xlQ291bnQgPSAwO1xuXG5cblx0XHQvLyBNYXAgb2YgdW5pZm9ybXMgdG8gYmUgYXBwbGllZCB0byB0aGUgU2hhZGVyTWF0ZXJpYWwgaW5zdGFuY2UuXG5cdFx0dGhpcy51bmlmb3JtcyA9IHtcblx0XHRcdHRleDoge1xuXHRcdFx0XHR0eXBlOiAndCcsXG5cdFx0XHRcdHZhbHVlOiB0aGlzLnRleHR1cmUsXG5cdFx0XHR9LFxuXHRcdFx0dGV4dHVyZUFuaW1hdGlvbjoge1xuXHRcdFx0XHR0eXBlOiAndjQnLFxuXHRcdFx0XHR2YWx1ZTogbmV3IFRIUkVFLlZlY3RvcjQoXG5cdFx0XHRcdFx0dGhpcy50ZXh0dXJlRnJhbWVzLngsXG5cdFx0XHRcdFx0dGhpcy50ZXh0dXJlRnJhbWVzLnksXG5cdFx0XHRcdFx0dGhpcy50ZXh0dXJlRnJhbWVDb3VudCxcblx0XHRcdFx0XHRNYXRoLm1heCggTWF0aC5hYnMoIHRoaXMudGV4dHVyZUxvb3AgKSwgMS4wIClcblx0XHRcdFx0KSxcblx0XHRcdH0sXG5cdFx0XHRmb2dDb2xvcjoge1xuXHRcdFx0XHR0eXBlOiAnYycsXG5cdFx0XHRcdHZhbHVlOiB0aGlzLmZvZyA/IG5ldyBUSFJFRS5Db2xvcigpIDogbnVsbCxcblx0XHRcdH0sXG5cdFx0XHRmb2dOZWFyOiB7XG5cdFx0XHRcdHR5cGU6ICdmJyxcblx0XHRcdFx0dmFsdWU6IDEwLFxuXHRcdFx0fSxcblx0XHRcdGZvZ0Zhcjoge1xuXHRcdFx0XHR0eXBlOiAnZicsXG5cdFx0XHRcdHZhbHVlOiAyMDAsXG5cdFx0XHR9LFxuXHRcdFx0Zm9nRGVuc2l0eToge1xuXHRcdFx0XHR0eXBlOiAnZicsXG5cdFx0XHRcdHZhbHVlOiAwLjUsXG5cdFx0XHR9LFxuXHRcdFx0ZGVsdGFUaW1lOiB7XG5cdFx0XHRcdHR5cGU6ICdmJyxcblx0XHRcdFx0dmFsdWU6IDAsXG5cdFx0XHR9LFxuXHRcdFx0cnVuVGltZToge1xuXHRcdFx0XHR0eXBlOiAnZicsXG5cdFx0XHRcdHZhbHVlOiAwLFxuXHRcdFx0fSxcblx0XHRcdHNjYWxlOiB7XG5cdFx0XHRcdHR5cGU6ICdmJyxcblx0XHRcdFx0dmFsdWU6IHRoaXMuc2NhbGUsXG5cdFx0XHR9LFxuXHRcdH07XG5cblx0XHQvLyBBZGQgc29tZSBkZWZpbmVzIGludG8gdGhlIG1peC4uLlxuXHRcdHRoaXMuZGVmaW5lcyA9IHtcblx0XHRcdEhBU19QRVJTUEVDVElWRTogdGhpcy5oYXNQZXJzcGVjdGl2ZSxcblx0XHRcdENPTE9SSVpFOiB0aGlzLmNvbG9yaXplLFxuXHRcdFx0VkFMVUVfT1ZFUl9MSUZFVElNRV9MRU5HVEg6IGdsb2JhbHMudmFsdWVPdmVyTGlmZXRpbWVMZW5ndGgsXG5cblx0XHRcdFNIT1VMRF9ST1RBVEVfVEVYVFVSRTogZmFsc2UsXG5cdFx0XHRTSE9VTERfUk9UQVRFX1BBUlRJQ0xFUzogZmFsc2UsXG5cdFx0XHRTSE9VTERfV0lHR0xFX1BBUlRJQ0xFUzogZmFsc2UsXG5cblx0XHRcdFNIT1VMRF9DQUxDVUxBVEVfU1BSSVRFOiB0aGlzLnRleHR1cmVGcmFtZXMueCA+IDEgfHwgdGhpcy50ZXh0dXJlRnJhbWVzLnkgPiAxLFxuXHRcdH07XG5cblx0XHQvLyBNYXAgb2YgYWxsIGF0dHJpYnV0ZXMgdG8gYmUgYXBwbGllZCB0byB0aGUgcGFydGljbGVzLlxuXHRcdC8vXG5cdFx0Ly8gU2VlIGBTaGFkZXJBdHRyaWJ1dGVgIGZvciBhIGJpdCBtb3JlIGluZm8gb24gdGhpcy5cblx0XHR0aGlzLmF0dHJpYnV0ZXMgPSB7XG5cdFx0XHRwb3NpdGlvbjogbmV3IFNoYWRlckF0dHJpYnV0ZSggJ3YzJywgdHJ1ZSApLFxuXHRcdFx0YWNjZWxlcmF0aW9uOiBuZXcgU2hhZGVyQXR0cmlidXRlKCAndjQnLCB0cnVlICksIC8vIHcgY29tcG9uZW50IGlzIGRyYWdcblx0XHRcdHZlbG9jaXR5OiBuZXcgU2hhZGVyQXR0cmlidXRlKCAndjMnLCB0cnVlICksXG5cdFx0XHRyb3RhdGlvbjogbmV3IFNoYWRlckF0dHJpYnV0ZSggJ3Y0JywgdHJ1ZSApLFxuXHRcdFx0cm90YXRpb25DZW50ZXI6IG5ldyBTaGFkZXJBdHRyaWJ1dGUoICd2MycsIHRydWUgKSxcblx0XHRcdHBhcmFtczogbmV3IFNoYWRlckF0dHJpYnV0ZSggJ3Y0JywgdHJ1ZSApLCAvLyBIb2xkcyAoYWxpdmUsIGFnZSwgZGVsYXksIHdpZ2dsZSlcblx0XHRcdHNpemU6IG5ldyBTaGFkZXJBdHRyaWJ1dGUoICd2NCcsIHRydWUgKSxcblx0XHRcdGFuZ2xlOiBuZXcgU2hhZGVyQXR0cmlidXRlKCAndjQnLCB0cnVlICksXG5cdFx0XHRjb2xvcjogbmV3IFNoYWRlckF0dHJpYnV0ZSggJ3Y0JywgdHJ1ZSApLFxuXHRcdFx0b3BhY2l0eTogbmV3IFNoYWRlckF0dHJpYnV0ZSggJ3Y0JywgdHJ1ZSApLFxuXHRcdH07XG5cblx0XHR0aGlzLmF0dHJpYnV0ZUtleXMgPSBPYmplY3Qua2V5cyggdGhpcy5hdHRyaWJ1dGVzICk7XG5cdFx0dGhpcy5hdHRyaWJ1dGVDb3VudCA9IHRoaXMuYXR0cmlidXRlS2V5cy5sZW5ndGg7XG5cblx0XHQvLyBDcmVhdGUgdGhlIFNoYWRlck1hdGVyaWFsIGluc3RhbmNlIHRoYXQnbGwgaGVscCByZW5kZXIgdGhlXG5cdFx0Ly8gcGFydGljbGVzLlxuXHRcdHRoaXMubWF0ZXJpYWwgPSBuZXcgVEhSRUUuU2hhZGVyTWF0ZXJpYWwoIHtcblx0XHRcdHVuaWZvcm1zOiB0aGlzLnVuaWZvcm1zLFxuXHRcdFx0dmVydGV4U2hhZGVyOiBzaGFkZXJzLnZlcnRleCxcblx0XHRcdGZyYWdtZW50U2hhZGVyOiBzaGFkZXJzLmZyYWdtZW50LFxuXHRcdFx0YmxlbmRpbmc6IHRoaXMuYmxlbmRpbmcsXG5cdFx0XHR0cmFuc3BhcmVudDogdGhpcy50cmFuc3BhcmVudCxcblx0XHRcdGFscGhhVGVzdDogdGhpcy5hbHBoYVRlc3QsXG5cdFx0XHRkZXB0aFdyaXRlOiB0aGlzLmRlcHRoV3JpdGUsXG5cdFx0XHRkZXB0aFRlc3Q6IHRoaXMuZGVwdGhUZXN0LFxuXHRcdFx0ZGVmaW5lczogdGhpcy5kZWZpbmVzLFxuXHRcdFx0Zm9nOiB0aGlzLmZvZyxcblx0XHR9ICk7XG5cblx0XHQvLyBDcmVhdGUgdGhlIEJ1ZmZlckdlb21ldHJ5IGFuZCBQb2ludHMgaW5zdGFuY2VzLCBlbnN1cmluZ1xuXHRcdC8vIHRoZSBnZW9tZXRyeSBhbmQgbWF0ZXJpYWwgYXJlIGdpdmVuIHRvIHRoZSBsYXR0ZXIuXG5cdFx0dGhpcy5nZW9tZXRyeSA9IG5ldyBUSFJFRS5CdWZmZXJHZW9tZXRyeSgpO1xuXHRcdHRoaXMubWVzaCA9IG5ldyBUSFJFRS5Qb2ludHMoIHRoaXMuZ2VvbWV0cnksIHRoaXMubWF0ZXJpYWwgKTtcblxuXHRcdGlmICggdGhpcy5tYXhQYXJ0aWNsZUNvdW50ID09PSBudWxsICkge1xuXHRcdFx0Y29uc29sZS53YXJuKCAnU1BFLkdyb3VwOiBObyBtYXhQYXJ0aWNsZUNvdW50IHNwZWNpZmllZC4gQWRkaW5nIGVtaXR0ZXJzIGFmdGVyIHJlbmRlcmluZyB3aWxsIHByb2JhYmx5IGNhdXNlIGVycm9ycy4nICk7XG5cdFx0fVxuXHR9XG5cblx0X3VwZGF0ZURlZmluZXMoKSB7XG5cdFx0Y29uc3QgZW1pdHRlcnMgPSB0aGlzLmVtaXR0ZXJzLFxuXHRcdFx0ZGVmaW5lcyA9IHRoaXMuZGVmaW5lcztcblx0XHRsZXQgZW1pdHRlcixcblx0XHRcdGkgPSBlbWl0dGVycy5sZW5ndGggLSAxO1xuXG5cdFx0Zm9yICggaTsgaSA+PSAwOyAtLWkgKSB7XG5cdFx0XHRlbWl0dGVyID0gZW1pdHRlcnNbIGkgXTtcblxuXHRcdFx0Ly8gT25seSBkbyBhbmdsZSBjYWxjdWxhdGlvbiBpZiB0aGVyZSdzIG5vIHNwcml0ZXNoZWV0IGRlZmluZWQuXG5cdFx0XHQvL1xuXHRcdFx0Ly8gU2F2ZXMgY2FsY3VsYXRpb25zIGJlaW5nIGRvbmUgYW5kIHRoZW4gb3ZlcndyaXR0ZW4gaW4gdGhlIHNoYWRlcnMuXG5cdFx0XHRpZiAoICFkZWZpbmVzLlNIT1VMRF9DQUxDVUxBVEVfU1BSSVRFICkge1xuXHRcdFx0XHRkZWZpbmVzLlNIT1VMRF9ST1RBVEVfVEVYVFVSRSA9IGRlZmluZXMuU0hPVUxEX1JPVEFURV9URVhUVVJFIHx8ICEhTWF0aC5tYXgoXG5cdFx0XHRcdFx0TWF0aC5tYXguYXBwbHkoIG51bGwsIGVtaXR0ZXIuYW5nbGUudmFsdWUgKSxcblx0XHRcdFx0XHRNYXRoLm1heC5hcHBseSggbnVsbCwgZW1pdHRlci5hbmdsZS5zcHJlYWQgKVxuXHRcdFx0XHQpO1xuXHRcdFx0fVxuXG5cdFx0XHRkZWZpbmVzLlNIT1VMRF9ST1RBVEVfUEFSVElDTEVTID0gZGVmaW5lcy5TSE9VTERfUk9UQVRFX1BBUlRJQ0xFUyB8fCAhIU1hdGgubWF4KFxuXHRcdFx0XHRlbWl0dGVyLnJvdGF0aW9uLmFuZ2xlLFxuXHRcdFx0XHRlbWl0dGVyLnJvdGF0aW9uLmFuZ2xlU3ByZWFkXG5cdFx0XHQpO1xuXG5cdFx0XHRkZWZpbmVzLlNIT1VMRF9XSUdHTEVfUEFSVElDTEVTID0gZGVmaW5lcy5TSE9VTERfV0lHR0xFX1BBUlRJQ0xFUyB8fCAhIU1hdGgubWF4KFxuXHRcdFx0XHRlbWl0dGVyLndpZ2dsZS52YWx1ZSxcblx0XHRcdFx0ZW1pdHRlci53aWdnbGUuc3ByZWFkXG5cdFx0XHQpO1xuXHRcdH1cblxuXHRcdHRoaXMubWF0ZXJpYWwubmVlZHNVcGRhdGUgPSB0cnVlO1xuXHR9XG5cblx0X2FwcGx5QXR0cmlidXRlc1RvR2VvbWV0cnkoKSB7XG5cdFx0Y29uc3QgYXR0cmlidXRlcyA9IHRoaXMuYXR0cmlidXRlcyxcblx0XHRcdGdlb21ldHJ5ID0gdGhpcy5nZW9tZXRyeSxcblx0XHRcdGdlb21ldHJ5QXR0cmlidXRlcyA9IGdlb21ldHJ5LmF0dHJpYnV0ZXM7XG5cdFx0bGV0IGF0dHJpYnV0ZSxcblx0XHRcdGdlb21ldHJ5QXR0cmlidXRlO1xuXG5cdFx0Ly8gTG9vcCB0aHJvdWdoIGFsbCB0aGUgc2hhZGVyIGF0dHJpYnV0ZXMgYW5kIGFzc2lnbiAob3IgcmUtYXNzaWduKVxuXHRcdC8vIHR5cGVkIGFycmF5IGJ1ZmZlcnMgdG8gZWFjaCBvbmUuXG5cdFx0Zm9yICggY29uc3QgYXR0ciBpbiBhdHRyaWJ1dGVzICkge1xuXHRcdFx0aWYgKCBIQVNfT1dOLmNhbGwoIGF0dHJpYnV0ZXMsIGF0dHIgKSApIHtcblx0XHRcdFx0YXR0cmlidXRlID0gYXR0cmlidXRlc1sgYXR0ciBdO1xuXHRcdFx0XHRnZW9tZXRyeUF0dHJpYnV0ZSA9IGdlb21ldHJ5QXR0cmlidXRlc1sgYXR0ciBdO1xuXG5cdFx0XHRcdC8vIFVwZGF0ZSB0aGUgYXJyYXkgaWYgdGhpcyBhdHRyaWJ1dGUgZXhpc3RzIG9uIHRoZSBnZW9tZXRyeS5cblx0XHRcdFx0Ly9cblx0XHRcdFx0Ly8gVGhpcyBuZWVkcyB0byBiZSBkb25lIGJlY2F1c2UgdGhlIGF0dHJpYnV0ZSdzIHR5cGVkIGFycmF5IG1pZ2h0IGhhdmVcblx0XHRcdFx0Ly8gYmVlbiByZXNpemVkIGFuZCByZWluc3RhbnRpYXRlZCwgYW5kIG1pZ2h0IG5vdyBiZSBsb29raW5nIGF0IGFcblx0XHRcdFx0Ly8gZGlmZmVyZW50IEFycmF5QnVmZmVyLCBzbyByZWZlcmVuY2UgbmVlZHMgdXBkYXRpbmcuXG5cdFx0XHRcdGlmICggZ2VvbWV0cnlBdHRyaWJ1dGUgKSB7XG5cdFx0XHRcdFx0Z2VvbWV0cnlBdHRyaWJ1dGUuYXJyYXkgPSBhdHRyaWJ1dGUudHlwZWRBcnJheS5hcnJheTtcblx0XHRcdFx0fVxuXG5cdFx0XHRcdC8vIC8vIEFkZCB0aGUgYXR0cmlidXRlIHRvIHRoZSBnZW9tZXRyeSBpZiBpdCBkb2Vzbid0IGFscmVhZHkgZXhpc3QuXG5cdFx0XHRcdGVsc2Uge1xuXHRcdFx0XHRcdGdlb21ldHJ5LmFkZEF0dHJpYnV0ZSggYXR0ciwgYXR0cmlidXRlLmJ1ZmZlckF0dHJpYnV0ZSApO1xuXHRcdFx0XHR9XG5cblx0XHRcdFx0Ly8gTWFyayB0aGUgYXR0cmlidXRlIGFzIG5lZWRpbmcgYW4gdXBkYXRlIHRoZSBuZXh0IHRpbWUgYSBmcmFtZSBpcyByZW5kZXJlZC5cblx0XHRcdFx0YXR0cmlidXRlLmJ1ZmZlckF0dHJpYnV0ZS5uZWVkc1VwZGF0ZSA9IHRydWU7XG5cdFx0XHR9XG5cdFx0fVxuXG5cdFx0Ly8gTWFyayB0aGUgZHJhdyByYW5nZSBvbiB0aGUgZ2VvbWV0cnkuIFRoaXMgd2lsbCBlbnN1cmVcblx0XHQvLyBvbmx5IHRoZSB2YWx1ZXMgaW4gdGhlIGF0dHJpYnV0ZSBidWZmZXJzIHRoYXQgYXJlXG5cdFx0Ly8gYXNzb2NpYXRlZCB3aXRoIGEgcGFydGljbGUgd2lsbCBiZSB1c2VkIGluIFRIUkVFJ3Ncblx0XHQvLyByZW5kZXIgY3ljbGUuXG5cdFx0dGhpcy5nZW9tZXRyeS5zZXREcmF3UmFuZ2UoIDAsIHRoaXMucGFydGljbGVDb3VudCApO1xuXHR9XG5cblx0LyoqXG4gICAgICogQWRkcyBhbiBTUEUuRW1pdHRlciBpbnN0YW5jZSB0byB0aGlzIGdyb3VwLCBjcmVhdGluZyBwYXJ0aWNsZSB2YWx1ZXMgYW5kXG4gICAgICogYXNzaWduaW5nIHRoZW0gdG8gdGhpcyBncm91cCdzIHNoYWRlciBhdHRyaWJ1dGVzLlxuICAgICAqXG4gICAgICogQHBhcmFtIHtFbWl0dGVyfSBlbWl0dGVyIFRoZSBlbWl0dGVyIHRvIGFkZCB0byB0aGlzIGdyb3VwLlxuICAgICAqL1xuXHRhZGRFbWl0dGVyKCBlbWl0dGVyICkge1xuXHRcdC8vIEVuc3VyZSBhbiBhY3R1YWwgZW1pdHRlciBpbnN0YW5jZSBpcyBwYXNzZWQgaGVyZS5cblx0XHQvL1xuXHRcdC8vIERlY2lkZWQgbm90IHRvIHRocm93IGhlcmUsIGp1c3QgaW4gY2FzZSBhIHNjZW5lJ3Ncblx0XHQvLyByZW5kZXJpbmcgd291bGQgYmUgcGF1c2VkLiBMb2dnaW5nIGFuIGVycm9yIGluc3RlYWRcblx0XHQvLyBvZiBzdG9wcGluZyBleGVjdXRpb24gaWYgZXhjZXB0aW9ucyBhcmVuJ3QgY2F1Z2h0LlxuXHRcdGlmICggZW1pdHRlciBpbnN0YW5jZW9mIEVtaXR0ZXIgPT09IGZhbHNlICkge1xuXHRcdFx0Y29uc29sZS5lcnJvciggJ2BlbWl0dGVyYCBhcmd1bWVudCBtdXN0IGJlIGluc3RhbmNlIG9mIEVtaXR0ZXIuIFdhcyBwcm92aWRlZCB3aXRoOicsIGVtaXR0ZXIgKTtcblx0XHRcdHJldHVybjtcblx0XHR9XG5cblx0XHQvLyBJZiB0aGUgZW1pdHRlciBhbHJlYWR5IGV4aXN0cyBhcyBhIG1lbWJlciBvZiB0aGlzIGdyb3VwLCB0aGVuXG5cdFx0Ly8gc3RvcCBoZXJlLCB3ZSBkb24ndCB3YW50IHRvIGFkZCBpdCBhZ2Fpbi5cblx0XHRlbHNlIGlmICggdGhpcy5lbWl0dGVySURzLmluZGV4T2YoIGVtaXR0ZXIudXVpZCApID4gLTEgKSB7XG5cdFx0XHRjb25zb2xlLmVycm9yKCAnRW1pdHRlciBhbHJlYWR5IGV4aXN0cyBpbiB0aGlzIGdyb3VwLiBXaWxsIG5vdCBhZGQgYWdhaW4uJyApO1xuXHRcdFx0cmV0dXJuO1xuXHRcdH1cblxuXHRcdC8vIEFuZCBmaW5hbGx5LCBpZiB0aGUgZW1pdHRlciBpcyBhIG1lbWJlciBvZiBhbm90aGVyIGdyb3VwLFxuXHRcdC8vIGRvbid0IGFkZCBpdCB0byB0aGlzIGdyb3VwLlxuXHRcdGVsc2UgaWYgKCBlbWl0dGVyLmdyb3VwICE9PSBudWxsICkge1xuXHRcdFx0Y29uc29sZS5lcnJvciggJ0VtaXR0ZXIgYWxyZWFkeSBiZWxvbmdzIHRvIGFub3RoZXIgZ3JvdXAuIFdpbGwgbm90IGFkZCB0byByZXF1ZXN0ZWQgZ3JvdXAuJyApO1xuXHRcdFx0cmV0dXJuO1xuXHRcdH1cblxuXHRcdGNvbnN0IGF0dHJpYnV0ZXMgPSB0aGlzLmF0dHJpYnV0ZXMsXG5cdFx0XHRzdGFydCA9IHRoaXMucGFydGljbGVDb3VudCxcblx0XHRcdGVuZCA9IHN0YXJ0ICsgZW1pdHRlci5wYXJ0aWNsZUNvdW50O1xuXG5cdFx0Ly8gVXBkYXRlIHRoaXMgZ3JvdXAncyBwYXJ0aWNsZSBjb3VudC5cblx0XHR0aGlzLnBhcnRpY2xlQ291bnQgPSBlbmQ7XG5cblx0XHQvLyBFbWl0IGEgd2FybmluZyBpZiB0aGUgZW1pdHRlciBiZWluZyBhZGRlZCB3aWxsIGV4Y2VlZCB0aGUgYnVmZmVyIHNpemVzIHNwZWNpZmllZC5cblx0XHRpZiAoIHRoaXMubWF4UGFydGljbGVDb3VudCAhPT0gbnVsbCAmJiB0aGlzLnBhcnRpY2xlQ291bnQgPiB0aGlzLm1heFBhcnRpY2xlQ291bnQgKSB7XG5cdFx0XHRjb25zb2xlLndhcm4oICdTUEUuR3JvdXA6IG1heFBhcnRpY2xlQ291bnQgZXhjZWVkZWQuIFJlcXVlc3RpbmcnLCB0aGlzLnBhcnRpY2xlQ291bnQsICdwYXJ0aWNsZXMsIGNhbiBzdXBwb3J0IG9ubHknLCB0aGlzLm1heFBhcnRpY2xlQ291bnQgKTtcblx0XHR9XG5cblxuXHRcdC8vIFNldCB0aGUgYHBhcnRpY2xlc1BlclNlY29uZGAgdmFsdWUgKFBQUykgb24gdGhlIGVtaXR0ZXIuXG5cdFx0Ly8gSXQncyB1c2VkIHRvIGRldGVybWluZSBob3cgbWFueSBwYXJ0aWNsZXMgdG8gcmVsZWFzZVxuXHRcdC8vIG9uIGEgcGVyLWZyYW1lIGJhc2lzLlxuXHRcdGVtaXR0ZXIuX2NhbGN1bGF0ZVBQU1ZhbHVlKCBlbWl0dGVyLm1heEFnZS5fdmFsdWUgKyBlbWl0dGVyLm1heEFnZS5fc3ByZWFkICk7XG5cdFx0ZW1pdHRlci5fc2V0QnVmZmVyVXBkYXRlUmFuZ2VzKCB0aGlzLmF0dHJpYnV0ZUtleXMgKTtcblxuXHRcdC8vIFN0b3JlIHRoZSBvZmZzZXQgdmFsdWUgaW4gdGhlIFR5cGVkQXJyYXkgYXR0cmlidXRlcyBmb3IgdGhpcyBlbWl0dGVyLlxuXHRcdGVtaXR0ZXIuX3NldEF0dHJpYnV0ZU9mZnNldCggc3RhcnQgKTtcblxuXHRcdC8vIFNhdmUgYSByZWZlcmVuY2UgdG8gdGhpcyBncm91cCBvbiB0aGUgZW1pdHRlciBzbyBpdCBrbm93c1xuXHRcdC8vIHdoZXJlIGl0IGJlbG9uZ3MuXG5cdFx0ZW1pdHRlci5ncm91cCA9IHRoaXM7XG5cblx0XHQvLyBTdG9yZSByZWZlcmVuY2UgdG8gdGhlIGF0dHJpYnV0ZXMgb24gdGhlIGVtaXR0ZXIgZm9yXG5cdFx0Ly8gZWFzaWVyIGFjY2VzcyBkdXJpbmcgdGhlIGVtaXR0ZXIncyB0aWNrIGZ1bmN0aW9uLlxuXHRcdGVtaXR0ZXIuYXR0cmlidXRlcyA9IHRoaXMuYXR0cmlidXRlcztcblxuXG5cblx0XHQvLyBFbnN1cmUgdGhlIGF0dHJpYnV0ZXMgYW5kIHRoZWlyIEJ1ZmZlckF0dHJpYnV0ZXMgZXhpc3QsIGFuZCB0aGVpclxuXHRcdC8vIFR5cGVkQXJyYXlzIGFyZSBvZiB0aGUgY29ycmVjdCBzaXplLlxuXHRcdGZvciAoIGNvbnN0IGF0dHIgaW4gYXR0cmlidXRlcyApIHtcblx0XHRcdGlmICggSEFTX09XTi5jYWxsKCBhdHRyaWJ1dGVzLCBhdHRyICkgKSB7XG5cdFx0XHRcdC8vIFdoZW4gY3JlYXRpbmcgYSBidWZmZXIsIHBhc3MgdGhyb3VnaCB0aGUgbWF4UGFydGljbGUgY291bnRcblx0XHRcdFx0Ly8gaWYgb25lIGlzIHNwZWNpZmllZC5cblx0XHRcdFx0YXR0cmlidXRlc1sgYXR0ciBdLl9jcmVhdGVCdWZmZXJBdHRyaWJ1dGUoXG5cdFx0XHRcdFx0dGhpcy5tYXhQYXJ0aWNsZUNvdW50ICE9PSBudWxsID9cblx0XHRcdFx0XHRcdHRoaXMubWF4UGFydGljbGVDb3VudCA6XG5cdFx0XHRcdFx0XHR0aGlzLnBhcnRpY2xlQ291bnRcblx0XHRcdFx0KTtcblx0XHRcdH1cblx0XHR9XG5cblx0XHQvLyBMb29wIHRocm91Z2ggZWFjaCBwYXJ0aWNsZSB0aGlzIGVtaXR0ZXIgd2FudHMgdG8gaGF2ZSwgYW5kIGNyZWF0ZSB0aGUgYXR0cmlidXRlcyB2YWx1ZXMsXG5cdFx0Ly8gc3RvcmluZyB0aGVtIGluIHRoZSBUeXBlZEFycmF5cyB0aGF0IGVhY2ggYXR0cmlidXRlIGhvbGRzLlxuXHRcdGZvciAoIGxldCBpID0gc3RhcnQ7IGkgPCBlbmQ7ICsraSApIHtcblx0XHRcdGVtaXR0ZXIuX2Fzc2lnblBvc2l0aW9uVmFsdWUoIGkgKTtcblx0XHRcdGVtaXR0ZXIuX2Fzc2lnbkZvcmNlVmFsdWUoIGksICd2ZWxvY2l0eScgKTtcblx0XHRcdGVtaXR0ZXIuX2Fzc2lnbkZvcmNlVmFsdWUoIGksICdhY2NlbGVyYXRpb24nICk7XG5cdFx0XHRlbWl0dGVyLl9hc3NpZ25BYnNMaWZldGltZVZhbHVlKCBpLCAnb3BhY2l0eScgKTtcblx0XHRcdGVtaXR0ZXIuX2Fzc2lnbkFic0xpZmV0aW1lVmFsdWUoIGksICdzaXplJyApO1xuXHRcdFx0ZW1pdHRlci5fYXNzaWduQW5nbGVWYWx1ZSggaSApO1xuXHRcdFx0ZW1pdHRlci5fYXNzaWduUm90YXRpb25WYWx1ZSggaSApO1xuXHRcdFx0ZW1pdHRlci5fYXNzaWduUGFyYW1zVmFsdWUoIGkgKTtcblx0XHRcdGVtaXR0ZXIuX2Fzc2lnbkNvbG9yVmFsdWUoIGkgKTtcblx0XHR9XG5cblx0XHQvLyBVcGRhdGUgdGhlIGdlb21ldHJ5IGFuZCBtYWtlIHN1cmUgdGhlIGF0dHJpYnV0ZXMgYXJlIHJlZmVyZW5jaW5nXG5cdFx0Ly8gdGhlIHR5cGVkIGFycmF5cyBwcm9wZXJseS5cblx0XHR0aGlzLl9hcHBseUF0dHJpYnV0ZXNUb0dlb21ldHJ5KCk7XG5cblx0XHQvLyBTdG9yZSB0aGlzIGVtaXR0ZXIgaW4gdGhpcyBncm91cCdzIGVtaXR0ZXIncyBzdG9yZS5cblx0XHR0aGlzLmVtaXR0ZXJzLnB1c2goIGVtaXR0ZXIgKTtcblx0XHR0aGlzLmVtaXR0ZXJJRHMucHVzaCggZW1pdHRlci51dWlkICk7XG5cblx0XHQvLyBVcGRhdGUgY2VydGFpbiBmbGFncyB0byBlbmFibGUgc2hhZGVyIGNhbGN1bGF0aW9ucyBvbmx5IGlmIHRoZXkncmUgbmVjZXNzYXJ5LlxuXHRcdHRoaXMuX3VwZGF0ZURlZmluZXMoIGVtaXR0ZXIgKTtcblxuXHRcdC8vIFVwZGF0ZSB0aGUgbWF0ZXJpYWwgc2luY2UgZGVmaW5lcyBtaWdodCBoYXZlIGNoYW5nZWRcblx0XHR0aGlzLm1hdGVyaWFsLm5lZWRzVXBkYXRlID0gdHJ1ZTtcblx0XHR0aGlzLmdlb21ldHJ5Lm5lZWRzVXBkYXRlID0gdHJ1ZTtcblx0XHR0aGlzLl9hdHRyaWJ1dGVzTmVlZFJlZnJlc2ggPSB0cnVlO1xuXG5cdFx0Ly8gUmV0dXJuIHRoZSBncm91cCB0byBlbmFibGUgY2hhaW5pbmcuXG5cdFx0cmV0dXJuIHRoaXM7XG5cdH1cblxuXHQvKipcbiAgICAgKiBSZW1vdmVzIGFuIEVtaXR0ZXIgaW5zdGFuY2UgZnJvbSB0aGlzIGdyb3VwLiBXaGVuIGNhbGxlZCxcbiAgICAgKiBhbGwgcGFydGljbGUncyBiZWxvbmdpbmcgdG8gdGhlIGdpdmVuIGVtaXR0ZXIgd2lsbCBiZSBpbnN0YW50bHlcbiAgICAgKiByZW1vdmVkIGZyb20gdGhlIHNjZW5lLlxuICAgICAqXG4gICAgICogQHBhcmFtIHtFbWl0dGVyfSBlbWl0dGVyIFRoZSBlbWl0dGVyIHRvIGFkZCB0byB0aGlzIGdyb3VwLlxuICAgICAqL1xuXHRyZW1vdmVFbWl0dGVyKCBlbWl0dGVyICkge1xuXHRcdGNvbnN0IGVtaXR0ZXJJbmRleCA9IHRoaXMuZW1pdHRlcklEcy5pbmRleE9mKCBlbWl0dGVyLnV1aWQgKTtcblxuXHRcdC8vIEVuc3VyZSBhbiBhY3R1YWwgZW1pdHRlciBpbnN0YW5jZSBpcyBwYXNzZWQgaGVyZS5cblx0XHQvL1xuXHRcdC8vIERlY2lkZWQgbm90IHRvIHRocm93IGhlcmUsIGp1c3QgaW4gY2FzZSBhIHNjZW5lJ3Ncblx0XHQvLyByZW5kZXJpbmcgd291bGQgYmUgcGF1c2VkLiBMb2dnaW5nIGFuIGVycm9yIGluc3RlYWRcblx0XHQvLyBvZiBzdG9wcGluZyBleGVjdXRpb24gaWYgZXhjZXB0aW9ucyBhcmVuJ3QgY2F1Z2h0LlxuXHRcdGlmICggZW1pdHRlciBpbnN0YW5jZW9mIEVtaXR0ZXIgPT09IGZhbHNlICkge1xuXHRcdFx0Y29uc29sZS5lcnJvciggJ2BlbWl0dGVyYCBhcmd1bWVudCBtdXN0IGJlIGluc3RhbmNlIG9mIFNQRS5FbWl0dGVyLiBXYXMgcHJvdmlkZWQgd2l0aDonLCBlbWl0dGVyICk7XG5cdFx0XHRyZXR1cm47XG5cdFx0fVxuXG5cdFx0Ly8gSXNzdWUgYW4gZXJyb3IgaWYgdGhlIGVtaXR0ZXIgaXNuJ3QgYSBtZW1iZXIgb2YgdGhpcyBncm91cC5cblx0XHRlbHNlIGlmICggZW1pdHRlckluZGV4ID09PSAtMSApIHtcblx0XHRcdGNvbnNvbGUuZXJyb3IoICdFbWl0dGVyIGRvZXMgbm90IGV4aXN0IGluIHRoaXMgZ3JvdXAuIFdpbGwgbm90IHJlbW92ZS4nICk7XG5cdFx0XHRyZXR1cm47XG5cdFx0fVxuXG5cdFx0Ly8gS2lsbCBhbGwgcGFydGljbGVzIGJ5IG1hcmtpbmcgdGhlbSBhcyBkZWFkXG5cdFx0Ly8gYW5kIHRoZWlyIGFnZSBhcyAwLlxuXHRcdGNvbnN0IHN0YXJ0ID0gZW1pdHRlci5hdHRyaWJ1dGVPZmZzZXQsXG5cdFx0XHRlbmQgPSBzdGFydCArIGVtaXR0ZXIucGFydGljbGVDb3VudCxcblx0XHRcdHBhcmFtcyA9IHRoaXMuYXR0cmlidXRlcy5wYXJhbXMudHlwZWRBcnJheTtcblxuXHRcdC8vIFNldCBhbGl2ZSBhbmQgYWdlIHRvIHplcm8uXG5cdFx0Zm9yICggbGV0IGkgPSBzdGFydDsgaSA8IGVuZDsgKytpICkge1xuXHRcdFx0cGFyYW1zLmFycmF5WyBpICogNCBdID0gMC4wO1xuXHRcdFx0cGFyYW1zLmFycmF5WyBpICogNCArIDEgXSA9IDAuMDtcblx0XHR9XG5cblx0XHQvLyBSZW1vdmUgdGhlIGVtaXR0ZXIgZnJvbSB0aGlzIGdyb3VwJ3MgXCJzdG9yZVwiLlxuXHRcdHRoaXMuZW1pdHRlcnMuc3BsaWNlKCBlbWl0dGVySW5kZXgsIDEgKTtcblx0XHR0aGlzLmVtaXR0ZXJJRHMuc3BsaWNlKCBlbWl0dGVySW5kZXgsIDEgKTtcblxuXHRcdC8vIFJlbW92ZSB0aGlzIGVtaXR0ZXIncyBhdHRyaWJ1dGUgdmFsdWVzIGZyb20gYWxsIHNoYWRlciBhdHRyaWJ1dGVzLlxuXHRcdC8vIFRoZSBgLnNwbGljZSgpYCBjYWxsIGhlcmUgYWxzbyBtYXJrcyBlYWNoIGF0dHJpYnV0ZSdzIGJ1ZmZlclxuXHRcdC8vIGFzIG5lZWRpbmcgdG8gdXBkYXRlIGl0J3MgZW50aXJlIGNvbnRlbnRzLlxuXHRcdGZvciAoIGNvbnN0IGF0dHIgaW4gdGhpcy5hdHRyaWJ1dGVzICkge1xuXHRcdFx0aWYgKCBIQVNfT1dOLmNhbGwoIHRoaXMuYXR0cmlidXRlcywgYXR0ciApICkge1xuXHRcdFx0XHR0aGlzLmF0dHJpYnV0ZXNbIGF0dHIgXS5zcGxpY2UoIHN0YXJ0LCBlbmQgKTtcblx0XHRcdH1cblx0XHR9XG5cblx0XHQvLyBFbnN1cmUgdGhpcyBncm91cCdzIHBhcnRpY2xlIGNvdW50IGlzIGNvcnJlY3QuXG5cdFx0dGhpcy5wYXJ0aWNsZUNvdW50IC09IGVtaXR0ZXIucGFydGljbGVDb3VudDtcblxuXHRcdC8vIENhbGwgdGhlIGVtaXR0ZXIncyByZW1vdmUgbWV0aG9kLlxuXHRcdGVtaXR0ZXIuX29uUmVtb3ZlKCk7XG5cblx0XHQvLyBTZXQgYSBmbGFnIHRvIGluZGljYXRlIHRoYXQgdGhlIGF0dHJpYnV0ZSBidWZmZXJzIHNob3VsZFxuXHRcdC8vIGJlIHVwZGF0ZWQgaW4gdGhlaXIgZW50aXJldHkgb24gdGhlIG5leHQgZnJhbWUuXG5cdFx0dGhpcy5fYXR0cmlidXRlc05lZWRSZWZyZXNoID0gdHJ1ZTtcblx0fVxuXG5cblx0LyoqXG4gICAgICogRmV0Y2ggYSBzaW5nbGUgZW1pdHRlciBpbnN0YW5jZSBmcm9tIHRoZSBwb29sLlxuICAgICAqIElmIHRoZXJlIGFyZSBubyBvYmplY3RzIGluIHRoZSBwb29sLCBhIG5ldyBlbWl0dGVyIHdpbGwgYmVcbiAgICAgKiBjcmVhdGVkIGlmIHNwZWNpZmllZC5cbiAgICAgKlxuICAgICAqIEByZXR1cm4ge0VtaXR0ZXJ8bnVsbH1cbiAgICAgKi9cblx0Z2V0RnJvbVBvb2woKSB7XG5cdFx0Y29uc3QgcG9vbCA9IHRoaXMuX3Bvb2wsXG5cdFx0XHRjcmVhdGVOZXcgPSB0aGlzLl9jcmVhdGVOZXdXaGVuUG9vbEVtcHR5O1xuXG5cdFx0aWYgKCBwb29sLmxlbmd0aCApIHtcblx0XHRcdHJldHVybiBwb29sLnBvcCgpO1xuXHRcdH1cblx0XHRlbHNlIGlmICggY3JlYXRlTmV3ICkge1xuXHRcdFx0Y29uc3QgZW1pdHRlciA9IG5ldyBFbWl0dGVyKCB0aGlzLl9wb29sQ3JlYXRpb25TZXR0aW5ncyApO1xuXG5cdFx0XHR0aGlzLmFkZEVtaXR0ZXIoIGVtaXR0ZXIgKTtcblxuXHRcdFx0cmV0dXJuIGVtaXR0ZXI7XG5cdFx0fVxuXG5cdFx0cmV0dXJuIG51bGw7XG5cdH1cblxuXG5cdC8qKlxuICAgICAqIFJlbGVhc2UgYW4gZW1pdHRlciBpbnRvIHRoZSBwb29sLlxuICAgICAqXG4gICAgICogQHBhcmFtICB7U2hhZGVyUGFydGljbGVFbWl0dGVyfSBlbWl0dGVyXG4gICAgICogQHJldHVybiB7R3JvdXB9IFRoaXMgZ3JvdXAgaW5zdGFuY2UuXG4gICAgICovXG5cdHJlbGVhc2VJbnRvUG9vbCggZW1pdHRlciApIHtcblx0XHRpZiAoIGVtaXR0ZXIgaW5zdGFuY2VvZiBFbWl0dGVyID09PSBmYWxzZSApIHtcblx0XHRcdGNvbnNvbGUuZXJyb3IoICdBcmd1bWVudCBpcyBub3QgaW5zdGFuY2VvZiBFbWl0dGVyOicsIGVtaXR0ZXIgKTtcblx0XHRcdHJldHVybjtcblx0XHR9XG5cblx0XHRlbWl0dGVyLnJlc2V0KCk7XG5cdFx0dGhpcy5fcG9vbC51bnNoaWZ0KCBlbWl0dGVyICk7XG5cblx0XHRyZXR1cm4gdGhpcztcblx0fVxuXG5cblx0LyoqXG4gICAgICogR2V0IHRoZSBwb29sIGFycmF5XG4gICAgICpcbiAgICAgKiBAcmV0dXJuIHtBcnJheX1cbiAgICAgKi9cblx0Z2V0UG9vbCgpIHtcblx0XHRyZXR1cm4gdGhpcy5fcG9vbDtcblx0fVxuXG5cblx0LyoqXG4gICAgICogQWRkIGEgcG9vbCBvZiBlbWl0dGVycyB0byB0aGlzIHBhcnRpY2xlIGdyb3VwXG4gICAgICpcbiAgICAgKiBAcGFyYW0ge051bWJlcn0gbnVtRW1pdHRlcnMgICAgICBUaGUgbnVtYmVyIG9mIGVtaXR0ZXJzIHRvIGFkZCB0byB0aGUgcG9vbC5cbiAgICAgKiBAcGFyYW0ge0VtaXR0ZXJPcHRpb25zfEFycmF5fSBlbWl0dGVyT3B0aW9ucyAgQW4gb2JqZWN0LCBvciBhcnJheSBvZiBvYmplY3RzLCBkZXNjcmliaW5nIHRoZSBvcHRpb25zIHRvIHBhc3MgdG8gZWFjaCBlbWl0dGVyLlxuICAgICAqIEBwYXJhbSB7Qm9vbGVhbn0gY3JlYXRlTmV3ICAgICAgIFNob3VsZCBhIG5ldyBlbWl0dGVyIGJlIGNyZWF0ZWQgaWYgdGhlIHBvb2wgcnVucyBvdXQ/XG4gICAgICogQHJldHVybiB7R3JvdXB9IFRoaXMgZ3JvdXAgaW5zdGFuY2UuXG4gICAgICovXG5cdGFkZFBvb2woIG51bUVtaXR0ZXJzLCBlbWl0dGVyT3B0aW9ucywgY3JlYXRlTmV3ICkge1xuXHRcdC8vIFNhdmUgcmVsZXZhbnQgc2V0dGluZ3MgYW5kIGZsYWdzLlxuXHRcdHRoaXMuX3Bvb2xDcmVhdGlvblNldHRpbmdzID0gZW1pdHRlck9wdGlvbnM7XG5cdFx0dGhpcy5fY3JlYXRlTmV3V2hlblBvb2xFbXB0eSA9ICEhY3JlYXRlTmV3O1xuXG5cdFx0Ly8gQ3JlYXRlIHRoZSBlbWl0dGVycywgYWRkIHRoZW0gdG8gdGhpcyBncm91cCBhbmQgdGhlIHBvb2wuXG5cdFx0Zm9yICggbGV0IGkgPSAwOyBpIDwgbnVtRW1pdHRlcnM7ICsraSApIHtcblx0XHRcdGxldCBhcmdzO1xuXG5cdFx0XHRpZiAoIEFycmF5LmlzQXJyYXkoIGVtaXR0ZXJPcHRpb25zICkgKSB7XG5cdFx0XHRcdGFyZ3MgPSBlbWl0dGVyT3B0aW9uc1sgaSBdO1xuXHRcdFx0fVxuXHRcdFx0ZWxzZSB7XG5cdFx0XHRcdGFyZ3MgPSBlbWl0dGVyT3B0aW9ucztcblx0XHRcdH1cblxuXHRcdFx0Y29uc3QgZW1pdHRlciA9IG5ldyBFbWl0dGVyKCBhcmdzICk7XG5cblx0XHRcdHRoaXMuYWRkRW1pdHRlciggZW1pdHRlciApO1xuXHRcdFx0dGhpcy5yZWxlYXNlSW50b1Bvb2woIGVtaXR0ZXIgKTtcblx0XHR9XG5cblx0XHRyZXR1cm4gdGhpcztcblx0fVxuXG5cblxuXHRfdHJpZ2dlclNpbmdsZUVtaXR0ZXIoIHBvcyApIHtcblx0XHRjb25zdCBlbWl0dGVyID0gdGhpcy5nZXRGcm9tUG9vbCgpO1xuXG5cdFx0aWYgKCBlbWl0dGVyID09PSBudWxsICkge1xuXHRcdFx0Y29uc29sZS5sb2coICdHcm91cCBwb29sIHJhbiBvdXQuJyApO1xuXHRcdFx0cmV0dXJuO1xuXHRcdH1cblxuXHRcdC8vIFRPRE86XG5cdFx0Ly8gLSBNYWtlIHN1cmUgYnVmZmVycyBhcmUgdXBkYXRlIHdpdGggdGhlIG5ldyBwb3NpdGlvbi5cblx0XHRpZiAoIHBvcyBpbnN0YW5jZW9mIFRIUkVFLlZlY3RvcjMgKSB7XG5cdFx0XHRlbWl0dGVyLnBvc2l0aW9uLnZhbHVlLmNvcHkoIHBvcyApO1xuXG5cdFx0XHQvLyBUcmlnZ2VyIHRoZSBzZXR0ZXIgZm9yIHRoaXMgcHJvcGVydHkgdG8gZm9yY2UgYW5cblx0XHRcdC8vIHVwZGF0ZSB0byB0aGUgZW1pdHRlcidzIHBvc2l0aW9uIGF0dHJpYnV0ZS5cblx0XHRcdGVtaXR0ZXIucG9zaXRpb24udmFsdWUgPSBlbWl0dGVyLnBvc2l0aW9uLnZhbHVlOyAvLyBlc2xpbnQtZGlzYWJsZS1saW5lXG5cdFx0fVxuXG5cdFx0ZW1pdHRlci5lbmFibGUoKTtcblxuXHRcdHNldFRpbWVvdXQoICgpID0+IHtcblx0XHRcdGVtaXR0ZXIuZGlzYWJsZSgpO1xuXHRcdFx0dGhpcy5yZWxlYXNlSW50b1Bvb2woIGVtaXR0ZXIgKTtcblx0XHR9LCAoIE1hdGgubWF4KCBlbWl0dGVyLmR1cmF0aW9uLCAoIGVtaXR0ZXIubWF4QWdlLnZhbHVlICsgZW1pdHRlci5tYXhBZ2Uuc3ByZWFkICkgKSApICogMTAwMCApO1xuXG5cdFx0cmV0dXJuIHRoaXM7XG5cdH1cblxuXG5cdC8qKlxuICAgICAqIFNldCBhIGdpdmVuIG51bWJlciBvZiBlbWl0dGVycyBhcyBhbGl2ZSwgd2l0aCBhbiBvcHRpb25hbCBwb3NpdGlvblxuICAgICAqIHZlY3RvcjMgdG8gbW92ZSB0aGVtIHRvLlxuICAgICAqXG4gICAgICogQHBhcmFtICB7TnVtYmVyfSBudW1FbWl0dGVycyBUaGUgbnVtYmVyIG9mIGVtaXR0ZXJzIHRvIGFjdGl2YXRlXG4gICAgICogQHBhcmFtICB7T2JqZWN0fSBbcG9zaXRpb249dW5kZWZpbmVkXSBBIFRIUkVFLlZlY3RvcjMgaW5zdGFuY2UgZGVzY3JpYmluZyB0aGUgcG9zaXRpb24gdG8gYWN0aXZhdGUgdGhlIGVtaXR0ZXIocykgYXQuXG4gICAgICogQHJldHVybiB7R3JvdXB9IFRoaXMgZ3JvdXAgaW5zdGFuY2UuXG4gICAgICovXG5cdHRyaWdnZXJQb29sRW1pdHRlciggbnVtRW1pdHRlcnMsIHBvc2l0aW9uICkge1xuXHRcdGlmICggdHlwZW9mIG51bUVtaXR0ZXJzID09PSAnbnVtYmVyJyAmJiBudW1FbWl0dGVycyA+IDEgKSB7XG5cdFx0XHRmb3IgKCBsZXQgaSA9IDA7IGkgPCBudW1FbWl0dGVyczsgKytpICkge1xuXHRcdFx0XHR0aGlzLl90cmlnZ2VyU2luZ2xlRW1pdHRlciggcG9zaXRpb24gKTtcblx0XHRcdH1cblx0XHR9XG5cdFx0ZWxzZSB7XG5cdFx0XHR0aGlzLl90cmlnZ2VyU2luZ2xlRW1pdHRlciggcG9zaXRpb24gKTtcblx0XHR9XG5cblx0XHRyZXR1cm4gdGhpcztcblx0fVxuXG5cblxuXHRfdXBkYXRlVW5pZm9ybXMoIGR0ICkge1xuXHRcdHRoaXMudW5pZm9ybXMucnVuVGltZS52YWx1ZSArPSBkdDtcblx0XHR0aGlzLnVuaWZvcm1zLmRlbHRhVGltZS52YWx1ZSA9IGR0O1xuXHR9XG5cblx0X3Jlc2V0QnVmZmVyUmFuZ2VzKCkge1xuXHRcdGNvbnN0IGtleXMgPSB0aGlzLmF0dHJpYnV0ZUtleXMsXG5cdFx0XHRhdHRycyA9IHRoaXMuYXR0cmlidXRlcztcblx0XHRsZXQgaSA9IHRoaXMuYXR0cmlidXRlQ291bnQgLSAxO1xuXG5cdFx0Zm9yICggaTsgaSA+PSAwOyAtLWkgKSB7XG5cdFx0XHRhdHRyc1sga2V5c1sgaSBdIF0ucmVzZXRVcGRhdGVSYW5nZSgpO1xuXHRcdH1cblx0fVxuXG5cblx0X3VwZGF0ZUJ1ZmZlcnMoIGVtaXR0ZXIgKSB7XG5cdFx0Y29uc3Qga2V5cyA9IHRoaXMuYXR0cmlidXRlS2V5cyxcblx0XHRcdGF0dHJzID0gdGhpcy5hdHRyaWJ1dGVzLFxuXHRcdFx0ZW1pdHRlclJhbmdlcyA9IGVtaXR0ZXIuYnVmZmVyVXBkYXRlUmFuZ2VzO1xuXHRcdGxldCBrZXksXG5cdFx0XHRlbWl0dGVyQXR0cixcblx0XHRcdGF0dHI7XG5cblx0XHRmb3IgKCBsZXQgaSA9IHRoaXMuYXR0cmlidXRlQ291bnQgLSAxOyBpID49IDA7IC0taSApIHtcblx0XHRcdGtleSA9IGtleXNbIGkgXTtcblx0XHRcdGVtaXR0ZXJBdHRyID0gZW1pdHRlclJhbmdlc1sga2V5IF07XG5cdFx0XHRhdHRyID0gYXR0cnNbIGtleSBdO1xuXHRcdFx0YXR0ci5zZXRVcGRhdGVSYW5nZSggZW1pdHRlckF0dHIubWluLCBlbWl0dGVyQXR0ci5tYXggKTtcblx0XHRcdGF0dHIuZmxhZ1VwZGF0ZSgpO1xuXHRcdH1cblx0fVxuXG5cblx0LyoqXG4gICAgICogU2ltdWxhdGUgYWxsIHRoZSBlbWl0dGVyJ3MgYmVsb25naW5nIHRvIHRoaXMgZ3JvdXAsIHVwZGF0aW5nXG4gICAgICogYXR0cmlidXRlIHZhbHVlcyBhbG9uZyB0aGUgd2F5LlxuICAgICAqIEBwYXJhbSAge051bWJlcn0gW2R0PUdyb3VwJ3MgYGZpeGVkVGltZVN0ZXBgIHZhbHVlXSBUaGUgbnVtYmVyIG9mIHNlY29uZHMgdG8gc2ltdWxhdGUgdGhlIGdyb3VwJ3MgZW1pdHRlcnMgZm9yIChkZWx0YVRpbWUpXG4gICAgICovXG5cdHRpY2soIGR0ICkge1xuXHRcdGNvbnN0IGVtaXR0ZXJzID0gdGhpcy5lbWl0dGVycyxcblx0XHRcdG51bUVtaXR0ZXJzID0gZW1pdHRlcnMubGVuZ3RoLFxuXHRcdFx0ZGVsdGFUaW1lID0gZHQgfHwgdGhpcy5maXhlZFRpbWVTdGVwLFxuXHRcdFx0a2V5cyA9IHRoaXMuYXR0cmlidXRlS2V5cyxcblx0XHRcdGF0dHJzID0gdGhpcy5hdHRyaWJ1dGVzO1xuXG5cdFx0Ly8gVXBkYXRlIHVuaWZvcm0gdmFsdWVzLlxuXHRcdHRoaXMuX3VwZGF0ZVVuaWZvcm1zKCBkZWx0YVRpbWUgKTtcblxuXHRcdC8vIFJlc2V0IGJ1ZmZlciB1cGRhdGUgcmFuZ2VzIG9uIHRoZSBzaGFkZXIgYXR0cmlidXRlcy5cblx0XHR0aGlzLl9yZXNldEJ1ZmZlclJhbmdlcygpO1xuXG5cblx0XHQvLyBJZiBub3RoaW5nIG5lZWRzIHVwZGF0aW5nLCB0aGVuIHN0b3AgaGVyZS5cblx0XHRpZiAoXG5cdFx0XHRudW1FbWl0dGVycyA9PT0gMCAmJlxuICAgICAgICAgICAgdGhpcy5fYXR0cmlidXRlc05lZWRSZWZyZXNoID09PSBmYWxzZSAmJlxuICAgICAgICAgICAgdGhpcy5fYXR0cmlidXRlc05lZWREeW5hbWljUmVzZXQgPT09IGZhbHNlXG5cdFx0KSB7XG5cdFx0XHRyZXR1cm47XG5cdFx0fVxuXG5cdFx0Ly8gTG9vcCB0aHJvdWdoIGVhY2ggZW1pdHRlciBpbiB0aGlzIGdyb3VwIGFuZFxuXHRcdC8vIHNpbXVsYXRlIGl0LCB0aGVuIHVwZGF0ZSB0aGUgc2hhZGVyIGF0dHJpYnV0ZVxuXHRcdC8vIGJ1ZmZlcnMuXG5cdFx0Zm9yICggbGV0IGkgPSAwLCBlbWl0dGVyOyBpIDwgbnVtRW1pdHRlcnM7ICsraSApIHtcblx0XHRcdGVtaXR0ZXIgPSBlbWl0dGVyc1sgaSBdO1xuXHRcdFx0ZW1pdHRlci50aWNrKCBkZWx0YVRpbWUgKTtcblx0XHRcdHRoaXMuX3VwZGF0ZUJ1ZmZlcnMoIGVtaXR0ZXIgKTtcblx0XHR9XG5cblx0XHQvLyBJZiB0aGUgc2hhZGVyIGF0dHJpYnV0ZXMgaGF2ZSBiZWVuIHJlZnJlc2hlZCxcblx0XHQvLyB0aGVuIHRoZSBkeW5hbWljIHByb3BlcnRpZXMgb2YgZWFjaCBidWZmZXJcblx0XHQvLyBhdHRyaWJ1dGUgd2lsbCBuZWVkIHRvIGJlIHJlc2V0IGJhY2sgdG9cblx0XHQvLyB3aGF0IHRoZXkgc2hvdWxkIGJlLlxuXHRcdGlmICggdGhpcy5fYXR0cmlidXRlc05lZWREeW5hbWljUmVzZXQgPT09IHRydWUgKSB7XG5cdFx0XHRmb3IgKCBsZXQgaSA9IHRoaXMuYXR0cmlidXRlQ291bnQgLSAxOyBpID49IDA7IC0taSApIHtcblx0XHRcdFx0YXR0cnNbIGtleXNbIGkgXSBdLnJlc2V0RHluYW1pYygpO1xuXHRcdFx0fVxuXG5cdFx0XHR0aGlzLl9hdHRyaWJ1dGVzTmVlZER5bmFtaWNSZXNldCA9IGZhbHNlO1xuXHRcdH1cblxuXHRcdC8vIElmIHRoaXMgZ3JvdXAncyBzaGFkZXIgYXR0cmlidXRlcyBuZWVkIGEgZnVsbCByZWZyZXNoXG5cdFx0Ly8gdGhlbiBtYXJrIGVhY2ggYXR0cmlidXRlJ3MgYnVmZmVyIGF0dHJpYnV0ZSBhc1xuXHRcdC8vIG5lZWRpbmcgc28uXG5cdFx0aWYgKCB0aGlzLl9hdHRyaWJ1dGVzTmVlZFJlZnJlc2ggPT09IHRydWUgKSB7XG5cdFx0XHRmb3IgKCBsZXQgaSA9IHRoaXMuYXR0cmlidXRlQ291bnQgLSAxOyBpID49IDA7IC0taSApIHtcblx0XHRcdFx0YXR0cnNbIGtleXNbIGkgXSBdLmZvcmNlVXBkYXRlQWxsKCk7XG5cdFx0XHR9XG5cblx0XHRcdHRoaXMuX2F0dHJpYnV0ZXNOZWVkUmVmcmVzaCA9IGZhbHNlO1xuXHRcdFx0dGhpcy5fYXR0cmlidXRlc05lZWREeW5hbWljUmVzZXQgPSB0cnVlO1xuXHRcdH1cblx0fVxuXG5cblx0LyoqXG4gICAgICogRGlwb3NlIHRoZSBnZW9tZXRyeSBhbmQgbWF0ZXJpYWwgZm9yIHRoZSBncm91cC5cbiAgICAgKlxuICAgICAqIEByZXR1cm4ge0dyb3VwfSBHcm91cCBpbnN0YW5jZS5cbiAgICAgKi9cblx0ZGlzcG9zZSgpIHtcblx0XHR0aGlzLmdlb21ldHJ5LmRpc3Bvc2UoKTtcblx0XHR0aGlzLm1hdGVyaWFsLmRpc3Bvc2UoKTtcblx0XHRyZXR1cm4gdGhpcztcblx0fVxufVxuIl0sInNvdXJjZVJvb3QiOiIifQ== \ No newline at end of file diff --git a/docs/api/Emitter.html b/docs/api/Emitter.html deleted file mode 100644 index 619633f..0000000 --- a/docs/api/Emitter.html +++ /dev/null @@ -1,213 +0,0 @@ - - - - - JSDoc: Class: Emitter - - - - - - - - - - -
- -

Class: Emitter

- - - - - - -
- -
- -

- Emitter -

- - -
- -
-
- - - - - -

new Emitter(options)

- - - - - -
- The SPE.Emitter class. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
options - - -EmitterOptions - - - - A map of options to configure the emitter.
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - -
- -
- - - - -
- - - -
- -
- Documentation generated by JSDoc 3.3.3 on Sun Oct 18 2015 22:34:14 GMT+0100 (BST) -
- - - - - \ No newline at end of file diff --git a/docs/api/SPE.Emitter.html b/docs/api/SPE.Emitter.html deleted file mode 100644 index b79414a..0000000 --- a/docs/api/SPE.Emitter.html +++ /dev/null @@ -1,848 +0,0 @@ - - - - - JSDoc: Class: Emitter - - - - - - - - - - -
- -

Class: Emitter

- - - - - - -
- -
- -

- SPE. - - Emitter -

- - -
- -
-
- - - - - -

new Emitter(options)

- - - - - -
- The SPE.Emitter class. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
options - - -EmitterOptions - - - - A map of options to configure the emitter.
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - -

Methods

- - - - - - -

disable() → {Emitter}

- - - - - -
- Disables th emitter, but does not instantly remove it's -particles fromt the scene. When called, the emitter will be -'switched off' and just stop emitting. Any particle's alive will -be allowed to finish their lifecycle. -
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - -
Returns:
- - -
- This emitter instance. -
- - - -
-
- Type -
-
- -Emitter - - -
-
- - - - - - - - - - -

enable() → {Emitter}

- - - - - -
- Enables the emitter. If not already enabled, the emitter -will start emitting particles. -
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - -
Returns:
- - -
- This emitter instance. -
- - - -
-
- Type -
-
- -Emitter - - -
-
- - - - - - - - - - -

remove() → {Emitter}

- - - - - -
- Remove this emitter from it's parent group (if it has been added to one). -Delgates to SPE.group.prototype.removeEmitter(). - -When called, all particle's belonging to this emitter will be instantly -removed from the scene. -
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - -
See:
-
-
    -
  • SPE.Group.prototype.removeEmitter
  • -
-
- - - -
- - - - - - - - - - - - - -
Returns:
- - -
- This emitter instance. -
- - - -
-
- Type -
-
- -Emitter - - -
-
- - - - - - - - - - -

reset(forceopt) → {Emitter}

- - - - - -
- Resets all the emitter's particles to their start positions -and marks the particles as dead if the `force` argument is -true. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDescription
force - - -Boolean - - - - - - <optional>
- - - - - -
If true, all particles will be marked as dead instantly.
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - -
Returns:
- - -
- This emitter instance. -
- - - -
-
- Type -
-
- -Emitter - - -
-
- - - - - - - - - - -

tick(dt)

- - - - - -
- Simulates one frame's worth of particles, updating particles -that are already alive, and marking ones that are currently dead -but should be alive as alive. - -If the emitter is marked as static, then this function will do nothing. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
dt - - -Number - - - - The number of seconds to simulate (deltaTime)
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - -
- - - -
- -
- Documentation generated by JSDoc 3.3.3 on Sat Nov 14 2015 15:30:50 GMT+0000 (GMT) -
- - - - - \ No newline at end of file diff --git a/docs/api/SPE.Group.html b/docs/api/SPE.Group.html deleted file mode 100644 index 5351bc0..0000000 --- a/docs/api/SPE.Group.html +++ /dev/null @@ -1,1497 +0,0 @@ - - - - - JSDoc: Class: Group - - - - - - - - - - -
- -

Class: Group

- - - - - - -
- -
- -

- SPE. - - Group -

- - -
- -
-
- - - - - -

new Group(options)

- - - - - -
- The SPE.Group class. Creates a new group, containing a material, geometry, and mesh. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
options - - -GroupOptions - - - - A map of options to configure the group instance.
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - -

Methods

- - - - - - -

addEmitter(emitter)

- - - - - -
- Adds an SPE.Emitter instance to this group, creating particle values and -assigning them to this group's shader attributes. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
emitter - - -Emitter - - - - The emitter to add to this group.
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - -

addPool(numEmitters, emitterOptions, createNew) → {Group}

- - - - - -
- Add a pool of emitters to this particle group -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
numEmitters - - -Number - - - - The number of emitters to add to the pool.
emitterOptions - - -EmitterOptions -| - -Array - - - - An object, or array of objects, describing the options to pass to each emitter.
createNew - - -Boolean - - - - Should a new emitter be created if the pool runs out?
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - -
Returns:
- - -
- This group instance. -
- - - -
-
- Type -
-
- -Group - - -
-
- - - - - - - - - - -

dispose() → {Group}

- - - - - -
- Dipose the geometry and material for the group. -
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - -
Returns:
- - -
- Group instance. -
- - - -
-
- Type -
-
- -Group - - -
-
- - - - - - - - - - -

getFromPool() → {Emitter|null}

- - - - - -
- Fetch a single emitter instance from the pool. -If there are no objects in the pool, a new emitter will be -created if specified. -
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - -
Returns:
- - - - -
-
- Type -
-
- -Emitter -| - -null - - -
-
- - - - - - - - - - -

getPool() → {Array}

- - - - - -
- Get the pool array -
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - -
Returns:
- - - - -
-
- Type -
-
- -Array - - -
-
- - - - - - - - - - -

releaseIntoPool(emitter) → {Group}

- - - - - -
- Release an emitter into the pool. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
emitter - - -ShaderParticleEmitter - - - -
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - -
Returns:
- - -
- This group instance. -
- - - -
-
- Type -
-
- -Group - - -
-
- - - - - - - - - - -

removeEmitter(emitter)

- - - - - -
- Removes an SPE.Emitter instance from this group. When called, -all particle's belonging to the given emitter will be instantly -removed from the scene. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
emitter - - -Emitter - - - - The emitter to add to this group.
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - -

tick(dtopt)

- - - - - -
- Simulate all the emitter's belonging to this group, updating -attribute values along the way. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDefaultDescription
dt - - -Number - - - - - - <optional>
- - - - - -
- - Group's `fixedTimeStep` value - - The number of seconds to simulate the group's emitters for (deltaTime)
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - -

triggerPoolEmitter(numEmitters, positionopt) → {Group}

- - - - - -
- Set a given number of emitters as alive, with an optional position -vector3 to move them to. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDescription
numEmitters - - -Number - - - - - - - - - - The number of emitters to activate
position - - -Object - - - - - - <optional>
- - - - - -
A THREE.Vector3 instance describing the position to activate the emitter(s) at.
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - -
Returns:
- - -
- This group instance. -
- - - -
-
- Type -
-
- -Group - - -
-
- - - - - - - - - - - -
- -
- - - - -
- - - -
- -
- Documentation generated by JSDoc 3.3.3 on Sat Nov 14 2015 15:30:50 GMT+0000 (GMT) -
- - - - - \ No newline at end of file diff --git a/docs/api/SPE.ShaderAttribute.html b/docs/api/SPE.ShaderAttribute.html deleted file mode 100644 index 593b636..0000000 --- a/docs/api/SPE.ShaderAttribute.html +++ /dev/null @@ -1,1416 +0,0 @@ - - - - - JSDoc: Class: ShaderAttribute - - - - - - - - - - -
- -

Class: ShaderAttribute

- - - - - - -
- -
- -

- SPE. - - ShaderAttribute -

- - -
- -
-
- - - - - -

new ShaderAttribute(type, dynamicBufferopt, arrayTypeopt)

- - - - - -
- A helper to handle creating and updating a THREE.BufferAttribute instance. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDescription
type - - -String - - - - - - - - - - The buffer attribute type. See SPE.ShaderAttribute.typeSizeMap for valid values.
dynamicBuffer - - -Boolean - - - - - - <optional>
- - - - - -
Whether this buffer attribute should be marked as dynamic or not.
arrayType - - -function - - - - - - <optional>
- - - - - -
A reference to a TypedArray constructor. Defaults to Float32Array if none provided.
- - - - - - -
- - - - - - - - - - - - - - - - - - -
Author:
-
-
    -
  • Luke Moody
  • -
-
- - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - -

Members

- - - -

(static) typeSizeMap :Number

- - - - -
- A map of uniform types to their component size. -
- - - -
Type:
-
    -
  • - -Number - - -
  • -
- - - - - -
Properties:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
f - - -Number - - - - Float
v2 - - -Number - - - - Vec2
v3 - - -Number - - - - Vec3
v4 - - -Number - - - - Vec4
c - - -Number - - - - Color
m3 - - -Number - - - - Mat3
m4 - - -Number - - - - Mat4
- - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - -

Methods

- - - - - - -

_createBufferAttribute(size)

- - - - - -
- Creates a THREE.BufferAttribute instance if one doesn't exist already. - -Ensures a typed array is present by calling _ensureTypedArray() first. - -If a buffer attribute exists already, then it will be marked as needing an update. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
size - - -Number - - - - The size of the typed array to create if one doesn't exist, or resize existing array to.
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - -

_ensureTypedArray(size)

- - - - - -
- Make sure this attribute has a typed array associated with it. - -If it does, then it will ensure the typed array is of the correct size. - -If not, a new SPE.TypedArrayHelper instance will be created. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
size - - -Number - - - - The size of the typed array to create or update to.
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - -

flagUpdate()

- - - - - -
- Calculate the number of indices that this attribute should mark as needing -updating. Also marks the attribute as needing an update. -
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - -

getLength() → {Number}

- - - - - -
- Returns the length of the typed array associated with this attribute. -
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - -
Returns:
- - -
- The length of the typed array. Will be 0 if no typed array has been created yet. -
- - - -
-
- Type -
-
- -Number - - -
-
- - - - - - - - - - -

resetUpdateRange()

- - - - - -
- Reset the index update counts for this attribute -
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - -

setUpdateRange(min, max)

- - - - - -
- Calculate the minimum and maximum update range for this buffer attribute using -component size independant min and max values. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
min - - -Number - - - - The start of the range to mark as needing an update.
max - - -Number - - - - The end of the range to mark as needing an update.
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - -

splice(start, end)

- - - - - -
- Perform a splice operation on this attribute's buffer. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
start - - -Number - - - - The start index of the splice. Will be multiplied by the number of components for this attribute.
end - - -Number - - - - The end index of the splice. Will be multiplied by the number of components for this attribute.
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - -
- - - -
- -
- Documentation generated by JSDoc 3.3.3 on Sat Nov 14 2015 15:30:50 GMT+0000 (GMT) -
- - - - - \ No newline at end of file diff --git a/docs/api/SPE.TypedArrayHelper.html b/docs/api/SPE.TypedArrayHelper.html deleted file mode 100644 index a1f3109..0000000 --- a/docs/api/SPE.TypedArrayHelper.html +++ /dev/null @@ -1,3308 +0,0 @@ - - - - - JSDoc: Class: TypedArrayHelper - - - - - - - - - - -
- -

Class: TypedArrayHelper

- - - - - - -
- -
- -

- SPE. - - TypedArrayHelper -

- - -
- -
-
- - - - - -

new TypedArrayHelper(TypedArrayConstructor, size, componentSize, indexOffset)

- - - - - -
- A helper class for TypedArrays. - -Allows for easy resizing, assignment of various component-based -types (Vector2s, Vector3s, Vector4s, Mat3s, Mat4s), -as well as Colors (where components are `r`, `g`, `b`), -Numbers, and setting from other TypedArrays. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
TypedArrayConstructor - - -function - - - - The constructor to use (Float32Array, Uint8Array, etc.)
size - - -Number - - - - The size of the array to create
componentSize - - -Number - - - - The number of components per-value (ie. 3 for a vec3, 9 for a Mat3, etc.)
indexOffset - - -Number - - - - The index in the array from which to start assigning values. Default `0` if none provided
- - - - - - -
- - - - - - - - - - - - - - - - - - -
Author:
-
-
    -
  • Luke Moody
  • -
-
- - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - -

Methods

- - - - - - -

getComponentValueAtIndex(index) → {TypedArray}

- - - - - -
- Returns the component value of the array at the given index, taking into account -the `indexOffset` property of this class. - -If the componentSize is set to 3, then it will return a new TypedArray -of length 3. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
index - - -Number - - - - The index in the array to fetch.
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - -
Returns:
- - -
- The component value at the given index. -
- - - -
-
- Type -
-
- -TypedArray - - -
-
- - - - - - - - - - -

getValueAtIndex(index) → {Number}

- - - - - -
- Returns the value of the array at the given index, taking into account -the `indexOffset` property of this class. - -Note that this function ignores the component size and will just return a -single value. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
index - - -Number - - - - The index in the array to fetch.
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - -
Returns:
- - -
- The value at the given index. -
- - - -
-
- Type -
-
- -Number - - -
-
- - - - - - - - - - -

grow(size) → {SPE.TypedArrayHelper}

- - - - - -
- Grows the internal array. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
size - - -Number - - - - The new size of the typed array. Must be larger than `this.array.length`.
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - -
Returns:
- - -
- Instance of this class. -
- - - -
-
- Type -
-
- -SPE.TypedArrayHelper - - -
-
- - - - - - - - - - -

setColor(index, color) → {SPE.TypedArrayHelper}

- - - - - -
- Set a Color value at `index`. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
index - - -Number - - - - The index at which to set the vec3 values from.
color - - -Color - - - - Any object that has `r`, `g`, and `b` properties.
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - -
Returns:
- - -
- Instance of this class. -
- - - -
-
- Type -
-
- -SPE.TypedArrayHelper - - -
-
- - - - - - - - - - -

setFromArray(index, array) → {SPE.TypedArrayHelper}

- - - - - -
- Copies from the given TypedArray into this one, using the index argument -as the start position. Alias for `TypedArray.set`. Will automatically resize -if the given source array is of a larger size than the internal array. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
index - - -Number - - - - The start position from which to copy into this array.
array - - -TypedArray - - - - The array from which to copy; the source array.
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - -
Returns:
- - -
- Instance of this class. -
- - - -
-
- Type -
-
- -SPE.TypedArrayHelper - - -
-
- - - - - - - - - - -

setMat3(index, mat3) → {SPE.TypedArrayHelper}

- - - - - -
- Set a Matrix3 value at `index`. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
index - - -Number - - - - The index at which to set the matrix values from.
mat3 - - -Matrix3 - - - - The 3x3 matrix to set from. Must have a TypedArray property named `elements` to copy from.
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - -
Returns:
- - -
- Instance of this class. -
- - - -
-
- Type -
-
- -SPE.TypedArrayHelper - - -
-
- - - - - - - - - - -

setMat4(index, mat3) → {SPE.TypedArrayHelper}

- - - - - -
- Set a Matrix4 value at `index`. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
index - - -Number - - - - The index at which to set the matrix values from.
mat3 - - -Matrix4 - - - - The 4x4 matrix to set from. Must have a TypedArray property named `elements` to copy from.
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - -
Returns:
- - -
- Instance of this class. -
- - - -
-
- Type -
-
- -SPE.TypedArrayHelper - - -
-
- - - - - - - - - - -

setNumber(index, numericValue) → {SPE.TypedArrayHelper}

- - - - - -
- Set a Number value at `index`. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
index - - -Number - - - - The index at which to set the vec3 values from.
numericValue - - -Number - - - - The number to assign to this index in the array.
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - -
Returns:
- - -
- Instance of this class. -
- - - -
-
- Type -
-
- -SPE.TypedArrayHelper - - -
-
- - - - - - - - - - -

setSize(size)

- - - - - -
- Sets the size of the internal array. - -Delegates to `this.shrink` or `this.grow` depending on size -argument's relation to the current size of the internal array. - -Note that if the array is to be shrunk, data will be lost. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
size - - -Number - - - - The new size of the array.
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - -

setVec2(index, vec2) → {SPE.TypedArrayHelper}

- - - - - -
- Set a Vector2 value at `index`. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
index - - -Number - - - - The index at which to set the vec2 values from.
vec2 - - -Vector2 - - - - Any object that has `x` and `y` properties.
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - -
Returns:
- - -
- Instance of this class. -
- - - -
-
- Type -
-
- -SPE.TypedArrayHelper - - -
-
- - - - - - - - - - -

setVec2Components(index, x, y) → {SPE.TypedArrayHelper}

- - - - - -
- Set a Vector2 value using raw components. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
index - - -Number - - - - The index at which to set the vec2 values from.
x - - -Number - - - - The Vec2's `x` component.
y - - -Number - - - - The Vec2's `y` component.
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - -
Returns:
- - -
- Instance of this class. -
- - - -
-
- Type -
-
- -SPE.TypedArrayHelper - - -
-
- - - - - - - - - - -

setVec3(index, vec2) → {SPE.TypedArrayHelper}

- - - - - -
- Set a Vector3 value at `index`. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
index - - -Number - - - - The index at which to set the vec3 values from.
vec2 - - -Vector3 - - - - Any object that has `x`, `y`, and `z` properties.
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - -
Returns:
- - -
- Instance of this class. -
- - - -
-
- Type -
-
- -SPE.TypedArrayHelper - - -
-
- - - - - - - - - - -

setVec3Components(index, x, y, z) → {SPE.TypedArrayHelper}

- - - - - -
- Set a Vector3 value using raw components. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
index - - -Number - - - - The index at which to set the vec3 values from.
x - - -Number - - - - The Vec3's `x` component.
y - - -Number - - - - The Vec3's `y` component.
z - - -Number - - - - The Vec3's `z` component.
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - -
Returns:
- - -
- Instance of this class. -
- - - -
-
- Type -
-
- -SPE.TypedArrayHelper - - -
-
- - - - - - - - - - -

setVec4(index, vec2) → {SPE.TypedArrayHelper}

- - - - - -
- Set a Vector4 value at `index`. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
index - - -Number - - - - The index at which to set the vec4 values from.
vec2 - - -Vector4 - - - - Any object that has `x`, `y`, `z`, and `w` properties.
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - -
Returns:
- - -
- Instance of this class. -
- - - -
-
- Type -
-
- -SPE.TypedArrayHelper - - -
-
- - - - - - - - - - -

setVec4Components(index, x, y, z, w) → {SPE.TypedArrayHelper}

- - - - - -
- Set a Vector4 value using raw components. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
index - - -Number - - - - The index at which to set the vec4 values from.
x - - -Number - - - - The Vec4's `x` component.
y - - -Number - - - - The Vec4's `y` component.
z - - -Number - - - - The Vec4's `z` component.
w - - -Number - - - - The Vec4's `w` component.
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - -
Returns:
- - -
- Instance of this class. -
- - - -
-
- Type -
-
- -SPE.TypedArrayHelper - - -
-
- - - - - - - - - - -

shrink(size) → {SPE.TypedArrayHelper}

- - - - - -
- Shrinks the internal array. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
size - - -Number - - - - The new size of the typed array. Must be smaller than `this.array.length`.
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - -
Returns:
- - -
- Instance of this class. -
- - - -
-
- Type -
-
- -SPE.TypedArrayHelper - - -
-
- - - - - - - - - - -

splice(start, end) → {Object}

- - - - - -
- Perform a splice operation on this array's buffer. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
start - - -Number - - - - The start index of the splice. Will be multiplied by the number of components for this attribute.
end - - -Number - - - - The end index of the splice. Will be multiplied by the number of components for this attribute.
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - -
Returns:
- - -
- The SPE.TypedArrayHelper instance. -
- - - -
-
- Type -
-
- -Object - - -
-
- - - - - - - - - - - -
- -
- - - - -
- - - -
- -
- Documentation generated by JSDoc 3.3.3 on Sat Nov 14 2015 15:30:50 GMT+0000 (GMT) -
- - - - - \ No newline at end of file diff --git a/docs/api/SPE.html b/docs/api/SPE.html deleted file mode 100644 index 2098aee..0000000 --- a/docs/api/SPE.html +++ /dev/null @@ -1,427 +0,0 @@ - - - - - JSDoc: Namespace: SPE - - - - - - - - - - -
- -

Namespace: SPE

- - - - - - -
- -
- -

- SPE -

- - -
- -
-
- - -
Namespace for Shader Particle Engine. - -All SPE-related code sits under this namespace.
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - -
- - - - - - -

Classes

- -
-
Emitter
-
- -
Group
-
- -
ShaderAttribute
-
- -
TypedArrayHelper
-
-
- - - - - -

Namespaces

- -
-
utils
-
-
- - - -

Members

- - - -

(static) distributions :Number

- - - - -
- A map of supported distribution types used -by SPE.Emitter instances. - -These distribution types can be applied to -an emitter globally, which will affect the -`position`, `velocity`, and `acceleration` -value calculations for an emitter, or they -can be applied on a per-property basis. -
- - - -
Type:
-
    -
  • - -Number - - -
  • -
- - - - - -
Properties:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
BOX - - -Number - - - - Values will be distributed within a box.
SPHERE - - -Number - - - - Values will be distributed on a sphere.
DISC - - -Number - - - - Values will be distributed on a 2d-disc shape.
- - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - -

(static) valueOverLifetimeLength :Number

- - - - -
- Set this value to however many 'steps' you -want value-over-lifetime properties to have. - -It's adjustable to fix an interpolation problem: - -Assuming you specify an opacity value as [0, 1, 0] - and the `valueOverLifetimeLength` is 4, then the - opacity value array will be reinterpolated to - be [0, 0.66, 0.66, 0]. - This isn't ideal, as particles would never reach - full opacity. - -NOTE: - This property affects the length of ALL - value-over-lifetime properties for ALL - emitters and ALL groups. - - Only values >= 3 && <= 4 are allowed. -
- - - -
Type:
-
    -
  • - -Number - - -
  • -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - -
- -
- - - - -
- - - -
- -
- Documentation generated by JSDoc 3.3.3 on Sat Nov 14 2015 15:30:50 GMT+0000 (GMT) -
- - - - - \ No newline at end of file diff --git a/docs/api/SPE.js.html b/docs/api/SPE.js.html deleted file mode 100644 index 1bafb2f..0000000 --- a/docs/api/SPE.js.html +++ /dev/null @@ -1,132 +0,0 @@ - - - - - JSDoc: Source: SPE.js - - - - - - - - - - -
- -

Source: SPE.js

- - - - - - -
-
-
/**
- * @typedef {Number} distribution
- * @property {Number} SPE.distributions.BOX Values will be distributed within a box.
- * @property {Number} SPE.distributions.SPHERE Values will be distributed within a sphere.
- * @property {Number} SPE.distributions.DISC Values will be distributed within a 2D disc.
- */
-
-/**
- * Namespace for Shader Particle Engine.
- *
- * All SPE-related code sits under this namespace.
- *
- * @type {Object}
- * @namespace
- */
-var SPE = {
-
-    /**
-     * A map of supported distribution types used
-     * by SPE.Emitter instances.
-     *
-     * These distribution types can be applied to
-     * an emitter globally, which will affect the
-     * `position`, `velocity`, and `acceleration`
-     * value calculations for an emitter, or they
-     * can be applied on a per-property basis.
-     *
-     * @enum {Number}
-     */
-    distributions: {
-        /**
-         * Values will be distributed within a box.
-         * @type {Number}
-         */
-        BOX: 1,
-
-        /**
-         * Values will be distributed on a sphere.
-         * @type {Number}
-         */
-        SPHERE: 2,
-
-        /**
-         * Values will be distributed on a 2d-disc shape.
-         * @type {Number}
-         */
-        DISC: 3,
-    },
-
-
-    /**
-     * Set this value to however many 'steps' you
-     * want value-over-lifetime properties to have.
-     *
-     * It's adjustable to fix an interpolation problem:
-     *
-     * Assuming you specify an opacity value as [0, 1, 0]
-     *      and the `valueOverLifetimeLength` is 4, then the
-     *      opacity value array will be reinterpolated to
-     *      be [0, 0.66, 0.66, 0].
-     *   This isn't ideal, as particles would never reach
-     *   full opacity.
-     *
-     * NOTE:
-     *     This property affects the length of ALL
-     *       value-over-lifetime properties for ALL
-     *       emitters and ALL groups.
-     *
-     *     Only values >= 3 && <= 4 are allowed.
-     *
-     * @type {Number}
-     */
-    valueOverLifetimeLength: 4
-};
-
-// Module loader support:
-if ( typeof define === 'function' && define.amd ) {
-    define( 'spe', SPE );
-}
-else if ( typeof exports !== 'undefined' && typeof module !== 'undefined' ) {
-    module.exports = SPE;
-}
-
-
- - - - -
- - - -
- -
- Documentation generated by JSDoc 3.3.3 on Sat Nov 14 2015 15:30:50 GMT+0000 (GMT) -
- - - - - diff --git a/docs/api/constants_distributions.js.html b/docs/api/constants_distributions.js.html new file mode 100644 index 0000000..e0d684c --- /dev/null +++ b/docs/api/constants_distributions.js.html @@ -0,0 +1,88 @@ + + + + + JSDoc: Source: constants/distributions.js + + + + + + + + + + +
+ +

Source: constants/distributions.js

+ + + + + + +
+
+
/**
+ * A map of supported distribution types used
+ * by SPE.Emitter instances.
+ *
+ * These distribution types can be applied to
+ * an emitter globally, which will affect the
+ * `position`, `velocity`, and `acceleration`
+ * value calculations for an emitter, or they
+ * can be applied on a per-property basis.
+ *
+ * @enum {Number}
+ */
+export default {
+
+	/**
+	 * Values will be distributed within a box.
+	 * @type {Number}
+	 */
+	BOX: 1,
+
+	/**
+	 * Values will be distributed on a sphere.
+	 * @type {Number}
+	 */
+	SPHERE: 2,
+
+	/**
+	 * Values will be distributed on a 2d-disc shape.
+	 * @type {Number}
+	 */
+	DISC: 3,
+
+	/**
+	 * Values will be distributed along a line.
+	 * @type {Number}
+	 */
+	LINE: 4,
+};
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.4 on Sun Jun 28 2020 15:22:31 GMT+0100 (British Summer Time) +
+ + + + + diff --git a/docs/api/constants_typeSizeMap.js.html b/docs/api/constants_typeSizeMap.js.html new file mode 100644 index 0000000..d8e1d3a --- /dev/null +++ b/docs/api/constants_typeSizeMap.js.html @@ -0,0 +1,100 @@ + + + + + JSDoc: Source: constants/typeSizeMap.js + + + + + + + + + + +
+ +

Source: constants/typeSizeMap.js

+ + + + + + +
+
+
/**
+ * A map of uniform types to their component size.
+ * @enum {Number}
+ */
+export default {
+
+	/**
+	 * Float
+	 * @type {Number}
+	 */
+	f: 1,
+
+	/**
+	 * Vec2
+	 * @type {Number}
+	 */
+	v2: 2,
+
+	/**
+	 * Vec3
+	 * @type {Number}
+	 */
+	v3: 3,
+
+	/**
+	 * Vec4
+	 * @type {Number}
+	 */
+	v4: 4,
+
+	/**
+	 * Color
+	 * @type {Number}
+	 */
+	c: 3,
+
+	/**
+	 * Mat3
+	 * @type {Number}
+	 */
+	m3: 9,
+
+	/**
+	 * Mat4
+	 * @type {Number}
+	 */
+	m4: 16,
+};
+
+
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.4 on Sun Jun 28 2020 15:22:31 GMT+0100 (British Summer Time) +
+ + + + + diff --git a/docs/api/constants_valueTypes.js.html b/docs/api/constants_valueTypes.js.html new file mode 100644 index 0000000..4f9aaae --- /dev/null +++ b/docs/api/constants_valueTypes.js.html @@ -0,0 +1,77 @@ + + + + + JSDoc: Source: constants/valueTypes.js + + + + + + + + + + +
+ +

Source: constants/valueTypes.js

+ + + + + + +
+
+
export default {
+
+	/**
+	 * Boolean type.
+	 * @type {String}
+	 */
+	BOOLEAN: 'boolean',
+
+	/**
+	 * String type.
+	 * @type {String}
+	 */
+	STRING: 'string',
+
+	/**
+	 * Number type.
+	 * @type {String}
+	 */
+	NUMBER: 'number',
+
+	/**
+	 * Object type.
+	 * @type {String}
+	 */
+	OBJECT: 'object',
+};
+
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.4 on Sun Jun 28 2020 15:22:31 GMT+0100 (British Summer Time) +
+ + + + + diff --git a/docs/api/core_Emitter.js.html b/docs/api/core_Emitter.js.html new file mode 100644 index 0000000..aa12c5d --- /dev/null +++ b/docs/api/core_Emitter.js.html @@ -0,0 +1,1005 @@ + + + + + JSDoc: Source: core/Emitter.js + + + + + + + + + + +
+ +

Source: core/Emitter.js

+ + + + + + +
+
+
import * as THREE from 'three';
+import valueTypes from '@/constants/valueTypes';
+import distributions from '@/constants/distributions';
+import globals from '@/constants/globals';
+import utils from './utils';
+
+const HAS_OWN = Object.prototype.hasOwnProperty;
+
+/**
+ * An SPE.Emitter instance.
+ * @typedef {Object} Emitter
+ * @see SPE.Emitter
+ */
+
+/**
+ * A map of options to configure an SPE.Emitter instance.
+ *
+ * @typedef {Object} EmitterOptions
+ *
+ * @property {distribution} [type=BOX] The default distribution this emitter should use to control
+ *                         its particle's spawn position and force behaviour.
+ *                         Must be an distributions.* value.
+ *
+ *
+ * @property {Number} [particleCount=100] The total number of particles this emitter will hold. NOTE: this is not the number
+ *                                  of particles emitted in a second, or anything like that. The number of particles
+ *                                  emitted per-second is calculated by particleCount / maxAge (approximately!)
+ *
+ * @property {Number|null} [duration=null] The duration in seconds that this emitter should live for. If not specified, the emitter
+ *                                         will emit particles indefinitely.
+ *                                         NOTE: When an emitter is older than a specified duration, the emitter is NOT removed from
+ *                                         it's group, but rather is just marked as dead, allowing it to be reanimated at a later time
+ *                                         using `SPE.Emitter.prototype.enable()`.
+ *
+ * @property {Boolean} [isStatic=false] Whether this emitter should be not be simulated (true).
+ * @property {Boolean} [activeMultiplier=1] A value between 0 and 1 describing what percentage of this emitter's particlesPerSecond should be
+ *                                          emitted, where 0 is 0%, and 1 is 100%.
+ *                                          For example, having an emitter with 100 particles, a maxAge of 2, yields a particlesPerSecond
+ *                                          value of 50. Setting `activeMultiplier` to 0.5, then, will only emit 25 particles per second (0.5 = 50%).
+ *                                          Values greater than 1 will emulate a burst of particles, causing the emitter to run out of particles
+ *                                          before it's next activation cycle.
+ *
+ * @property {Boolean} [direction=1] The direction of the emitter. If value is `1`, emitter will start at beginning of particle's lifecycle.
+ *                                   If value is `-1`, emitter will start at end of particle's lifecycle and work it's way backwards.
+ *
+ * @property {Object} [maxAge={}] An object describing the particle's maximum age in seconds.
+ * @property {Number} [maxAge.value=2] A number between 0 and 1 describing the amount of maxAge to apply to all particles.
+ * @property {Number} [maxAge.spread=0] A number describing the maxAge variance on a per-particle basis.
+ *
+ *
+ * @property {Object} [position={}] An object describing this emitter's position.
+ * @property {Object} [position.value=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's base position.
+ * @property {Object} [position.spread=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's position variance on a per-particle basis.
+ *                                                          Note that when using a SPHERE or DISC distribution, only the x-component
+ *                                                          of this vector is used.
+ *                                                          When using a LINE distribution, this value is the endpoint of the LINE.
+ * @property {Object} [position.spreadClamp=new THREE.Vector3()] A THREE.Vector3 instance describing the numeric multiples the particle's should
+ *                                                               be spread out over.
+ *                                                               Note that when using a SPHERE or DISC distribution, only the x-component
+ *                                                               of this vector is used.
+ *                                                               When using a LINE distribution, this property is ignored.
+ * @property {Number} [position.radius=10] This emitter's base radius.
+ * @property {Object} [position.radiusScale=new THREE.Vector3()] A THREE.Vector3 instance describing the radius's scale in all three axes. Allows a SPHERE or DISC to be squashed or stretched.
+ * @property {distribution} [position.distribution=value of the `type` option.] A specific distribution to use when radiusing particles. Overrides the `type` option.
+ * @property {Boolean} [position.randomise=false] When a particle is re-spawned, whether it's position should be re-randomised or not. Can incur a performance hit.
+ *
+ *
+ * @property {Object} [velocity={}] An object describing this particle velocity.
+ * @property {Object} [velocity.value=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's base velocity.
+ * @property {Object} [velocity.spread=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's velocity variance on a per-particle basis.
+ *                                                          Note that when using a SPHERE or DISC distribution, only the x-component
+ *                                                          of this vector is used.
+ * @property {distribution} [velocity.distribution=value of the `type` option.] A specific distribution to use when calculating a particle's velocity. Overrides the `type` option.
+ * @property {Boolean} [velocity.randomise=false] When a particle is re-spawned, whether it's velocity should be re-randomised or not. Can incur a performance hit.
+ *
+ *
+ * @property {Object} [acceleration={}] An object describing this particle's acceleration.
+ * @property {Object} [acceleration.value=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's base acceleration.
+ * @property {Object} [acceleration.spread=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's acceleration variance on a per-particle basis.
+ *                           Note that when using a SPHERE or DISC distribution, only the x-component
+ *                           of this vector is used.
+ * @property {distribution} [acceleration.distribution=value of the `type` option.] A specific distribution to use when calculating a particle's acceleration. Overrides the `type` option.
+ * @property {Boolean} [acceleration.randomise=false] When a particle is re-spawned, whether it's acceleration should be re-randomised or not. Can incur a performance hit.
+ *
+ *
+ * @property {Object} [drag={}] An object describing this particle drag. Drag is applied to both velocity and acceleration values.
+ * @property {Number} [drag.value=0] A number between 0 and 1 describing the amount of drag to apply to all particles.
+ * @property {Number} [drag.spread=0] A number describing the drag variance on a per-particle basis.
+ * @property {Boolean} [drag.randomise=false] When a particle is re-spawned, whether it's drag should be re-randomised or not. Can incur a performance hit.
+ *
+ *
+ * @property {Object} [wiggle={}] This is quite a fun one! The values of this object will determine whether a particle will wiggle, or jiggle, or wave,
+ *                                or shimmy, or waggle, or... Well you get the idea. The wiggle is calculated over-time, meaning that a particle will
+ *                                start off with no wiggle, and end up wiggling about with the distance of the `value` specified by the time it dies.
+ *                                It's quite handy to simulate fire embers, or similar effects where the particle's position should slightly change over
+ *                                time, and such change isn't easily controlled by rotation, velocity, or acceleration. The wiggle is a combination of sin and cos calculations, so is circular in nature.
+ * @property {Number} [wiggle.value=0] A number describing the amount of wiggle to apply to all particles. It's measured in distance.
+ * @property {Number} [wiggle.spread=0] A number describing the wiggle variance on a per-particle basis.
+ *
+ *
+ * @property {Object} [rotation={}] An object describing this emitter's rotation. It can either be static, or set to rotate from 0radians to the value of `rotation.value`
+ *                                  over a particle's lifetime. Rotation values affect both a particle's position and the forces applied to it.
+ * @property {Object} [rotation.axis=new THREE.Vector3(0, 1, 0)] A THREE.Vector3 instance describing this emitter's axis of rotation.
+ * @property {Object} [rotation.axisSpread=new THREE.Vector3()] A THREE.Vector3 instance describing the amount of variance to apply to the axis of rotation on
+ *                                                              a per-particle basis.
+ * @property {Number} [rotation.angle=0] The angle of rotation, given in radians. If `rotation.static` is true, the emitter will start off rotated at this angle, and stay as such.
+ *                                       Otherwise, the particles will rotate from 0radians to this value over their lifetimes.
+ * @property {Number} [rotation.angleSpread=0] The amount of variance in each particle's rotation angle.
+ * @property {Boolean} [rotation.static=false] Whether the rotation should be static or not.
+ * @property {Object} [rotation.center=The value of `position.value`] A THREE.Vector3 instance describing the center point of rotation.
+ * @property {Boolean} [rotation.randomise=false] When a particle is re-spawned, whether it's rotation should be re-randomised or not. Can incur a performance hit.
+ *
+ *
+ * @property {Object} [color={}] An object describing a particle's color. This property is a "value-over-lifetime" property, meaning an array of values and spreads can be
+ *                               given to describe specific value changes over a particle's lifetime.
+ *                               Depending on the value of SPE.valueOverLifetimeLength, if arrays of THREE.Color instances are given, then the array will be interpolated to
+ *                               have a length matching the value of SPE.valueOverLifetimeLength.
+ * @property {Object} [color.value=new THREE.Color()] Either a single THREE.Color instance, or an array of THREE.Color instances to describe the color of a particle over it's lifetime.
+ * @property {Object} [color.spread=new THREE.Vector3()] Either a single THREE.Vector3 instance, or an array of THREE.Vector3 instances to describe the color variance of a particle over it's lifetime.
+ * @property {Boolean} [color.randomise=false] When a particle is re-spawned, whether it's color should be re-randomised or not. Can incur a performance hit.
+ *
+ *
+ * @property {Object} [opacity={}] An object describing a particle's opacity. This property is a "value-over-lifetime" property, meaning an array of values and spreads can be
+ *                               given to describe specific value changes over a particle's lifetime.
+ *                               Depending on the value of SPE.valueOverLifetimeLength, if arrays of numbers are given, then the array will be interpolated to
+ *                               have a length matching the value of SPE.valueOverLifetimeLength.
+ * @property {Number} [opacity.value=1] Either a single number, or an array of numbers to describe the opacity of a particle over it's lifetime.
+ * @property {Number} [opacity.spread=0] Either a single number, or an array of numbers to describe the opacity variance of a particle over it's lifetime.
+ * @property {Boolean} [opacity.randomise=false] When a particle is re-spawned, whether it's opacity should be re-randomised or not. Can incur a performance hit.
+ *
+ *
+ * @property {Object} [size={}] An object describing a particle's size. This property is a "value-over-lifetime" property, meaning an array of values and spreads can be
+ *                               given to describe specific value changes over a particle's lifetime.
+ *                               Depending on the value of SPE.valueOverLifetimeLength, if arrays of numbers are given, then the array will be interpolated to
+ *                               have a length matching the value of SPE.valueOverLifetimeLength.
+ * @property {Number} [size.value=1] Either a single number, or an array of numbers to describe the size of a particle over it's lifetime.
+ * @property {Number} [size.spread=0] Either a single number, or an array of numbers to describe the size variance of a particle over it's lifetime.
+ * @property {Boolean} [size.randomise=false] When a particle is re-spawned, whether it's size should be re-randomised or not. Can incur a performance hit.
+ *
+ *
+ * @property {Object} [angle={}] An object describing a particle's angle. The angle is a 2d-rotation, measured in radians, applied to the particle's texture.
+ *                               NOTE: if a particle's texture is a sprite-sheet, this value IS IGNORED.
+ *                               This property is a "value-over-lifetime" property, meaning an array of values and spreads can be
+ *                               given to describe specific value changes over a particle's lifetime.
+ *                               Depending on the value of SPE.valueOverLifetimeLength, if arrays of numbers are given, then the array will be interpolated to
+ *                               have a length matching the value of SPE.valueOverLifetimeLength.
+ * @property {Number} [angle.value=0] Either a single number, or an array of numbers to describe the angle of a particle over it's lifetime.
+ * @property {Number} [angle.spread=0] Either a single number, or an array of numbers to describe the angle variance of a particle over it's lifetime.
+ * @property {Boolean} [angle.randomise=false] When a particle is re-spawned, whether it's angle should be re-randomised or not. Can incur a performance hit.
+ *
+ */
+
+/**
+ * The SPE.Emitter class.
+ *
+ * @constructor
+ *
+ * @param {EmitterOptions} options A map of options to configure the emitter.
+ */
+export default class Emitter {
+	constructor( opts ) {
+		// Ensure we have a map of options to play with,
+		// and that each option is in the correct format.
+		const options = utils.ensureTypedArg( opts, valueTypes.OBJECT, {} );
+		options.position = utils.ensureTypedArg( options.position, valueTypes.OBJECT, {} );
+		options.velocity = utils.ensureTypedArg( options.velocity, valueTypes.OBJECT, {} );
+		options.acceleration = utils.ensureTypedArg( options.acceleration, valueTypes.OBJECT, {} );
+		options.radius = utils.ensureTypedArg( options.radius, valueTypes.OBJECT, {} );
+		options.drag = utils.ensureTypedArg( options.drag, valueTypes.OBJECT, {} );
+		options.rotation = utils.ensureTypedArg( options.rotation, valueTypes.OBJECT, {} );
+		options.color = utils.ensureTypedArg( options.color, valueTypes.OBJECT, {} );
+		options.opacity = utils.ensureTypedArg( options.opacity, valueTypes.OBJECT, {} );
+		options.size = utils.ensureTypedArg( options.size, valueTypes.OBJECT, {} );
+		options.angle = utils.ensureTypedArg( options.angle, valueTypes.OBJECT, {} );
+		options.wiggle = utils.ensureTypedArg( options.wiggle, valueTypes.OBJECT, {} );
+		options.maxAge = utils.ensureTypedArg( options.maxAge, valueTypes.OBJECT, {} );
+
+		if ( options.onParticleSpawn ) {
+			console.warn( 'onParticleSpawn has been removed. Please set properties directly to alter values at runtime.' );
+		}
+
+		this.uuid = THREE.Math.generateUUID();
+
+		this.type = utils.ensureTypedArg( options.type, valueTypes.NUMBER, distributions.BOX );
+
+		// Start assigning properties...kicking it off with props that DON'T support values over
+		// lifetimes.
+		//
+		// Btw, values over lifetimes are just the new way of referring to *Start, *Middle, and *End.
+		this.position = {
+			_value: utils.ensureInstanceOf( options.position.value, THREE.Vector3, new THREE.Vector3() ),
+			_spread: utils.ensureInstanceOf( options.position.spread, THREE.Vector3, new THREE.Vector3() ),
+			_spreadClamp: utils.ensureInstanceOf( options.position.spreadClamp, THREE.Vector3, new THREE.Vector3() ),
+			_distribution: utils.ensureTypedArg( options.position.distribution, valueTypes.NUMBER, this.type ),
+			_randomise: utils.ensureTypedArg( options.position.randomise, valueTypes.BOOLEAN, false ),
+			_radius: utils.ensureTypedArg( options.position.radius, valueTypes.NUMBER, 10 ),
+			_radiusScale: utils.ensureInstanceOf( options.position.radiusScale, THREE.Vector3, new THREE.Vector3( 1, 1, 1 ) ),
+			_distributionClamp: utils.ensureTypedArg( options.position.distributionClamp, valueTypes.NUMBER, 0 ),
+		};
+
+		this.velocity = {
+			_value: utils.ensureInstanceOf( options.velocity.value, THREE.Vector3, new THREE.Vector3() ),
+			_spread: utils.ensureInstanceOf( options.velocity.spread, THREE.Vector3, new THREE.Vector3() ),
+			_distribution: utils.ensureTypedArg( options.velocity.distribution, valueTypes.NUMBER, this.type ),
+			_randomise: utils.ensureTypedArg( options.position.randomise, valueTypes.BOOLEAN, false ),
+		};
+
+		this.acceleration = {
+			_value: utils.ensureInstanceOf( options.acceleration.value, THREE.Vector3, new THREE.Vector3() ),
+			_spread: utils.ensureInstanceOf( options.acceleration.spread, THREE.Vector3, new THREE.Vector3() ),
+			_distribution: utils.ensureTypedArg( options.acceleration.distribution, valueTypes.NUMBER, this.type ),
+			_randomise: utils.ensureTypedArg( options.position.randomise, valueTypes.BOOLEAN, false ),
+		};
+
+		this.drag = {
+			_value: utils.ensureTypedArg( options.drag.value, valueTypes.NUMBER, 0 ),
+			_spread: utils.ensureTypedArg( options.drag.spread, valueTypes.NUMBER, 0 ),
+			_randomise: utils.ensureTypedArg( options.position.randomise, valueTypes.BOOLEAN, false ),
+		};
+
+		this.wiggle = {
+			_value: utils.ensureTypedArg( options.wiggle.value, valueTypes.NUMBER, 0 ),
+			_spread: utils.ensureTypedArg( options.wiggle.spread, valueTypes.NUMBER, 0 ),
+		};
+
+		this.rotation = {
+			_axis: utils.ensureInstanceOf( options.rotation.axis, THREE.Vector3, new THREE.Vector3( 0.0, 1.0, 0.0 ) ),
+			_axisSpread: utils.ensureInstanceOf( options.rotation.axisSpread, THREE.Vector3, new THREE.Vector3() ),
+			_angle: utils.ensureTypedArg( options.rotation.angle, valueTypes.NUMBER, 0 ),
+			_angleSpread: utils.ensureTypedArg( options.rotation.angleSpread, valueTypes.NUMBER, 0 ),
+			_static: utils.ensureTypedArg( options.rotation.static, valueTypes.BOOLEAN, false ),
+			_center: utils.ensureInstanceOf( options.rotation.center, THREE.Vector3, this.position._value.clone() ),
+			_randomise: utils.ensureTypedArg( options.position.randomise, valueTypes.BOOLEAN, false ),
+		};
+
+
+		this.maxAge = {
+			_value: utils.ensureTypedArg( options.maxAge.value, valueTypes.NUMBER, 2 ),
+			_spread: utils.ensureTypedArg( options.maxAge.spread, valueTypes.NUMBER, 0 ),
+		};
+
+
+
+		// The following properties can support either single values, or an array of values that change
+		// the property over a particle's lifetime (value over lifetime).
+		this.color = {
+			_value: utils.ensureArrayInstanceOf( options.color.value, THREE.Color, new THREE.Color() ),
+			_spread: utils.ensureArrayInstanceOf( options.color.spread, THREE.Vector3, new THREE.Vector3() ),
+			_randomise: utils.ensureTypedArg( options.position.randomise, valueTypes.BOOLEAN, false ),
+		};
+
+		this.opacity = {
+			_value: utils.ensureArrayTypedArg( options.opacity.value, valueTypes.NUMBER, 1 ),
+			_spread: utils.ensureArrayTypedArg( options.opacity.spread, valueTypes.NUMBER, 0 ),
+			_randomise: utils.ensureTypedArg( options.position.randomise, valueTypes.BOOLEAN, false ),
+		};
+
+		this.size = {
+			_value: utils.ensureArrayTypedArg( options.size.value, valueTypes.NUMBER, 1 ),
+			_spread: utils.ensureArrayTypedArg( options.size.spread, valueTypes.NUMBER, 0 ),
+			_randomise: utils.ensureTypedArg( options.position.randomise, valueTypes.BOOLEAN, false ),
+		};
+
+		this.angle = {
+			_value: utils.ensureArrayTypedArg( options.angle.value, valueTypes.NUMBER, 0 ),
+			_spread: utils.ensureArrayTypedArg( options.angle.spread, valueTypes.NUMBER, 0 ),
+			_randomise: utils.ensureTypedArg( options.position.randomise, valueTypes.BOOLEAN, false ),
+		};
+
+
+		// Assign renaining option values.
+		this.particleCount = utils.ensureTypedArg( options.particleCount, valueTypes.NUMBER, 100 );
+		this.duration = utils.ensureTypedArg( options.duration, valueTypes.NUMBER, null );
+		this.isStatic = utils.ensureTypedArg( options.isStatic, valueTypes.BOOLEAN, false );
+		this.activeMultiplier = utils.ensureTypedArg( options.activeMultiplier, valueTypes.NUMBER, 1 );
+		this.direction = utils.ensureTypedArg( options.direction, valueTypes.NUMBER, 1 );
+
+		// Whether this emitter is alive or not.
+		this.alive = utils.ensureTypedArg( options.alive, valueTypes.BOOLEAN, true );
+
+
+		// The following properties are set internally and are not
+		// user-controllable.
+		this.particlesPerSecond = 0;
+
+		// The current particle index for which particles should
+		// be marked as active on the next update cycle.
+		this.activationIndex = 0;
+
+		// The offset in the typed arrays this emitter's
+		// particle's values will start at
+		this.attributeOffset = 0;
+
+		// The end of the range in the attribute buffers
+		this.attributeEnd = 0;
+
+
+
+		// Holds the time the emitter has been alive for.
+		this.age = 0.0;
+
+		// Holds the number of currently-alive particles
+		this.activeParticleCount = 0.0;
+
+		// Holds a reference to this emitter's group once
+		// it's added to one.
+		this.group = null;
+
+		// Holds a reference to this emitter's group's attributes object
+		// for easier access.
+		this.attributes = null;
+
+		// Holds a reference to the params attribute's typed array
+		// for quicker access.
+		this.paramsArray = null;
+
+		// A set of flags to determine whether particular properties
+		// should be re-randomised when a particle is reset.
+		//
+		// If a `randomise` property is given, this is preferred.
+		// Otherwise, it looks at whether a spread value has been
+		// given.
+		//
+		// It allows randomization to be turned off as desired. If
+		// all randomization is turned off, then I'd expect a performance
+		// boost as no attribute buffers (excluding the `params`)
+		// would have to be re-passed to the GPU each frame (since nothing
+		// except the `params` attribute would have changed).
+		this.resetFlags = {
+			// params: utils.ensureTypedArg( options.maxAge.randomise, valueTypes.BOOLEAN, !!options.maxAge.spread ) ||
+			//     utils.ensureTypedArg( options.wiggle.randomise, valueTypes.BOOLEAN, !!options.wiggle.spread ),
+			position: utils.ensureTypedArg( options.position.randomise, valueTypes.BOOLEAN, false ) ||
+				utils.ensureTypedArg( options.radius.randomise, valueTypes.BOOLEAN, false ),
+			velocity: utils.ensureTypedArg( options.velocity.randomise, valueTypes.BOOLEAN, false ),
+			acceleration: utils.ensureTypedArg( options.acceleration.randomise, valueTypes.BOOLEAN, false ) ||
+				utils.ensureTypedArg( options.drag.randomise, valueTypes.BOOLEAN, false ),
+			rotation: utils.ensureTypedArg( options.rotation.randomise, valueTypes.BOOLEAN, false ),
+			rotationCenter: utils.ensureTypedArg( options.rotation.randomise, valueTypes.BOOLEAN, false ),
+			size: utils.ensureTypedArg( options.size.randomise, valueTypes.BOOLEAN, false ),
+			color: utils.ensureTypedArg( options.color.randomise, valueTypes.BOOLEAN, false ),
+			opacity: utils.ensureTypedArg( options.opacity.randomise, valueTypes.BOOLEAN, false ),
+			angle: utils.ensureTypedArg( options.angle.randomise, valueTypes.BOOLEAN, false ),
+		};
+
+		this.updateFlags = {};
+		this.updateCounts = {};
+
+		// A map to indicate which emitter parameters should update
+		// which attribute.
+		this.updateMap = {
+			maxAge: 'params',
+			position: 'position',
+			velocity: 'velocity',
+			acceleration: 'acceleration',
+			drag: 'acceleration',
+			wiggle: 'params',
+			rotation: 'rotation',
+			size: 'size',
+			color: 'color',
+			opacity: 'opacity',
+			angle: 'angle',
+		};
+
+		for ( const i in this.updateMap ) {
+			if ( HAS_OWN.call( this.updateMap, i ) ) {
+				this.updateCounts[ this.updateMap[ i ] ] = 0.0;
+				this.updateFlags[ this.updateMap[ i ] ] = false;
+				this._createGetterSetters( this[ i ], i );
+			}
+		}
+
+		this.bufferUpdateRanges = {};
+		this.attributeKeys = null;
+		this.attributeCount = 0;
+
+
+		// Ensure that the value-over-lifetime property objects above
+		// have value and spread properties that are of the same length.
+		//
+		// Also, for now, make sure they have a length of 3 (min/max arguments here).
+		utils.ensureValueOverLifetimeCompliance( this.color, globals.valueOverLifetimeLength, globals.valueOverLifetimeLength );
+		utils.ensureValueOverLifetimeCompliance( this.opacity, globals.valueOverLifetimeLength, globals.valueOverLifetimeLength );
+		utils.ensureValueOverLifetimeCompliance( this.size, globals.valueOverLifetimeLength, globals.valueOverLifetimeLength );
+		utils.ensureValueOverLifetimeCompliance( this.angle, globals.valueOverLifetimeLength, globals.valueOverLifetimeLength );
+	}
+
+	_createGetterSetters( propObj, propName ) {
+		const self = this;
+
+		for ( const i in propObj ) {
+			if ( HAS_OWN.call( propObj, i ) ) {
+
+				const name = i.replace( '_', '' );
+
+				Object.defineProperty( propObj, name, {
+					get: ( function( prop ) {
+						return function() {
+							return this[ prop ];
+						};
+					}( i ) ),
+
+					set: ( function( prop ) {
+						return function( value ) {
+							const mapName = self.updateMap[ propName ],
+								prevValue = this[ prop ],
+								length = globals.valueOverLifetimeLength;
+
+							if ( prop === '_rotationCenter' ) {
+								self.updateFlags.rotationCenter = true;
+								self.updateCounts.rotationCenter = 0.0;
+							}
+							else if ( prop === '_randomise' ) {
+								self.resetFlags[ mapName ] = value;
+							}
+							else {
+								self.updateFlags[ mapName ] = true;
+								self.updateCounts[ mapName ] = 0.0;
+							}
+
+							self.group._updateDefines();
+
+							this[ prop ] = value;
+
+							// If the previous value was an array, then make
+							// sure the provided value is interpolated correctly.
+							if ( Array.isArray( prevValue ) ) {
+								utils.ensureValueOverLifetimeCompliance( self[ propName ], length, length );
+							}
+						};
+					}( i ) ),
+				} );
+			}
+		}
+	}
+
+	_setBufferUpdateRanges( keys ) {
+		this.attributeKeys = keys;
+		this.attributeCount = keys.length;
+
+		for ( let i = this.attributeCount - 1; i >= 0; --i ) {
+			this.bufferUpdateRanges[ keys[ i ] ] = {
+				min: Number.POSITIVE_INFINITY,
+				max: Number.NEGATIVE_INFINITY,
+			};
+		}
+	}
+
+	_calculatePPSValue( groupMaxAge ) {
+		const particleCount = this.particleCount;
+
+		// Calculate the `particlesPerSecond` value for this emitter. It's used
+		// when determining which particles should die and which should live to
+		// see another day. Or be born, for that matter. The "God" property.
+		if ( this.duration ) {
+			this.particlesPerSecond = particleCount / ( groupMaxAge < this.duration ? groupMaxAge : this.duration );
+		}
+		else {
+			this.particlesPerSecond = particleCount / groupMaxAge;
+		}
+	}
+
+	_setAttributeOffset( startIndex ) {
+		this.attributeOffset = startIndex;
+		this.activationIndex = startIndex;
+		this.activationEnd = startIndex + this.particleCount;
+	}
+
+
+	_assignValue( prop, index ) {
+		switch ( prop ) {
+			case 'position':
+				this._assignPositionValue( index );
+				break;
+
+			case 'velocity':
+			case 'acceleration':
+				this._assignForceValue( index, prop );
+				break;
+
+			case 'size':
+			case 'opacity':
+				this._assignAbsLifetimeValue( index, prop );
+				break;
+
+			case 'angle':
+				this._assignAngleValue( index );
+				break;
+
+			case 'params':
+				this._assignParamsValue( index );
+				break;
+
+			case 'rotation':
+				this._assignRotationValue( index );
+				break;
+
+			case 'color':
+				this._assignColorValue( index );
+				break;
+		}
+	}
+
+	_assignPositionValue( index ) {
+		const prop = this.position,
+			attr = this.attributes.position,
+			value = prop._value,
+			spread = prop._spread,
+			distribution = prop._distribution;
+
+		switch ( distribution ) {
+			case distributions.BOX:
+				utils.randomVector3( attr, index, value, spread, prop._spreadClamp );
+				break;
+
+			case distributions.SPHERE:
+				utils.randomVector3OnSphere( attr, index, value, prop._radius, prop._spread.x, prop._radiusScale, prop._spreadClamp.x, prop._distributionClamp || this.particleCount );
+				break;
+
+			case distributions.DISC:
+				utils.randomVector3OnDisc( attr, index, value, prop._radius, prop._spread.x, prop._radiusScale, prop._spreadClamp.x );
+				break;
+
+			case distributions.LINE:
+				utils.randomVector3OnLine( attr, index, value, spread );
+				break;
+		}
+	}
+
+	_assignForceValue( index, attrName ) {
+		const prop = this[ attrName ],
+			value = prop._value,
+			spread = prop._spread,
+			distribution = prop._distribution;
+		let pos,
+			positionX,
+			positionY,
+			positionZ,
+			i;
+
+		switch ( distribution ) {
+			case distributions.BOX:
+				utils.randomVector3( this.attributes[ attrName ], index, value, spread );
+				break;
+
+			case distributions.SPHERE:
+				pos = this.attributes.position.typedArray.array;
+				i = index * 3;
+
+				// Ensure position values aren't zero, otherwise no force will be
+				// applied.
+				// positionX = utils.zeroToEpsilon( pos[ i ], true );
+				// positionY = utils.zeroToEpsilon( pos[ i + 1 ], true );
+				// positionZ = utils.zeroToEpsilon( pos[ i + 2 ], true );
+				positionX = pos[ i ];
+				positionY = pos[ i + 1 ];
+				positionZ = pos[ i + 2 ];
+
+				utils.randomDirectionVector3OnSphere(
+					this.attributes[ attrName ], index,
+					positionX, positionY, positionZ,
+					this.position._value,
+					prop._value.x,
+					prop._spread.x
+				);
+				break;
+
+			case distributions.DISC:
+				pos = this.attributes.position.typedArray.array;
+				i = index * 3;
+
+				// Ensure position values aren't zero, otherwise no force will be
+				// applied.
+				// positionX = utils.zeroToEpsilon( pos[ i ], true );
+				// positionY = utils.zeroToEpsilon( pos[ i + 1 ], true );
+				// positionZ = utils.zeroToEpsilon( pos[ i + 2 ], true );
+				positionX = pos[ i ];
+				positionY = pos[ i + 1 ];
+				positionZ = pos[ i + 2 ];
+
+				utils.randomDirectionVector3OnDisc(
+					this.attributes[ attrName ], index,
+					positionX, positionY, positionZ,
+					this.position._value,
+					prop._value.x,
+					prop._spread.x
+				);
+				break;
+
+			case distributions.LINE:
+				utils.randomVector3OnLine( this.attributes[ attrName ], index, value, spread );
+				break;
+		}
+
+		if ( attrName === 'acceleration' ) {
+			const drag = utils.clamp( utils.randomFloat( this.drag._value, this.drag._spread ), 0, 1 );
+			this.attributes.acceleration.typedArray.array[ index * 4 + 3 ] = drag;
+		}
+	}
+
+	_assignAbsLifetimeValue( index, propName ) {
+		const array = this.attributes[ propName ].typedArray,
+			prop = this[ propName ];
+
+		if ( utils.arrayValuesAreEqual( prop._value ) && utils.arrayValuesAreEqual( prop._spread ) ) {
+			const value = Math.abs( utils.randomFloat( prop._value[ 0 ], prop._spread[ 0 ] ) );
+			array.setVec4Components( index, value, value, value, value );
+		}
+		else {
+			array.setVec4Components( index,
+				Math.abs( utils.randomFloat( prop._value[ 0 ], prop._spread[ 0 ] ) ),
+				Math.abs( utils.randomFloat( prop._value[ 1 ], prop._spread[ 1 ] ) ),
+				Math.abs( utils.randomFloat( prop._value[ 2 ], prop._spread[ 2 ] ) ),
+				Math.abs( utils.randomFloat( prop._value[ 3 ], prop._spread[ 3 ] ) )
+			);
+		}
+	}
+
+	_assignAngleValue( index ) {
+		const array = this.attributes.angle.typedArray,
+			prop = this.angle;
+
+		if ( utils.arrayValuesAreEqual( prop._value ) && utils.arrayValuesAreEqual( prop._spread ) ) {
+			const value = utils.randomFloat( prop._value[ 0 ], prop._spread[ 0 ] );
+			array.setVec4Components( index, value, value, value, value );
+		}
+		else {
+			array.setVec4Components( index,
+				utils.randomFloat( prop._value[ 0 ], prop._spread[ 0 ] ),
+				utils.randomFloat( prop._value[ 1 ], prop._spread[ 1 ] ),
+				utils.randomFloat( prop._value[ 2 ], prop._spread[ 2 ] ),
+				utils.randomFloat( prop._value[ 3 ], prop._spread[ 3 ] )
+			);
+		}
+	}
+
+	_assignParamsValue( index ) {
+		this.attributes.params.typedArray.setVec4Components( index,
+			this.isStatic ? 1 : 0,
+			0.0,
+			Math.abs( utils.randomFloat( this.maxAge._value, this.maxAge._spread ) ),
+			utils.randomFloat( this.wiggle._value, this.wiggle._spread )
+		);
+	}
+
+	_assignRotationValue( index ) {
+		this.attributes.rotation.typedArray.setVec3Components( index,
+			utils.getPackedRotationAxis( this.rotation._axis, this.rotation._axisSpread ),
+			utils.randomFloat( this.rotation._angle, this.rotation._angleSpread ),
+			this.rotation._static ? 0 : 1
+		);
+
+		this.attributes.rotationCenter.typedArray.setVec3( index, this.rotation._center );
+	}
+
+	_assignColorValue( index ) {
+		utils.randomColorAsHex( this.attributes.color, index, this.color._value, this.color._spread );
+	}
+
+	_resetParticle( index ) {
+		const resetFlags = this.resetFlags,
+			updateFlags = this.updateFlags,
+			updateCounts = this.updateCounts,
+			keys = this.attributeKeys;
+		let key,
+			updateFlag;
+
+		for ( let i = this.attributeCount - 1; i >= 0; --i ) {
+			key = keys[ i ];
+			updateFlag = updateFlags[ key ];
+
+			if ( resetFlags[ key ] === true || updateFlag === true ) {
+				this._assignValue( key, index );
+				this._updateAttributeUpdateRange( key, index );
+
+				if ( updateFlag === true && updateCounts[ key ] === this.particleCount ) {
+					updateFlags[ key ] = false;
+					updateCounts[ key ] = 0.0;
+				}
+				else if ( updateFlag == true ) {
+					++updateCounts[ key ];
+				}
+			}
+		}
+	}
+
+	_updateAttributeUpdateRange( attr, i ) {
+		var ranges = this.bufferUpdateRanges[ attr ];
+
+		ranges.min = Math.min( i, ranges.min );
+		ranges.max = Math.max( i, ranges.max );
+	}
+
+	_resetBufferRanges() {
+		const ranges = this.bufferUpdateRanges,
+			keys = this.bufferUpdateKeys;
+		let i = this.bufferUpdateCount - 1,
+			key;
+
+		for ( i; i >= 0; --i ) {
+			key = keys[ i ];
+			ranges[ key ].min = Number.POSITIVE_INFINITY;
+			ranges[ key ].max = Number.NEGATIVE_INFINITY;
+		}
+	}
+
+	_onRemove() {
+		// Reset any properties of the emitter that were set by
+		// a group when it was added.
+		this.particlesPerSecond = 0;
+		this.attributeOffset = 0;
+		this.activationIndex = 0;
+		this.activeParticleCount = 0;
+		this.group = null;
+		this.attributes = null;
+		this.paramsArray = null;
+		this.age = 0.0;
+	}
+
+	_decrementParticleCount() {
+		--this.activeParticleCount;
+
+		// TODO:
+		//  - Trigger event if count === 0.
+	}
+
+	_incrementParticleCount() {
+		'use strict';
+		++this.activeParticleCount;
+
+		// TODO:
+		//  - Trigger event if count === this.particleCount.
+	}
+
+	_checkParticleAges( start, end, params, dt ) {
+		for ( let i = end - 1, index, maxAge, age, alive; i >= start; --i ) {
+			index = i * 4;
+
+			alive = params[ index ];
+
+			if ( alive === 0.0 ) {
+				continue;
+			}
+
+			// Increment age
+			age = params[ index + 1 ];
+			maxAge = params[ index + 2 ];
+
+			if ( this.direction === 1 ) {
+				age += dt;
+
+				if ( age >= maxAge ) {
+					age = 0.0;
+					alive = 0.0;
+					this._decrementParticleCount();
+				}
+			}
+			else {
+				age -= dt;
+
+				if ( age <= 0.0 ) {
+					age = maxAge;
+					alive = 0.0;
+					this._decrementParticleCount();
+				}
+			}
+
+			params[ index ] = alive;
+			params[ index + 1 ] = age;
+
+			this._updateAttributeUpdateRange( 'params', i );
+		}
+	}
+
+	_activateParticles( activationStart, activationEnd, params, dtPerParticle ) {
+		const direction = this.direction;
+
+		for ( let i = activationStart, index, dtValue; i < activationEnd; ++i ) {
+			index = i * 4;
+
+			// Don't re-activate particles that aren't dead yet.
+			// if ( params[ index ] !== 0.0 && ( this.particleCount !== 1 || this.activeMultiplier !== 1 ) ) {
+			//     continue;
+			// }
+
+			if ( params[ index ] != 0.0 && this.particleCount !== 1 ) {
+				continue;
+			}
+
+			// Increment the active particle count.
+			this._incrementParticleCount();
+
+			// Mark the particle as alive.
+			params[ index ] = 1.0;
+
+			// Reset the particle
+			this._resetParticle( i );
+
+			// Move each particle being activated to
+			// it's actual position in time.
+			//
+			// This stops particles being 'clumped' together
+			// when frame rates are on the lower side of 60fps
+			// or not constant (a very real possibility!)
+			dtValue = dtPerParticle * ( i - activationStart );
+			params[ index + 1 ] = direction === -1 ? params[ index + 2 ] - dtValue : dtValue;
+
+			this._updateAttributeUpdateRange( 'params', i );
+		}
+	}
+
+	/**
+	 * Simulates one frame's worth of particles, updating particles
+	 * that are already alive, and marking ones that are currently dead
+	 * but should be alive as alive.
+	 *
+	 * If the emitter is marked as static, then this function will do nothing.
+	 *
+	 * @param  {Number} dt The number of seconds to simulate (deltaTime)
+	 */
+	tick( dt ) {
+		if ( this.isStatic ) {
+			return;
+		}
+
+		if ( this.paramsArray === null ) {
+			this.paramsArray = this.attributes.params.typedArray.array;
+		}
+
+		const start = this.attributeOffset,
+			end = start + this.particleCount,
+			params = this.paramsArray, // vec3( alive, age, maxAge, wiggle )
+			ppsDt = this.particlesPerSecond * this.activeMultiplier * dt,
+			activationIndex = this.activationIndex;
+
+		// Reset the buffer update indices.
+		this._resetBufferRanges();
+
+		// Increment age for those particles that are alive,
+		// and kill off any particles whose age is over the limit.
+		this._checkParticleAges( start, end, params, dt );
+
+		// If the emitter is dead, reset the age of the emitter to zero,
+		// ready to go again if required
+		if ( this.alive === false ) {
+			this.age = 0.0;
+			return;
+		}
+
+		// If the emitter has a specified lifetime and we've exceeded it,
+		// mark the emitter as dead.
+		if ( this.duration !== null && this.age > this.duration ) {
+			this.alive = false;
+			this.age = 0.0;
+			return;
+		}
+
+
+		const activationStart = this.particleCount === 1 ? activationIndex : ( activationIndex | 0 ),
+			activationEnd = Math.min( activationStart + ppsDt, this.activationEnd ),
+			activationCount = activationEnd - this.activationIndex | 0,
+			dtPerParticle = activationCount > 0 ? dt / activationCount : 0;
+
+		this._activateParticles( activationStart, activationEnd, params, dtPerParticle );
+
+		// Move the activation window forward, soldier.
+		this.activationIndex += ppsDt;
+
+		if ( this.activationIndex > end ) {
+			this.activationIndex = start;
+		}
+
+
+		// Increment the age of the emitter.
+		this.age += dt;
+	}
+
+	/**
+	 * Resets all the emitter's particles to their start positions
+	 * and marks the particles as dead if the `force` argument is
+	 * true.
+	 *
+	 * @param  {Boolean} [force=undefined] If true, all particles will be marked as dead instantly.
+	 * @return {Emitter}       This emitter instance.
+	 */
+	reset( force ) {
+		this.age = 0.0;
+		this.alive = false;
+
+		if ( force === true ) {
+			const start = this.attributeOffset,
+				end = start + this.particleCount,
+				array = this.paramsArray,
+				attr = this.attributes.params.bufferAttribute;
+
+			for ( let i = end - 1, index; i >= start; --i ) {
+				index = i * 4;
+
+				array[ index ] = 0.0;
+				array[ index + 1 ] = 0.0;
+			}
+
+			attr.updateRange.offset = 0;
+			attr.updateRange.count = -1;
+			attr.needsUpdate = true;
+		}
+
+		return this;
+	}
+
+	/**
+	 * Enables the emitter. If not already enabled, the emitter
+	 * will start emitting particles.
+	 *
+	 * @return {Emitter} This emitter instance.
+	 */
+	enable() {
+		this.alive = true;
+		return this;
+	}
+
+	/**
+	 * Disables th emitter, but does not instantly remove it's
+	 * particles fromt the scene. When called, the emitter will be
+	 * 'switched off' and just stop emitting. Any particle's alive will
+	 * be allowed to finish their lifecycle.
+	 *
+	 * @return {Emitter} This emitter instance.
+	 */
+	disable() {
+		this.alive = false;
+		return this;
+	}
+
+	/**
+	 * Remove this emitter from it's parent group (if it has been added to one).
+	 * Delgates to Group.prototype.removeEmitter().
+	 *
+	 * When called, all particle's belonging to this emitter will be instantly
+	 * removed from the scene.
+	 *
+	 * @return {Emitter} This emitter instance.
+	 *
+	 * @see Group.prototype.removeEmitter
+	 */
+	remove() {
+		if ( this.group !== null ) {
+			this.group.removeEmitter( this );
+		}
+		else {
+			console.error( 'Emitter does not belong to a group, cannot remove.' );
+		}
+
+		return this;
+	}
+}
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.4 on Sun Jun 28 2020 15:22:31 GMT+0100 (British Summer Time) +
+ + + + + diff --git a/docs/api/core_Group.js.html b/docs/api/core_Group.js.html new file mode 100644 index 0000000..37b4d4f --- /dev/null +++ b/docs/api/core_Group.js.html @@ -0,0 +1,775 @@ + + + + + JSDoc: Source: core/Group.js + + + + + + + + + + +
+ +

Source: core/Group.js

+ + + + + + +
+
+
import * as THREE from 'three';
+import valueTypes from '@/constants/valueTypes';
+import globals from '@/constants/globals';
+import utils from './utils';
+import ShaderAttribute from '@/helpers/ShaderAttribute';
+import shaders from '@/shaders/shaders';
+import Emitter from './Emitter';
+
+const HAS_OWN = Object.prototype.hasOwnProperty;
+
+/**
+ * An SPE.Group instance.
+ * @typedef {Object} Group
+ * @see SPE.Group
+ */
+
+/**
+ * A map of options to configure an SPE.Group instance.
+ * @typedef {Object} GroupOptions
+ *
+ * @property {Object} texture An object describing the texture used by the group.
+ *
+ * @property {Object} texture.value An instance of THREE.Texture.
+ *
+ * @property {Object=} texture.frames A THREE.Vector2 instance describing the number
+ *                                    of frames on the x- and y-axis of the given texture.
+ *                                    If not provided, the texture will NOT be treated as
+ *                                    a sprite-sheet and as such will NOT be animated.
+ *
+ * @property {Number} [texture.frameCount=texture.frames.x * texture.frames.y] The total number of frames in the sprite-sheet.
+ *                                                                   Allows for sprite-sheets that don't fill the entire
+ *                                                                   texture.
+ *
+ * @property {Number} texture.loop The number of loops through the sprite-sheet that should
+ *                                 be performed over the course of a single particle's lifetime.
+ *
+ * @property {Number} fixedTimeStep If no `dt` (or `deltaTime`) value is passed to this group's
+ *                                  `tick()` function, this number will be used to move the particle
+ *                                  simulation forward. Value in SECONDS.
+ *
+ * @property {Boolean} hasPerspective Whether the distance a particle is from the camera should affect
+ *                                    the particle's size.
+ *
+ * @property {Boolean} colorize Whether the particles in this group should be rendered with color, or
+ *                              whether the only color of particles will come from the provided texture.
+ *
+ * @property {Number} blending One of Three.js's blending modes to apply to this group's `ShaderMaterial`.
+ *
+ * @property {Boolean} transparent Whether these particle's should be rendered with transparency.
+ *
+ * @property {Number} alphaTest Sets the alpha value to be used when running an alpha test on the `texture.value` property. Value between 0 and 1.
+ *
+ * @property {Boolean} depthWrite Whether rendering the group has any effect on the depth buffer.
+ *
+ * @property {Boolean} depthTest Whether to have depth test enabled when rendering this group.
+ *
+ * @property {Boolean} fog Whether this group's particles should be affected by their scene's fog.
+ *
+ * @property {Number} scale The scale factor to apply to this group's particle sizes. Useful for
+ *                          setting particle sizes to be relative to renderer size.
+ */
+
+
+/**
+ * The SPE.Group class. Creates a new group, containing a material, geometry, and mesh.
+ *
+ * @constructor
+ * @param {GroupOptions} options A map of options to configure the group instance.
+ */
+export default class Group {
+	constructor( opts ) {
+		// Ensure we have a map of options to play with
+		const options = utils.ensureTypedArg( opts, valueTypes.OBJECT, {} );
+		options.texture = utils.ensureTypedArg( options.texture, valueTypes.OBJECT, {} );
+
+		// Assign a UUID to this instance
+		this.uuid = THREE.Math.generateUUID();
+
+		// If no `deltaTime` value is passed to the `SPE.Group.tick` function,
+		// the value of this property will be used to advance the simulation.
+		this.fixedTimeStep = utils.ensureTypedArg( options.fixedTimeStep, valueTypes.NUMBER, 0.016 );
+
+		// Set properties used in the uniforms map, starting with the
+		// texture stuff.
+		this.texture = utils.ensureInstanceOf( options.texture.value, THREE.Texture, null );
+		this.textureFrames = utils.ensureInstanceOf( options.texture.frames, THREE.Vector2, new THREE.Vector2( 1, 1 ) );
+		this.textureFrameCount = utils.ensureTypedArg( options.texture.frameCount, valueTypes.NUMBER, this.textureFrames.x * this.textureFrames.y );
+		this.textureLoop = utils.ensureTypedArg( options.texture.loop, valueTypes.NUMBER, 1 );
+		this.textureFrames.max( new THREE.Vector2( 1, 1 ) );
+
+		this.hasPerspective = utils.ensureTypedArg( options.hasPerspective, valueTypes.BOOLEAN, true );
+		this.colorize = utils.ensureTypedArg( options.colorize, valueTypes.BOOLEAN, true );
+
+		this.maxParticleCount = utils.ensureTypedArg( options.maxParticleCount, valueTypes.NUMBER, null );
+
+
+		// Set properties used to define the ShaderMaterial's appearance.
+		this.blending = utils.ensureTypedArg( options.blending, valueTypes.NUMBER, THREE.AdditiveBlending );
+		this.transparent = utils.ensureTypedArg( options.transparent, valueTypes.BOOLEAN, true );
+		this.alphaTest = parseFloat( utils.ensureTypedArg( options.alphaTest, valueTypes.NUMBER, 0.0 ) );
+		this.depthWrite = utils.ensureTypedArg( options.depthWrite, valueTypes.BOOLEAN, false );
+		this.depthTest = utils.ensureTypedArg( options.depthTest, valueTypes.BOOLEAN, true );
+		this.fog = utils.ensureTypedArg( options.fog, valueTypes.BOOLEAN, true );
+		this.scale = utils.ensureTypedArg( options.scale, valueTypes.NUMBER, 300 );
+
+		// Where emitter's go to curl up in a warm blanket and live
+		// out their days.
+		this.emitters = [];
+		this.emitterIDs = [];
+
+		// Create properties for use by the emitter pooling functions.
+		this._pool = [];
+		this._poolCreationSettings = null;
+		this._createNewWhenPoolEmpty = 0;
+
+		// Whether all attributes should be forced to updated
+		// their entire buffer contents on the next tick.
+		//
+		// Used when an emitter is removed.
+		this._attributesNeedRefresh = false;
+		this._attributesNeedDynamicReset = false;
+
+		this.particleCount = 0;
+
+
+		// Map of uniforms to be applied to the ShaderMaterial instance.
+		this.uniforms = {
+			tex: {
+				type: 't',
+				value: this.texture,
+			},
+			textureAnimation: {
+				type: 'v4',
+				value: new THREE.Vector4(
+					this.textureFrames.x,
+					this.textureFrames.y,
+					this.textureFrameCount,
+					Math.max( Math.abs( this.textureLoop ), 1.0 )
+				),
+			},
+			fogColor: {
+				type: 'c',
+				value: this.fog ? new THREE.Color() : null,
+			},
+			fogNear: {
+				type: 'f',
+				value: 10,
+			},
+			fogFar: {
+				type: 'f',
+				value: 200,
+			},
+			fogDensity: {
+				type: 'f',
+				value: 0.5,
+			},
+			deltaTime: {
+				type: 'f',
+				value: 0,
+			},
+			runTime: {
+				type: 'f',
+				value: 0,
+			},
+			scale: {
+				type: 'f',
+				value: this.scale,
+			},
+		};
+
+		// Add some defines into the mix...
+		this.defines = {
+			HAS_PERSPECTIVE: this.hasPerspective,
+			COLORIZE: this.colorize,
+			VALUE_OVER_LIFETIME_LENGTH: globals.valueOverLifetimeLength,
+
+			SHOULD_ROTATE_TEXTURE: false,
+			SHOULD_ROTATE_PARTICLES: false,
+			SHOULD_WIGGLE_PARTICLES: false,
+
+			SHOULD_CALCULATE_SPRITE: this.textureFrames.x > 1 || this.textureFrames.y > 1,
+		};
+
+		// Map of all attributes to be applied to the particles.
+		//
+		// See `ShaderAttribute` for a bit more info on this.
+		this.attributes = {
+			position: new ShaderAttribute( 'v3', true ),
+			acceleration: new ShaderAttribute( 'v4', true ), // w component is drag
+			velocity: new ShaderAttribute( 'v3', true ),
+			rotation: new ShaderAttribute( 'v4', true ),
+			rotationCenter: new ShaderAttribute( 'v3', true ),
+			params: new ShaderAttribute( 'v4', true ), // Holds (alive, age, delay, wiggle)
+			size: new ShaderAttribute( 'v4', true ),
+			angle: new ShaderAttribute( 'v4', true ),
+			color: new ShaderAttribute( 'v4', true ),
+			opacity: new ShaderAttribute( 'v4', true ),
+		};
+
+		this.attributeKeys = Object.keys( this.attributes );
+		this.attributeCount = this.attributeKeys.length;
+
+		// Create the ShaderMaterial instance that'll help render the
+		// particles.
+		this.material = new THREE.ShaderMaterial( {
+			uniforms: this.uniforms,
+			vertexShader: shaders.vertex,
+			fragmentShader: shaders.fragment,
+			blending: this.blending,
+			transparent: this.transparent,
+			alphaTest: this.alphaTest,
+			depthWrite: this.depthWrite,
+			depthTest: this.depthTest,
+			defines: this.defines,
+			fog: this.fog,
+		} );
+
+		// Create the BufferGeometry and Points instances, ensuring
+		// the geometry and material are given to the latter.
+		this.geometry = new THREE.BufferGeometry();
+		this.mesh = new THREE.Points( this.geometry, this.material );
+
+		if ( this.maxParticleCount === null ) {
+			console.warn( 'SPE.Group: No maxParticleCount specified. Adding emitters after rendering will probably cause errors.' );
+		}
+	}
+
+	_updateDefines() {
+		const emitters = this.emitters,
+			defines = this.defines;
+		let emitter,
+			i = emitters.length - 1;
+
+		for ( i; i >= 0; --i ) {
+			emitter = emitters[ i ];
+
+			// Only do angle calculation if there's no spritesheet defined.
+			//
+			// Saves calculations being done and then overwritten in the shaders.
+			if ( !defines.SHOULD_CALCULATE_SPRITE ) {
+				defines.SHOULD_ROTATE_TEXTURE = defines.SHOULD_ROTATE_TEXTURE || !!Math.max(
+					Math.max.apply( null, emitter.angle.value ),
+					Math.max.apply( null, emitter.angle.spread )
+				);
+			}
+
+			defines.SHOULD_ROTATE_PARTICLES = defines.SHOULD_ROTATE_PARTICLES || !!Math.max(
+				emitter.rotation.angle,
+				emitter.rotation.angleSpread
+			);
+
+			defines.SHOULD_WIGGLE_PARTICLES = defines.SHOULD_WIGGLE_PARTICLES || !!Math.max(
+				emitter.wiggle.value,
+				emitter.wiggle.spread
+			);
+		}
+
+		this.material.needsUpdate = true;
+	}
+
+	_applyAttributesToGeometry() {
+		const attributes = this.attributes,
+			geometry = this.geometry,
+			geometryAttributes = geometry.attributes;
+		let attribute,
+			geometryAttribute;
+
+		// Loop through all the shader attributes and assign (or re-assign)
+		// typed array buffers to each one.
+		for ( const attr in attributes ) {
+			if ( HAS_OWN.call( attributes, attr ) ) {
+				attribute = attributes[ attr ];
+				geometryAttribute = geometryAttributes[ attr ];
+
+				// Update the array if this attribute exists on the geometry.
+				//
+				// This needs to be done because the attribute's typed array might have
+				// been resized and reinstantiated, and might now be looking at a
+				// different ArrayBuffer, so reference needs updating.
+				if ( geometryAttribute ) {
+					geometryAttribute.array = attribute.typedArray.array;
+				}
+
+				// // Add the attribute to the geometry if it doesn't already exist.
+				else {
+					geometry.addAttribute( attr, attribute.bufferAttribute );
+				}
+
+				// Mark the attribute as needing an update the next time a frame is rendered.
+				attribute.bufferAttribute.needsUpdate = true;
+			}
+		}
+
+		// Mark the draw range on the geometry. This will ensure
+		// only the values in the attribute buffers that are
+		// associated with a particle will be used in THREE's
+		// render cycle.
+		this.geometry.setDrawRange( 0, this.particleCount );
+	}
+
+	/**
+     * Adds an SPE.Emitter instance to this group, creating particle values and
+     * assigning them to this group's shader attributes.
+     *
+     * @param {Emitter} emitter The emitter to add to this group.
+     */
+	addEmitter( emitter ) {
+		// Ensure an actual emitter instance is passed here.
+		//
+		// Decided not to throw here, just in case a scene's
+		// rendering would be paused. Logging an error instead
+		// of stopping execution if exceptions aren't caught.
+		if ( emitter instanceof Emitter === false ) {
+			console.error( '`emitter` argument must be instance of Emitter. Was provided with:', emitter );
+			return;
+		}
+
+		// If the emitter already exists as a member of this group, then
+		// stop here, we don't want to add it again.
+		else if ( this.emitterIDs.indexOf( emitter.uuid ) > -1 ) {
+			console.error( 'Emitter already exists in this group. Will not add again.' );
+			return;
+		}
+
+		// And finally, if the emitter is a member of another group,
+		// don't add it to this group.
+		else if ( emitter.group !== null ) {
+			console.error( 'Emitter already belongs to another group. Will not add to requested group.' );
+			return;
+		}
+
+		const attributes = this.attributes,
+			start = this.particleCount,
+			end = start + emitter.particleCount;
+
+		// Update this group's particle count.
+		this.particleCount = end;
+
+		// Emit a warning if the emitter being added will exceed the buffer sizes specified.
+		if ( this.maxParticleCount !== null && this.particleCount > this.maxParticleCount ) {
+			console.warn( 'SPE.Group: maxParticleCount exceeded. Requesting', this.particleCount, 'particles, can support only', this.maxParticleCount );
+		}
+
+
+		// Set the `particlesPerSecond` value (PPS) on the emitter.
+		// It's used to determine how many particles to release
+		// on a per-frame basis.
+		emitter._calculatePPSValue( emitter.maxAge._value + emitter.maxAge._spread );
+		emitter._setBufferUpdateRanges( this.attributeKeys );
+
+		// Store the offset value in the TypedArray attributes for this emitter.
+		emitter._setAttributeOffset( start );
+
+		// Save a reference to this group on the emitter so it knows
+		// where it belongs.
+		emitter.group = this;
+
+		// Store reference to the attributes on the emitter for
+		// easier access during the emitter's tick function.
+		emitter.attributes = this.attributes;
+
+
+
+		// Ensure the attributes and their BufferAttributes exist, and their
+		// TypedArrays are of the correct size.
+		for ( const attr in attributes ) {
+			if ( HAS_OWN.call( attributes, attr ) ) {
+				// When creating a buffer, pass through the maxParticle count
+				// if one is specified.
+				attributes[ attr ]._createBufferAttribute(
+					this.maxParticleCount !== null ?
+						this.maxParticleCount :
+						this.particleCount
+				);
+			}
+		}
+
+		// Loop through each particle this emitter wants to have, and create the attributes values,
+		// storing them in the TypedArrays that each attribute holds.
+		for ( let i = start; i < end; ++i ) {
+			emitter._assignPositionValue( i );
+			emitter._assignForceValue( i, 'velocity' );
+			emitter._assignForceValue( i, 'acceleration' );
+			emitter._assignAbsLifetimeValue( i, 'opacity' );
+			emitter._assignAbsLifetimeValue( i, 'size' );
+			emitter._assignAngleValue( i );
+			emitter._assignRotationValue( i );
+			emitter._assignParamsValue( i );
+			emitter._assignColorValue( i );
+		}
+
+		// Update the geometry and make sure the attributes are referencing
+		// the typed arrays properly.
+		this._applyAttributesToGeometry();
+
+		// Store this emitter in this group's emitter's store.
+		this.emitters.push( emitter );
+		this.emitterIDs.push( emitter.uuid );
+
+		// Update certain flags to enable shader calculations only if they're necessary.
+		this._updateDefines( emitter );
+
+		// Update the material since defines might have changed
+		this.material.needsUpdate = true;
+		this.geometry.needsUpdate = true;
+		this._attributesNeedRefresh = true;
+
+		// Return the group to enable chaining.
+		return this;
+	}
+
+	/**
+     * Removes an Emitter instance from this group. When called,
+     * all particle's belonging to the given emitter will be instantly
+     * removed from the scene.
+     *
+     * @param {Emitter} emitter The emitter to add to this group.
+     */
+	removeEmitter( emitter ) {
+		const emitterIndex = this.emitterIDs.indexOf( emitter.uuid );
+
+		// Ensure an actual emitter instance is passed here.
+		//
+		// Decided not to throw here, just in case a scene's
+		// rendering would be paused. Logging an error instead
+		// of stopping execution if exceptions aren't caught.
+		if ( emitter instanceof Emitter === false ) {
+			console.error( '`emitter` argument must be instance of SPE.Emitter. Was provided with:', emitter );
+			return;
+		}
+
+		// Issue an error if the emitter isn't a member of this group.
+		else if ( emitterIndex === -1 ) {
+			console.error( 'Emitter does not exist in this group. Will not remove.' );
+			return;
+		}
+
+		// Kill all particles by marking them as dead
+		// and their age as 0.
+		const start = emitter.attributeOffset,
+			end = start + emitter.particleCount,
+			params = this.attributes.params.typedArray;
+
+		// Set alive and age to zero.
+		for ( let i = start; i < end; ++i ) {
+			params.array[ i * 4 ] = 0.0;
+			params.array[ i * 4 + 1 ] = 0.0;
+		}
+
+		// Remove the emitter from this group's "store".
+		this.emitters.splice( emitterIndex, 1 );
+		this.emitterIDs.splice( emitterIndex, 1 );
+
+		// Remove this emitter's attribute values from all shader attributes.
+		// The `.splice()` call here also marks each attribute's buffer
+		// as needing to update it's entire contents.
+		for ( const attr in this.attributes ) {
+			if ( HAS_OWN.call( this.attributes, attr ) ) {
+				this.attributes[ attr ].splice( start, end );
+			}
+		}
+
+		// Ensure this group's particle count is correct.
+		this.particleCount -= emitter.particleCount;
+
+		// Call the emitter's remove method.
+		emitter._onRemove();
+
+		// Set a flag to indicate that the attribute buffers should
+		// be updated in their entirety on the next frame.
+		this._attributesNeedRefresh = true;
+	}
+
+
+	/**
+     * Fetch a single emitter instance from the pool.
+     * If there are no objects in the pool, a new emitter will be
+     * created if specified.
+     *
+     * @return {Emitter|null}
+     */
+	getFromPool() {
+		const pool = this._pool,
+			createNew = this._createNewWhenPoolEmpty;
+
+		if ( pool.length ) {
+			return pool.pop();
+		}
+		else if ( createNew ) {
+			const emitter = new Emitter( this._poolCreationSettings );
+
+			this.addEmitter( emitter );
+
+			return emitter;
+		}
+
+		return null;
+	}
+
+
+	/**
+     * Release an emitter into the pool.
+     *
+     * @param  {ShaderParticleEmitter} emitter
+     * @return {Group} This group instance.
+     */
+	releaseIntoPool( emitter ) {
+		if ( emitter instanceof Emitter === false ) {
+			console.error( 'Argument is not instanceof Emitter:', emitter );
+			return;
+		}
+
+		emitter.reset();
+		this._pool.unshift( emitter );
+
+		return this;
+	}
+
+
+	/**
+     * Get the pool array
+     *
+     * @return {Array}
+     */
+	getPool() {
+		return this._pool;
+	}
+
+
+	/**
+     * Add a pool of emitters to this particle group
+     *
+     * @param {Number} numEmitters      The number of emitters to add to the pool.
+     * @param {EmitterOptions|Array} emitterOptions  An object, or array of objects, describing the options to pass to each emitter.
+     * @param {Boolean} createNew       Should a new emitter be created if the pool runs out?
+     * @return {Group} This group instance.
+     */
+	addPool( numEmitters, emitterOptions, createNew ) {
+		// Save relevant settings and flags.
+		this._poolCreationSettings = emitterOptions;
+		this._createNewWhenPoolEmpty = !!createNew;
+
+		// Create the emitters, add them to this group and the pool.
+		for ( let i = 0; i < numEmitters; ++i ) {
+			let args;
+
+			if ( Array.isArray( emitterOptions ) ) {
+				args = emitterOptions[ i ];
+			}
+			else {
+				args = emitterOptions;
+			}
+
+			const emitter = new Emitter( args );
+
+			this.addEmitter( emitter );
+			this.releaseIntoPool( emitter );
+		}
+
+		return this;
+	}
+
+
+
+	_triggerSingleEmitter( pos ) {
+		const emitter = this.getFromPool();
+
+		if ( emitter === null ) {
+			console.log( 'Group pool ran out.' );
+			return;
+		}
+
+		// TODO:
+		// - Make sure buffers are update with the new position.
+		if ( pos instanceof THREE.Vector3 ) {
+			emitter.position.value.copy( pos );
+
+			// Trigger the setter for this property to force an
+			// update to the emitter's position attribute.
+			emitter.position.value = emitter.position.value; // eslint-disable-line
+		}
+
+		emitter.enable();
+
+		setTimeout( () => {
+			emitter.disable();
+			this.releaseIntoPool( emitter );
+		}, ( Math.max( emitter.duration, ( emitter.maxAge.value + emitter.maxAge.spread ) ) ) * 1000 );
+
+		return this;
+	}
+
+
+	/**
+     * Set a given number of emitters as alive, with an optional position
+     * vector3 to move them to.
+     *
+     * @param  {Number} numEmitters The number of emitters to activate
+     * @param  {Object} [position=undefined] A THREE.Vector3 instance describing the position to activate the emitter(s) at.
+     * @return {Group} This group instance.
+     */
+	triggerPoolEmitter( numEmitters, position ) {
+		if ( typeof numEmitters === 'number' && numEmitters > 1 ) {
+			for ( let i = 0; i < numEmitters; ++i ) {
+				this._triggerSingleEmitter( position );
+			}
+		}
+		else {
+			this._triggerSingleEmitter( position );
+		}
+
+		return this;
+	}
+
+
+
+	_updateUniforms( dt ) {
+		this.uniforms.runTime.value += dt;
+		this.uniforms.deltaTime.value = dt;
+	}
+
+	_resetBufferRanges() {
+		const keys = this.attributeKeys,
+			attrs = this.attributes;
+		let i = this.attributeCount - 1;
+
+		for ( i; i >= 0; --i ) {
+			attrs[ keys[ i ] ].resetUpdateRange();
+		}
+	}
+
+
+	_updateBuffers( emitter ) {
+		const keys = this.attributeKeys,
+			attrs = this.attributes,
+			emitterRanges = emitter.bufferUpdateRanges;
+		let key,
+			emitterAttr,
+			attr;
+
+		for ( let i = this.attributeCount - 1; i >= 0; --i ) {
+			key = keys[ i ];
+			emitterAttr = emitterRanges[ key ];
+			attr = attrs[ key ];
+			attr.setUpdateRange( emitterAttr.min, emitterAttr.max );
+			attr.flagUpdate();
+		}
+	}
+
+
+	/**
+     * Simulate all the emitter's belonging to this group, updating
+     * attribute values along the way.
+     * @param  {Number} [dt=Group's `fixedTimeStep` value] The number of seconds to simulate the group's emitters for (deltaTime)
+     */
+	tick( dt ) {
+		const emitters = this.emitters,
+			numEmitters = emitters.length,
+			deltaTime = dt || this.fixedTimeStep,
+			keys = this.attributeKeys,
+			attrs = this.attributes;
+
+		// Update uniform values.
+		this._updateUniforms( deltaTime );
+
+		// Reset buffer update ranges on the shader attributes.
+		this._resetBufferRanges();
+
+
+		// If nothing needs updating, then stop here.
+		if (
+			numEmitters === 0 &&
+            this._attributesNeedRefresh === false &&
+            this._attributesNeedDynamicReset === false
+		) {
+			return;
+		}
+
+		// Loop through each emitter in this group and
+		// simulate it, then update the shader attribute
+		// buffers.
+		for ( let i = 0, emitter; i < numEmitters; ++i ) {
+			emitter = emitters[ i ];
+			emitter.tick( deltaTime );
+			this._updateBuffers( emitter );
+		}
+
+		// If the shader attributes have been refreshed,
+		// then the dynamic properties of each buffer
+		// attribute will need to be reset back to
+		// what they should be.
+		if ( this._attributesNeedDynamicReset === true ) {
+			for ( let i = this.attributeCount - 1; i >= 0; --i ) {
+				attrs[ keys[ i ] ].resetDynamic();
+			}
+
+			this._attributesNeedDynamicReset = false;
+		}
+
+		// If this group's shader attributes need a full refresh
+		// then mark each attribute's buffer attribute as
+		// needing so.
+		if ( this._attributesNeedRefresh === true ) {
+			for ( let i = this.attributeCount - 1; i >= 0; --i ) {
+				attrs[ keys[ i ] ].forceUpdateAll();
+			}
+
+			this._attributesNeedRefresh = false;
+			this._attributesNeedDynamicReset = true;
+		}
+	}
+
+
+	/**
+     * Dipose the geometry and material for the group.
+     *
+     * @return {Group} Group instance.
+     */
+	dispose() {
+		this.geometry.dispose();
+		this.material.dispose();
+		return this;
+	}
+}
+
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.4 on Sun Jun 28 2020 15:22:31 GMT+0100 (British Summer Time) +
+ + + + + diff --git a/docs/api/core_SPE.Emitter.js.html b/docs/api/core_SPE.Emitter.js.html deleted file mode 100644 index d677e2a..0000000 --- a/docs/api/core_SPE.Emitter.js.html +++ /dev/null @@ -1,1041 +0,0 @@ - - - - - JSDoc: Source: core/SPE.Emitter.js - - - - - - - - - - -
- -

Source: core/SPE.Emitter.js

- - - - - - -
-
-
/**
- * An SPE.Emitter instance.
- * @typedef {Object} Emitter
- * @see SPE.Emitter
- */
-
-/**
- * A map of options to configure an SPE.Emitter instance.
- *
- * @typedef {Object} EmitterOptions
- *
- * @property {distribution} [type=BOX] The default distribution this emitter should use to control
- *                         its particle's spawn position and force behaviour.
- *                         Must be an SPE.distributions.* value.
- *
- *
- * @property {Number} [particleCount=100] The total number of particles this emitter will hold. NOTE: this is not the number
- *                                  of particles emitted in a second, or anything like that. The number of particles
- *                                  emitted per-second is calculated by particleCount / maxAge (approximately!)
- *
- * @property {Number|null} [duration=null] The duration in seconds that this emitter should live for. If not specified, the emitter
- *                                         will emit particles indefinitely.
- *                                         NOTE: When an emitter is older than a specified duration, the emitter is NOT removed from
- *                                         it's group, but rather is just marked as dead, allowing it to be reanimated at a later time
- *                                         using `SPE.Emitter.prototype.enable()`.
- *
- * @property {Boolean} [isStatic=false] Whether this emitter should be not be simulated (true).
- * @property {Boolean} [activeMultiplier=1] A value between 0 and 1 describing what percentage of this emitter's particlesPerSecond should be
- *                                          emitted, where 0 is 0%, and 1 is 100%.
- *                                          For example, having an emitter with 100 particles, a maxAge of 2, yields a particlesPerSecond
- *                                          value of 50. Setting `activeMultiplier` to 0.5, then, will only emit 25 particles per second (0.5 = 50%).
- *                                          Values greater than 1 will emulate a burst of particles, causing the emitter to run out of particles
- *                                          before it's next activation cycle.
- *
- * @property {Boolean} [direction=1] The direction of the emitter. If value is `1`, emitter will start at beginning of particle's lifecycle.
- *                                   If value is `-1`, emitter will start at end of particle's lifecycle and work it's way backwards.
- *
- * @property {Object} [maxAge={}] An object describing the particle's maximum age in seconds.
- * @property {Number} [maxAge.value=2] A number between 0 and 1 describing the amount of maxAge to apply to all particles.
- * @property {Number} [maxAge.spread=0] A number describing the maxAge variance on a per-particle basis.
- *
- *
- * @property {Object} [position={}] An object describing this emitter's position.
- * @property {Object} [position.value=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's base position.
- * @property {Object} [position.spread=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's position variance on a per-particle basis.
- *                                                          Note that when using a SPHERE or DISC distribution, only the x-component
- *                                                          of this vector is used.
- * @property {Object} [position.spreadClamp=new THREE.Vector3()] A THREE.Vector3 instance describing the numeric multiples the particle's should
- *                                                               be spread out over.
- *                                                               Note that when using a SPHERE or DISC distribution, only the x-component
- *                                                               of this vector is used.
- * @property {Number} [position.radius=10] This emitter's base radius.
- * @property {Object} [position.radiusScale=new THREE.Vector3()] A THREE.Vector3 instance describing the radius's scale in all three axes. Allows a SPHERE or DISC to be squashed or stretched.
- * @property {distribution} [position.distribution=value of the `type` option.] A specific distribution to use when radiusing particles. Overrides the `type` option.
- * @property {Boolean} [position.randomise=false] When a particle is re-spawned, whether it's position should be re-randomised or not. Can incur a performance hit.
- *
- *
- * @property {Object} [velocity={}] An object describing this particle velocity.
- * @property {Object} [velocity.value=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's base velocity.
- * @property {Object} [velocity.spread=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's velocity variance on a per-particle basis.
- *                                                          Note that when using a SPHERE or DISC distribution, only the x-component
- *                                                          of this vector is used.
- * @property {distribution} [velocity.distribution=value of the `type` option.] A specific distribution to use when calculating a particle's velocity. Overrides the `type` option.
- * @property {Boolean} [velocity.randomise=false] When a particle is re-spawned, whether it's velocity should be re-randomised or not. Can incur a performance hit.
- *
- *
- * @property {Object} [acceleration={}] An object describing this particle's acceleration.
- * @property {Object} [acceleration.value=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's base acceleration.
- * @property {Object} [acceleration.spread=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's acceleration variance on a per-particle basis.
- *                           Note that when using a SPHERE or DISC distribution, only the x-component
- *                           of this vector is used.
- * @property {distribution} [acceleration.distribution=value of the `type` option.] A specific distribution to use when calculating a particle's acceleration. Overrides the `type` option.
- * @property {Boolean} [acceleration.randomise=false] When a particle is re-spawned, whether it's acceleration should be re-randomised or not. Can incur a performance hit.
- *
- *
- * @property {Object} [drag={}] An object describing this particle drag. Drag is applied to both velocity and acceleration values.
- * @property {Number} [drag.value=0] A number between 0 and 1 describing the amount of drag to apply to all particles.
- * @property {Number} [drag.spread=0] A number describing the drag variance on a per-particle basis.
- * @property {Boolean} [drag.randomise=false] When a particle is re-spawned, whether it's drag should be re-randomised or not. Can incur a performance hit.
- *
- *
- * @property {Object} [wiggle={}] This is quite a fun one! The values of this object will determine whether a particle will wiggle, or jiggle, or wave,
- *                                or shimmy, or waggle, or... Well you get the idea. The wiggle is calculated over-time, meaning that a particle will
- *                                start off with no wiggle, and end up wiggling about with the distance of the `value` specified by the time it dies.
- *                                It's quite handy to simulate fire embers, or similar effects where the particle's position should slightly change over
- *                                time, and such change isn't easily controlled by rotation, velocity, or acceleration. The wiggle is a combination of sin and cos calculations, so is circular in nature.
- * @property {Number} [wiggle.value=0] A number describing the amount of wiggle to apply to all particles. It's measured in distance.
- * @property {Number} [wiggle.spread=0] A number describing the wiggle variance on a per-particle basis.
- *
- *
- * @property {Object} [rotation={}] An object describing this emitter's rotation. It can either be static, or set to rotate from 0radians to the value of `rotation.value`
- *                                  over a particle's lifetime. Rotation values affect both a particle's position and the forces applied to it.
- * @property {Object} [rotation.axis=new THREE.Vector3(0, 1, 0)] A THREE.Vector3 instance describing this emitter's axis of rotation.
- * @property {Object} [rotation.axisSpread=new THREE.Vector3()] A THREE.Vector3 instance describing the amount of variance to apply to the axis of rotation on
- *                                                              a per-particle basis.
- * @property {Number} [rotation.angle=0] The angle of rotation, given in radians. If `rotation.static` is true, the emitter will start off rotated at this angle, and stay as such.
- *                                       Otherwise, the particles will rotate from 0radians to this value over their lifetimes.
- * @property {Number} [rotation.angleSpread=0] The amount of variance in each particle's rotation angle.
- * @property {Boolean} [rotation.static=false] Whether the rotation should be static or not.
- * @property {Object} [rotation.center=The value of `position.value`] A THREE.Vector3 instance describing the center point of rotation.
- * @property {Boolean} [rotation.randomise=false] When a particle is re-spawned, whether it's rotation should be re-randomised or not. Can incur a performance hit.
- *
- *
- * @property {Object} [color={}] An object describing a particle's color. This property is a "value-over-lifetime" property, meaning an array of values and spreads can be
- *                               given to describe specific value changes over a particle's lifetime.
- *                               Depending on the value of SPE.valueOverLifetimeLength, if arrays of THREE.Color instances are given, then the array will be interpolated to
- *                               have a length matching the value of SPE.valueOverLifetimeLength.
- * @property {Object} [color.value=new THREE.Color()] Either a single THREE.Color instance, or an array of THREE.Color instances to describe the color of a particle over it's lifetime.
- * @property {Object} [color.spread=new THREE.Vector3()] Either a single THREE.Vector3 instance, or an array of THREE.Vector3 instances to describe the color variance of a particle over it's lifetime.
- * @property {Boolean} [color.randomise=false] When a particle is re-spawned, whether it's color should be re-randomised or not. Can incur a performance hit.
- *
- *
- * @property {Object} [opacity={}] An object describing a particle's opacity. This property is a "value-over-lifetime" property, meaning an array of values and spreads can be
- *                               given to describe specific value changes over a particle's lifetime.
- *                               Depending on the value of SPE.valueOverLifetimeLength, if arrays of numbers are given, then the array will be interpolated to
- *                               have a length matching the value of SPE.valueOverLifetimeLength.
- * @property {Number} [opacity.value=1] Either a single number, or an array of numbers to describe the opacity of a particle over it's lifetime.
- * @property {Number} [opacity.spread=0] Either a single number, or an array of numbers to describe the opacity variance of a particle over it's lifetime.
- * @property {Boolean} [opacity.randomise=false] When a particle is re-spawned, whether it's opacity should be re-randomised or not. Can incur a performance hit.
- *
- *
- * @property {Object} [size={}] An object describing a particle's size. This property is a "value-over-lifetime" property, meaning an array of values and spreads can be
- *                               given to describe specific value changes over a particle's lifetime.
- *                               Depending on the value of SPE.valueOverLifetimeLength, if arrays of numbers are given, then the array will be interpolated to
- *                               have a length matching the value of SPE.valueOverLifetimeLength.
- * @property {Number} [size.value=1] Either a single number, or an array of numbers to describe the size of a particle over it's lifetime.
- * @property {Number} [size.spread=0] Either a single number, or an array of numbers to describe the size variance of a particle over it's lifetime.
- * @property {Boolean} [size.randomise=false] When a particle is re-spawned, whether it's size should be re-randomised or not. Can incur a performance hit.
- *
- *
- * @property {Object} [angle={}] An object describing a particle's angle. The angle is a 2d-rotation, measured in radians, applied to the particle's texture.
- *                               NOTE: if a particle's texture is a sprite-sheet, this value IS IGNORED.
- *                               This property is a "value-over-lifetime" property, meaning an array of values and spreads can be
- *                               given to describe specific value changes over a particle's lifetime.
- *                               Depending on the value of SPE.valueOverLifetimeLength, if arrays of numbers are given, then the array will be interpolated to
- *                               have a length matching the value of SPE.valueOverLifetimeLength.
- * @property {Number} [angle.value=0] Either a single number, or an array of numbers to describe the angle of a particle over it's lifetime.
- * @property {Number} [angle.spread=0] Either a single number, or an array of numbers to describe the angle variance of a particle over it's lifetime.
- * @property {Boolean} [angle.randomise=false] When a particle is re-spawned, whether it's angle should be re-randomised or not. Can incur a performance hit.
- *
- */
-
-/**
- * The SPE.Emitter class.
- *
- * @constructor
- *
- * @param {EmitterOptions} options A map of options to configure the emitter.
- */
-SPE.Emitter = function( options ) {
-    'use strict';
-
-    var utils = SPE.utils,
-        types = utils.types,
-        lifetimeLength = SPE.valueOverLifetimeLength;
-
-    // Ensure we have a map of options to play with,
-    // and that each option is in the correct format.
-    options = utils.ensureTypedArg( options, types.OBJECT, {} );
-    options.position = utils.ensureTypedArg( options.position, types.OBJECT, {} );
-    options.velocity = utils.ensureTypedArg( options.velocity, types.OBJECT, {} );
-    options.acceleration = utils.ensureTypedArg( options.acceleration, types.OBJECT, {} );
-    options.radius = utils.ensureTypedArg( options.radius, types.OBJECT, {} );
-    options.drag = utils.ensureTypedArg( options.drag, types.OBJECT, {} );
-    options.rotation = utils.ensureTypedArg( options.rotation, types.OBJECT, {} );
-    options.color = utils.ensureTypedArg( options.color, types.OBJECT, {} );
-    options.opacity = utils.ensureTypedArg( options.opacity, types.OBJECT, {} );
-    options.size = utils.ensureTypedArg( options.size, types.OBJECT, {} );
-    options.angle = utils.ensureTypedArg( options.angle, types.OBJECT, {} );
-    options.wiggle = utils.ensureTypedArg( options.wiggle, types.OBJECT, {} );
-    options.maxAge = utils.ensureTypedArg( options.maxAge, types.OBJECT, {} );
-
-    if ( options.onParticleSpawn ) {
-        console.warn( 'onParticleSpawn has been removed. Please set properties directly to alter values at runtime.' );
-    }
-
-    this.uuid = THREE.Math.generateUUID();
-
-    this.type = utils.ensureTypedArg( options.type, types.NUMBER, SPE.distributions.BOX );
-
-    // Start assigning properties...kicking it off with props that DON'T support values over
-    // lifetimes.
-    //
-    // Btw, values over lifetimes are just the new way of referring to *Start, *Middle, and *End.
-    this.position = {
-        _value: utils.ensureInstanceOf( options.position.value, THREE.Vector3, new THREE.Vector3() ),
-        _spread: utils.ensureInstanceOf( options.position.spread, THREE.Vector3, new THREE.Vector3() ),
-        _spreadClamp: utils.ensureInstanceOf( options.position.spreadClamp, THREE.Vector3, new THREE.Vector3() ),
-        _distribution: utils.ensureTypedArg( options.position.distribution, types.NUMBER, this.type ),
-        _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false ),
-        _radius: utils.ensureTypedArg( options.position.radius, types.NUMBER, 10 ),
-        _radiusScale: utils.ensureInstanceOf( options.position.radiusScale, THREE.Vector3, new THREE.Vector3( 1, 1, 1 ) ),
-        _distributionClamp: utils.ensureTypedArg( options.position.distributionClamp, types.NUMBER, 0 ),
-    };
-
-    this.velocity = {
-        _value: utils.ensureInstanceOf( options.velocity.value, THREE.Vector3, new THREE.Vector3() ),
-        _spread: utils.ensureInstanceOf( options.velocity.spread, THREE.Vector3, new THREE.Vector3() ),
-        _distribution: utils.ensureTypedArg( options.velocity.distribution, types.NUMBER, this.type ),
-        _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )
-    };
-
-    this.acceleration = {
-        _value: utils.ensureInstanceOf( options.acceleration.value, THREE.Vector3, new THREE.Vector3() ),
-        _spread: utils.ensureInstanceOf( options.acceleration.spread, THREE.Vector3, new THREE.Vector3() ),
-        _distribution: utils.ensureTypedArg( options.acceleration.distribution, types.NUMBER, this.type ),
-        _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )
-    };
-
-    this.drag = {
-        _value: utils.ensureTypedArg( options.drag.value, types.NUMBER, 0 ),
-        _spread: utils.ensureTypedArg( options.drag.spread, types.NUMBER, 0 ),
-        _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )
-    };
-
-    this.wiggle = {
-        _value: utils.ensureTypedArg( options.wiggle.value, types.NUMBER, 0 ),
-        _spread: utils.ensureTypedArg( options.wiggle.spread, types.NUMBER, 0 )
-    };
-
-    this.rotation = {
-        _axis: utils.ensureInstanceOf( options.rotation.axis, THREE.Vector3, new THREE.Vector3( 0.0, 1.0, 0.0 ) ),
-        _axisSpread: utils.ensureInstanceOf( options.rotation.axisSpread, THREE.Vector3, new THREE.Vector3() ),
-        _angle: utils.ensureTypedArg( options.rotation.angle, types.NUMBER, 0 ),
-        _angleSpread: utils.ensureTypedArg( options.rotation.angleSpread, types.NUMBER, 0 ),
-        _static: utils.ensureTypedArg( options.rotation.static, types.BOOLEAN, false ),
-        _center: utils.ensureInstanceOf( options.rotation.center, THREE.Vector3, this.position._value.clone() ),
-        _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )
-    };
-
-
-    this.maxAge = {
-        _value: utils.ensureTypedArg( options.maxAge.value, types.NUMBER, 2 ),
-        _spread: utils.ensureTypedArg( options.maxAge.spread, types.NUMBER, 0 )
-    };
-
-
-
-    // The following properties can support either single values, or an array of values that change
-    // the property over a particle's lifetime (value over lifetime).
-    this.color = {
-        _value: utils.ensureArrayInstanceOf( options.color.value, THREE.Color, new THREE.Color() ),
-        _spread: utils.ensureArrayInstanceOf( options.color.spread, THREE.Vector3, new THREE.Vector3() ),
-        _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )
-    };
-
-    this.opacity = {
-        _value: utils.ensureArrayTypedArg( options.opacity.value, types.NUMBER, 1 ),
-        _spread: utils.ensureArrayTypedArg( options.opacity.spread, types.NUMBER, 0 ),
-        _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )
-    };
-
-    this.size = {
-        _value: utils.ensureArrayTypedArg( options.size.value, types.NUMBER, 1 ),
-        _spread: utils.ensureArrayTypedArg( options.size.spread, types.NUMBER, 0 ),
-        _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )
-    };
-
-    this.angle = {
-        _value: utils.ensureArrayTypedArg( options.angle.value, types.NUMBER, 0 ),
-        _spread: utils.ensureArrayTypedArg( options.angle.spread, types.NUMBER, 0 ),
-        _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )
-    };
-
-
-    // Assign renaining option values.
-    this.particleCount = utils.ensureTypedArg( options.particleCount, types.NUMBER, 100 );
-    this.duration = utils.ensureTypedArg( options.duration, types.NUMBER, null );
-    this.isStatic = utils.ensureTypedArg( options.isStatic, types.BOOLEAN, false );
-    this.activeMultiplier = utils.ensureTypedArg( options.activeMultiplier, types.NUMBER, 1 );
-    this.direction = utils.ensureTypedArg( options.direction, types.NUMBER, 1 );
-
-    // Whether this emitter is alive or not.
-    this.alive = utils.ensureTypedArg( options.alive, types.BOOLEAN, true );
-
-
-    // The following properties are set internally and are not
-    // user-controllable.
-    this.particlesPerSecond = 0;
-
-    // The current particle index for which particles should
-    // be marked as active on the next update cycle.
-    this.activationIndex = 0;
-
-    // The offset in the typed arrays this emitter's
-    // particle's values will start at
-    this.attributeOffset = 0;
-
-    // The end of the range in the attribute buffers
-    this.attributeEnd = 0;
-
-
-
-    // Holds the time the emitter has been alive for.
-    this.age = 0.0;
-
-    // Holds the number of currently-alive particles
-    this.activeParticleCount = 0.0;
-
-    // Holds a reference to this emitter's group once
-    // it's added to one.
-    this.group = null;
-
-    // Holds a reference to this emitter's group's attributes object
-    // for easier access.
-    this.attributes = null;
-
-    // Holds a reference to the params attribute's typed array
-    // for quicker access.
-    this.paramsArray = null;
-
-    // A set of flags to determine whether particular properties
-    // should be re-randomised when a particle is reset.
-    //
-    // If a `randomise` property is given, this is preferred.
-    // Otherwise, it looks at whether a spread value has been
-    // given.
-    //
-    // It allows randomization to be turned off as desired. If
-    // all randomization is turned off, then I'd expect a performance
-    // boost as no attribute buffers (excluding the `params`)
-    // would have to be re-passed to the GPU each frame (since nothing
-    // except the `params` attribute would have changed).
-    this.resetFlags = {
-        // params: utils.ensureTypedArg( options.maxAge.randomise, types.BOOLEAN, !!options.maxAge.spread ) ||
-        //     utils.ensureTypedArg( options.wiggle.randomise, types.BOOLEAN, !!options.wiggle.spread ),
-        position: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false ) ||
-            utils.ensureTypedArg( options.radius.randomise, types.BOOLEAN, false ),
-        velocity: utils.ensureTypedArg( options.velocity.randomise, types.BOOLEAN, false ),
-        acceleration: utils.ensureTypedArg( options.acceleration.randomise, types.BOOLEAN, false ) ||
-            utils.ensureTypedArg( options.drag.randomise, types.BOOLEAN, false ),
-        rotation: utils.ensureTypedArg( options.rotation.randomise, types.BOOLEAN, false ),
-        rotationCenter: utils.ensureTypedArg( options.rotation.randomise, types.BOOLEAN, false ),
-        size: utils.ensureTypedArg( options.size.randomise, types.BOOLEAN, false ),
-        color: utils.ensureTypedArg( options.color.randomise, types.BOOLEAN, false ),
-        opacity: utils.ensureTypedArg( options.opacity.randomise, types.BOOLEAN, false ),
-        angle: utils.ensureTypedArg( options.angle.randomise, types.BOOLEAN, false )
-    };
-
-    this.updateFlags = {};
-    this.updateCounts = {};
-
-    // A map to indicate which emitter parameters should update
-    // which attribute.
-    this.updateMap = {
-        maxAge: 'params',
-        position: 'position',
-        velocity: 'velocity',
-        acceleration: 'acceleration',
-        drag: 'acceleration',
-        wiggle: 'params',
-        rotation: 'rotation',
-        size: 'size',
-        color: 'color',
-        opacity: 'opacity',
-        angle: 'angle'
-    };
-
-    for ( var i in this.updateMap ) {
-        if ( this.updateMap.hasOwnProperty( i ) ) {
-            this.updateCounts[ this.updateMap[ i ] ] = 0.0;
-            this.updateFlags[ this.updateMap[ i ] ] = false;
-            this._createGetterSetters( this[ i ], i );
-        }
-    }
-
-    this.bufferUpdateRanges = {};
-    this.attributeKeys = null;
-    this.attributeCount = 0;
-
-
-    // Ensure that the value-over-lifetime property objects above
-    // have value and spread properties that are of the same length.
-    //
-    // Also, for now, make sure they have a length of 3 (min/max arguments here).
-    utils.ensureValueOverLifetimeCompliance( this.color, lifetimeLength, lifetimeLength );
-    utils.ensureValueOverLifetimeCompliance( this.opacity, lifetimeLength, lifetimeLength );
-    utils.ensureValueOverLifetimeCompliance( this.size, lifetimeLength, lifetimeLength );
-    utils.ensureValueOverLifetimeCompliance( this.angle, lifetimeLength, lifetimeLength );
-};
-
-SPE.Emitter.constructor = SPE.Emitter;
-
-SPE.Emitter.prototype._createGetterSetters = function( propObj, propName ) {
-    'use strict';
-
-    var self = this;
-
-    for ( var i in propObj ) {
-        if ( propObj.hasOwnProperty( i ) ) {
-
-            var name = i.replace( '_', '' );
-
-            Object.defineProperty( propObj, name, {
-                get: ( function( prop ) {
-                    return function() {
-                        return this[ prop ];
-                    };
-                }( i ) ),
-
-                set: ( function( prop ) {
-                    return function( value ) {
-                        var mapName = self.updateMap[ propName ],
-                            prevValue = this[ prop ],
-                            length = SPE.valueOverLifetimeLength;
-
-                        if ( prop === '_rotationCenter' ) {
-                            self.updateFlags.rotationCenter = true;
-                            self.updateCounts.rotationCenter = 0.0;
-                        }
-                        else if ( prop === '_randomise' ) {
-                            self.resetFlags[ mapName ] = value;
-                        }
-                        else {
-                            self.updateFlags[ mapName ] = true;
-                            self.updateCounts[ mapName ] = 0.0;
-                        }
-
-                        self.group._updateDefines();
-
-                        this[ prop ] = value;
-
-                        // If the previous value was an array, then make
-                        // sure the provided value is interpolated correctly.
-                        if ( Array.isArray( prevValue ) ) {
-                            SPE.utils.ensureValueOverLifetimeCompliance( self[ propName ], length, length );
-                        }
-                    };
-                }( i ) )
-            } );
-        }
-    }
-};
-
-SPE.Emitter.prototype._setBufferUpdateRanges = function( keys ) {
-    'use strict';
-
-    this.attributeKeys = keys;
-    this.attributeCount = keys.length;
-
-    for ( var i = this.attributeCount - 1; i >= 0; --i ) {
-        this.bufferUpdateRanges[ keys[ i ] ] = {
-            min: Number.POSITIVE_INFINITY,
-            max: Number.NEGATIVE_INFINITY
-        };
-    }
-};
-
-SPE.Emitter.prototype._calculatePPSValue = function( groupMaxAge ) {
-    'use strict';
-
-    var particleCount = this.particleCount;
-
-
-    // Calculate the `particlesPerSecond` value for this emitter. It's used
-    // when determining which particles should die and which should live to
-    // see another day. Or be born, for that matter. The "God" property.
-    if ( this.duration ) {
-        this.particlesPerSecond = particleCount / ( groupMaxAge < this.duration ? groupMaxAge : this.duration );
-    }
-    else {
-        this.particlesPerSecond = particleCount / groupMaxAge;
-    }
-};
-
-SPE.Emitter.prototype._setAttributeOffset = function( startIndex ) {
-    this.attributeOffset = startIndex;
-    this.activationIndex = startIndex;
-    this.activationEnd = startIndex + this.particleCount;
-};
-
-
-SPE.Emitter.prototype._assignValue = function( prop, index ) {
-    'use strict';
-
-    switch ( prop ) {
-        case 'position':
-            this._assignPositionValue( index );
-            break;
-
-        case 'velocity':
-        case 'acceleration':
-            this._assignForceValue( index, prop );
-            break;
-
-        case 'size':
-        case 'opacity':
-            this._assignAbsLifetimeValue( index, prop );
-            break;
-
-        case 'angle':
-            this._assignAngleValue( index );
-            break;
-
-        case 'params':
-            this._assignParamsValue( index );
-            break;
-
-        case 'rotation':
-            this._assignRotationValue( index );
-            break;
-
-        case 'color':
-            this._assignColorValue( index );
-            break;
-    }
-};
-
-SPE.Emitter.prototype._assignPositionValue = function( index ) {
-    'use strict';
-
-    var distributions = SPE.distributions,
-        utils = SPE.utils,
-        prop = this.position,
-        attr = this.attributes.position,
-        value = prop._value,
-        spread = prop._spread,
-        distribution = prop._distribution;
-
-    switch ( distribution ) {
-        case distributions.BOX:
-            utils.randomVector3( attr, index, value, spread, prop._spreadClamp );
-            break;
-
-        case distributions.SPHERE:
-            utils.randomVector3OnSphere( attr, index, value, prop._radius, prop._spread.x, prop._radiusScale, prop._spreadClamp.x, prop._distributionClamp || this.particleCount );
-            break;
-
-        case distributions.DISC:
-            utils.randomVector3OnDisc( attr, index, value, prop._radius, prop._spread.x, prop._radiusScale, prop._spreadClamp.x );
-            break;
-    }
-};
-
-SPE.Emitter.prototype._assignForceValue = function( index, attrName ) {
-    'use strict';
-
-    var distributions = SPE.distributions,
-        utils = SPE.utils,
-        prop = this[ attrName ],
-        value = prop._value,
-        spread = prop._spread,
-        distribution = prop._distribution,
-        pos,
-        positionX,
-        positionY,
-        positionZ,
-        i;
-
-    switch ( distribution ) {
-        case distributions.BOX:
-            utils.randomVector3( this.attributes[ attrName ], index, value, spread );
-            break;
-
-        case distributions.SPHERE:
-            pos = this.attributes.position.typedArray.array;
-            i = index * 3;
-
-            // Ensure position values aren't zero, otherwise no force will be
-            // applied.
-            // positionX = utils.zeroToEpsilon( pos[ i ], true );
-            // positionY = utils.zeroToEpsilon( pos[ i + 1 ], true );
-            // positionZ = utils.zeroToEpsilon( pos[ i + 2 ], true );
-            positionX = pos[ i ];
-            positionY = pos[ i + 1 ];
-            positionZ = pos[ i + 2 ];
-
-            utils.randomDirectionVector3OnSphere(
-                this.attributes[ attrName ], index,
-                positionX, positionY, positionZ,
-                this.position._value,
-                prop._value.x,
-                prop._spread.x
-            );
-            break;
-
-        case distributions.DISC:
-            pos = this.attributes.position.typedArray.array;
-            i = index * 3;
-
-            // Ensure position values aren't zero, otherwise no force will be
-            // applied.
-            // positionX = utils.zeroToEpsilon( pos[ i ], true );
-            // positionY = utils.zeroToEpsilon( pos[ i + 1 ], true );
-            // positionZ = utils.zeroToEpsilon( pos[ i + 2 ], true );
-            positionX = pos[ i ];
-            positionY = pos[ i + 1 ];
-            positionZ = pos[ i + 2 ];
-
-            utils.randomDirectionVector3OnDisc(
-                this.attributes[ attrName ], index,
-                positionX, positionY, positionZ,
-                this.position._value,
-                prop._value.x,
-                prop._spread.x
-            );
-            break;
-    }
-
-    if ( attrName === 'acceleration' ) {
-        var drag = utils.clamp( utils.randomFloat( this.drag._value, this.drag._spread ), 0, 1 );
-        this.attributes.acceleration.typedArray.array[ index * 4 + 3 ] = drag;
-    }
-};
-
-SPE.Emitter.prototype._assignAbsLifetimeValue = function( index, propName ) {
-    'use strict';
-
-    var array = this.attributes[ propName ].typedArray,
-        prop = this[ propName ],
-        utils = SPE.utils,
-        value;
-
-    if ( utils.arrayValuesAreEqual( prop._value ) && utils.arrayValuesAreEqual( prop._spread ) ) {
-        value = Math.abs( utils.randomFloat( prop._value[ 0 ], prop._spread[ 0 ] ) );
-        array.setVec4Components( index, value, value, value, value );
-    }
-    else {
-        array.setVec4Components( index,
-            Math.abs( utils.randomFloat( prop._value[ 0 ], prop._spread[ 0 ] ) ),
-            Math.abs( utils.randomFloat( prop._value[ 1 ], prop._spread[ 1 ] ) ),
-            Math.abs( utils.randomFloat( prop._value[ 2 ], prop._spread[ 2 ] ) ),
-            Math.abs( utils.randomFloat( prop._value[ 3 ], prop._spread[ 3 ] ) )
-        );
-    }
-};
-
-SPE.Emitter.prototype._assignAngleValue = function( index ) {
-    'use strict';
-
-    var array = this.attributes.angle.typedArray,
-        prop = this.angle,
-        utils = SPE.utils,
-        value;
-
-    if ( utils.arrayValuesAreEqual( prop._value ) && utils.arrayValuesAreEqual( prop._spread ) ) {
-        value = utils.randomFloat( prop._value[ 0 ], prop._spread[ 0 ] );
-        array.setVec4Components( index, value, value, value, value );
-    }
-    else {
-        array.setVec4Components( index,
-            utils.randomFloat( prop._value[ 0 ], prop._spread[ 0 ] ),
-            utils.randomFloat( prop._value[ 1 ], prop._spread[ 1 ] ),
-            utils.randomFloat( prop._value[ 2 ], prop._spread[ 2 ] ),
-            utils.randomFloat( prop._value[ 3 ], prop._spread[ 3 ] )
-        );
-    }
-};
-
-SPE.Emitter.prototype._assignParamsValue = function( index ) {
-    'use strict';
-
-    this.attributes.params.typedArray.setVec4Components( index,
-        this.isStatic ? 1 : 0,
-        0.0,
-        Math.abs( SPE.utils.randomFloat( this.maxAge._value, this.maxAge._spread ) ),
-        SPE.utils.randomFloat( this.wiggle._value, this.wiggle._spread )
-    );
-};
-
-SPE.Emitter.prototype._assignRotationValue = function( index ) {
-    'use strict';
-
-    this.attributes.rotation.typedArray.setVec3Components( index,
-        SPE.utils.getPackedRotationAxis( this.rotation._axis, this.rotation._axisSpread ),
-        SPE.utils.randomFloat( this.rotation._angle, this.rotation._angleSpread ),
-        this.rotation._static ? 0 : 1
-    );
-
-    this.attributes.rotationCenter.typedArray.setVec3( index, this.rotation._center );
-};
-
-SPE.Emitter.prototype._assignColorValue = function( index ) {
-    'use strict';
-    SPE.utils.randomColorAsHex( this.attributes.color, index, this.color._value, this.color._spread );
-};
-
-SPE.Emitter.prototype._resetParticle = function( index ) {
-    'use strict';
-
-    var resetFlags = this.resetFlags,
-        updateFlags = this.updateFlags,
-        updateCounts = this.updateCounts,
-        keys = this.attributeKeys,
-        key,
-        updateFlag;
-
-    for ( var i = this.attributeCount - 1; i >= 0; --i ) {
-        key = keys[ i ];
-        updateFlag = updateFlags[ key ];
-
-        if ( resetFlags[ key ] === true || updateFlag === true ) {
-            this._assignValue( key, index );
-            this._updateAttributeUpdateRange( key, index );
-
-            if ( updateFlag === true && updateCounts[ key ] === this.particleCount ) {
-                updateFlags[ key ] = false;
-                updateCounts[ key ] = 0.0;
-            }
-            else if ( updateFlag == true ) {
-                ++updateCounts[ key ];
-            }
-        }
-    }
-};
-
-SPE.Emitter.prototype._updateAttributeUpdateRange = function( attr, i ) {
-    'use strict';
-
-    var ranges = this.bufferUpdateRanges[ attr ];
-
-    ranges.min = Math.min( i, ranges.min );
-    ranges.max = Math.max( i, ranges.max );
-};
-
-SPE.Emitter.prototype._resetBufferRanges = function() {
-    'use strict';
-
-    var ranges = this.bufferUpdateRanges,
-        keys = this.bufferUpdateKeys,
-        i = this.bufferUpdateCount - 1,
-        key;
-
-    for ( i; i >= 0; --i ) {
-        key = keys[ i ];
-        ranges[ key ].min = Number.POSITIVE_INFINITY;
-        ranges[ key ].max = Number.NEGATIVE_INFINITY;
-    }
-};
-
-SPE.Emitter.prototype._onRemove = function() {
-    'use strict';
-    // Reset any properties of the emitter that were set by
-    // a group when it was added.
-    this.particlesPerSecond = 0;
-    this.attributeOffset = 0;
-    this.activationIndex = 0;
-    this.activeParticleCount = 0;
-    this.group = null;
-    this.attributes = null;
-    this.paramsArray = null;
-    this.age = 0.0;
-};
-
-SPE.Emitter.prototype._decrementParticleCount = function() {
-    'use strict';
-    --this.activeParticleCount;
-
-    // TODO:
-    //  - Trigger event if count === 0.
-};
-
-SPE.Emitter.prototype._incrementParticleCount = function() {
-    'use strict';
-    ++this.activeParticleCount;
-
-    // TODO:
-    //  - Trigger event if count === this.particleCount.
-};
-
-SPE.Emitter.prototype._checkParticleAges = function( start, end, params, dt ) {
-    'use strict';
-    for ( var i = end - 1, index, maxAge, age, alive; i >= start; --i ) {
-        index = i * 4;
-
-        alive = params[ index ];
-
-        if ( alive === 0.0 ) {
-            continue;
-        }
-
-        // Increment age
-        age = params[ index + 1 ];
-        maxAge = params[ index + 2 ];
-
-        if ( this.direction === 1 ) {
-            age += dt;
-
-            if ( age >= maxAge ) {
-                age = 0.0;
-                alive = 0.0;
-                this._decrementParticleCount();
-            }
-        }
-        else {
-            age -= dt;
-
-            if ( age <= 0.0 ) {
-                age = maxAge;
-                alive = 0.0;
-                this._decrementParticleCount();
-            }
-        }
-
-        params[ index ] = alive;
-        params[ index + 1 ] = age;
-
-        this._updateAttributeUpdateRange( 'params', i );
-    }
-};
-
-SPE.Emitter.prototype._activateParticles = function( activationStart, activationEnd, params, dtPerParticle ) {
-    'use strict';
-    var direction = this.direction;
-
-    for ( var i = activationStart, index, dtValue; i < activationEnd; ++i ) {
-        index = i * 4;
-
-        // Don't re-activate particles that aren't dead yet.
-        // if ( params[ index ] !== 0.0 && ( this.particleCount !== 1 || this.activeMultiplier !== 1 ) ) {
-        //     continue;
-        // }
-
-        if ( params[ index ] != 0.0 && this.particleCount !== 1 ) {
-            continue;
-        }
-
-        // Increment the active particle count.
-        this._incrementParticleCount();
-
-        // Mark the particle as alive.
-        params[ index ] = 1.0;
-
-        // Reset the particle
-        this._resetParticle( i );
-
-        // Move each particle being activated to
-        // it's actual position in time.
-        //
-        // This stops particles being 'clumped' together
-        // when frame rates are on the lower side of 60fps
-        // or not constant (a very real possibility!)
-        dtValue = dtPerParticle * ( i - activationStart )
-        params[ index + 1 ] = direction === -1 ? params[ index + 2 ] - dtValue : dtValue;
-
-        this._updateAttributeUpdateRange( 'params', i );
-    }
-};
-
-/**
- * Simulates one frame's worth of particles, updating particles
- * that are already alive, and marking ones that are currently dead
- * but should be alive as alive.
- *
- * If the emitter is marked as static, then this function will do nothing.
- *
- * @param  {Number} dt The number of seconds to simulate (deltaTime)
- */
-SPE.Emitter.prototype.tick = function( dt ) {
-    'use strict';
-
-    if ( this.isStatic ) {
-        return;
-    }
-
-    if ( this.paramsArray === null ) {
-        this.paramsArray = this.attributes.params.typedArray.array;
-    }
-
-    var start = this.attributeOffset,
-        end = start + this.particleCount,
-        params = this.paramsArray, // vec3( alive, age, maxAge, wiggle )
-        ppsDt = this.particlesPerSecond * this.activeMultiplier * dt,
-        activationIndex = this.activationIndex;
-
-    // Reset the buffer update indices.
-    this._resetBufferRanges();
-
-    // Increment age for those particles that are alive,
-    // and kill off any particles whose age is over the limit.
-    this._checkParticleAges( start, end, params, dt );
-
-    // If the emitter is dead, reset the age of the emitter to zero,
-    // ready to go again if required
-    if ( this.alive === false ) {
-        this.age = 0.0;
-        return;
-    }
-
-    // If the emitter has a specified lifetime and we've exceeded it,
-    // mark the emitter as dead.
-    if ( this.duration !== null && this.age > this.duration ) {
-        this.alive = false;
-        this.age = 0.0;
-        return;
-    }
-
-
-    var activationStart = this.particleCount === 1 ? activationIndex : ( activationIndex | 0 ),
-        activationEnd = Math.min( activationStart + ppsDt, this.activationEnd ),
-        activationCount = activationEnd - this.activationIndex | 0,
-        dtPerParticle = activationCount > 0 ? dt / activationCount : 0;
-
-    this._activateParticles( activationStart, activationEnd, params, dtPerParticle );
-
-    // Move the activation window forward, soldier.
-    this.activationIndex += ppsDt;
-
-    if ( this.activationIndex > end ) {
-        this.activationIndex = start;
-    }
-
-
-    // Increment the age of the emitter.
-    this.age += dt;
-};
-
-/**
- * Resets all the emitter's particles to their start positions
- * and marks the particles as dead if the `force` argument is
- * true.
- *
- * @param  {Boolean} [force=undefined] If true, all particles will be marked as dead instantly.
- * @return {Emitter}       This emitter instance.
- */
-SPE.Emitter.prototype.reset = function( force ) {
-    'use strict';
-
-    this.age = 0.0;
-    this.alive = false;
-
-    if ( force === true ) {
-        var start = this.attributeOffset,
-            end = start + this.particleCount,
-            array = this.paramsArray,
-            attr = this.attributes.params.bufferAttribute;
-
-        for ( var i = end - 1, index; i >= start; --i ) {
-            index = i * 4;
-
-            array[ index ] = 0.0;
-            array[ index + 1 ] = 0.0;
-        }
-
-        attr.updateRange.offset = 0;
-        attr.updateRange.count = -1;
-        attr.needsUpdate = true;
-    }
-
-    return this;
-};
-
-/**
- * Enables the emitter. If not already enabled, the emitter
- * will start emitting particles.
- *
- * @return {Emitter} This emitter instance.
- */
-SPE.Emitter.prototype.enable = function() {
-    'use strict';
-    this.alive = true;
-    return this;
-};
-
-/**
- * Disables th emitter, but does not instantly remove it's
- * particles fromt the scene. When called, the emitter will be
- * 'switched off' and just stop emitting. Any particle's alive will
- * be allowed to finish their lifecycle.
- *
- * @return {Emitter} This emitter instance.
- */
-SPE.Emitter.prototype.disable = function() {
-    'use strict';
-
-    this.alive = false;
-    return this;
-};
-
-/**
- * Remove this emitter from it's parent group (if it has been added to one).
- * Delgates to SPE.group.prototype.removeEmitter().
- *
- * When called, all particle's belonging to this emitter will be instantly
- * removed from the scene.
- *
- * @return {Emitter} This emitter instance.
- *
- * @see SPE.Group.prototype.removeEmitter
- */
-SPE.Emitter.prototype.remove = function() {
-    'use strict';
-    if ( this.group !== null ) {
-        this.group.removeEmitter( this );
-    }
-    else {
-        console.error( 'Emitter does not belong to a group, cannot remove.' );
-    }
-
-    return this;
-};
-
-
- - - - -
- - - -
- -
- Documentation generated by JSDoc 3.3.3 on Sat Nov 14 2015 15:30:50 GMT+0000 (GMT) -
- - - - - diff --git a/docs/api/core_SPE.Group.js.html b/docs/api/core_SPE.Group.js.html deleted file mode 100644 index 21963dc..0000000 --- a/docs/api/core_SPE.Group.js.html +++ /dev/null @@ -1,798 +0,0 @@ - - - - - JSDoc: Source: core/SPE.Group.js - - - - - - - - - - -
- -

Source: core/SPE.Group.js

- - - - - - -
-
-
/**
- * An SPE.Group instance.
- * @typedef {Object} Group
- * @see SPE.Group
- */
-
-/**
- * A map of options to configure an SPE.Group instance.
- * @typedef {Object} GroupOptions
- *
- * @property {Object} texture An object describing the texture used by the group.
- *
- * @property {Object} texture.value An instance of THREE.Texture.
- *
- * @property {Object=} texture.frames A THREE.Vector2 instance describing the number
- *                                    of frames on the x- and y-axis of the given texture.
- *                                    If not provided, the texture will NOT be treated as
- *                                    a sprite-sheet and as such will NOT be animated.
- *
- * @property {Number} [texture.frameCount=texture.frames.x * texture.frames.y] The total number of frames in the sprite-sheet.
- *                                                                   Allows for sprite-sheets that don't fill the entire
- *                                                                   texture.
- *
- * @property {Number} texture.loop The number of loops through the sprite-sheet that should
- *                                 be performed over the course of a single particle's lifetime.
- *
- * @property {Number} fixedTimeStep If no `dt` (or `deltaTime`) value is passed to this group's
- *                                  `tick()` function, this number will be used to move the particle
- *                                  simulation forward. Value in SECONDS.
- *
- * @property {Boolean} hasPerspective Whether the distance a particle is from the camera should affect
- *                                    the particle's size.
- *
- * @property {Boolean} colorize Whether the particles in this group should be rendered with color, or
- *                              whether the only color of particles will come from the provided texture.
- *
- * @property {Number} blending One of Three.js's blending modes to apply to this group's `ShaderMaterial`.
- *
- * @property {Boolean} transparent Whether these particle's should be rendered with transparency.
- *
- * @property {Number} alphaTest Sets the alpha value to be used when running an alpha test on the `texture.value` property. Value between 0 and 1.
- *
- * @property {Boolean} depthWrite Whether rendering the group has any effect on the depth buffer.
- *
- * @property {Boolean} depthTest Whether to have depth test enabled when rendering this group.
- *
- * @property {Boolean} fog Whether this group's particles should be affected by their scene's fog.
- *
- * @property {Number} scale The scale factor to apply to this group's particle sizes. Useful for
- *                          setting particle sizes to be relative to renderer size.
- */
-
-
-/**
- * The SPE.Group class. Creates a new group, containing a material, geometry, and mesh.
- *
- * @constructor
- * @param {GroupOptions} options A map of options to configure the group instance.
- */
-SPE.Group = function( options ) {
-    'use strict';
-
-    var utils = SPE.utils,
-        types = utils.types;
-
-    // Ensure we have a map of options to play with
-    options = utils.ensureTypedArg( options, types.OBJECT, {} );
-    options.texture = utils.ensureTypedArg( options.texture, types.OBJECT, {} );
-
-    // Assign a UUID to this instance
-    this.uuid = THREE.Math.generateUUID();
-
-    // If no `deltaTime` value is passed to the `SPE.Group.tick` function,
-    // the value of this property will be used to advance the simulation.
-    this.fixedTimeStep = utils.ensureTypedArg( options.fixedTimeStep, types.NUMBER, 0.016 );
-
-    // Set properties used in the uniforms map, starting with the
-    // texture stuff.
-    this.texture = utils.ensureInstanceOf( options.texture.value, THREE.Texture, null );
-    this.textureFrames = utils.ensureInstanceOf( options.texture.frames, THREE.Vector2, new THREE.Vector2( 1, 1 ) );
-    this.textureFrameCount = utils.ensureTypedArg( options.texture.frameCount, types.NUMBER, this.textureFrames.x * this.textureFrames.y );
-    this.textureLoop = utils.ensureTypedArg( options.texture.loop, types.NUMBER, 1 );
-    this.textureFrames.max( new THREE.Vector2( 1, 1 ) );
-
-    this.hasPerspective = utils.ensureTypedArg( options.hasPerspective, types.BOOLEAN, true );
-    this.colorize = utils.ensureTypedArg( options.colorize, types.BOOLEAN, true );
-
-    this.maxParticleCount = utils.ensureTypedArg( options.maxParticleCount, types.NUMBER, null );
-
-
-    // Set properties used to define the ShaderMaterial's appearance.
-    this.blending = utils.ensureTypedArg( options.blending, types.NUMBER, THREE.AdditiveBlending );
-    this.transparent = utils.ensureTypedArg( options.transparent, types.BOOLEAN, true );
-    this.alphaTest = parseFloat( utils.ensureTypedArg( options.alphaTest, types.NUMBER, 0.0 ) );
-    this.depthWrite = utils.ensureTypedArg( options.depthWrite, types.BOOLEAN, false );
-    this.depthTest = utils.ensureTypedArg( options.depthTest, types.BOOLEAN, true );
-    this.fog = utils.ensureTypedArg( options.fog, types.BOOLEAN, true );
-    this.scale = utils.ensureTypedArg( options.scale, types.NUMBER, 300 );
-
-    // Where emitter's go to curl up in a warm blanket and live
-    // out their days.
-    this.emitters = [];
-    this.emitterIDs = [];
-
-    // Create properties for use by the emitter pooling functions.
-    this._pool = [];
-    this._poolCreationSettings = null;
-    this._createNewWhenPoolEmpty = 0;
-
-    // Whether all attributes should be forced to updated
-    // their entire buffer contents on the next tick.
-    //
-    // Used when an emitter is removed.
-    this._attributesNeedRefresh = false;
-    this._attributesNeedDynamicReset = false;
-
-    this.particleCount = 0;
-
-
-    // Map of uniforms to be applied to the ShaderMaterial instance.
-    this.uniforms = {
-        texture: {
-            type: 't',
-            value: this.texture
-        },
-        textureAnimation: {
-            type: 'v4',
-            value: new THREE.Vector4(
-                this.textureFrames.x,
-                this.textureFrames.y,
-                this.textureFrameCount,
-                Math.max( Math.abs( this.textureLoop ), 1.0 )
-            )
-        },
-        fogColor: {
-            type: 'c',
-            value: null
-        },
-        fogNear: {
-            type: 'f',
-            value: 10
-        },
-        fogFar: {
-            type: 'f',
-            value: 200
-        },
-        fogDensity: {
-            type: 'f',
-            value: 0.5
-        },
-        deltaTime: {
-            type: 'f',
-            value: 0
-        },
-        runTime: {
-            type: 'f',
-            value: 0
-        },
-        scale: {
-            type: 'f',
-            value: this.scale
-        }
-    };
-
-    // Add some defines into the mix...
-    this.defines = {
-        HAS_PERSPECTIVE: this.hasPerspective,
-        COLORIZE: this.colorize,
-        VALUE_OVER_LIFETIME_LENGTH: SPE.valueOverLifetimeLength,
-
-        SHOULD_ROTATE_TEXTURE: false,
-        SHOULD_ROTATE_PARTICLES: false,
-        SHOULD_WIGGLE_PARTICLES: false,
-
-        SHOULD_CALCULATE_SPRITE: this.textureFrames.x > 1 || this.textureFrames.y > 1
-    };
-
-    // Map of all attributes to be applied to the particles.
-    //
-    // See SPE.ShaderAttribute for a bit more info on this bit.
-    this.attributes = {
-        position: new SPE.ShaderAttribute( 'v3', true ),
-        acceleration: new SPE.ShaderAttribute( 'v4', true ), // w component is drag
-        velocity: new SPE.ShaderAttribute( 'v3', true ),
-        rotation: new SPE.ShaderAttribute( 'v4', true ),
-        rotationCenter: new SPE.ShaderAttribute( 'v3', true ),
-        params: new SPE.ShaderAttribute( 'v4', true ), // Holds (alive, age, delay, wiggle)
-        size: new SPE.ShaderAttribute( 'v4', true ),
-        angle: new SPE.ShaderAttribute( 'v4', true ),
-        color: new SPE.ShaderAttribute( 'v4', true ),
-        opacity: new SPE.ShaderAttribute( 'v4', true )
-    };
-
-    this.attributeKeys = Object.keys( this.attributes );
-    this.attributeCount = this.attributeKeys.length;
-
-    // Create the ShaderMaterial instance that'll help render the
-    // particles.
-    this.material = new THREE.ShaderMaterial( {
-        uniforms: this.uniforms,
-        vertexShader: SPE.shaders.vertex,
-        fragmentShader: SPE.shaders.fragment,
-        blending: this.blending,
-        transparent: this.transparent,
-        alphaTest: this.alphaTest,
-        depthWrite: this.depthWrite,
-        depthTest: this.depthTest,
-        defines: this.defines,
-        fog: this.fog
-    } );
-
-    // Create the BufferGeometry and Points instances, ensuring
-    // the geometry and material are given to the latter.
-    this.geometry = new THREE.BufferGeometry();
-    this.mesh = new THREE.Points( this.geometry, this.material );
-
-    if ( this.maxParticleCount === null ) {
-        console.warn( 'SPE.Group: No maxParticleCount specified. Adding emitters after rendering will probably cause errors.' );
-    }
-};
-
-SPE.Group.constructor = SPE.Group;
-
-
-SPE.Group.prototype._updateDefines = function() {
-    'use strict';
-
-    var emitters = this.emitters,
-        i = emitters.length - 1,
-        emitter,
-        defines = this.defines;
-
-    for ( i; i >= 0; --i ) {
-        emitter = emitters[ i ];
-
-        // Only do angle calculation if there's no spritesheet defined.
-        //
-        // Saves calculations being done and then overwritten in the shaders.
-        if ( !defines.SHOULD_CALCULATE_SPRITE ) {
-            defines.SHOULD_ROTATE_TEXTURE = defines.SHOULD_ROTATE_TEXTURE || !!Math.max(
-                Math.max.apply( null, emitter.angle.value ),
-                Math.max.apply( null, emitter.angle.spread )
-            );
-        }
-
-        defines.SHOULD_ROTATE_PARTICLES = defines.SHOULD_ROTATE_PARTICLES || !!Math.max(
-            emitter.rotation.angle,
-            emitter.rotation.angleSpread
-        );
-
-        defines.SHOULD_WIGGLE_PARTICLES = defines.SHOULD_WIGGLE_PARTICLES || !!Math.max(
-            emitter.wiggle.value,
-            emitter.wiggle.spread
-        );
-    }
-
-    this.material.needsUpdate = true;
-};
-
-SPE.Group.prototype._applyAttributesToGeometry = function() {
-    'use strict';
-
-    var attributes = this.attributes,
-        geometry = this.geometry,
-        geometryAttributes = geometry.attributes,
-        attribute,
-        geometryAttribute;
-
-    // Loop through all the shader attributes and assign (or re-assign)
-    // typed array buffers to each one.
-    for ( var attr in attributes ) {
-        if ( attributes.hasOwnProperty( attr ) ) {
-            attribute = attributes[ attr ];
-            geometryAttribute = geometryAttributes[ attr ];
-
-            // Update the array if this attribute exists on the geometry.
-            //
-            // This needs to be done because the attribute's typed array might have
-            // been resized and reinstantiated, and might now be looking at a
-            // different ArrayBuffer, so reference needs updating.
-            if ( geometryAttribute ) {
-                geometryAttribute.array = attribute.typedArray.array;
-            }
-
-            // // Add the attribute to the geometry if it doesn't already exist.
-            else {
-                geometry.addAttribute( attr, attribute.bufferAttribute );
-            }
-
-            // Mark the attribute as needing an update the next time a frame is rendered.
-            attribute.bufferAttribute.needsUpdate = true;
-        }
-    }
-
-    // Mark the draw range on the geometry. This will ensure
-    // only the values in the attribute buffers that are
-    // associated with a particle will be used in THREE's
-    // render cycle.
-    this.geometry.setDrawRange( 0, this.particleCount );
-};
-
-/**
- * Adds an SPE.Emitter instance to this group, creating particle values and
- * assigning them to this group's shader attributes.
- *
- * @param {Emitter} emitter The emitter to add to this group.
- */
-SPE.Group.prototype.addEmitter = function( emitter ) {
-    'use strict';
-
-    // Ensure an actual emitter instance is passed here.
-    //
-    // Decided not to throw here, just in case a scene's
-    // rendering would be paused. Logging an error instead
-    // of stopping execution if exceptions aren't caught.
-    if ( emitter instanceof SPE.Emitter === false ) {
-        console.error( '`emitter` argument must be instance of SPE.Emitter. Was provided with:', emitter );
-        return;
-    }
-
-    // If the emitter already exists as a member of this group, then
-    // stop here, we don't want to add it again.
-    else if ( this.emitterIDs.indexOf( emitter.uuid ) > -1 ) {
-        console.error( 'Emitter already exists in this group. Will not add again.' );
-        return;
-    }
-
-    // And finally, if the emitter is a member of another group,
-    // don't add it to this group.
-    else if ( emitter.group !== null ) {
-        console.error( 'Emitter already belongs to another group. Will not add to requested group.' );
-        return;
-    }
-
-    var attributes = this.attributes,
-        start = this.particleCount,
-        end = start + emitter.particleCount;
-
-    // Update this group's particle count.
-    this.particleCount = end;
-
-    // Emit a warning if the emitter being added will exceed the buffer sizes specified.
-    if ( this.maxParticleCount !== null && this.particleCount > this.maxParticleCount ) {
-        console.warn( 'SPE.Group: maxParticleCount exceeded. Requesting', this.particleCount, 'particles, can support only', this.maxParticleCount );
-    }
-
-
-    // Set the `particlesPerSecond` value (PPS) on the emitter.
-    // It's used to determine how many particles to release
-    // on a per-frame basis.
-    emitter._calculatePPSValue( emitter.maxAge._value + emitter.maxAge._spread );
-    emitter._setBufferUpdateRanges( this.attributeKeys );
-
-    // Store the offset value in the TypedArray attributes for this emitter.
-    emitter._setAttributeOffset( start );
-
-    // Save a reference to this group on the emitter so it knows
-    // where it belongs.
-    emitter.group = this;
-
-    // Store reference to the attributes on the emitter for
-    // easier access during the emitter's tick function.
-    emitter.attributes = this.attributes;
-
-
-
-    // Ensure the attributes and their BufferAttributes exist, and their
-    // TypedArrays are of the correct size.
-    for ( var attr in attributes ) {
-        if ( attributes.hasOwnProperty( attr ) ) {
-            // When creating a buffer, pass through the maxParticle count
-            // if one is specified.
-            attributes[ attr ]._createBufferAttribute(
-                this.maxParticleCount !== null ?
-                this.maxParticleCount :
-                this.particleCount
-            );
-        }
-    }
-
-    // Loop through each particle this emitter wants to have, and create the attributes values,
-    // storing them in the TypedArrays that each attribute holds.
-    for ( var i = start; i < end; ++i ) {
-        emitter._assignPositionValue( i );
-        emitter._assignForceValue( i, 'velocity' );
-        emitter._assignForceValue( i, 'acceleration' );
-        emitter._assignAbsLifetimeValue( i, 'opacity' );
-        emitter._assignAbsLifetimeValue( i, 'size' );
-        emitter._assignAngleValue( i );
-        emitter._assignRotationValue( i );
-        emitter._assignParamsValue( i );
-        emitter._assignColorValue( i );
-    }
-
-    // Update the geometry and make sure the attributes are referencing
-    // the typed arrays properly.
-    this._applyAttributesToGeometry();
-
-    // Store this emitter in this group's emitter's store.
-    this.emitters.push( emitter );
-    this.emitterIDs.push( emitter.uuid );
-
-    // Update certain flags to enable shader calculations only if they're necessary.
-    this._updateDefines( emitter );
-
-    // Update the material since defines might have changed
-    this.material.needsUpdate = true;
-    this.geometry.needsUpdate = true;
-    this._attributesNeedRefresh = true;
-
-    // Return the group to enable chaining.
-    return this;
-};
-
-/**
- * Removes an SPE.Emitter instance from this group. When called,
- * all particle's belonging to the given emitter will be instantly
- * removed from the scene.
- *
- * @param {Emitter} emitter The emitter to add to this group.
- */
-SPE.Group.prototype.removeEmitter = function( emitter ) {
-    'use strict';
-
-    var emitterIndex = this.emitterIDs.indexOf( emitter.uuid );
-
-    // Ensure an actual emitter instance is passed here.
-    //
-    // Decided not to throw here, just in case a scene's
-    // rendering would be paused. Logging an error instead
-    // of stopping execution if exceptions aren't caught.
-    if ( emitter instanceof SPE.Emitter === false ) {
-        console.error( '`emitter` argument must be instance of SPE.Emitter. Was provided with:', emitter );
-        return;
-    }
-
-    // Issue an error if the emitter isn't a member of this group.
-    else if ( emitterIndex === -1 ) {
-        console.error( 'Emitter does not exist in this group. Will not remove.' );
-        return;
-    }
-
-    // Kill all particles by marking them as dead
-    // and their age as 0.
-    var start = emitter.attributeOffset,
-        end = start + emitter.particleCount,
-        params = this.attributes.params.typedArray;
-
-    // Set alive and age to zero.
-    for ( var i = start; i < end; ++i ) {
-        params.array[ i * 4 ] = 0.0;
-        params.array[ i * 4 + 1 ] = 0.0;
-    }
-
-    // Remove the emitter from this group's "store".
-    this.emitters.splice( emitterIndex, 1 );
-    this.emitterIDs.splice( emitterIndex, 1 );
-
-    // Remove this emitter's attribute values from all shader attributes.
-    // The `.splice()` call here also marks each attribute's buffer
-    // as needing to update it's entire contents.
-    for ( var attr in this.attributes ) {
-        if ( this.attributes.hasOwnProperty( attr ) ) {
-            this.attributes[ attr ].splice( start, end );
-        }
-    }
-
-    // Ensure this group's particle count is correct.
-    this.particleCount -= emitter.particleCount;
-
-    // Call the emitter's remove method.
-    emitter._onRemove();
-
-    // Set a flag to indicate that the attribute buffers should
-    // be updated in their entirety on the next frame.
-    this._attributesNeedRefresh = true;
-};
-
-
-/**
- * Fetch a single emitter instance from the pool.
- * If there are no objects in the pool, a new emitter will be
- * created if specified.
- *
- * @return {Emitter|null}
- */
-SPE.Group.prototype.getFromPool = function() {
-    'use strict';
-
-    var pool = this._pool,
-        createNew = this._createNewWhenPoolEmpty;
-
-    if ( pool.length ) {
-        return pool.pop();
-    }
-    else if ( createNew ) {
-        return new SPE.Emitter( this._poolCreationSettings );
-    }
-
-    return null;
-};
-
-
-/**
- * Release an emitter into the pool.
- *
- * @param  {ShaderParticleEmitter} emitter
- * @return {Group} This group instance.
- */
-SPE.Group.prototype.releaseIntoPool = function( emitter ) {
-    'use strict';
-
-    if ( emitter instanceof SPE.Emitter === false ) {
-        console.error( 'Argument is not instanceof SPE.Emitter:', emitter );
-        return;
-    }
-
-    emitter.reset();
-    this._pool.unshift( emitter );
-
-    return this;
-};
-
-
-/**
- * Get the pool array
- *
- * @return {Array}
- */
-SPE.Group.prototype.getPool = function() {
-    'use strict';
-    return this._pool;
-};
-
-
-/**
- * Add a pool of emitters to this particle group
- *
- * @param {Number} numEmitters      The number of emitters to add to the pool.
- * @param {EmitterOptions|Array} emitterOptions  An object, or array of objects, describing the options to pass to each emitter.
- * @param {Boolean} createNew       Should a new emitter be created if the pool runs out?
- * @return {Group} This group instance.
- */
-SPE.Group.prototype.addPool = function( numEmitters, emitterOptions, createNew ) {
-    'use strict';
-
-    var emitter;
-
-    // Save relevant settings and flags.
-    this._poolCreationSettings = emitterOptions;
-    this._createNewWhenPoolEmpty = !!createNew;
-
-    // Create the emitters, add them to this group and the pool.
-    for ( var i = 0; i < numEmitters; ++i ) {
-        if ( Array.isArray( emitterOptions ) ) {
-            emitter = new SPE.Emitter( emitterOptions[ i ] );
-        }
-        else {
-            emitter = new SPE.Emitter( emitterOptions );
-        }
-        this.addEmitter( emitter );
-        this.releaseIntoPool( emitter );
-    }
-
-    return this;
-};
-
-
-
-SPE.Group.prototype._triggerSingleEmitter = function( pos ) {
-    'use strict';
-
-    var emitter = this.getFromPool(),
-        self = this;
-
-    if ( emitter === null ) {
-        console.log( 'SPE.Group pool ran out.' );
-        return;
-    }
-
-    // TODO:
-    // - Make sure buffers are update with thus new position.
-    if ( pos instanceof THREE.Vector3 ) {
-        emitter.position.value.copy( pos );
-
-        // Trigger the setter for this property to force an
-        // update to the emitter's position attribute.
-        emitter.position.value = emitter.position.value;
-    }
-
-    emitter.enable();
-
-    setTimeout( function() {
-        emitter.disable();
-        self.releaseIntoPool( emitter );
-    }, ( emitter.maxAge.value + emitter.maxAge.spread ) * 1000 );
-
-    return this;
-};
-
-
-/**
- * Set a given number of emitters as alive, with an optional position
- * vector3 to move them to.
- *
- * @param  {Number} numEmitters The number of emitters to activate
- * @param  {Object} [position=undefined] A THREE.Vector3 instance describing the position to activate the emitter(s) at.
- * @return {Group} This group instance.
- */
-SPE.Group.prototype.triggerPoolEmitter = function( numEmitters, position ) {
-    'use strict';
-
-    if ( typeof numEmitters === 'number' && numEmitters > 1 ) {
-        for ( var i = 0; i < numEmitters; ++i ) {
-            this._triggerSingleEmitter( position );
-        }
-    }
-    else {
-        this._triggerSingleEmitter( position );
-    }
-
-    return this;
-};
-
-
-
-SPE.Group.prototype._updateUniforms = function( dt ) {
-    'use strict';
-
-    this.uniforms.runTime.value += dt;
-    this.uniforms.deltaTime.value = dt;
-};
-
-SPE.Group.prototype._resetBufferRanges = function() {
-    'use strict';
-
-    var keys = this.attributeKeys,
-        i = this.attributeCount - 1,
-        attrs = this.attributes;
-
-    for ( i; i >= 0; --i ) {
-        attrs[ keys[ i ] ].resetUpdateRange();
-    }
-};
-
-
-SPE.Group.prototype._updateBuffers = function( emitter ) {
-    'use strict';
-
-    var keys = this.attributeKeys,
-        i = this.attributeCount - 1,
-        attrs = this.attributes,
-        emitterRanges = emitter.bufferUpdateRanges,
-        key,
-        emitterAttr,
-        attr;
-
-    for ( i; i >= 0; --i ) {
-        key = keys[ i ];
-        emitterAttr = emitterRanges[ key ];
-        attr = attrs[ key ];
-        attr.setUpdateRange( emitterAttr.min, emitterAttr.max );
-        attr.flagUpdate();
-    }
-};
-
-
-/**
- * Simulate all the emitter's belonging to this group, updating
- * attribute values along the way.
- * @param  {Number} [dt=Group's `fixedTimeStep` value] The number of seconds to simulate the group's emitters for (deltaTime)
- */
-SPE.Group.prototype.tick = function( dt ) {
-    'use strict';
-
-    var emitters = this.emitters,
-        numEmitters = emitters.length,
-        deltaTime = dt || this.fixedTimeStep,
-        keys = this.attributeKeys,
-        i,
-        attrs = this.attributes;
-
-    // Update uniform values.
-    this._updateUniforms( deltaTime );
-
-    // Reset buffer update ranges on the shader attributes.
-    this._resetBufferRanges();
-
-
-    // If nothing needs updating, then stop here.
-    if (
-        numEmitters === 0 &&
-        this._attributesNeedRefresh === false &&
-        this._attributesNeedDynamicReset === false
-    ) {
-        return;
-    }
-
-    // Loop through each emitter in this group and
-    // simulate it, then update the shader attribute
-    // buffers.
-    for ( var i = 0, emitter; i < numEmitters; ++i ) {
-        emitter = emitters[ i ];
-        emitter.tick( deltaTime );
-        this._updateBuffers( emitter );
-    }
-
-    // If the shader attributes have been refreshed,
-    // then the dynamic properties of each buffer
-    // attribute will need to be reset back to
-    // what they should be.
-    if ( this._attributesNeedDynamicReset === true ) {
-        i = this.attributeCount - 1;
-
-        for ( i; i >= 0; --i ) {
-            attrs[ keys[ i ] ].resetDynamic();
-        }
-
-        this._attributesNeedDynamicReset = false;
-    }
-
-    // If this group's shader attributes need a full refresh
-    // then mark each attribute's buffer attribute as
-    // needing so.
-    if ( this._attributesNeedRefresh === true ) {
-        i = this.attributeCount - 1;
-
-        for ( i; i >= 0; --i ) {
-            attrs[ keys[ i ] ].forceUpdateAll();
-        }
-
-        this._attributesNeedRefresh = false;
-        this._attributesNeedDynamicReset = true;
-    }
-};
-
-
-/**
- * Dipose the geometry and material for the group.
- *
- * @return {Group} Group instance.
- */
-SPE.Group.prototype.dispose = function() {
-    'use strict';
-    this.geometry.dispose();
-    this.material.dispose();
-    return this;
-};
-
-
- - - - -
- - - -
- -
- Documentation generated by JSDoc 3.3.3 on Sat Nov 14 2015 15:30:50 GMT+0000 (GMT) -
- - - - - diff --git a/docs/api/core_SPE.utils.js.html b/docs/api/core_SPE.utils.js.html deleted file mode 100644 index 42f94e9..0000000 --- a/docs/api/core_SPE.utils.js.html +++ /dev/null @@ -1,755 +0,0 @@ - - - - - JSDoc: Source: core/SPE.utils.js - - - - - - - - - - -
- -

Source: core/SPE.utils.js

- - - - - - -
-
-
/**
- * A bunch of utility functions used throughout the library.
- * @namespace
- * @type {Object}
- */
-SPE.utils = {
-    /**
-     * A map of types used by `SPE.utils.ensureTypedArg` and
-     * `SPE.utils.ensureArrayTypedArg` to compare types against.
-     *
-     * @enum {String}
-     */
-    types: {
-        /**
-         * Boolean type.
-         * @type {String}
-         */
-        BOOLEAN: 'boolean',
-
-        /**
-         * String type.
-         * @type {String}
-         */
-        STRING: 'string',
-
-        /**
-         * Number type.
-         * @type {String}
-         */
-        NUMBER: 'number',
-
-        /**
-         * Object type.
-         * @type {String}
-         */
-        OBJECT: 'object'
-    },
-
-    /**
-     * Given a value, a type, and a default value to fallback to,
-     * ensure the given argument adheres to the type requesting,
-     * returning the default value if type check is false.
-     *
-     * @param  {(boolean|string|number|object)} arg          The value to perform a type-check on.
-     * @param  {String} type         The type the `arg` argument should adhere to.
-     * @param  {(boolean|string|number|object)} defaultValue A default value to fallback on if the type check fails.
-     * @return {(boolean|string|number|object)}              The given value if type check passes, or the default value if it fails.
-     */
-    ensureTypedArg: function( arg, type, defaultValue ) {
-        'use strict';
-
-        if ( typeof arg === type ) {
-            return arg;
-        }
-        else {
-            return defaultValue;
-        }
-    },
-
-    /**
-     * Given an array of values, a type, and a default value,
-     * ensure the given array's contents ALL adhere to the provided type,
-     * returning the default value if type check fails.
-     *
-     * If the given value to check isn't an Array, delegates to SPE.utils.ensureTypedArg.
-     *
-     * @param  {Array|boolean|string|number|object} arg          The array of values to check type of.
-     * @param  {String} type         The type that should be adhered to.
-     * @param  {(boolean|string|number|object)} defaultValue A default fallback value.
-     * @return {(boolean|string|number|object)}              The given value if type check passes, or the default value if it fails.
-     */
-    ensureArrayTypedArg: function( arg, type, defaultValue ) {
-        'use strict';
-
-        // If the argument being checked is an array, loop through
-        // it and ensure all the values are of the correct type,
-        // falling back to the defaultValue if any aren't.
-        if ( Array.isArray( arg ) ) {
-            for ( var i = arg.length - 1; i >= 0; --i ) {
-                if ( typeof arg[ i ] !== type ) {
-                    return defaultValue;
-                }
-            }
-
-            return arg;
-        }
-
-        // If the arg isn't an array then just fallback to
-        // checking the type.
-        return this.ensureTypedArg( arg, type, defaultValue );
-    },
-
-    /**
-     * Ensures the given value is an instance of a constructor function.
-     *
-     * @param  {Object} arg          The value to check instance of.
-     * @param  {Function} instance     The constructor of the instance to check against.
-     * @param  {Object} defaultValue A default fallback value if instance check fails
-     * @return {Object}              The given value if type check passes, or the default value if it fails.
-     */
-    ensureInstanceOf: function( arg, instance, defaultValue ) {
-        'use strict';
-
-        if ( instance !== undefined && arg instanceof instance ) {
-            return arg;
-        }
-        else {
-            return defaultValue;
-        }
-    },
-
-    /**
-     * Given an array of values, ensure the instances of all items in the array
-     * matches the given instance constructor falling back to a default value if
-     * the check fails.
-     *
-     * If given value isn't an Array, delegates to `SPE.utils.ensureInstanceOf`.
-     *
-     * @param  {Array|Object} arg          The value to perform the instanceof check on.
-     * @param  {Function} instance     The constructor of the instance to check against.
-     * @param  {Object} defaultValue A default fallback value if instance check fails
-     * @return {Object}              The given value if type check passes, or the default value if it fails.
-     */
-    ensureArrayInstanceOf: function( arg, instance, defaultValue ) {
-        'use strict';
-
-        // If the argument being checked is an array, loop through
-        // it and ensure all the values are of the correct type,
-        // falling back to the defaultValue if any aren't.
-        if ( Array.isArray( arg ) ) {
-            for ( var i = arg.length - 1; i >= 0; --i ) {
-                if ( instance !== undefined && arg[ i ] instanceof instance === false ) {
-                    return defaultValue;
-                }
-            }
-
-            return arg;
-        }
-
-        // If the arg isn't an array then just fallback to
-        // checking the type.
-        return this.ensureInstanceOf( arg, instance, defaultValue );
-    },
-
-    /**
-     * Ensures that any "value-over-lifetime" properties of an emitter are
-     * of the correct length (as dictated by `SPE.valueOverLifetimeLength`).
-     *
-     * Delegates to `SPE.utils.interpolateArray` for array resizing.
-     *
-     * If properties aren't arrays, then property values are put into one.
-     *
-     * @param  {Object} property  The property of an SPE.Emitter instance to check compliance of.
-     * @param  {Number} minLength The minimum length of the array to create.
-     * @param  {Number} maxLength The maximum length of the array to create.
-     */
-    ensureValueOverLifetimeCompliance: function( property, minLength, maxLength ) {
-        'use strict';
-
-        minLength = minLength || 3;
-        maxLength = maxLength || 3;
-
-        // First, ensure both properties are arrays.
-        if ( Array.isArray( property._value ) === false ) {
-            property._value = [ property._value ];
-        }
-
-        if ( Array.isArray( property._spread ) === false ) {
-            property._spread = [ property._spread ];
-        }
-
-        var valueLength = this.clamp( property._value.length, minLength, maxLength ),
-            spreadLength = this.clamp( property._spread.length, minLength, maxLength ),
-            desiredLength = Math.max( valueLength, spreadLength );
-
-        if ( property._value.length !== desiredLength ) {
-            property._value = this.interpolateArray( property._value, desiredLength );
-        }
-
-        if ( property._spread.length !== desiredLength ) {
-            property._spread = this.interpolateArray( property._spread, desiredLength );
-        }
-    },
-
-    /**
-     * Performs linear interpolation (lerp) on an array.
-     *
-     * For example, lerping [1, 10], with a `newLength` of 10 will produce [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].
-     *
-     * Delegates to `SPE.utils.lerpTypeAgnostic` to perform the actual
-     * interpolation.
-     *
-     * @param  {Array} srcArray  The array to lerp.
-     * @param  {Number} newLength The length the array should be interpolated to.
-     * @return {Array}           The interpolated array.
-     */
-    interpolateArray: function( srcArray, newLength ) {
-        'use strict';
-
-        var sourceLength = srcArray.length,
-            newArray = [ typeof srcArray[ 0 ].clone === 'function' ? srcArray[ 0 ].clone() : srcArray[ 0 ] ],
-            factor = ( sourceLength - 1 ) / ( newLength - 1 );
-
-
-        for ( var i = 1; i < newLength - 1; ++i ) {
-            var f = i * factor,
-                before = Math.floor( f ),
-                after = Math.ceil( f ),
-                delta = f - before;
-
-            newArray[ i ] = this.lerpTypeAgnostic( srcArray[ before ], srcArray[ after ], delta );
-        }
-
-        newArray.push(
-            typeof srcArray[ sourceLength - 1 ].clone === 'function' ?
-            srcArray[ sourceLength - 1 ].clone() :
-            srcArray[ sourceLength - 1 ]
-        );
-
-        return newArray;
-    },
-
-    /**
-     * Clamp a number to between the given min and max values.
-     * @param  {Number} value The number to clamp.
-     * @param  {Number} min   The minimum value.
-     * @param  {Number} max   The maximum value.
-     * @return {Number}       The clamped number.
-     */
-    clamp: function( value, min, max ) {
-        'use strict';
-
-        return Math.max( min, Math.min( value, max ) );
-    },
-
-    /**
-     * If the given value is less than the epsilon value, then return
-     * a randomised epsilon value if specified, or just the epsilon value if not.
-     * Works for negative numbers as well as positive.
-     *
-     * @param  {Number} value     The value to perform the operation on.
-     * @param  {Boolean} randomise Whether the value should be randomised.
-     * @return {Number}           The result of the operation.
-     */
-    zeroToEpsilon: function( value, randomise ) {
-        'use strict';
-
-        var epsilon = 0.00001,
-            result = value;
-
-        result = randomise ? Math.random() * epsilon * 10 : epsilon;
-
-        if ( value < 0 && value > -epsilon ) {
-            result = -result;
-        }
-
-        // if ( value === 0 ) {
-        //     result = randomise ? Math.random() * epsilon * 10 : epsilon;
-        // }
-        // else if ( value > 0 && value < epsilon ) {
-        //     result = randomise ? Math.random() * epsilon * 10 : epsilon;
-        // }
-        // else if ( value < 0 && value > -epsilon ) {
-        //     result = -( randomise ? Math.random() * epsilon * 10 : epsilon );
-        // }
-
-        return result;
-    },
-
-    /**
-     * Linearly interpolates two values of various types. The given values
-     * must be of the same type for the interpolation to work.
-     * @param  {(number|Object)} start The start value of the lerp.
-     * @param  {(number|object)} end   The end value of the lerp.
-     * @param  {Number} delta The delta posiiton of the lerp operation. Ideally between 0 and 1 (inclusive).
-     * @return {(number|object|undefined)}       The result of the operation. Result will be undefined if
-     *                                               the start and end arguments aren't a supported type, or
-     *                                               if their types do not match.
-     */
-    lerpTypeAgnostic: function( start, end, delta ) {
-        'use strict';
-
-        var types = this.types,
-            out;
-
-        if ( typeof start === types.NUMBER && typeof end === types.NUMBER ) {
-            return start + ( ( end - start ) * delta );
-        }
-        else if ( start instanceof THREE.Vector2 && end instanceof THREE.Vector2 ) {
-            out = start.clone();
-            out.x = this.lerp( start.x, end.x, delta );
-            out.y = this.lerp( start.y, end.y, delta );
-            return out;
-        }
-        else if ( start instanceof THREE.Vector3 && end instanceof THREE.Vector3 ) {
-            out = start.clone();
-            out.x = this.lerp( start.x, end.x, delta );
-            out.y = this.lerp( start.y, end.y, delta );
-            out.z = this.lerp( start.z, end.z, delta );
-            return out;
-        }
-        else if ( start instanceof THREE.Vector4 && end instanceof THREE.Vector4 ) {
-            out = start.clone();
-            out.x = this.lerp( start.x, end.x, delta );
-            out.y = this.lerp( start.y, end.y, delta );
-            out.z = this.lerp( start.z, end.z, delta );
-            out.w = this.lerp( start.w, end.w, delta );
-            return out;
-        }
-        else if ( start instanceof THREE.Color && end instanceof THREE.Color ) {
-            out = start.clone();
-            out.r = this.lerp( start.r, end.r, delta );
-            out.g = this.lerp( start.g, end.g, delta );
-            out.b = this.lerp( start.b, end.b, delta );
-            return out;
-        }
-        else {
-            console.warn( 'Invalid argument types, or argument types do not match:', start, end );
-        }
-    },
-
-    /**
-     * Perform a linear interpolation operation on two numbers.
-     * @param  {Number} start The start value.
-     * @param  {Number} end   The end value.
-     * @param  {Number} delta The position to interpolate to.
-     * @return {Number}       The result of the lerp operation.
-     */
-    lerp: function( start, end, delta ) {
-        'use strict';
-        return start + ( ( end - start ) * delta );
-    },
-
-    /**
-     * Rounds a number to a nearest multiple.
-     *
-     * @param  {Number} n        The number to round.
-     * @param  {Number} multiple The multiple to round to.
-     * @return {Number}          The result of the round operation.
-     */
-    roundToNearestMultiple: function( n, multiple ) {
-        'use strict';
-
-        var remainder = 0;
-
-        if ( multiple === 0 ) {
-            return n;
-        }
-
-        remainder = Math.abs( n ) % multiple;
-
-        if ( remainder === 0 ) {
-            return n;
-        }
-
-        if ( n < 0 ) {
-            return -( Math.abs( n ) - remainder );
-        }
-
-        return n + multiple - remainder;
-    },
-
-    /**
-     * Check if all items in an array are equal. Uses strict equality.
-     *
-     * @param  {Array} array The array of values to check equality of.
-     * @return {Boolean}       Whether the array's values are all equal or not.
-     */
-    arrayValuesAreEqual: function( array ) {
-        'use strict';
-
-        for ( var i = 0; i < array.length - 1; ++i ) {
-            if ( array[ i ] !== array[ i + 1 ] ) {
-                return false;
-            }
-        }
-
-        return true;
-    },
-
-    // colorsAreEqual: function() {
-    //     var colors = Array.prototype.slice.call( arguments ),
-    //         numColors = colors.length;
-
-    //     for ( var i = 0, color1, color2; i < numColors - 1; ++i ) {
-    //         color1 = colors[ i ];
-    //         color2 = colors[ i + 1 ];
-
-    //         if (
-    //             color1.r !== color2.r ||
-    //             color1.g !== color2.g ||
-    //             color1.b !== color2.b
-    //         ) {
-    //             return false
-    //         }
-    //     }
-
-    //     return true;
-    // },
-
-
-    /**
-     * Given a start value and a spread value, create and return a random
-     * number.
-     * @param  {Number} base   The start value.
-     * @param  {Number} spread The size of the random variance to apply.
-     * @return {Number}        A randomised number.
-     */
-    randomFloat: function( base, spread ) {
-        'use strict';
-        return base + spread * ( Math.random() - 0.5 );
-    },
-
-
-
-    /**
-     * Given an SPE.ShaderAttribute instance, and various other settings,
-     * assign values to the attribute's array in a `vec3` format.
-     *
-     * @param  {Object} attribute   The instance of SPE.ShaderAttribute to save the result to.
-     * @param  {Number} index       The offset in the attribute's TypedArray to save the result from.
-     * @param  {Object} base        THREE.Vector3 instance describing the start value.
-     * @param  {Object} spread      THREE.Vector3 instance describing the random variance to apply to the start value.
-     * @param  {Object} spreadClamp THREE.Vector3 instance describing the multiples to clamp the randomness to.
-     */
-    randomVector3: function( attribute, index, base, spread, spreadClamp ) {
-        'use strict';
-
-        var x = base.x + ( Math.random() * spread.x - ( spread.x * 0.5 ) ),
-            y = base.y + ( Math.random() * spread.y - ( spread.y * 0.5 ) ),
-            z = base.z + ( Math.random() * spread.z - ( spread.z * 0.5 ) );
-
-        // var x = this.randomFloat( base.x, spread.x ),
-        // y = this.randomFloat( base.y, spread.y ),
-        // z = this.randomFloat( base.z, spread.z );
-
-        if ( spreadClamp ) {
-            x = -spreadClamp.x * 0.5 + this.roundToNearestMultiple( x, spreadClamp.x );
-            y = -spreadClamp.y * 0.5 + this.roundToNearestMultiple( y, spreadClamp.y );
-            z = -spreadClamp.z * 0.5 + this.roundToNearestMultiple( z, spreadClamp.z );
-        }
-
-        attribute.typedArray.setVec3Components( index, x, y, z );
-    },
-
-    /**
-     * Given an SPE.Shader attribute instance, and various other settings,
-     * assign Color values to the attribute.
-     * @param  {Object} attribute The instance of SPE.ShaderAttribute to save the result to.
-     * @param  {Number} index     The offset in the attribute's TypedArray to save the result from.
-     * @param  {Object} base      THREE.Color instance describing the start color.
-     * @param  {Object} spread    THREE.Vector3 instance describing the random variance to apply to the start color.
-     */
-    randomColor: function( attribute, index, base, spread ) {
-        'use strict';
-
-        var r = base.r + ( Math.random() * spread.x ),
-            g = base.g + ( Math.random() * spread.y ),
-            b = base.b + ( Math.random() * spread.z );
-
-        r = this.clamp( r, 0, 1 );
-        g = this.clamp( g, 0, 1 );
-        b = this.clamp( b, 0, 1 );
-
-
-        attribute.typedArray.setVec3Components( index, r, g, b );
-    },
-
-
-    randomColorAsHex: ( function() {
-        'use strict';
-
-        var workingColor = new THREE.Color();
-
-        /**
-         * Assigns a random color value, encoded as a hex value in decimal
-         * format, to a SPE.ShaderAttribute instance.
-         * @param  {Object} attribute The instance of SPE.ShaderAttribute to save the result to.
-         * @param  {Number} index     The offset in the attribute's TypedArray to save the result from.
-         * @param  {Object} base      THREE.Color instance describing the start color.
-         * @param  {Object} spread    THREE.Vector3 instance describing the random variance to apply to the start color.
-         */
-        return function( attribute, index, base, spread ) {
-            var numItems = base.length,
-                colors = [];
-
-            for ( var i = 0; i < numItems; ++i ) {
-                var spreadVector = spread[ i ];
-
-                workingColor.copy( base[ i ] );
-
-                workingColor.r += ( Math.random() * spreadVector.x ) - ( spreadVector.x * 0.5 );
-                workingColor.g += ( Math.random() * spreadVector.y ) - ( spreadVector.y * 0.5 );
-                workingColor.b += ( Math.random() * spreadVector.z ) - ( spreadVector.z * 0.5 );
-
-                workingColor.r = this.clamp( workingColor.r, 0, 1 );
-                workingColor.g = this.clamp( workingColor.g, 0, 1 );
-                workingColor.b = this.clamp( workingColor.b, 0, 1 );
-
-                colors.push( workingColor.getHex() );
-            }
-
-            attribute.typedArray.setVec4Components( index, colors[ 0 ], colors[ 1 ], colors[ 2 ], colors[ 3 ] );
-        };
-    }() ),
-
-    /**
-     * Assigns a random vector 3 value to an SPE.ShaderAttribute instance, projecting the
-     * given values onto a sphere.
-     *
-     * @param  {Object} attribute The instance of SPE.ShaderAttribute to save the result to.
-     * @param  {Number} index     The offset in the attribute's TypedArray to save the result from.
-     * @param  {Object} base              THREE.Vector3 instance describing the origin of the transform.
-     * @param  {Number} radius            The radius of the sphere to project onto.
-     * @param  {Number} radiusSpread      The amount of randomness to apply to the projection result
-     * @param  {Object} radiusScale       THREE.Vector3 instance describing the scale of each axis of the sphere.
-     * @param  {Number} radiusSpreadClamp What numeric multiple the projected value should be clamped to.
-     */
-    randomVector3OnSphere: function(
-        attribute, index, base, radius, radiusSpread, radiusScale, radiusSpreadClamp, distributionClamp
-    ) {
-        'use strict';
-
-        var depth = 2 * Math.random() - 1,
-            t = 6.2832 * Math.random(),
-            r = Math.sqrt( 1 - depth * depth ),
-            rand = this.randomFloat( radius, radiusSpread ),
-            x = 0,
-            y = 0,
-            z = 0;
-
-
-        if ( radiusSpreadClamp ) {
-            rand = Math.round( rand / radiusSpreadClamp ) * radiusSpreadClamp;
-        }
-
-
-
-        // Set position on sphere
-        x = r * Math.cos( t ) * rand;
-        y = r * Math.sin( t ) * rand;
-        z = depth * rand;
-
-        // Apply radius scale to this position
-        x *= radiusScale.x;
-        y *= radiusScale.y;
-        z *= radiusScale.z;
-
-        // Translate to the base position.
-        x += base.x;
-        y += base.y;
-        z += base.z;
-
-        // Set the values in the typed array.
-        attribute.typedArray.setVec3Components( index, x, y, z );
-    },
-
-    seededRandom: function( seed ) {
-        var x = Math.sin( seed ) * 10000;
-        return x - ( x | 0 );
-    },
-
-
-
-    /**
-     * Assigns a random vector 3 value to an SPE.ShaderAttribute instance, projecting the
-     * given values onto a 2d-disc.
-     *
-     * @param  {Object} attribute The instance of SPE.ShaderAttribute to save the result to.
-     * @param  {Number} index     The offset in the attribute's TypedArray to save the result from.
-     * @param  {Object} base              THREE.Vector3 instance describing the origin of the transform.
-     * @param  {Number} radius            The radius of the sphere to project onto.
-     * @param  {Number} radiusSpread      The amount of randomness to apply to the projection result
-     * @param  {Object} radiusScale       THREE.Vector3 instance describing the scale of each axis of the disc. The z-component is ignored.
-     * @param  {Number} radiusSpreadClamp What numeric multiple the projected value should be clamped to.
-     */
-    randomVector3OnDisc: function( attribute, index, base, radius, radiusSpread, radiusScale, radiusSpreadClamp ) {
-        'use strict';
-
-        var t = 6.2832 * Math.random(),
-            rand = Math.abs( this.randomFloat( radius, radiusSpread ) ),
-            x = 0,
-            y = 0,
-            z = 0;
-
-        if ( radiusSpreadClamp ) {
-            rand = Math.round( rand / radiusSpreadClamp ) * radiusSpreadClamp;
-        }
-
-        // Set position on sphere
-        x = Math.cos( t ) * rand;
-        y = Math.sin( t ) * rand;
-
-        // Apply radius scale to this position
-        x *= radiusScale.x;
-        y *= radiusScale.y;
-
-        // Translate to the base position.
-        x += base.x;
-        y += base.y;
-        z += base.z;
-
-        // Set the values in the typed array.
-        attribute.typedArray.setVec3Components( index, x, y, z );
-    },
-
-    randomDirectionVector3OnSphere: ( function() {
-        'use strict';
-
-        var v = new THREE.Vector3();
-
-        /**
-         * Given an SPE.ShaderAttribute instance, create a direction vector from the given
-         * position, using `speed` as the magnitude. Values are saved to the attribute.
-         *
-         * @param  {Object} attribute       The instance of SPE.ShaderAttribute to save the result to.
-         * @param  {Number} index           The offset in the attribute's TypedArray to save the result from.
-         * @param  {Number} posX            The particle's x coordinate.
-         * @param  {Number} posY            The particle's y coordinate.
-         * @param  {Number} posZ            The particle's z coordinate.
-         * @param  {Object} emitterPosition THREE.Vector3 instance describing the emitter's base position.
-         * @param  {Number} speed           The magnitude to apply to the vector.
-         * @param  {Number} speedSpread     The amount of randomness to apply to the magnitude.
-         */
-        return function( attribute, index, posX, posY, posZ, emitterPosition, speed, speedSpread ) {
-            v.copy( emitterPosition );
-
-            v.x -= posX;
-            v.y -= posY;
-            v.z -= posZ;
-
-            v.normalize().multiplyScalar( -this.randomFloat( speed, speedSpread ) );
-
-            attribute.typedArray.setVec3Components( index, v.x, v.y, v.z );
-        };
-    }() ),
-
-
-    randomDirectionVector3OnDisc: ( function() {
-        'use strict';
-
-        var v = new THREE.Vector3();
-
-        /**
-         * Given an SPE.ShaderAttribute instance, create a direction vector from the given
-         * position, using `speed` as the magnitude. Values are saved to the attribute.
-         *
-         * @param  {Object} attribute       The instance of SPE.ShaderAttribute to save the result to.
-         * @param  {Number} index           The offset in the attribute's TypedArray to save the result from.
-         * @param  {Number} posX            The particle's x coordinate.
-         * @param  {Number} posY            The particle's y coordinate.
-         * @param  {Number} posZ            The particle's z coordinate.
-         * @param  {Object} emitterPosition THREE.Vector3 instance describing the emitter's base position.
-         * @param  {Number} speed           The magnitude to apply to the vector.
-         * @param  {Number} speedSpread     The amount of randomness to apply to the magnitude.
-         */
-        return function( attribute, index, posX, posY, posZ, emitterPosition, speed, speedSpread ) {
-            v.copy( emitterPosition );
-
-            v.x -= posX;
-            v.y -= posY;
-            v.z -= posZ;
-
-            v.normalize().multiplyScalar( -this.randomFloat( speed, speedSpread ) );
-
-            attribute.typedArray.setVec3Components( index, v.x, v.y, 0 );
-        };
-    }() ),
-
-    getPackedRotationAxis: ( function() {
-        'use strict';
-
-        var v = new THREE.Vector3(),
-            vSpread = new THREE.Vector3(),
-            c = new THREE.Color(),
-            addOne = new THREE.Vector3( 1, 1, 1 );
-
-        /**
-         * Given a rotation axis, and a rotation axis spread vector,
-         * calculate a randomised rotation axis, and pack it into
-         * a hexadecimal value represented in decimal form.
-         * @param  {Object} axis       THREE.Vector3 instance describing the rotation axis.
-         * @param  {Object} axisSpread THREE.Vector3 instance describing the amount of randomness to apply to the rotation axis.
-         * @return {Number}            The packed rotation axis, with randomness.
-         */
-        return function( axis, axisSpread ) {
-            v.copy( axis ).normalize();
-            vSpread.copy( axisSpread ).normalize();
-
-            v.x += ( -axisSpread.x * 0.5 ) + ( Math.random() * axisSpread.x );
-            v.y += ( -axisSpread.y * 0.5 ) + ( Math.random() * axisSpread.y );
-            v.z += ( -axisSpread.z * 0.5 ) + ( Math.random() * axisSpread.z );
-
-            // v.x = Math.abs( v.x );
-            // v.y = Math.abs( v.y );
-            // v.z = Math.abs( v.z );
-
-            v.normalize().add( addOne ).multiplyScalar( 0.5 );
-
-            c.setRGB( v.x, v.y, v.z );
-
-            return c.getHex();
-        };
-    }() )
-};
-
-
- - - - -
- - - -
- -
- Documentation generated by JSDoc 3.3.3 on Sat Nov 14 2015 15:30:50 GMT+0000 (GMT) -
- - - - - diff --git a/docs/api/core_utils.js.html b/docs/api/core_utils.js.html new file mode 100644 index 0000000..f072e86 --- /dev/null +++ b/docs/api/core_utils.js.html @@ -0,0 +1,755 @@ + + + + + JSDoc: Source: core/utils.js + + + + + + + + + + +
+ +

Source: core/utils.js

+ + + + + + +
+
+
import * as THREE from 'three';
+import valueTypes from '@/constants/valueTypes';
+
+/**
+ * A bunch of utility functions used throughout the library.
+ * @namespace
+ * @type {Object}
+ */
+export default {
+
+	/**
+     * Given a value, a type, and a default value to fallback to,
+     * ensure the given argument adheres to the type requesting,
+     * returning the default value if type check is false.
+     *
+     * @param  {(boolean|string|number|object)} arg          The value to perform a type-check on.
+     * @param  {String} type         The type the `arg` argument should adhere to.
+     * @param  {(boolean|string|number|object)} defaultValue A default value to fallback on if the type check fails.
+     * @return {(boolean|string|number|object)}              The given value if type check passes, or the default value if it fails.
+     */
+	ensureTypedArg( arg, type, defaultValue ) {
+		'use strict';
+
+		if ( typeof arg === type ) {
+			return arg;
+		}
+		else {
+			return defaultValue;
+		}
+	},
+
+	/**
+     * Given an array of values, a type, and a default value,
+     * ensure the given array's contents ALL adhere to the provided type,
+     * returning the default value if type check fails.
+     *
+     * If the given value to check isn't an Array, delegates to SPE.utils.ensureTypedArg.
+     *
+     * @param  {Array|boolean|string|number|object} arg          The array of values to check type of.
+     * @param  {String} type         The type that should be adhered to.
+     * @param  {(boolean|string|number|object)} defaultValue A default fallback value.
+     * @return {(boolean|string|number|object)}              The given value if type check passes, or the default value if it fails.
+     */
+	ensureArrayTypedArg( arg, type, defaultValue ) {
+		'use strict';
+
+		// If the argument being checked is an array, loop through
+		// it and ensure all the values are of the correct type,
+		// falling back to the defaultValue if any aren't.
+		if ( Array.isArray( arg ) ) {
+			for ( var i = arg.length - 1; i >= 0; --i ) {
+				if ( typeof arg[ i ] !== type ) {
+					return defaultValue;
+				}
+			}
+
+			return arg;
+		}
+
+		// If the arg isn't an array then just fallback to
+		// checking the type.
+		return this.ensureTypedArg( arg, type, defaultValue );
+	},
+
+	/**
+     * Ensures the given value is an instance of a constructor function.
+     *
+     * @param  {Object} arg          The value to check instance of.
+     * @param  {Function} instance     The constructor of the instance to check against.
+     * @param  {Object} defaultValue A default fallback value if instance check fails
+     * @return {Object}              The given value if type check passes, or the default value if it fails.
+     */
+	ensureInstanceOf( arg, instance, defaultValue ) {
+		'use strict';
+
+		if ( instance !== undefined && arg instanceof instance ) {
+			return arg;
+		}
+		else {
+			return defaultValue;
+		}
+	},
+
+	/**
+     * Given an array of values, ensure the instances of all items in the array
+     * matches the given instance constructor falling back to a default value if
+     * the check fails.
+     *
+     * If given value isn't an Array, delegates to `SPE.utils.ensureInstanceOf`.
+     *
+     * @param  {Array|Object} arg          The value to perform the instanceof check on.
+     * @param  {Function} instance     The constructor of the instance to check against.
+     * @param  {Object} defaultValue A default fallback value if instance check fails
+     * @return {Object}              The given value if type check passes, or the default value if it fails.
+     */
+	ensureArrayInstanceOf( arg, instance, defaultValue ) {
+		'use strict';
+
+		// If the argument being checked is an array, loop through
+		// it and ensure all the values are of the correct type,
+		// falling back to the defaultValue if any aren't.
+		if ( Array.isArray( arg ) ) {
+			for ( var i = arg.length - 1; i >= 0; --i ) {
+				if ( instance !== undefined && arg[ i ] instanceof instance === false ) {
+					return defaultValue;
+				}
+			}
+
+			return arg;
+		}
+
+		// If the arg isn't an array then just fallback to
+		// checking the type.
+		return this.ensureInstanceOf( arg, instance, defaultValue );
+	},
+
+	/**
+     * Ensures that any "value-over-lifetime" properties of an emitter are
+     * of the correct length (as dictated by `SPE.valueOverLifetimeLength`).
+     *
+     * Delegates to `SPE.utils.interpolateArray` for array resizing.
+     *
+     * If properties aren't arrays, then property values are put into one.
+     *
+     * @param  {Object} property  The property of an SPE.Emitter instance to check compliance of.
+     * @param  {Number} minLength The minimum length of the array to create.
+     * @param  {Number} maxLength The maximum length of the array to create.
+     */
+	ensureValueOverLifetimeCompliance( property, minLength, maxLength ) {
+		'use strict';
+
+		minLength = minLength || 3;
+		maxLength = maxLength || 3;
+
+		// First, ensure both properties are arrays.
+		if ( Array.isArray( property._value ) === false ) {
+			property._value = [ property._value ];
+		}
+
+		if ( Array.isArray( property._spread ) === false ) {
+			property._spread = [ property._spread ];
+		}
+
+		var valueLength = this.clamp( property._value.length, minLength, maxLength ),
+			spreadLength = this.clamp( property._spread.length, minLength, maxLength ),
+			desiredLength = Math.max( valueLength, spreadLength );
+
+		if ( property._value.length !== desiredLength ) {
+			property._value = this.interpolateArray( property._value, desiredLength );
+		}
+
+		if ( property._spread.length !== desiredLength ) {
+			property._spread = this.interpolateArray( property._spread, desiredLength );
+		}
+	},
+
+	/**
+     * Performs linear interpolation (lerp) on an array.
+     *
+     * For example, lerping [1, 10], with a `newLength` of 10 will produce [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].
+     *
+     * Delegates to `SPE.utils.lerpTypeAgnostic` to perform the actual
+     * interpolation.
+     *
+     * @param  {Array} srcArray  The array to lerp.
+     * @param  {Number} newLength The length the array should be interpolated to.
+     * @return {Array}           The interpolated array.
+     */
+	interpolateArray( srcArray, newLength ) {
+		'use strict';
+
+		var sourceLength = srcArray.length,
+			newArray = [ typeof srcArray[ 0 ].clone === 'function' ? srcArray[ 0 ].clone() : srcArray[ 0 ] ],
+			factor = ( sourceLength - 1 ) / ( newLength - 1 );
+
+
+		for ( var i = 1; i < newLength - 1; ++i ) {
+			var f = i * factor,
+				before = Math.floor( f ),
+				after = Math.ceil( f ),
+				delta = f - before;
+
+			newArray[ i ] = this.lerpTypeAgnostic( srcArray[ before ], srcArray[ after ], delta );
+		}
+
+		newArray.push(
+			typeof srcArray[ sourceLength - 1 ].clone === 'function' ?
+				srcArray[ sourceLength - 1 ].clone() :
+				srcArray[ sourceLength - 1 ]
+		);
+
+		return newArray;
+	},
+
+	/**
+     * Clamp a number to between the given min and max values.
+     * @param  {Number} value The number to clamp.
+     * @param  {Number} min   The minimum value.
+     * @param  {Number} max   The maximum value.
+     * @return {Number}       The clamped number.
+     */
+	clamp( value, min, max ) {
+		'use strict';
+
+		return Math.max( min, Math.min( value, max ) );
+	},
+
+	/**
+     * If the given value is less than the epsilon value, then return
+     * a randomised epsilon value if specified, or just the epsilon value if not.
+     * Works for negative numbers as well as positive.
+     *
+     * @param  {Number} value     The value to perform the operation on.
+     * @param  {Boolean} randomise Whether the value should be randomised.
+     * @return {Number}           The result of the operation.
+     */
+	zeroToEpsilon( value, randomise ) {
+		'use strict';
+
+		var epsilon = 0.00001,
+			result = value;
+
+		result = randomise ? Math.random() * epsilon * 10 : epsilon;
+
+		if ( value < 0 && value > -epsilon ) {
+			result = -result;
+		}
+
+		// if ( value === 0 ) {
+		//     result = randomise ? Math.random() * epsilon * 10 : epsilon;
+		// }
+		// else if ( value > 0 && value < epsilon ) {
+		//     result = randomise ? Math.random() * epsilon * 10 : epsilon;
+		// }
+		// else if ( value < 0 && value > -epsilon ) {
+		//     result = -( randomise ? Math.random() * epsilon * 10 : epsilon );
+		// }
+
+		return result;
+	},
+
+	/**
+     * Linearly interpolates two values of various valueTypes. The given values
+     * must be of the same type for the interpolation to work.
+     * @param  {(number|Object)} start The start value of the lerp.
+     * @param  {(number|object)} end   The end value of the lerp.
+     * @param  {Number} delta The delta posiiton of the lerp operation. Ideally between 0 and 1 (inclusive).
+     * @return {(number|object|undefined)}       The result of the operation. Result will be undefined if
+     *                                               the start and end arguments aren't a supported type, or
+     *                                               if their types do not match.
+     */
+	lerpTypeAgnostic( start, end, delta ) {
+		'use strict';
+
+		var out;
+
+		if ( typeof start === valueTypes.NUMBER && typeof end === valueTypes.NUMBER ) {
+			return start + ( ( end - start ) * delta );
+		}
+		else if ( start instanceof THREE.Vector2 && end instanceof THREE.Vector2 ) {
+			out = start.clone();
+			out.x = this.lerp( start.x, end.x, delta );
+			out.y = this.lerp( start.y, end.y, delta );
+			return out;
+		}
+		else if ( start instanceof THREE.Vector3 && end instanceof THREE.Vector3 ) {
+			out = start.clone();
+			out.x = this.lerp( start.x, end.x, delta );
+			out.y = this.lerp( start.y, end.y, delta );
+			out.z = this.lerp( start.z, end.z, delta );
+			return out;
+		}
+		else if ( start instanceof THREE.Vector4 && end instanceof THREE.Vector4 ) {
+			out = start.clone();
+			out.x = this.lerp( start.x, end.x, delta );
+			out.y = this.lerp( start.y, end.y, delta );
+			out.z = this.lerp( start.z, end.z, delta );
+			out.w = this.lerp( start.w, end.w, delta );
+			return out;
+		}
+		else if ( start instanceof THREE.Color && end instanceof THREE.Color ) {
+			out = start.clone();
+			out.r = this.lerp( start.r, end.r, delta );
+			out.g = this.lerp( start.g, end.g, delta );
+			out.b = this.lerp( start.b, end.b, delta );
+			return out;
+		}
+		else {
+			console.warn( 'Invalid argument types, or argument types do not match:', start, end );
+		}
+	},
+
+	/**
+     * Perform a linear interpolation operation on two numbers.
+     * @param  {Number} start The start value.
+     * @param  {Number} end   The end value.
+     * @param  {Number} delta The position to interpolate to.
+     * @return {Number}       The result of the lerp operation.
+     */
+	lerp( start, end, delta ) {
+		'use strict';
+		return start + ( ( end - start ) * delta );
+	},
+
+	/**
+     * Rounds a number to a nearest multiple.
+     *
+     * @param  {Number} n        The number to round.
+     * @param  {Number} multiple The multiple to round to.
+     * @return {Number}          The result of the round operation.
+     */
+	roundToNearestMultiple( n, multiple ) {
+		'use strict';
+
+		var remainder = 0;
+
+		if ( multiple === 0 ) {
+			return n;
+		}
+
+		remainder = Math.abs( n ) % multiple;
+
+		if ( remainder === 0 ) {
+			return n;
+		}
+
+		if ( n < 0 ) {
+			return -( Math.abs( n ) - remainder );
+		}
+
+		return n + multiple - remainder;
+	},
+
+	/**
+     * Check if all items in an array are equal. Uses strict equality.
+     *
+     * @param  {Array} array The array of values to check equality of.
+     * @return {Boolean}       Whether the array's values are all equal or not.
+     */
+	arrayValuesAreEqual( array ) {
+		'use strict';
+
+		for ( var i = 0; i < array.length - 1; ++i ) {
+			if ( array[ i ] !== array[ i + 1 ] ) {
+				return false;
+			}
+		}
+
+		return true;
+	},
+
+	// colorsAreEqual: function() {
+	//     var colors = Array.prototype.slice.call( arguments ),
+	//         numColors = colors.length;
+
+	//     for ( var i = 0, color1, color2; i < numColors - 1; ++i ) {
+	//         color1 = colors[ i ];
+	//         color2 = colors[ i + 1 ];
+
+	//         if (
+	//             color1.r !== color2.r ||
+	//             color1.g !== color2.g ||
+	//             color1.b !== color2.b
+	//         ) {
+	//             return false
+	//         }
+	//     }
+
+	//     return true;
+	// },
+
+
+	/**
+     * Given a start value and a spread value, create and return a random
+     * number.
+     * @param  {Number} base   The start value.
+     * @param  {Number} spread The size of the random variance to apply.
+     * @return {Number}        A randomised number.
+     */
+	randomFloat( base, spread ) {
+		'use strict';
+		return base + spread * ( Math.random() - 0.5 );
+	},
+
+
+
+	/**
+     * Given an SPE.ShaderAttribute instance, and various other settings,
+     * assign values to the attribute's array in a `vec3` format.
+     *
+     * @param  {Object} attribute   The instance of SPE.ShaderAttribute to save the result to.
+     * @param  {Number} index       The offset in the attribute's TypedArray to save the result from.
+     * @param  {Object} base        THREE.Vector3 instance describing the start value.
+     * @param  {Object} spread      THREE.Vector3 instance describing the random variance to apply to the start value.
+     * @param  {Object} spreadClamp THREE.Vector3 instance describing the multiples to clamp the randomness to.
+     */
+	randomVector3( attribute, index, base, spread, spreadClamp ) {
+		'use strict';
+
+		var x = base.x + ( Math.random() * spread.x - ( spread.x * 0.5 ) ),
+			y = base.y + ( Math.random() * spread.y - ( spread.y * 0.5 ) ),
+			z = base.z + ( Math.random() * spread.z - ( spread.z * 0.5 ) );
+
+		// var x = this.randomFloat( base.x, spread.x ),
+		// y = this.randomFloat( base.y, spread.y ),
+		// z = this.randomFloat( base.z, spread.z );
+
+		if ( spreadClamp ) {
+			x = -spreadClamp.x * 0.5 + this.roundToNearestMultiple( x, spreadClamp.x );
+			y = -spreadClamp.y * 0.5 + this.roundToNearestMultiple( y, spreadClamp.y );
+			z = -spreadClamp.z * 0.5 + this.roundToNearestMultiple( z, spreadClamp.z );
+		}
+
+		attribute.typedArray.setVec3Components( index, x, y, z );
+	},
+
+	/**
+     * Given an SPE.Shader attribute instance, and various other settings,
+     * assign Color values to the attribute.
+     * @param  {Object} attribute The instance of SPE.ShaderAttribute to save the result to.
+     * @param  {Number} index     The offset in the attribute's TypedArray to save the result from.
+     * @param  {Object} base      THREE.Color instance describing the start color.
+     * @param  {Object} spread    THREE.Vector3 instance describing the random variance to apply to the start color.
+     */
+	randomColor( attribute, index, base, spread ) {
+		'use strict';
+
+		var r = base.r + ( Math.random() * spread.x ),
+			g = base.g + ( Math.random() * spread.y ),
+			b = base.b + ( Math.random() * spread.z );
+
+		r = this.clamp( r, 0, 1 );
+		g = this.clamp( g, 0, 1 );
+		b = this.clamp( b, 0, 1 );
+
+
+		attribute.typedArray.setVec3Components( index, r, g, b );
+	},
+
+
+	randomColorAsHex: ( function() {
+		'use strict';
+
+		var workingColor = new THREE.Color();
+
+		/**
+         * Assigns a random color value, encoded as a hex value in decimal
+         * format, to a SPE.ShaderAttribute instance.
+         * @param  {Object} attribute The instance of SPE.ShaderAttribute to save the result to.
+         * @param  {Number} index     The offset in the attribute's TypedArray to save the result from.
+         * @param  {Object} base      THREE.Color instance describing the start color.
+         * @param  {Object} spread    THREE.Vector3 instance describing the random variance to apply to the start color.
+         */
+		return function( attribute, index, base, spread ) {
+			var numItems = base.length,
+				colors = [];
+
+			for ( var i = 0; i < numItems; ++i ) {
+				var spreadVector = spread[ i ];
+
+				workingColor.copy( base[ i ] );
+
+				workingColor.r += ( Math.random() * spreadVector.x ) - ( spreadVector.x * 0.5 );
+				workingColor.g += ( Math.random() * spreadVector.y ) - ( spreadVector.y * 0.5 );
+				workingColor.b += ( Math.random() * spreadVector.z ) - ( spreadVector.z * 0.5 );
+
+				workingColor.r = this.clamp( workingColor.r, 0, 1 );
+				workingColor.g = this.clamp( workingColor.g, 0, 1 );
+				workingColor.b = this.clamp( workingColor.b, 0, 1 );
+
+				colors.push( workingColor.getHex() );
+			}
+
+			attribute.typedArray.setVec4Components( index, colors[ 0 ], colors[ 1 ], colors[ 2 ], colors[ 3 ] );
+		};
+	}() ),
+
+	/**
+     * Given an SPE.ShaderAttribute instance, and various other settings,
+     * assign values to the attribute's array in a `vec3` format.
+     *
+     * @param  {Object} attribute   The instance of SPE.ShaderAttribute to save the result to.
+     * @param  {Number} index       The offset in the attribute's TypedArray to save the result from.
+     * @param  {Object} start       THREE.Vector3 instance describing the start line position.
+     * @param  {Object} end         THREE.Vector3 instance describing the end line position.
+     */
+	randomVector3OnLine( attribute, index, start, end ) {
+		'use strict';
+		var pos = start.clone();
+
+		pos.lerp( end, Math.random() );
+
+		attribute.typedArray.setVec3Components( index, pos.x, pos.y, pos.z );
+	},
+
+	/**
+     * Given an SPE.Shader attribute instance, and various other settings,
+     * assign Color values to the attribute.
+     * @param  {Object} attribute The instance of SPE.ShaderAttribute to save the result to.
+     * @param  {Number} index     The offset in the attribute's TypedArray to save the result from.
+     * @param  {Object} base      THREE.Color instance describing the start color.
+     * @param  {Object} spread    THREE.Vector3 instance describing the random variance to apply to the start color.
+     */
+
+	/**
+     * Assigns a random vector 3 value to an SPE.ShaderAttribute instance, projecting the
+     * given values onto a sphere.
+     *
+     * @param  {Object} attribute The instance of SPE.ShaderAttribute to save the result to.
+     * @param  {Number} index     The offset in the attribute's TypedArray to save the result from.
+     * @param  {Object} base              THREE.Vector3 instance describing the origin of the transform.
+     * @param  {Number} radius            The radius of the sphere to project onto.
+     * @param  {Number} radiusSpread      The amount of randomness to apply to the projection result
+     * @param  {Object} radiusScale       THREE.Vector3 instance describing the scale of each axis of the sphere.
+     * @param  {Number} radiusSpreadClamp What numeric multiple the projected value should be clamped to.
+     */
+	randomVector3OnSphere(
+		attribute, index, base, radius, radiusSpread, radiusScale, radiusSpreadClamp, distributionClamp
+	) {
+		'use strict';
+
+		var depth = 2 * Math.random() - 1,
+			t = 6.2832 * Math.random(),
+			r = Math.sqrt( 1 - depth * depth ),
+			rand = this.randomFloat( radius, radiusSpread ),
+			x = 0,
+			y = 0,
+			z = 0;
+
+
+		if ( radiusSpreadClamp ) {
+			rand = Math.round( rand / radiusSpreadClamp ) * radiusSpreadClamp;
+		}
+
+
+
+		// Set position on sphere
+		x = r * Math.cos( t ) * rand;
+		y = r * Math.sin( t ) * rand;
+		z = depth * rand;
+
+		// Apply radius scale to this position
+		x *= radiusScale.x;
+		y *= radiusScale.y;
+		z *= radiusScale.z;
+
+		// Translate to the base position.
+		x += base.x;
+		y += base.y;
+		z += base.z;
+
+		// Set the values in the typed array.
+		attribute.typedArray.setVec3Components( index, x, y, z );
+	},
+
+	seededRandom( seed ) {
+		var x = Math.sin( seed ) * 10000;
+		return x - ( x | 0 );
+	},
+
+
+
+	/**
+     * Assigns a random vector 3 value to an SPE.ShaderAttribute instance, projecting the
+     * given values onto a 2d-disc.
+     *
+     * @param  {Object} attribute The instance of SPE.ShaderAttribute to save the result to.
+     * @param  {Number} index     The offset in the attribute's TypedArray to save the result from.
+     * @param  {Object} base              THREE.Vector3 instance describing the origin of the transform.
+     * @param  {Number} radius            The radius of the sphere to project onto.
+     * @param  {Number} radiusSpread      The amount of randomness to apply to the projection result
+     * @param  {Object} radiusScale       THREE.Vector3 instance describing the scale of each axis of the disc. The z-component is ignored.
+     * @param  {Number} radiusSpreadClamp What numeric multiple the projected value should be clamped to.
+     */
+	randomVector3OnDisc( attribute, index, base, radius, radiusSpread, radiusScale, radiusSpreadClamp ) {
+		'use strict';
+
+		var t = 6.2832 * Math.random(),
+			rand = Math.abs( this.randomFloat( radius, radiusSpread ) ),
+			x = 0,
+			y = 0,
+			z = 0;
+
+		if ( radiusSpreadClamp ) {
+			rand = Math.round( rand / radiusSpreadClamp ) * radiusSpreadClamp;
+		}
+
+		// Set position on sphere
+		x = Math.cos( t ) * rand;
+		y = Math.sin( t ) * rand;
+
+		// Apply radius scale to this position
+		x *= radiusScale.x;
+		y *= radiusScale.y;
+
+		// Translate to the base position.
+		x += base.x;
+		y += base.y;
+		z += base.z;
+
+		// Set the values in the typed array.
+		attribute.typedArray.setVec3Components( index, x, y, z );
+	},
+
+	randomDirectionVector3OnSphere: ( function() {
+		'use strict';
+
+		var v = new THREE.Vector3();
+
+		/**
+         * Given an SPE.ShaderAttribute instance, create a direction vector from the given
+         * position, using `speed` as the magnitude. Values are saved to the attribute.
+         *
+         * @param  {Object} attribute       The instance of SPE.ShaderAttribute to save the result to.
+         * @param  {Number} index           The offset in the attribute's TypedArray to save the result from.
+         * @param  {Number} posX            The particle's x coordinate.
+         * @param  {Number} posY            The particle's y coordinate.
+         * @param  {Number} posZ            The particle's z coordinate.
+         * @param  {Object} emitterPosition THREE.Vector3 instance describing the emitter's base position.
+         * @param  {Number} speed           The magnitude to apply to the vector.
+         * @param  {Number} speedSpread     The amount of randomness to apply to the magnitude.
+         */
+		return function( attribute, index, posX, posY, posZ, emitterPosition, speed, speedSpread ) {
+			v.copy( emitterPosition );
+
+			v.x -= posX;
+			v.y -= posY;
+			v.z -= posZ;
+
+			v.normalize().multiplyScalar( -this.randomFloat( speed, speedSpread ) );
+
+			attribute.typedArray.setVec3Components( index, v.x, v.y, v.z );
+		};
+	}() ),
+
+
+	randomDirectionVector3OnDisc: ( function() {
+		'use strict';
+
+		var v = new THREE.Vector3();
+
+		/**
+         * Given an SPE.ShaderAttribute instance, create a direction vector from the given
+         * position, using `speed` as the magnitude. Values are saved to the attribute.
+         *
+         * @param  {Object} attribute       The instance of SPE.ShaderAttribute to save the result to.
+         * @param  {Number} index           The offset in the attribute's TypedArray to save the result from.
+         * @param  {Number} posX            The particle's x coordinate.
+         * @param  {Number} posY            The particle's y coordinate.
+         * @param  {Number} posZ            The particle's z coordinate.
+         * @param  {Object} emitterPosition THREE.Vector3 instance describing the emitter's base position.
+         * @param  {Number} speed           The magnitude to apply to the vector.
+         * @param  {Number} speedSpread     The amount of randomness to apply to the magnitude.
+         */
+		return function( attribute, index, posX, posY, posZ, emitterPosition, speed, speedSpread ) {
+			v.copy( emitterPosition );
+
+			v.x -= posX;
+			v.y -= posY;
+			v.z -= posZ;
+
+			v.normalize().multiplyScalar( -this.randomFloat( speed, speedSpread ) );
+
+			attribute.typedArray.setVec3Components( index, v.x, v.y, 0 );
+		};
+	}() ),
+
+	getPackedRotationAxis: ( function() {
+		'use strict';
+
+		var v = new THREE.Vector3(),
+			vSpread = new THREE.Vector3(),
+			c = new THREE.Color(),
+			addOne = new THREE.Vector3( 1, 1, 1 );
+
+		/**
+         * Given a rotation axis, and a rotation axis spread vector,
+         * calculate a randomised rotation axis, and pack it into
+         * a hexadecimal value represented in decimal form.
+         * @param  {Object} axis       THREE.Vector3 instance describing the rotation axis.
+         * @param  {Object} axisSpread THREE.Vector3 instance describing the amount of randomness to apply to the rotation axis.
+         * @return {Number}            The packed rotation axis, with randomness.
+         */
+		return function( axis, axisSpread ) {
+			v.copy( axis ).normalize();
+			vSpread.copy( axisSpread ).normalize();
+
+			v.x += ( -axisSpread.x * 0.5 ) + ( Math.random() * axisSpread.x );
+			v.y += ( -axisSpread.y * 0.5 ) + ( Math.random() * axisSpread.y );
+			v.z += ( -axisSpread.z * 0.5 ) + ( Math.random() * axisSpread.z );
+
+			// v.x = Math.abs( v.x );
+			// v.y = Math.abs( v.y );
+			// v.z = Math.abs( v.z );
+
+			v.normalize().add( addOne )
+				.multiplyScalar( 0.5 );
+
+			c.setRGB( v.x, v.y, v.z );
+
+			return c.getHex();
+		};
+	}() ),
+};
+
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.4 on Sun Jun 28 2020 15:22:31 GMT+0100 (British Summer Time) +
+ + + + + diff --git a/docs/api/global.html b/docs/api/global.html index 7d2320f..6a0e5da 100644 --- a/docs/api/global.html +++ b/docs/api/global.html @@ -28,19 +28,5645 @@

Global

-

+

- + +
+ +
+
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + + + + + + + + + + + +

Methods

+ + + + + + + +

_createBufferAttribute(size)

+ + + + + + +
+ Creates a THREE.BufferAttribute instance if one doesn't exist already. + +Ensures a typed array is present by calling _ensureTypedArray() first. + +If a buffer attribute exists already, then it will be marked as needing an update. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
size + + +Number + + + + The size of the typed array to create if one doesn't exist, or resize existing array to.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

_ensureTypedArray(size)

+ + + + + + +
+ Make sure this attribute has a typed array associated with it. + +If it does, then it will ensure the typed array is of the correct size. + +If not, a new `TypedArrayHelper` instance will be created. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
size + + +Number + + + + The size of the typed array to create or update to.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

addEmitter(emitter)

+ + + + + + +
+ Adds an SPE.Emitter instance to this group, creating particle values and +assigning them to this group's shader attributes. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
emitter + + +Emitter + + + + The emitter to add to this group.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

addPool(numEmitters, emitterOptions, createNew) → {Group}

+ + + + + + +
+ Add a pool of emitters to this particle group +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
numEmitters + + +Number + + + + The number of emitters to add to the pool.
emitterOptions + + +EmitterOptions +| + +Array + + + + An object, or array of objects, describing the options to pass to each emitter.
createNew + + +Boolean + + + + Should a new emitter be created if the pool runs out?
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ This group instance. +
+ + + +
+
+ Type +
+
+ +Group + + +
+
+ + + + + + + + + + + + + +

disable() → {Emitter}

+ + + + + + +
+ Disables th emitter, but does not instantly remove it's +particles fromt the scene. When called, the emitter will be +'switched off' and just stop emitting. Any particle's alive will +be allowed to finish their lifecycle. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ This emitter instance. +
+ + + +
+
+ Type +
+
+ +Emitter + + +
+
+ + + + + + + + + + + + + +

dispose() → {Group}

+ + + + + + +
+ Dipose the geometry and material for the group. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ Group instance. +
+ + + +
+
+ Type +
+
+ +Group + + +
+
+ + + + + + + + + + + + + +

enable() → {Emitter}

+ + + + + + +
+ Enables the emitter. If not already enabled, the emitter +will start emitting particles. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ This emitter instance. +
+ + + +
+
+ Type +
+
+ +Emitter + + +
+
+ + + + + + + + + + + + + +

flagUpdate()

+ + + + + + +
+ Calculate the number of indices that this attribute should mark as needing +updating. Also marks the attribute as needing an update. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

getComponentValueAtIndex(index) → {TypedArray}

+ + + + + + +
+ Returns the component value of the array at the given index, taking into account +the `indexOffset` property of this class. + +If the componentSize is set to 3, then it will return a new TypedArray +of length 3. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
index + + +Number + + + + The index in the array to fetch.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ The component value at the given index. +
+ + + +
+
+ Type +
+
+ +TypedArray + + +
+
+ + + + + + + + + + + + + +

getFromPool() → {Emitter|null}

+ + + + + + +
+ Fetch a single emitter instance from the pool. +If there are no objects in the pool, a new emitter will be +created if specified. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +Emitter +| + +null + + +
+
+ + + + + + + + + + + + + +

getLength() → {Number}

+ + + + + + +
+ Returns the length of the typed array associated with this attribute. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ The length of the typed array. Will be 0 if no typed array has been created yet. +
+ + + +
+
+ Type +
+
+ +Number + + +
+
+ + + + + + + + + + + + + +

getPool() → {Array}

+ + + + + + +
+ Get the pool array +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +Array + + +
+
+ + + + + + + + + + + + + +

getValueAtIndex(index) → {Number}

+ + + + + + +
+ Returns the value of the array at the given index, taking into account +the `indexOffset` property of this class. + +Note that this function ignores the component size and will just return a +single value. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
index + + +Number + + + + The index in the array to fetch.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ The value at the given index. +
+ + + +
+
+ Type +
+
+ +Number + + +
+
+ + + + + + + + + + + + + +

grow(size) → {SPE.TypedArrayHelper}

+ + + + + + +
+ Grows the internal array. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
size + + +Number + + + + The new size of the typed array. Must be larger than `this.array.length`.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ Instance of this class. +
+ + + +
+
+ Type +
+
+ +SPE.TypedArrayHelper + + +
+
+ + + + + + + + + + + + + +

releaseIntoPool(emitter) → {Group}

+ + + + + + +
+ Release an emitter into the pool. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
emitter + + +ShaderParticleEmitter + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ This group instance. +
+ + + +
+
+ Type +
+
+ +Group + + +
+
+ + + + + + + + + + + + + +

remove() → {Emitter}

+ + + + + + +
+ Remove this emitter from it's parent group (if it has been added to one). +Delgates to Group.prototype.removeEmitter(). + +When called, all particle's belonging to this emitter will be instantly +removed from the scene. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + +
See:
+
+
    +
  • Group.prototype.removeEmitter
  • +
+
+ + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ This emitter instance. +
+ + + +
+
+ Type +
+
+ +Emitter + + +
+
+ + + + + + + + + + + + + +

removeEmitter(emitter)

+ + + + + + +
+ Removes an Emitter instance from this group. When called, +all particle's belonging to the given emitter will be instantly +removed from the scene. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
emitter + + +Emitter + + + + The emitter to add to this group.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

reset(forceopt) → {Emitter}

+ + + + + + +
+ Resets all the emitter's particles to their start positions +and marks the particles as dead if the `force` argument is +true. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
force + + +Boolean + + + + + + <optional>
+ + + + + +
If true, all particles will be marked as dead instantly.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ This emitter instance. +
+ + + +
+
+ Type +
+
+ +Emitter + + +
+
+ + + + + + + + + + + + + +

resetUpdateRange()

+ + + + + + +
+ Reset the index update counts for this attribute +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

setColor(index, color) → {SPE.TypedArrayHelper}

+ + + + + + +
+ Set a Color value at `index`. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
index + + +Number + + + + The index at which to set the vec3 values from.
color + + +Color + + + + Any object that has `r`, `g`, and `b` properties.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ Instance of this class. +
+ + + +
+
+ Type +
+
+ +SPE.TypedArrayHelper + + +
+
+ + + + + + + + + + + + + +

setFromArray(index, array) → {SPE.TypedArrayHelper}

+ + + + + + +
+ Copies from the given TypedArray into this one, using the index argument +as the start position. Alias for `TypedArray.set`. Will automatically resize +if the given source array is of a larger size than the internal array. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
index + + +Number + + + + The start position from which to copy into this array.
array + + +TypedArray + + + + The array from which to copy; the source array.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ Instance of this class. +
+ + + +
+
+ Type +
+
+ +SPE.TypedArrayHelper + + +
+
+ + + + + + + + + + + + + +

setMat3(index, mat3) → {SPE.TypedArrayHelper}

+ + + + + + +
+ Set a Matrix3 value at `index`. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
index + + +Number + + + + The index at which to set the matrix values from.
mat3 + + +Matrix3 + + + + The 3x3 matrix to set from. Must have a TypedArray property named `elements` to copy from.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ Instance of this class. +
+ + + +
+
+ Type +
+
+ +SPE.TypedArrayHelper + + +
+
+ + + + + + + + + + + + + +

setMat4(index, mat3) → {SPE.TypedArrayHelper}

+ + + + + + +
+ Set a Matrix4 value at `index`. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
index + + +Number + + + + The index at which to set the matrix values from.
mat3 + + +Matrix4 + + + + The 4x4 matrix to set from. Must have a TypedArray property named `elements` to copy from.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ Instance of this class. +
+ + + +
+
+ Type +
+
+ +SPE.TypedArrayHelper + + +
+
+ + + + + + + + + + + + + +

setNumber(index, numericValue) → {SPE.TypedArrayHelper}

+ + + + + + +
+ Set a Number value at `index`. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
index + + +Number + + + + The index at which to set the vec3 values from.
numericValue + + +Number + + + + The number to assign to this index in the array.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ Instance of this class. +
+ + + +
+
+ Type +
+
+ +SPE.TypedArrayHelper + + +
+
+ + + + + + + + + + + + + +

setSize(size)

+ + + + + + +
+ Sets the size of the internal array. + +Delegates to `this.shrink` or `this.grow` depending on size +argument's relation to the current size of the internal array. + +Note that if the array is to be shrunk, data will be lost. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
size + + +Number + + + + The new size of the array.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

setUpdateRange(min, max)

+ + + + + + +
+ Calculate the minimum and maximum update range for this buffer attribute using +component size independant min and max values. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
min + + +Number + + + + The start of the range to mark as needing an update.
max + + +Number + + + + The end of the range to mark as needing an update.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

setVec2(index, vec2) → {SPE.TypedArrayHelper}

+ + + + + + +
+ Set a Vector2 value at `index`. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
index + + +Number + + + + The index at which to set the vec2 values from.
vec2 + + +Vector2 + + + + Any object that has `x` and `y` properties.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ Instance of this class. +
+ + + +
+
+ Type +
+
+ +SPE.TypedArrayHelper + + +
+
+ + + + + + + + + + + + + +

setVec2Components(index, x, y) → {SPE.TypedArrayHelper}

+ + + + + + +
+ Set a Vector2 value using raw components. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
index + + +Number + + + + The index at which to set the vec2 values from.
x + + +Number + + + + The Vec2's `x` component.
y + + +Number + + + + The Vec2's `y` component.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ Instance of this class. +
+ + + +
+
+ Type +
+
+ +SPE.TypedArrayHelper + + +
+
+ + + + + + + + + + + + + +

setVec3(index, vec2) → {SPE.TypedArrayHelper}

+ + + + + + +
+ Set a Vector3 value at `index`. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
index + + +Number + + + + The index at which to set the vec3 values from.
vec2 + + +Vector3 + + + + Any object that has `x`, `y`, and `z` properties.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ Instance of this class. +
+ + + +
+
+ Type +
+
+ +SPE.TypedArrayHelper + + +
+
+ + + + + + + + + + + + + +

setVec3Components(index, x, y, z) → {SPE.TypedArrayHelper}

+ + + + + + +
+ Set a Vector3 value using raw components. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
index + + +Number + + + + The index at which to set the vec3 values from.
x + + +Number + + + + The Vec3's `x` component.
y + + +Number + + + + The Vec3's `y` component.
z + + +Number + + + + The Vec3's `z` component.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ Instance of this class. +
+ + + +
+
+ Type +
+
+ +SPE.TypedArrayHelper + + +
+
+ + + + + + + + + + + + + +

setVec4(index, vec2) → {SPE.TypedArrayHelper}

+ + + + + + +
+ Set a Vector4 value at `index`. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
index + + +Number + + + + The index at which to set the vec4 values from.
vec2 + + +Vector4 + + + + Any object that has `x`, `y`, `z`, and `w` properties.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ Instance of this class. +
+ + + +
+
+ Type +
+
+ +SPE.TypedArrayHelper + + +
+
+ + + + + + + + + + + + + +

setVec4Components(index, x, y, z, w) → {SPE.TypedArrayHelper}

+ + + + + + +
+ Set a Vector4 value using raw components. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
index + + +Number + + + + The index at which to set the vec4 values from.
x + + +Number + + + + The Vec4's `x` component.
y + + +Number + + + + The Vec4's `y` component.
z + + +Number + + + + The Vec4's `z` component.
w + + +Number + + + + The Vec4's `w` component.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ Instance of this class. +
+ + + +
+
+ Type +
+
+ +SPE.TypedArrayHelper + + +
+
+ + + + + + + + + + + + + +

shrink(size) → {SPE.TypedArrayHelper}

+ + + + + + +
+ Shrinks the internal array. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
size + + +Number + + + + The new size of the typed array. Must be smaller than `this.array.length`.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ Instance of this class. +
+ + + +
+
+ Type +
+
+ +SPE.TypedArrayHelper + + +
+
+ + + + + + + + + + + + + +

splice(start, end) → {Object}

+ + + + + + +
+ Perform a splice operation on this array's buffer. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
start + + +Number + + + + The start index of the splice. Will be multiplied by the number of components for this attribute.
end + + +Number + + + + The end index of the splice. Will be multiplied by the number of components for this attribute.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ The SPE.TypedArrayHelper instance. +
+ + + +
+
+ Type +
+
+ +Object + + +
+
+ + + + + + + + + - -
-
- +

splice(start, end)

+ + + + + + +
+ Perform a splice operation on this attribute's buffer. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
start + + +Number + + + + The start index of the splice. Will be multiplied by the number of components for this attribute.
end + + +Number + + + + The end index of the splice. Will be multiplied by the number of components for this attribute.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

tick(dt)

+ + + + + + +
+ Simulates one frame's worth of particles, updating particles +that are already alive, and marking ones that are currently dead +but should be alive as alive. + +If the emitter is marked as static, then this function will do nothing. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
dt + + +Number + + + + The number of seconds to simulate (deltaTime)
+ + + -
@@ -69,6 +5695,11 @@

+ +
Source:
+
@@ -79,55 +5710,54 @@

- - -
- - - - - - - - -

Type Definitions

- - -

distribution

-
Type:
-
    -
  • + + + + -Number + + + +

    tick(dtopt)

    + + + + + + +
    + Simulate all the emitter's belonging to this group, updating +attribute values along the way. +
    -
  • -
-
Properties:
+ + +
Parameters:
- +
@@ -137,8 +5767,12 @@
Properties:
+ + + + @@ -149,7 +5783,7 @@
Properties:
- + + + + + - + + +
TypeAttributesDefaultDescription
SPE.distributions.BOXdt @@ -162,17 +5796,150 @@
Properties:
+ + <optional>
+ + + + + +
+ + Group's `fixedTimeStep` value + + Values will be distributed within a box.The number of seconds to simulate the group's emitters for (deltaTime)
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

triggerPoolEmitter(numEmitters, positionopt) → {Group}

+ + + + + + +
+ Set a given number of emitters as alive, with an optional position +vector3 to move them to. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + - + + + - + - + + + - + @@ -221,6 +6006,8 @@
Properties:
+ +
@@ -250,7 +6037,7 @@
Properties:
Source:
@@ -266,7 +6053,49 @@
Properties:
- + + + + + + + + + +
Returns:
+ + +
+ This group instance. +
+ + + +
+
+ Type +
+
+ +Group + + +
+
+ + + + + + + + + + + +

Type Definitions

+ +

Emitter

@@ -322,7 +6151,7 @@
Type:
Source:
@@ -332,7 +6161,7 @@
Type:
See:
@@ -408,7 +6237,7 @@
Properties:
+ Must be an distributions.* value. @@ -909,7 +6738,8 @@
Properties
+ of this vector is used. + When using a LINE distribution, this value is the endpoint of the LINE. @@ -949,7 +6779,8 @@
Properties
+ of this vector is used. + When using a LINE distribution, this property is ignored. @@ -1036,7 +6867,7 @@
Properties
NameTypeAttributesDescription
SPE.distributions.SPHEREnumEmitters @@ -185,33 +5952,51 @@
Properties:
+ + + + + + Values will be distributed within a sphere.The number of emitters to activate
SPE.distributions.DISCposition -Number +Object + + <optional>
+ + + + + +
Values will be distributed within a 2D disc.A THREE.Vector3 instance describing the position to activate the emitter(s) at.
-distribution +distribution @@ -434,7 +6263,7 @@
Properties:
The default distribution this emitter should use to control its particle's spawn position and force behaviour. - Must be an SPE.distributions.* value.
A THREE.Vector3 instance describing this emitter's position variance on a per-particle basis. Note that when using a SPHERE or DISC distribution, only the x-component - of this vector is used.
A THREE.Vector3 instance describing the numeric multiples the particle's should be spread out over. Note that when using a SPHERE or DISC distribution, only the x-component - of this vector is used.
-distribution +distribution @@ -1252,7 +7083,7 @@
Properties
-distribution +distribution @@ -1468,7 +7299,7 @@
Properties
-distribution +distribution @@ -2946,7 +8777,7 @@
Properties
Source:
@@ -3018,7 +8849,7 @@
Type:
Source:
@@ -3028,7 +8859,7 @@
Type:
See:
@@ -3557,7 +9388,7 @@
Properties
Source:
@@ -3587,13 +9418,13 @@
Properties

- Documentation generated by JSDoc 3.3.3 on Sat Nov 14 2015 15:30:50 GMT+0000 (GMT) + Documentation generated by JSDoc 3.6.4 on Sun Jun 28 2020 15:22:31 GMT+0100 (British Summer Time)
diff --git a/docs/api/helpers_SPE.ShaderAttribute.js.html b/docs/api/helpers_SPE.ShaderAttribute.js.html deleted file mode 100644 index 43240a2..0000000 --- a/docs/api/helpers_SPE.ShaderAttribute.js.html +++ /dev/null @@ -1,269 +0,0 @@ - - - - - JSDoc: Source: helpers/SPE.ShaderAttribute.js - - - - - - - - - - -
- -

Source: helpers/SPE.ShaderAttribute.js

- - - - - - -
-
-
/**
- * A helper to handle creating and updating a THREE.BufferAttribute instance.
- *
- * @author  Luke Moody
- * @constructor
- * @param {String} type          The buffer attribute type. See SPE.ShaderAttribute.typeSizeMap for valid values.
- * @param {Boolean=} dynamicBuffer Whether this buffer attribute should be marked as dynamic or not.
- * @param {Function=} arrayType     A reference to a TypedArray constructor. Defaults to Float32Array if none provided.
- */
-SPE.ShaderAttribute = function( type, dynamicBuffer, arrayType ) {
-    'use strict';
-
-    var typeMap = SPE.ShaderAttribute.typeSizeMap;
-
-    this.type = typeof type === 'string' && typeMap.hasOwnProperty( type ) ? type : 'f';
-    this.componentSize = typeMap[ this.type ];
-    this.arrayType = arrayType || Float32Array;
-    this.typedArray = null;
-    this.bufferAttribute = null;
-    this.dynamicBuffer = !!dynamicBuffer;
-
-    this.updateMin = 0;
-    this.updateMax = 0;
-};
-
-SPE.ShaderAttribute.constructor = SPE.ShaderAttribute;
-
-/**
- * A map of uniform types to their component size.
- * @enum {Number}
- */
-SPE.ShaderAttribute.typeSizeMap = {
-    /**
-     * Float
-     * @type {Number}
-     */
-    f: 1,
-
-    /**
-     * Vec2
-     * @type {Number}
-     */
-    v2: 2,
-
-    /**
-     * Vec3
-     * @type {Number}
-     */
-    v3: 3,
-
-    /**
-     * Vec4
-     * @type {Number}
-     */
-    v4: 4,
-
-    /**
-     * Color
-     * @type {Number}
-     */
-    c: 3,
-
-    /**
-     * Mat3
-     * @type {Number}
-     */
-    m3: 9,
-
-    /**
-     * Mat4
-     * @type {Number}
-     */
-    m4: 16
-};
-
-/**
- * Calculate the minimum and maximum update range for this buffer attribute using
- * component size independant min and max values.
- *
- * @param {Number} min The start of the range to mark as needing an update.
- * @param {Number} max The end of the range to mark as needing an update.
- */
-SPE.ShaderAttribute.prototype.setUpdateRange = function( min, max ) {
-    'use strict';
-
-    this.updateMin = Math.min( min * this.componentSize, this.updateMin * this.componentSize );
-    this.updateMax = Math.max( max * this.componentSize, this.updateMax * this.componentSize );
-};
-
-/**
- * Calculate the number of indices that this attribute should mark as needing
- * updating. Also marks the attribute as needing an update.
- */
-SPE.ShaderAttribute.prototype.flagUpdate = function() {
-    'use strict';
-
-    var attr = this.bufferAttribute,
-        range = attr.updateRange;
-
-    range.offset = this.updateMin;
-    range.count = Math.min( ( this.updateMax - this.updateMin ) + this.componentSize, this.typedArray.array.length );
-    // console.log( range.offset, range.count, this.typedArray.array.length );
-    // console.log( 'flagUpdate:', range.offset, range.count );
-    attr.needsUpdate = true;
-};
-
-
-
-/**
- * Reset the index update counts for this attribute
- */
-SPE.ShaderAttribute.prototype.resetUpdateRange = function() {
-    'use strict';
-
-    this.updateMin = 0;
-    this.updateMax = 0;
-};
-
-SPE.ShaderAttribute.prototype.resetDynamic = function() {
-    'use strict';
-    this.bufferAttribute.dynamic = this.dynamicBuffer;
-};
-
-/**
- * Perform a splice operation on this attribute's buffer.
- * @param  {Number} start The start index of the splice. Will be multiplied by the number of components for this attribute.
- * @param  {Number} end The end index of the splice. Will be multiplied by the number of components for this attribute.
- */
-SPE.ShaderAttribute.prototype.splice = function( start, end ) {
-    'use strict';
-
-    this.typedArray.splice( start, end );
-
-    // Reset the reference to the attribute's typed array
-    // since it has probably changed.
-    this.forceUpdateAll();
-};
-
-SPE.ShaderAttribute.prototype.forceUpdateAll = function() {
-    'use strict';
-
-    this.bufferAttribute.array = this.typedArray.array;
-    this.bufferAttribute.updateRange.offset = 0;
-    this.bufferAttribute.updateRange.count = -1;
-    this.bufferAttribute.dynamic = false;
-    this.bufferAttribute.needsUpdate = true;
-};
-
-/**
- * Make sure this attribute has a typed array associated with it.
- *
- * If it does, then it will ensure the typed array is of the correct size.
- *
- * If not, a new SPE.TypedArrayHelper instance will be created.
- *
- * @param  {Number} size The size of the typed array to create or update to.
- */
-SPE.ShaderAttribute.prototype._ensureTypedArray = function( size ) {
-    'use strict';
-
-    // Condition that's most likely to be true at the top: no change.
-    if ( this.typedArray !== null && this.typedArray.size === size * this.componentSize ) {
-        return;
-    }
-
-    // Resize the array if we need to, telling the TypedArrayHelper to
-    // ignore it's component size when evaluating size.
-    else if ( this.typedArray !== null && this.typedArray.size !== size ) {
-        this.typedArray.setSize( size );
-    }
-
-    // This condition should only occur once in an attribute's lifecycle.
-    else if ( this.typedArray === null ) {
-        this.typedArray = new SPE.TypedArrayHelper( this.arrayType, size, this.componentSize );
-    }
-};
-
-
-/**
- * Creates a THREE.BufferAttribute instance if one doesn't exist already.
- *
- * Ensures a typed array is present by calling _ensureTypedArray() first.
- *
- * If a buffer attribute exists already, then it will be marked as needing an update.
- *
- * @param  {Number} size The size of the typed array to create if one doesn't exist, or resize existing array to.
- */
-SPE.ShaderAttribute.prototype._createBufferAttribute = function( size ) {
-    'use strict';
-
-    // Make sure the typedArray is present and correct.
-    this._ensureTypedArray( size );
-
-    // Don't create it if it already exists, but do
-    // flag that it needs updating on the next render
-    // cycle.
-    if ( this.bufferAttribute !== null ) {
-        this.bufferAttribute.array = this.typedArray.array;
-        this.bufferAttribute.needsUpdate = true;
-        return;
-    }
-
-    this.bufferAttribute = new THREE.BufferAttribute( this.typedArray.array, this.componentSize );
-    this.bufferAttribute.dynamic = this.dynamicBuffer;
-};
-
-/**
- * Returns the length of the typed array associated with this attribute.
- * @return {Number} The length of the typed array. Will be 0 if no typed array has been created yet.
- */
-SPE.ShaderAttribute.prototype.getLength = function() {
-    'use strict';
-
-    if ( this.typedArray === null ) {
-        return 0;
-    }
-
-    return this.typedArray.array.length;
-};
-
-
- - - - -
- - - -
- -
- Documentation generated by JSDoc 3.3.3 on Sat Nov 14 2015 15:30:50 GMT+0000 (GMT) -
- - - - - diff --git a/docs/api/helpers_SPE.TypedArrayHelper.js.html b/docs/api/helpers_SPE.TypedArrayHelper.js.html deleted file mode 100644 index 641a187..0000000 --- a/docs/api/helpers_SPE.TypedArrayHelper.js.html +++ /dev/null @@ -1,381 +0,0 @@ - - - - - JSDoc: Source: helpers/SPE.TypedArrayHelper.js - - - - - - - - - - -
- -

Source: helpers/SPE.TypedArrayHelper.js

- - - - - - -
-
-
/**
- * A helper class for TypedArrays.
- *
- * Allows for easy resizing, assignment of various component-based
- * types (Vector2s, Vector3s, Vector4s, Mat3s, Mat4s),
- * as well as Colors (where components are `r`, `g`, `b`),
- * Numbers, and setting from other TypedArrays.
- *
- * @author Luke Moody
- * @constructor
- * @param {Function} TypedArrayConstructor The constructor to use (Float32Array, Uint8Array, etc.)
- * @param {Number} size                 The size of the array to create
- * @param {Number} componentSize        The number of components per-value (ie. 3 for a vec3, 9 for a Mat3, etc.)
- * @param {Number} indexOffset          The index in the array from which to start assigning values. Default `0` if none provided
- */
-SPE.TypedArrayHelper = function( TypedArrayConstructor, size, componentSize, indexOffset ) {
-    'use strict';
-
-    this.componentSize = componentSize || 1;
-    this.size = ( size || 1 );
-    this.TypedArrayConstructor = TypedArrayConstructor || Float32Array;
-    this.array = new TypedArrayConstructor( size * this.componentSize );
-    this.indexOffset = indexOffset || 0;
-};
-
-SPE.TypedArrayHelper.constructor = SPE.TypedArrayHelper;
-
-/**
- * Sets the size of the internal array.
- *
- * Delegates to `this.shrink` or `this.grow` depending on size
- * argument's relation to the current size of the internal array.
- *
- * Note that if the array is to be shrunk, data will be lost.
- *
- * @param {Number} size The new size of the array.
- */
-SPE.TypedArrayHelper.prototype.setSize = function( size, noComponentMultiply ) {
-    'use strict';
-
-    var currentArraySize = this.array.length;
-
-    if ( !noComponentMultiply ) {
-        size = size * this.componentSize;
-    }
-
-    if ( size < currentArraySize ) {
-        return this.shrink( size );
-    }
-    else if ( size > currentArraySize ) {
-        return this.grow( size );
-    }
-    else {
-        console.info( 'TypedArray is already of size:', size + '.', 'Will not resize.' );
-    }
-};
-
-/**
- * Shrinks the internal array.
- *
- * @param  {Number} size The new size of the typed array. Must be smaller than `this.array.length`.
- * @return {SPE.TypedArrayHelper}      Instance of this class.
- */
-SPE.TypedArrayHelper.prototype.shrink = function( size ) {
-    'use strict';
-
-    this.array = this.array.subarray( 0, size );
-    this.size = size;
-    return this;
-};
-
-/**
- * Grows the internal array.
- * @param  {Number} size The new size of the typed array. Must be larger than `this.array.length`.
- * @return {SPE.TypedArrayHelper}      Instance of this class.
- */
-SPE.TypedArrayHelper.prototype.grow = function( size ) {
-    'use strict';
-
-    var existingArray = this.array,
-        newArray = new this.TypedArrayConstructor( size );
-
-    newArray.set( existingArray );
-    this.array = newArray;
-    this.size = size;
-
-    return this;
-};
-
-
-/**
- * Perform a splice operation on this array's buffer.
- * @param  {Number} start The start index of the splice. Will be multiplied by the number of components for this attribute.
- * @param  {Number} end The end index of the splice. Will be multiplied by the number of components for this attribute.
- * @returns {Object} The SPE.TypedArrayHelper instance.
- */
-SPE.TypedArrayHelper.prototype.splice = function( start, end ) {
-    'use strict';
-    start *= this.componentSize;
-    end *= this.componentSize;
-
-    var data = [],
-        array = this.array,
-        size = array.length;
-
-    for ( var i = 0; i < size; ++i ) {
-        if ( i < start || i >= end ) {
-            data.push( array[ i ] );
-        }
-        // array[ i ] = 0;
-    }
-
-    this.setFromArray( 0, data );
-
-    return this;
-};
-
-
-/**
- * Copies from the given TypedArray into this one, using the index argument
- * as the start position. Alias for `TypedArray.set`. Will automatically resize
- * if the given source array is of a larger size than the internal array.
- *
- * @param {Number} index      The start position from which to copy into this array.
- * @param {TypedArray} array The array from which to copy; the source array.
- * @return {SPE.TypedArrayHelper} Instance of this class.
- */
-SPE.TypedArrayHelper.prototype.setFromArray = function( index, array ) {
-    'use strict';
-
-    var sourceArraySize = array.length,
-        newSize = index + sourceArraySize;
-
-    if ( newSize > this.array.length ) {
-        this.grow( newSize );
-    }
-    else if ( newSize < this.array.length ) {
-        this.shrink( newSize );
-    }
-
-    this.array.set( array, this.indexOffset + index );
-
-    return this;
-};
-
-/**
- * Set a Vector2 value at `index`.
- *
- * @param {Number} index The index at which to set the vec2 values from.
- * @param {Vector2} vec2  Any object that has `x` and `y` properties.
- * @return {SPE.TypedArrayHelper} Instance of this class.
- */
-SPE.TypedArrayHelper.prototype.setVec2 = function( index, vec2 ) {
-    'use strict';
-
-    return this.setVec2Components( index, vec2.x, vec2.y );
-};
-
-/**
- * Set a Vector2 value using raw components.
- *
- * @param {Number} index The index at which to set the vec2 values from.
- * @param {Number} x     The Vec2's `x` component.
- * @param {Number} y     The Vec2's `y` component.
- * @return {SPE.TypedArrayHelper} Instance of this class.
- */
-SPE.TypedArrayHelper.prototype.setVec2Components = function( index, x, y ) {
-    'use strict';
-
-    var array = this.array,
-        i = this.indexOffset + ( index * this.componentSize );
-
-    array[ i ] = x;
-    array[ i + 1 ] = y;
-    return this;
-};
-
-/**
- * Set a Vector3 value at `index`.
- *
- * @param {Number} index The index at which to set the vec3 values from.
- * @param {Vector3} vec2  Any object that has `x`, `y`, and `z` properties.
- * @return {SPE.TypedArrayHelper} Instance of this class.
- */
-SPE.TypedArrayHelper.prototype.setVec3 = function( index, vec3 ) {
-    'use strict';
-
-    return this.setVec3Components( index, vec3.x, vec3.y, vec3.z );
-};
-
-/**
- * Set a Vector3 value using raw components.
- *
- * @param {Number} index The index at which to set the vec3 values from.
- * @param {Number} x     The Vec3's `x` component.
- * @param {Number} y     The Vec3's `y` component.
- * @param {Number} z     The Vec3's `z` component.
- * @return {SPE.TypedArrayHelper} Instance of this class.
- */
-SPE.TypedArrayHelper.prototype.setVec3Components = function( index, x, y, z ) {
-    'use strict';
-
-    var array = this.array,
-        i = this.indexOffset + ( index * this.componentSize );
-
-    array[ i ] = x;
-    array[ i + 1 ] = y;
-    array[ i + 2 ] = z;
-    return this;
-};
-
-/**
- * Set a Vector4 value at `index`.
- *
- * @param {Number} index The index at which to set the vec4 values from.
- * @param {Vector4} vec2  Any object that has `x`, `y`, `z`, and `w` properties.
- * @return {SPE.TypedArrayHelper} Instance of this class.
- */
-SPE.TypedArrayHelper.prototype.setVec4 = function( index, vec4 ) {
-    'use strict';
-
-    return this.setVec4Components( index, vec4.x, vec4.y, vec4.z, vec4.w );
-};
-
-/**
- * Set a Vector4 value using raw components.
- *
- * @param {Number} index The index at which to set the vec4 values from.
- * @param {Number} x     The Vec4's `x` component.
- * @param {Number} y     The Vec4's `y` component.
- * @param {Number} z     The Vec4's `z` component.
- * @param {Number} w     The Vec4's `w` component.
- * @return {SPE.TypedArrayHelper} Instance of this class.
- */
-SPE.TypedArrayHelper.prototype.setVec4Components = function( index, x, y, z, w ) {
-    'use strict';
-
-    var array = this.array,
-        i = this.indexOffset + ( index * this.componentSize );
-
-    array[ i ] = x;
-    array[ i + 1 ] = y;
-    array[ i + 2 ] = z;
-    array[ i + 3 ] = w;
-    return this;
-};
-
-/**
- * Set a Matrix3 value at `index`.
- *
- * @param {Number} index The index at which to set the matrix values from.
- * @param {Matrix3} mat3 The 3x3 matrix to set from. Must have a TypedArray property named `elements` to copy from.
- * @return {SPE.TypedArrayHelper} Instance of this class.
- */
-SPE.TypedArrayHelper.prototype.setMat3 = function( index, mat3 ) {
-    'use strict';
-
-    return this.setFromArray( this.indexOffset + ( index * this.componentSize ), mat3.elements );
-};
-
-/**
- * Set a Matrix4 value at `index`.
- *
- * @param {Number} index The index at which to set the matrix values from.
- * @param {Matrix4} mat3 The 4x4 matrix to set from. Must have a TypedArray property named `elements` to copy from.
- * @return {SPE.TypedArrayHelper} Instance of this class.
- */
-SPE.TypedArrayHelper.prototype.setMat4 = function( index, mat4 ) {
-    'use strict';
-
-    return this.setFromArray( this.indexOffset + ( index * this.componentSize ), mat4.elements );
-};
-
-/**
- * Set a Color value at `index`.
- *
- * @param {Number} index The index at which to set the vec3 values from.
- * @param {Color} color  Any object that has `r`, `g`, and `b` properties.
- * @return {SPE.TypedArrayHelper} Instance of this class.
- */
-SPE.TypedArrayHelper.prototype.setColor = function( index, color ) {
-    'use strict';
-
-    return this.setVec3Components( index, color.r, color.g, color.b );
-};
-
-/**
- * Set a Number value at `index`.
- *
- * @param {Number} index The index at which to set the vec3 values from.
- * @param {Number} numericValue  The number to assign to this index in the array.
- * @return {SPE.TypedArrayHelper} Instance of this class.
- */
-SPE.TypedArrayHelper.prototype.setNumber = function( index, numericValue ) {
-    'use strict';
-
-    this.array[ this.indexOffset + ( index * this.componentSize ) ] = numericValue;
-    return this;
-};
-
-/**
- * Returns the value of the array at the given index, taking into account
- * the `indexOffset` property of this class.
- *
- * Note that this function ignores the component size and will just return a
- * single value.
- *
- * @param  {Number} index The index in the array to fetch.
- * @return {Number}       The value at the given index.
- */
-SPE.TypedArrayHelper.prototype.getValueAtIndex = function( index ) {
-    'use strict';
-
-    return this.array[ this.indexOffset + index ];
-};
-
-/**
- * Returns the component value of the array at the given index, taking into account
- * the `indexOffset` property of this class.
- *
- * If the componentSize is set to 3, then it will return a new TypedArray
- * of length 3.
- *
- * @param  {Number} index The index in the array to fetch.
- * @return {TypedArray}       The component value at the given index.
- */
-SPE.TypedArrayHelper.prototype.getComponentValueAtIndex = function( index ) {
-    'use strict';
-
-    return this.array.subarray( this.indexOffset + ( index * this.componentSize ) );
-};
-
-
- - - - -
- - - -
- -
- Documentation generated by JSDoc 3.3.3 on Sat Nov 14 2015 15:30:50 GMT+0000 (GMT) -
- - - - - diff --git a/docs/api/helpers_ShaderAttribute.js.html b/docs/api/helpers_ShaderAttribute.js.html new file mode 100644 index 0000000..43390b6 --- /dev/null +++ b/docs/api/helpers_ShaderAttribute.js.html @@ -0,0 +1,223 @@ + + + + + JSDoc: Source: helpers/ShaderAttribute.js + + + + + + + + + + +
+ +

Source: helpers/ShaderAttribute.js

+ + + + + + +
+
+
import * as THREE from 'three';
+import TypedArrayHelper from './TypedArrayHelper';
+import typeSizeMap from '@/constants/typeSizeMap';
+
+const HAS_OWN = Object.prototype.hasOwnProperty;
+
+/**
+ * A helper to handle creating and updating a THREE.BufferAttribute instance.
+ *
+ * @author  Luke Moody
+ * @constructor
+ * @param {String} type          The buffer attribute type. See `typeSizeMap`` for valid values.
+ * @param {Boolean=} dynamicBuffer Whether this buffer attribute should be marked as dynamic or not.
+ * @param {Function=} arrayType     A reference to a TypedArray constructor. Defaults to Float32Array if none provided.
+ */
+export default class ShaderAttribute {
+	constructor( type, dynamicBuffer, arrayType ) {
+		this.type = typeof type === 'string' && HAS_OWN.call( typeSizeMap, type ) ? type : 'f';
+		this.componentSize = typeSizeMap[ this.type ];
+		this.arrayType = arrayType || Float32Array;
+		this.typedArray = null;
+		this.bufferAttribute = null;
+		this.dynamicBuffer = !!dynamicBuffer;
+
+		this.updateMin = 0;
+		this.updateMax = 0;
+	}
+
+	/**
+	 * Calculate the minimum and maximum update range for this buffer attribute using
+	 * component size independant min and max values.
+	 *
+	 * @param {Number} min The start of the range to mark as needing an update.
+	 * @param {Number} max The end of the range to mark as needing an update.
+	 */
+	setUpdateRange( min, max ) {
+		this.updateMin = Math.min( min * this.componentSize, this.updateMin * this.componentSize );
+		this.updateMax = Math.max( max * this.componentSize, this.updateMax * this.componentSize );
+	}
+
+	/**
+	 * Calculate the number of indices that this attribute should mark as needing
+	 * updating. Also marks the attribute as needing an update.
+	 */
+	flagUpdate() {
+		const attr = this.bufferAttribute,
+			range = attr.updateRange;
+
+		range.offset = this.updateMin;
+		range.count = Math.min( ( this.updateMax - this.updateMin ) + this.componentSize, this.typedArray.array.length );
+		attr.needsUpdate = true;
+	}
+
+
+
+	/**
+	 * Reset the index update counts for this attribute
+	 */
+	resetUpdateRange() {
+		this.updateMin = 0;
+		this.updateMax = 0;
+	}
+
+	resetDynamic() {
+		this.bufferAttribute.usage = this.dynamicBuffer ?
+			THREE.DynamicDrawUsage :
+			THREE.StaticDrawUsage;
+	}
+
+	/**
+	 * Perform a splice operation on this attribute's buffer.
+	 * @param  {Number} start The start index of the splice. Will be multiplied by the number of components for this attribute.
+	 * @param  {Number} end The end index of the splice. Will be multiplied by the number of components for this attribute.
+	 */
+	splice( start, end ) {
+		this.typedArray.splice( start, end );
+
+		// Reset the reference to the attribute's typed array
+		// since it has probably changed.
+		this.forceUpdateAll();
+	}
+
+	forceUpdateAll() {
+		this.bufferAttribute.array = this.typedArray.array;
+		this.bufferAttribute.updateRange.offset = 0;
+		this.bufferAttribute.updateRange.count = -1;
+
+		this.bufferAttribute.usage = THREE.StaticDrawUsage;
+		this.bufferAttribute.needsUpdate = true;
+	}
+
+	/**
+	 * Make sure this attribute has a typed array associated with it.
+	 *
+	 * If it does, then it will ensure the typed array is of the correct size.
+	 *
+	 * If not, a new `TypedArrayHelper` instance will be created.
+	 *
+	 * @param  {Number} size The size of the typed array to create or update to.
+	 */
+	_ensureTypedArray( size ) {
+		// Condition that's most likely to be true at the top: no change.
+		if ( this.typedArray !== null && this.typedArray.size === size * this.componentSize ) {
+			return;
+		}
+
+		// Resize the array if we need to, telling the TypedArrayHelper to
+		// ignore it's component size when evaluating size.
+		else if ( this.typedArray !== null && this.typedArray.size !== size ) {
+			this.typedArray.setSize( size );
+		}
+
+		// This condition should only occur once in an attribute's lifecycle.
+		else if ( this.typedArray === null ) {
+			this.typedArray = new TypedArrayHelper( this.arrayType, size, this.componentSize );
+		}
+	}
+
+
+	/**
+	 * Creates a THREE.BufferAttribute instance if one doesn't exist already.
+	 *
+	 * Ensures a typed array is present by calling _ensureTypedArray() first.
+	 *
+	 * If a buffer attribute exists already, then it will be marked as needing an update.
+	 *
+	 * @param  {Number} size The size of the typed array to create if one doesn't exist, or resize existing array to.
+	 */
+	_createBufferAttribute( size ) {
+		// Make sure the typedArray is present and correct.
+		this._ensureTypedArray( size );
+
+		// Don't create it if it already exists, but do
+		// flag that it needs updating on the next render
+		// cycle.
+		if ( this.bufferAttribute !== null ) {
+			this.bufferAttribute.array = this.typedArray.array;
+
+			// Since THREE.js version 81, dynamic count calculation was removed
+			// so I need to do it manually here.
+			//
+			// In the next minor release, I may well remove this check and force
+			// dependency on THREE r81+.
+			if ( parseFloat( THREE.REVISION ) >= 81 ) {
+				this.bufferAttribute.count = this.bufferAttribute.array.length / this.bufferAttribute.itemSize;
+			}
+
+			this.bufferAttribute.needsUpdate = true;
+			return;
+		}
+
+		this.bufferAttribute = new THREE.BufferAttribute( this.typedArray.array, this.componentSize );
+
+		this.bufferAttribute.usage = this.dynamicBuffer ?
+			THREE.DynamicDrawUsage :
+			THREE.StaticDrawUsage;
+	}
+
+	/**
+	 * Returns the length of the typed array associated with this attribute.
+	 * @return {Number} The length of the typed array. Will be 0 if no typed array has been created yet.
+	 */
+	getLength() {
+		if ( this.typedArray === null ) {
+			return 0;
+		}
+
+		return this.typedArray.array.length;
+	}
+
+}
+
+
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.4 on Sun Jun 28 2020 15:22:31 GMT+0100 (British Summer Time) +
+ + + + + diff --git a/docs/api/helpers_TypedArrayHelper.js.html b/docs/api/helpers_TypedArrayHelper.js.html new file mode 100644 index 0000000..230919e --- /dev/null +++ b/docs/api/helpers_TypedArrayHelper.js.html @@ -0,0 +1,346 @@ + + + + + JSDoc: Source: helpers/TypedArrayHelper.js + + + + + + + + + + +
+ +

Source: helpers/TypedArrayHelper.js

+ + + + + + +
+
+
/**
+ * A helper class for TypedArrays.
+ *
+ * Allows for easy resizing, assignment of various component-based
+ * types (Vector2s, Vector3s, Vector4s, Mat3s, Mat4s),
+ * as well as Colors (where components are `r`, `g`, `b`),
+ * Numbers, and setting from other TypedArrays.
+ *
+ * @author Luke Moody
+ * @constructor
+ * @param {Function} TypedArrayConstructor The constructor to use (Float32Array, Uint8Array, etc.)
+ * @param {Number} size                 The size of the array to create
+ * @param {Number} componentSize        The number of components per-value (ie. 3 for a vec3, 9 for a Mat3, etc.)
+ * @param {Number} indexOffset          The index in the array from which to start assigning values. Default `0` if none provided
+ */
+export default class TypedArrayHelper {
+	constructor( TypedArrayConstructor, size, componentSize, indexOffset ) {
+		this.componentSize = componentSize || 1;
+		this.size = ( size || 1 );
+		this.TypedArrayConstructor = TypedArrayConstructor || Float32Array;
+		this.array = new TypedArrayConstructor( size * this.componentSize );
+		this.indexOffset = indexOffset || 0;
+	}
+
+	/**
+	 * Sets the size of the internal array.
+	 *
+	 * Delegates to `this.shrink` or `this.grow` depending on size
+	 * argument's relation to the current size of the internal array.
+	 *
+	 * Note that if the array is to be shrunk, data will be lost.
+	 *
+	 * @param {Number} size The new size of the array.
+	 */
+	setSize( s, noComponentMultiply ) {
+		const currentArraySize = this.array.length;
+		let size = s;
+
+		if ( !noComponentMultiply ) {
+			size = size * this.componentSize;
+		}
+
+		if ( size < currentArraySize ) {
+			return this.shrink( size );
+		}
+		else if ( size > currentArraySize ) {
+			return this.grow( size );
+		}
+		else {
+			console.info( 'TypedArray is already of size:', size + '.', 'Will not resize.' );
+		}
+	}
+
+	/**
+	 * Shrinks the internal array.
+	 *
+	 * @param  {Number} size The new size of the typed array. Must be smaller than `this.array.length`.
+	 * @return {SPE.TypedArrayHelper}      Instance of this class.
+	 */
+	shrink( size ) {
+		this.array = this.array.subarray( 0, size );
+		this.size = size;
+		return this;
+	}
+
+	/**
+	 * Grows the internal array.
+	 * @param  {Number} size The new size of the typed array. Must be larger than `this.array.length`.
+	 * @return {SPE.TypedArrayHelper}      Instance of this class.
+	 */
+	grow( size ) {
+		const newArray = new this.TypedArrayConstructor( size );
+
+		newArray.set( this.array );
+		this.array = newArray;
+		this.size = size;
+
+		return this;
+	}
+
+
+	/**
+	 * Perform a splice operation on this array's buffer.
+	 * @param  {Number} start The start index of the splice. Will be multiplied by the number of components for this attribute.
+	 * @param  {Number} end The end index of the splice. Will be multiplied by the number of components for this attribute.
+	 * @returns {Object} The SPE.TypedArrayHelper instance.
+	 */
+	splice( start, end ) {
+		const _start = start * this.componentSize,
+			_end = end * this.componentSize;
+
+		const data = [],
+			array = this.array,
+			size = array.length;
+
+		for ( let i = 0; i < size; ++i ) {
+			if ( i < _start || i >= _end ) {
+				data.push( array[ i ] );
+			}
+			// array[ i ] = 0;
+		}
+
+		this.setFromArray( 0, data );
+
+		return this;
+	}
+
+
+	/**
+	 * Copies from the given TypedArray into this one, using the index argument
+	 * as the start position. Alias for `TypedArray.set`. Will automatically resize
+	 * if the given source array is of a larger size than the internal array.
+	 *
+	 * @param {Number} index      The start position from which to copy into this array.
+	 * @param {TypedArray} array The array from which to copy; the source array.
+	 * @return {SPE.TypedArrayHelper} Instance of this class.
+	 */
+	setFromArray( index, array ) {
+		const sourceArraySize = array.length,
+			newSize = index + sourceArraySize;
+
+		if ( newSize > this.array.length ) {
+			this.grow( newSize );
+		}
+		else if ( newSize < this.array.length ) {
+			this.shrink( newSize );
+		}
+
+		this.array.set( array, this.indexOffset + index );
+
+		return this;
+	}
+
+	/**
+	 * Set a Vector2 value at `index`.
+	 *
+	 * @param {Number} index The index at which to set the vec2 values from.
+	 * @param {Vector2} vec2  Any object that has `x` and `y` properties.
+	 * @return {SPE.TypedArrayHelper} Instance of this class.
+	 */
+	setVec2( index, vec2 ) {
+		return this.setVec2Components( index, vec2.x, vec2.y );
+	}
+
+	/**
+	 * Set a Vector2 value using raw components.
+	 *
+	 * @param {Number} index The index at which to set the vec2 values from.
+	 * @param {Number} x     The Vec2's `x` component.
+	 * @param {Number} y     The Vec2's `y` component.
+	 * @return {SPE.TypedArrayHelper} Instance of this class.
+	 */
+	setVec2Components( index, x, y ) {
+		const array = this.array,
+			i = this.indexOffset + ( index * this.componentSize );
+
+		array[ i ] = x;
+		array[ i + 1 ] = y;
+		return this;
+	}
+
+	/**
+	 * Set a Vector3 value at `index`.
+	 *
+	 * @param {Number} index The index at which to set the vec3 values from.
+	 * @param {Vector3} vec2  Any object that has `x`, `y`, and `z` properties.
+	 * @return {SPE.TypedArrayHelper} Instance of this class.
+	 */
+	setVec3( index, vec3 ) {
+		return this.setVec3Components( index, vec3.x, vec3.y, vec3.z );
+	}
+
+	/**
+	 * Set a Vector3 value using raw components.
+	 *
+	 * @param {Number} index The index at which to set the vec3 values from.
+	 * @param {Number} x     The Vec3's `x` component.
+	 * @param {Number} y     The Vec3's `y` component.
+	 * @param {Number} z     The Vec3's `z` component.
+	 * @return {SPE.TypedArrayHelper} Instance of this class.
+	 */
+	setVec3Components( index, x, y, z ) {
+		const array = this.array,
+			i = this.indexOffset + ( index * this.componentSize );
+
+		array[ i ] = x;
+		array[ i + 1 ] = y;
+		array[ i + 2 ] = z;
+		return this;
+	}
+
+	/**
+	 * Set a Vector4 value at `index`.
+	 *
+	 * @param {Number} index The index at which to set the vec4 values from.
+	 * @param {Vector4} vec2  Any object that has `x`, `y`, `z`, and `w` properties.
+	 * @return {SPE.TypedArrayHelper} Instance of this class.
+	 */
+	setVec4( index, vec4 ) {
+		return this.setVec4Components( index, vec4.x, vec4.y, vec4.z, vec4.w );
+	}
+
+	/**
+	 * Set a Vector4 value using raw components.
+	 *
+	 * @param {Number} index The index at which to set the vec4 values from.
+	 * @param {Number} x     The Vec4's `x` component.
+	 * @param {Number} y     The Vec4's `y` component.
+	 * @param {Number} z     The Vec4's `z` component.
+	 * @param {Number} w     The Vec4's `w` component.
+	 * @return {SPE.TypedArrayHelper} Instance of this class.
+	 */
+	setVec4Components( index, x, y, z, w ) {
+		const array = this.array,
+			i = this.indexOffset + ( index * this.componentSize );
+
+		array[ i ] = x;
+		array[ i + 1 ] = y;
+		array[ i + 2 ] = z;
+		array[ i + 3 ] = w;
+		return this;
+	}
+
+	/**
+	 * Set a Matrix3 value at `index`.
+	 *
+	 * @param {Number} index The index at which to set the matrix values from.
+	 * @param {Matrix3} mat3 The 3x3 matrix to set from. Must have a TypedArray property named `elements` to copy from.
+	 * @return {SPE.TypedArrayHelper} Instance of this class.
+	 */
+	setMat3( index, mat3 ) {
+		return this.setFromArray( this.indexOffset + ( index * this.componentSize ), mat3.elements );
+	}
+
+	/**
+	 * Set a Matrix4 value at `index`.
+	 *
+	 * @param {Number} index The index at which to set the matrix values from.
+	 * @param {Matrix4} mat3 The 4x4 matrix to set from. Must have a TypedArray property named `elements` to copy from.
+	 * @return {SPE.TypedArrayHelper} Instance of this class.
+	 */
+	setMat4( index, mat4 ) {
+		return this.setFromArray( this.indexOffset + ( index * this.componentSize ), mat4.elements );
+	}
+
+	/**
+	 * Set a Color value at `index`.
+	 *
+	 * @param {Number} index The index at which to set the vec3 values from.
+	 * @param {Color} color  Any object that has `r`, `g`, and `b` properties.
+	 * @return {SPE.TypedArrayHelper} Instance of this class.
+	 */
+	setColor( index, color ) {
+		return this.setVec3Components( index, color.r, color.g, color.b );
+	}
+
+	/**
+	 * Set a Number value at `index`.
+	 *
+	 * @param {Number} index The index at which to set the vec3 values from.
+	 * @param {Number} numericValue  The number to assign to this index in the array.
+	 * @return {SPE.TypedArrayHelper} Instance of this class.
+	 */
+	setNumber( index, numericValue ) {
+		this.array[ this.indexOffset + ( index * this.componentSize ) ] = numericValue;
+		return this;
+	}
+
+	/**
+	 * Returns the value of the array at the given index, taking into account
+	 * the `indexOffset` property of this class.
+	 *
+	 * Note that this function ignores the component size and will just return a
+	 * single value.
+	 *
+	 * @param  {Number} index The index in the array to fetch.
+	 * @return {Number}       The value at the given index.
+	 */
+	getValueAtIndex( index ) {
+		return this.array[ this.indexOffset + index ];
+	}
+
+	/**
+	 * Returns the component value of the array at the given index, taking into account
+	 * the `indexOffset` property of this class.
+	 *
+	 * If the componentSize is set to 3, then it will return a new TypedArray
+	 * of length 3.
+	 *
+	 * @param  {Number} index The index in the array to fetch.
+	 * @return {TypedArray}       The component value at the given index.
+	 */
+	getComponentValueAtIndex( index ) {
+		return this.array.subarray( this.indexOffset + ( index * this.componentSize ) );
+	}
+}
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.4 on Sun Jun 28 2020 15:22:31 GMT+0100 (British Summer Time) +
+ + + + + diff --git a/docs/api/index.html b/docs/api/index.html index df42ea5..1bb4a95 100644 --- a/docs/api/index.html +++ b/docs/api/index.html @@ -50,13 +50,13 @@


- Documentation generated by JSDoc 3.3.3 on Sat Nov 14 2015 15:30:50 GMT+0000 (GMT) + Documentation generated by JSDoc 3.6.4 on Sun Jun 28 2020 15:22:31 GMT+0100 (British Summer Time)
diff --git a/docs/api/SPE.utils.html b/docs/api/module.html#.exports similarity index 71% rename from docs/api/SPE.utils.html rename to docs/api/module.html#.exports index 6fa6a09..87d654c 100644 --- a/docs/api/SPE.utils.html +++ b/docs/api/module.html#.exports @@ -2,7 +2,7 @@ - JSDoc: Namespace: utils + JSDoc: Namespace: exports @@ -17,7 +17,7 @@
-

Namespace: utils

+

Namespace: exports

@@ -28,23 +28,938 @@

Namespace: utils

-

- SPE. +

exports

- utils - + +
+ +
+
+ + +
A bunch of utility functions used throughout the library.
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + +
+ + + + + + + + + + + + + + +

Members

+ + + +

(static) BOOLEAN :String

+ + + + +
+ Boolean type. +
+ + + +
Type:
+
    +
  • + +String + + +
  • +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) BOX :Number

+ + + + +
+ Values will be distributed within a box. +
+ + + +
Type:
+
    +
  • + +Number + + +
  • +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • 1
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) c :Number

+ + + + +
+ Color +
+ + + +
Type:
+
    +
  • + +Number + + +
  • +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • 3
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) DISC :Number

+ + + + +
+ Values will be distributed on a 2d-disc shape. +
+ + + +
Type:
+
    +
  • + +Number + + +
  • +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • 3
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) f :Number

+ + + + +
+ Float +
+ + + +
Type:
+
    +
  • + +Number + + +
  • +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • 1
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) LINE :Number

+ + + + +
+ Values will be distributed along a line. +
+ + + +
Type:
+
    +
  • + +Number + + +
  • +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • 4
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) m3 :Number

+ + + + +
+ Mat3 +
+ + + +
Type:
+
    +
  • + +Number + + +
  • +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • 9
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + +

(static) m4 :Number

+ + + + +
+ Mat4 +
+ + + +
Type:
+
    +
  • + +Number + + +
  • +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • 16
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) NUMBER :String

+ + + + +
+ Number type. +
+ + + +
Type:
+
    +
  • + +String + + +
  • +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) OBJECT :String

+ + + + +
+ Object type. +
+ + + +
Type:
+
    +
  • + +String + + +
  • +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) SPHERE :Number

+ + + + +
+ Values will be distributed on a sphere. +
+ + + +
Type:
+
    +
  • + +Number + + +
  • +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • 2
  • +
- -
+ + + + + + +

(static) STRING :String

+ + + + +
+ String type. +
+ + + +
Type:
+
    +
  • + +String + + +
  • +
+ + +
@@ -76,7 +991,7 @@

Source:
@@ -88,33 +1003,19 @@

- - -
- - - - - - - - - -

Members

-

(static) types :String

+

(static) v2 :Number

- A map of types used by `SPE.utils.ensureTypedArg` and -`SPE.utils.ensureArrayTypedArg` to compare types against. + Vec2
@@ -123,7 +1024,7 @@
Type:
  • -String +Number
  • @@ -133,123 +1034,156 @@
    Type:
    -
    Properties:
    +
    - - - - - - + - + - + - + - - - + - - - - - + - + - + +
    Default Value:
    +
      +
    • 2
    • +
    + - + +
    Source:
    +
    + - - + - - - - + + - + + + +

    (static) v3 :Number

    + + + + +
    + Vec3 +
    + + +
    Type:
    +
      +
    • +Number -
    - + + + + + + + + +
    -
    - - - + - + - + - + - - + - - - - + - + +
    Source:
    +
    + + + + + + + + + + + + + + +

    (static) v4 :Number

    + + + + +
    + Vec4 +
    + + +
    Type:
    +
      +
    • +Number -
    - - - -
    NameTypeDescription
    BOOLEAN - - -String + + - - Boolean type.
    STRING - - -String - - String type.
    NUMBER - - -String + + - - Number type.
    OBJECT - - -String + + +
    Default Value:
    +
      +
    • 3
    • +
    + - -
    Object type.
    + +
+ @@ -278,12 +1212,17 @@
Properties:
+ +
Default Value:
+
    +
  • 4
  • +
Source:
@@ -310,7 +1249,9 @@

Methods

+

(static) arrayValuesAreEqual(array) → {Boolean}

+ @@ -410,7 +1351,7 @@
Parameters:
Source:
@@ -433,6 +1374,8 @@
Parameters:
+ +
Returns:
@@ -458,12 +1401,16 @@
Returns:
+ + +

(static) clamp(value, min, max) → {Number}

+ @@ -609,7 +1556,7 @@
Parameters:
Source:
@@ -632,6 +1579,8 @@
Parameters:
+ +
Returns:
@@ -657,12 +1606,16 @@
Returns:
+ + +

(static) ensureArrayInstanceOf(arg, instance, defaultValue) → {Object}

+ @@ -815,7 +1768,7 @@
Parameters:
Source:
@@ -838,6 +1791,8 @@
Parameters:
+ +
Returns:
@@ -863,12 +1818,16 @@
Returns:
+ + +

(static) ensureArrayTypedArg(arg, type, defaultValue) → {boolean|string|number|object}

+ @@ -1039,7 +1998,7 @@
Parameters:
Source:
@@ -1062,6 +2021,8 @@
Parameters:
+ +
Returns:
@@ -1096,12 +2057,16 @@
Returns:
+ + +

(static) ensureInstanceOf(arg, instance, defaultValue) → {Object}

+ @@ -1247,7 +2212,7 @@
Parameters:
Source:
@@ -1270,6 +2235,8 @@
Parameters:
+ +
Returns:
@@ -1295,12 +2262,16 @@
Returns:
+ + +

(static) ensureTypedArg(arg, type, defaultValue) → {boolean|string|number|object}

+ @@ -1466,7 +2437,7 @@
Parameters:
Source:
@@ -1489,6 +2460,8 @@
Parameters:
+ +
Returns:
@@ -1523,12 +2496,16 @@
Returns:
+ + +

(static) ensureValueOverLifetimeCompliance(property, minLength, maxLength)

+ @@ -1679,7 +2656,7 @@
Parameters:
Source:
@@ -1705,12 +2682,18 @@
Parameters:
+ + + + +

(static) interpolateArray(srcArray, newLength) → {Array}

+ @@ -1838,7 +2821,7 @@
Parameters:
Source:
@@ -1861,6 +2844,8 @@
Parameters:
+ +
Returns:
@@ -1886,12 +2871,16 @@
Returns:
+ + +

(static) lerp(start, end, delta) → {Number}

+ @@ -2037,7 +3026,7 @@
Parameters:
Source:
@@ -2060,6 +3049,8 @@
Parameters:
+ +
Returns:
@@ -2085,19 +3076,23 @@
Returns:
+ + +

(static) lerpTypeAgnostic(start, end, delta) → {number|object|undefined}

+
- Linearly interpolates two values of various types. The given values + Linearly interpolates two values of various valueTypes. The given values must be of the same type for the interpolation to work.
@@ -2243,7 +3238,7 @@
Parameters:
Source:
@@ -2266,6 +3261,8 @@
Parameters:
+ +
Returns:
@@ -2299,12 +3296,16 @@
Returns:
+ + +

(static) randomColor(attribute, index, base, spread)

+ @@ -2474,7 +3475,7 @@
Parameters:
Source:
@@ -2500,12 +3501,18 @@
Parameters:
+ + + + +

(static) randomFloat(base, spread) → {Number}

+ @@ -2629,7 +3636,7 @@
Parameters:
Source:
@@ -2652,6 +3659,8 @@
Parameters:
+ +
Returns:
@@ -2677,12 +3686,16 @@
Returns:
+ + +

(static) randomVector3(attribute, index, base, spread, spreadClamp)

+ @@ -2875,7 +3888,7 @@
Parameters:
Source:
@@ -2901,12 +3914,18 @@
Parameters:
+ + + + +

(static) randomVector3OnDisc(attribute, index, base, radius, radiusSpread, radiusScale, radiusSpreadClamp)

+ @@ -3145,7 +4164,214 @@
Parameters:
Source:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(static) randomVector3OnLine(attribute, index, start, end)

+ + + + + + +
+ Given an SPE.ShaderAttribute instance, and various other settings, +assign values to the attribute's array in a `vec3` format. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
attribute + + +Object + + + + The instance of SPE.ShaderAttribute to save the result to.
index + + +Number + + + + The offset in the attribute's TypedArray to save the result from.
start + + +Object + + + + THREE.Vector3 instance describing the start line position.
end + + +Object + + + + THREE.Vector3 instance describing the end line position.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
@@ -3171,12 +4397,18 @@
Parameters:
+ + + + +

(static) randomVector3OnSphere(attribute, index, base, radius, radiusSpread, radiusScale, radiusSpreadClamp)

+ @@ -3415,7 +4647,7 @@
Parameters:
Source:
@@ -3441,12 +4673,18 @@
Parameters:
+ + + + +

(static) roundToNearestMultiple(n, multiple) → {Number}

+ @@ -3569,7 +4807,7 @@
Parameters:
Source:
@@ -3592,6 +4830,8 @@
Parameters:
+ +
Returns:
@@ -3617,12 +4857,16 @@
Returns:
+ + +

(static) zeroToEpsilon(value, randomise) → {Number}

+ @@ -3747,7 +4991,7 @@
Parameters:
Source:
@@ -3770,6 +5014,8 @@
Parameters:
+ +
Returns:
@@ -3795,6 +5041,8 @@
Returns:
+ + @@ -3811,13 +5059,13 @@
Returns:

- Documentation generated by JSDoc 3.3.3 on Sat Nov 14 2015 15:30:50 GMT+0000 (GMT) + Documentation generated by JSDoc 3.6.4 on Sun Jun 28 2020 15:22:31 GMT+0100 (British Summer Time)
diff --git a/docs/api/scripts/linenumber.js b/docs/api/scripts/linenumber.js index 8d52f7e..4354785 100644 --- a/docs/api/scripts/linenumber.js +++ b/docs/api/scripts/linenumber.js @@ -1,12 +1,12 @@ /*global document */ -(function() { - var source = document.getElementsByClassName('prettyprint source linenums'); - var i = 0; - var lineNumber = 0; - var lineId; - var lines; - var totalLines; - var anchorHash; +(() => { + const source = document.getElementsByClassName('prettyprint source linenums'); + let i = 0; + let lineNumber = 0; + let lineId; + let lines; + let totalLines; + let anchorHash; if (source && source[0]) { anchorHash = document.location.hash.substring(1); @@ -15,7 +15,7 @@ for (; i < totalLines; i++) { lineNumber++; - lineId = 'line' + lineNumber; + lineId = `line${lineNumber}`; lines[i].id = lineId; if (lineId === anchorHash) { lines[i].className += ' selected'; diff --git a/docs/api/styles/jsdoc-default.css b/docs/api/styles/jsdoc-default.css index 24aa20f..7d1729d 100644 --- a/docs/api/styles/jsdoc-default.css +++ b/docs/api/styles/jsdoc-default.css @@ -78,6 +78,10 @@ article dl { margin-bottom: 40px; } +article img { + max-width: 100%; +} + section { display: block; @@ -153,7 +157,7 @@ h1 margin: 12px 24px 20px; } -h2, h3 +h2, h3.subsection-title { font-size: 30px; font-weight: 700; @@ -161,6 +165,13 @@ h2, h3 margin-bottom: 12px; } +h3 +{ + font-size: 24px; + letter-spacing: -0.5px; + margin-bottom: 12px; +} + h4 { font-size: 18px; @@ -185,8 +196,34 @@ h6 font-style: italic; } -.ancestors { color: #999; } -.ancestors a +table +{ + border-spacing: 0; + border: 0; + border-collapse: collapse; +} + +td, th +{ + border: 1px solid #ddd; + margin: 0px; + text-align: left; + vertical-align: top; + padding: 4px 6px; + display: table-cell; +} + +thead tr +{ + background-color: #ddd; + font-weight: bold; +} + +th { border-right: 1px solid #aaa; } +tr > th:last-child { border-right: 1px solid #ddd; } + +.ancestors, .attribs { color: #999; } +.ancestors a, .attribs a { color: #999 !important; text-decoration: none; @@ -236,7 +273,7 @@ h6 margin: 0; } -.prettyprint +.source { border: 1px solid #ddd; width: 80%; @@ -247,7 +284,7 @@ h6 width: inherit; } -.prettyprint code +.source code { font-size: 100%; line-height: 18px; @@ -296,44 +333,12 @@ h6 user-select: text; } -.params, .props -{ - border-spacing: 0; - border: 0; - border-collapse: collapse; -} - .params .name, .props .name, .name code { color: #4D4E53; font-family: Consolas, Monaco, 'Andale Mono', monospace; font-size: 100%; } -.params td, .params th, .props td, .props th -{ - border: 1px solid #ddd; - margin: 0px; - text-align: left; - vertical-align: top; - padding: 4px 6px; - display: table-cell; -} - -.params thead tr, .props thead tr -{ - background-color: #ddd; - font-weight: bold; -} - -.params .params thead tr, .props .props thead tr -{ - background-color: #fff; - font-weight: bold; -} - -.params th, .props th { border-right: 1px solid #aaa; } -.params thead .last, .props thead .last { border-right: 1px solid #ddd; } - .params td.description > p:first-child, .props td.description > p:first-child { diff --git a/docs/source/SPE.html b/docs/source/SPE.html deleted file mode 100644 index 28e8803..0000000 --- a/docs/source/SPE.html +++ /dev/null @@ -1,5342 +0,0 @@ - - - - - SPE.js - - - - - -
-
- -
    - -
  • -
    -

    SPE.js

    -
    -
  • - - - -
  • -
    - -
    - -
    - -
    - -
    /* shader-particle-engine 1.0.3
    - * 
    - * (c) 2015 Luke Moody (http://www.github.com/squarefeet)
    - *     Originally based on Lee Stemkoski's original work (https://github.com/stemkoski/stemkoski.github.com/blob/master/Three.js/js/ParticleEngine.js).
    - *
    - * shader-particle-engine may be freely distributed under the MIT license (See LICENSE at root of this repository.)
    - */
    -/**
    - * @typedef {Number} distribution
    - * @property {Number} SPE.distributions.BOX Values will be distributed within a box.
    - * @property {Number} SPE.distributions.SPHERE Values will be distributed within a sphere.
    - * @property {Number} SPE.distributions.DISC Values will be distributed within a 2D disc.
    - */
    -
    -/**
    - * Namespace for Shader Particle Engine.
    - *
    - * All SPE-related code sits under this namespace.
    - *
    - * @type {Object}
    - * @namespace
    - */
    -var SPE = {
    -
    -    /**
    -     * A map of supported distribution types used
    -     * by SPE.Emitter instances.
    -     *
    -     * These distribution types can be applied to
    -     * an emitter globally, which will affect the
    -     * `position`, `velocity`, and `acceleration`
    -     * value calculations for an emitter, or they
    -     * can be applied on a per-property basis.
    -     *
    -     * @enum {Number}
    -     */
    -    distributions: {
    -        /**
    -         * Values will be distributed within a box.
    -         * @type {Number}
    -         */
    -        BOX: 1,
    -
    -        /**
    -         * Values will be distributed on a sphere.
    -         * @type {Number}
    -         */
    -        SPHERE: 2,
    -
    -        /**
    -         * Values will be distributed on a 2d-disc shape.
    -         * @type {Number}
    -         */
    -        DISC: 3,
    -    },
    -
    -
    -    /**
    -     * Set this value to however many 'steps' you
    -     * want value-over-lifetime properties to have.
    -     *
    -     * It's adjustable to fix an interpolation problem:
    -     *
    -     * Assuming you specify an opacity value as [0, 1, 0]
    -     *      and the `valueOverLifetimeLength` is 4, then the
    -     *      opacity value array will be reinterpolated to
    -     *      be [0, 0.66, 0.66, 0].
    -     *   This isn't ideal, as particles would never reach
    -     *   full opacity.
    -     *
    -     * NOTE:
    -     *     This property affects the length of ALL
    -     *       value-over-lifetime properties for ALL
    -     *       emitters and ALL groups.
    -     *
    -     *     Only values >= 3 && <= 4 are allowed.
    -     *
    -     * @type {Number}
    -     */
    -    valueOverLifetimeLength: 4
    -};
    - -
  • - - -
  • -
    - -
    - -
    -

    Module loader support:

    - -
    - -
    if ( typeof define === 'function' && define.amd ) {
    -    define( 'spe', SPE );
    -}
    -else if ( typeof exports !== 'undefined' && typeof module !== 'undefined' ) {
    -    module.exports = SPE;
    -}
    -
    -/**
    - * A helper class for TypedArrays.
    - *
    - * Allows for easy resizing, assignment of various component-based
    - * types (Vector2s, Vector3s, Vector4s, Mat3s, Mat4s),
    - * as well as Colors (where components are `r`, `g`, `b`),
    - * Numbers, and setting from other TypedArrays.
    - *
    - * @author Luke Moody
    - * @constructor
    - * @param {Function} TypedArrayConstructor The constructor to use (Float32Array, Uint8Array, etc.)
    - * @param {Number} size                 The size of the array to create
    - * @param {Number} componentSize        The number of components per-value (ie. 3 for a vec3, 9 for a Mat3, etc.)
    - * @param {Number} indexOffset          The index in the array from which to start assigning values. Default `0` if none provided
    - */
    -SPE.TypedArrayHelper = function( TypedArrayConstructor, size, componentSize, indexOffset ) {
    -    'use strict';
    -
    -    this.componentSize = componentSize || 1;
    -    this.size = ( size || 1 );
    -    this.TypedArrayConstructor = TypedArrayConstructor || Float32Array;
    -    this.array = new TypedArrayConstructor( size * this.componentSize );
    -    this.indexOffset = indexOffset || 0;
    -};
    -
    -SPE.TypedArrayHelper.constructor = SPE.TypedArrayHelper;
    -
    -/**
    - * Sets the size of the internal array.
    - *
    - * Delegates to `this.shrink` or `this.grow` depending on size
    - * argument's relation to the current size of the internal array.
    - *
    - * Note that if the array is to be shrunk, data will be lost.
    - *
    - * @param {Number} size The new size of the array.
    - */
    -SPE.TypedArrayHelper.prototype.setSize = function( size, noComponentMultiply ) {
    -    'use strict';
    -
    -    var currentArraySize = this.array.length;
    -
    -    if ( !noComponentMultiply ) {
    -        size = size * this.componentSize;
    -    }
    -
    -    if ( size < currentArraySize ) {
    -        return this.shrink( size );
    -    }
    -    else if ( size > currentArraySize ) {
    -        return this.grow( size );
    -    }
    -    else {
    -        console.info( 'TypedArray is already of size:', size + '.', 'Will not resize.' );
    -    }
    -};
    -
    -/**
    - * Shrinks the internal array.
    - *
    - * @param  {Number} size The new size of the typed array. Must be smaller than `this.array.length`.
    - * @return {SPE.TypedArrayHelper}      Instance of this class.
    - */
    -SPE.TypedArrayHelper.prototype.shrink = function( size ) {
    -    'use strict';
    -
    -    this.array = this.array.subarray( 0, size );
    -    this.size = size;
    -    return this;
    -};
    -
    -/**
    - * Grows the internal array.
    - * @param  {Number} size The new size of the typed array. Must be larger than `this.array.length`.
    - * @return {SPE.TypedArrayHelper}      Instance of this class.
    - */
    -SPE.TypedArrayHelper.prototype.grow = function( size ) {
    -    'use strict';
    -
    -    var existingArray = this.array,
    -        newArray = new this.TypedArrayConstructor( size );
    -
    -    newArray.set( existingArray );
    -    this.array = newArray;
    -    this.size = size;
    -
    -    return this;
    -};
    -
    -
    -/**
    - * Perform a splice operation on this array's buffer.
    - * @param  {Number} start The start index of the splice. Will be multiplied by the number of components for this attribute.
    - * @param  {Number} end The end index of the splice. Will be multiplied by the number of components for this attribute.
    - * @returns {Object} The SPE.TypedArrayHelper instance.
    - */
    -SPE.TypedArrayHelper.prototype.splice = function( start, end ) {
    -    'use strict';
    -    start *= this.componentSize;
    -    end *= this.componentSize;
    -
    -    var data = [],
    -        array = this.array,
    -        size = array.length;
    -
    -    for ( var i = 0; i < size; ++i ) {
    -        if ( i < start || i >= end ) {
    -            data.push( array[ i ] );
    -        }
    - -
  • - - -
  • -
    - -
    - -
    -

    array[ i ] = 0;

    - -
    - -
        }
    -
    -    this.setFromArray( 0, data );
    -
    -    return this;
    -};
    -
    -
    -/**
    - * Copies from the given TypedArray into this one, using the index argument
    - * as the start position. Alias for `TypedArray.set`. Will automatically resize
    - * if the given source array is of a larger size than the internal array.
    - *
    - * @param {Number} index      The start position from which to copy into this array.
    - * @param {TypedArray} array The array from which to copy; the source array.
    - * @return {SPE.TypedArrayHelper} Instance of this class.
    - */
    -SPE.TypedArrayHelper.prototype.setFromArray = function( index, array ) {
    -    'use strict';
    -
    -    var sourceArraySize = array.length,
    -        newSize = index + sourceArraySize;
    -
    -    if ( newSize > this.array.length ) {
    -        this.grow( newSize );
    -    }
    -    else if ( newSize < this.array.length ) {
    -        this.shrink( newSize );
    -    }
    -
    -    this.array.set( array, this.indexOffset + index );
    -
    -    return this;
    -};
    -
    -/**
    - * Set a Vector2 value at `index`.
    - *
    - * @param {Number} index The index at which to set the vec2 values from.
    - * @param {Vector2} vec2  Any object that has `x` and `y` properties.
    - * @return {SPE.TypedArrayHelper} Instance of this class.
    - */
    -SPE.TypedArrayHelper.prototype.setVec2 = function( index, vec2 ) {
    -    'use strict';
    -
    -    return this.setVec2Components( index, vec2.x, vec2.y );
    -};
    -
    -/**
    - * Set a Vector2 value using raw components.
    - *
    - * @param {Number} index The index at which to set the vec2 values from.
    - * @param {Number} x     The Vec2's `x` component.
    - * @param {Number} y     The Vec2's `y` component.
    - * @return {SPE.TypedArrayHelper} Instance of this class.
    - */
    -SPE.TypedArrayHelper.prototype.setVec2Components = function( index, x, y ) {
    -    'use strict';
    -
    -    var array = this.array,
    -        i = this.indexOffset + ( index * this.componentSize );
    -
    -    array[ i ] = x;
    -    array[ i + 1 ] = y;
    -    return this;
    -};
    -
    -/**
    - * Set a Vector3 value at `index`.
    - *
    - * @param {Number} index The index at which to set the vec3 values from.
    - * @param {Vector3} vec2  Any object that has `x`, `y`, and `z` properties.
    - * @return {SPE.TypedArrayHelper} Instance of this class.
    - */
    -SPE.TypedArrayHelper.prototype.setVec3 = function( index, vec3 ) {
    -    'use strict';
    -
    -    return this.setVec3Components( index, vec3.x, vec3.y, vec3.z );
    -};
    -
    -/**
    - * Set a Vector3 value using raw components.
    - *
    - * @param {Number} index The index at which to set the vec3 values from.
    - * @param {Number} x     The Vec3's `x` component.
    - * @param {Number} y     The Vec3's `y` component.
    - * @param {Number} z     The Vec3's `z` component.
    - * @return {SPE.TypedArrayHelper} Instance of this class.
    - */
    -SPE.TypedArrayHelper.prototype.setVec3Components = function( index, x, y, z ) {
    -    'use strict';
    -
    -    var array = this.array,
    -        i = this.indexOffset + ( index * this.componentSize );
    -
    -    array[ i ] = x;
    -    array[ i + 1 ] = y;
    -    array[ i + 2 ] = z;
    -    return this;
    -};
    -
    -/**
    - * Set a Vector4 value at `index`.
    - *
    - * @param {Number} index The index at which to set the vec4 values from.
    - * @param {Vector4} vec2  Any object that has `x`, `y`, `z`, and `w` properties.
    - * @return {SPE.TypedArrayHelper} Instance of this class.
    - */
    -SPE.TypedArrayHelper.prototype.setVec4 = function( index, vec4 ) {
    -    'use strict';
    -
    -    return this.setVec4Components( index, vec4.x, vec4.y, vec4.z, vec4.w );
    -};
    -
    -/**
    - * Set a Vector4 value using raw components.
    - *
    - * @param {Number} index The index at which to set the vec4 values from.
    - * @param {Number} x     The Vec4's `x` component.
    - * @param {Number} y     The Vec4's `y` component.
    - * @param {Number} z     The Vec4's `z` component.
    - * @param {Number} w     The Vec4's `w` component.
    - * @return {SPE.TypedArrayHelper} Instance of this class.
    - */
    -SPE.TypedArrayHelper.prototype.setVec4Components = function( index, x, y, z, w ) {
    -    'use strict';
    -
    -    var array = this.array,
    -        i = this.indexOffset + ( index * this.componentSize );
    -
    -    array[ i ] = x;
    -    array[ i + 1 ] = y;
    -    array[ i + 2 ] = z;
    -    array[ i + 3 ] = w;
    -    return this;
    -};
    -
    -/**
    - * Set a Matrix3 value at `index`.
    - *
    - * @param {Number} index The index at which to set the matrix values from.
    - * @param {Matrix3} mat3 The 3x3 matrix to set from. Must have a TypedArray property named `elements` to copy from.
    - * @return {SPE.TypedArrayHelper} Instance of this class.
    - */
    -SPE.TypedArrayHelper.prototype.setMat3 = function( index, mat3 ) {
    -    'use strict';
    -
    -    return this.setFromArray( this.indexOffset + ( index * this.componentSize ), mat3.elements );
    -};
    -
    -/**
    - * Set a Matrix4 value at `index`.
    - *
    - * @param {Number} index The index at which to set the matrix values from.
    - * @param {Matrix4} mat3 The 4x4 matrix to set from. Must have a TypedArray property named `elements` to copy from.
    - * @return {SPE.TypedArrayHelper} Instance of this class.
    - */
    -SPE.TypedArrayHelper.prototype.setMat4 = function( index, mat4 ) {
    -    'use strict';
    -
    -    return this.setFromArray( this.indexOffset + ( index * this.componentSize ), mat4.elements );
    -};
    -
    -/**
    - * Set a Color value at `index`.
    - *
    - * @param {Number} index The index at which to set the vec3 values from.
    - * @param {Color} color  Any object that has `r`, `g`, and `b` properties.
    - * @return {SPE.TypedArrayHelper} Instance of this class.
    - */
    -SPE.TypedArrayHelper.prototype.setColor = function( index, color ) {
    -    'use strict';
    -
    -    return this.setVec3Components( index, color.r, color.g, color.b );
    -};
    -
    -/**
    - * Set a Number value at `index`.
    - *
    - * @param {Number} index The index at which to set the vec3 values from.
    - * @param {Number} numericValue  The number to assign to this index in the array.
    - * @return {SPE.TypedArrayHelper} Instance of this class.
    - */
    -SPE.TypedArrayHelper.prototype.setNumber = function( index, numericValue ) {
    -    'use strict';
    -
    -    this.array[ this.indexOffset + ( index * this.componentSize ) ] = numericValue;
    -    return this;
    -};
    -
    -/**
    - * Returns the value of the array at the given index, taking into account
    - * the `indexOffset` property of this class.
    - *
    - * Note that this function ignores the component size and will just return a
    - * single value.
    - *
    - * @param  {Number} index The index in the array to fetch.
    - * @return {Number}       The value at the given index.
    - */
    -SPE.TypedArrayHelper.prototype.getValueAtIndex = function( index ) {
    -    'use strict';
    -
    -    return this.array[ this.indexOffset + index ];
    -};
    -
    -/**
    - * Returns the component value of the array at the given index, taking into account
    - * the `indexOffset` property of this class.
    - *
    - * If the componentSize is set to 3, then it will return a new TypedArray
    - * of length 3.
    - *
    - * @param  {Number} index The index in the array to fetch.
    - * @return {TypedArray}       The component value at the given index.
    - */
    -SPE.TypedArrayHelper.prototype.getComponentValueAtIndex = function( index ) {
    -    'use strict';
    -
    -    return this.array.subarray( this.indexOffset + ( index * this.componentSize ) );
    -};
    -
    -/**
    - * A helper to handle creating and updating a THREE.BufferAttribute instance.
    - *
    - * @author  Luke Moody
    - * @constructor
    - * @param {String} type          The buffer attribute type. See SPE.ShaderAttribute.typeSizeMap for valid values.
    - * @param {Boolean=} dynamicBuffer Whether this buffer attribute should be marked as dynamic or not.
    - * @param {Function=} arrayType     A reference to a TypedArray constructor. Defaults to Float32Array if none provided.
    - */
    -SPE.ShaderAttribute = function( type, dynamicBuffer, arrayType ) {
    -    'use strict';
    -
    -    var typeMap = SPE.ShaderAttribute.typeSizeMap;
    -
    -    this.type = typeof type === 'string' && typeMap.hasOwnProperty( type ) ? type : 'f';
    -    this.componentSize = typeMap[ this.type ];
    -    this.arrayType = arrayType || Float32Array;
    -    this.typedArray = null;
    -    this.bufferAttribute = null;
    -    this.dynamicBuffer = !!dynamicBuffer;
    -
    -    this.updateMin = 0;
    -    this.updateMax = 0;
    -};
    -
    -SPE.ShaderAttribute.constructor = SPE.ShaderAttribute;
    -
    -/**
    - * A map of uniform types to their component size.
    - * @enum {Number}
    - */
    -SPE.ShaderAttribute.typeSizeMap = {
    -    /**
    -     * Float
    -     * @type {Number}
    -     */
    -    f: 1,
    -
    -    /**
    -     * Vec2
    -     * @type {Number}
    -     */
    -    v2: 2,
    -
    -    /**
    -     * Vec3
    -     * @type {Number}
    -     */
    -    v3: 3,
    -
    -    /**
    -     * Vec4
    -     * @type {Number}
    -     */
    -    v4: 4,
    -
    -    /**
    -     * Color
    -     * @type {Number}
    -     */
    -    c: 3,
    -
    -    /**
    -     * Mat3
    -     * @type {Number}
    -     */
    -    m3: 9,
    -
    -    /**
    -     * Mat4
    -     * @type {Number}
    -     */
    -    m4: 16
    -};
    -
    -/**
    - * Calculate the minimum and maximum update range for this buffer attribute using
    - * component size independant min and max values.
    - *
    - * @param {Number} min The start of the range to mark as needing an update.
    - * @param {Number} max The end of the range to mark as needing an update.
    - */
    -SPE.ShaderAttribute.prototype.setUpdateRange = function( min, max ) {
    -    'use strict';
    -
    -    this.updateMin = Math.min( min * this.componentSize, this.updateMin * this.componentSize );
    -    this.updateMax = Math.max( max * this.componentSize, this.updateMax * this.componentSize );
    -};
    -
    -/**
    - * Calculate the number of indices that this attribute should mark as needing
    - * updating. Also marks the attribute as needing an update.
    - */
    -SPE.ShaderAttribute.prototype.flagUpdate = function() {
    -    'use strict';
    -
    -    var attr = this.bufferAttribute,
    -        range = attr.updateRange;
    -
    -    range.offset = this.updateMin;
    -    range.count = Math.min( ( this.updateMax - this.updateMin ) + this.componentSize, this.typedArray.array.length );
    - -
  • - - -
  • -
    - -
    - -
    -

    console.log( range.offset, range.count, this.typedArray.array.length ); -console.log( ‘flagUpdate:’, range.offset, range.count );

    - -
    - -
        attr.needsUpdate = true;
    -};
    -
    -
    -
    -/**
    - * Reset the index update counts for this attribute
    - */
    -SPE.ShaderAttribute.prototype.resetUpdateRange = function() {
    -    'use strict';
    -
    -    this.updateMin = 0;
    -    this.updateMax = 0;
    -};
    -
    -SPE.ShaderAttribute.prototype.resetDynamic = function() {
    -    'use strict';
    -    this.bufferAttribute.dynamic = this.dynamicBuffer;
    -};
    -
    -/**
    - * Perform a splice operation on this attribute's buffer.
    - * @param  {Number} start The start index of the splice. Will be multiplied by the number of components for this attribute.
    - * @param  {Number} end The end index of the splice. Will be multiplied by the number of components for this attribute.
    - */
    -SPE.ShaderAttribute.prototype.splice = function( start, end ) {
    -    'use strict';
    -
    -    this.typedArray.splice( start, end );
    - -
  • - - -
  • -
    - -
    - -
    -

    Reset the reference to the attribute’s typed array -since it has probably changed.

    - -
    - -
        this.forceUpdateAll();
    -};
    -
    -SPE.ShaderAttribute.prototype.forceUpdateAll = function() {
    -    'use strict';
    -
    -    this.bufferAttribute.array = this.typedArray.array;
    -    this.bufferAttribute.updateRange.offset = 0;
    -    this.bufferAttribute.updateRange.count = -1;
    -    this.bufferAttribute.dynamic = false;
    -    this.bufferAttribute.needsUpdate = true;
    -};
    -
    -/**
    - * Make sure this attribute has a typed array associated with it.
    - *
    - * If it does, then it will ensure the typed array is of the correct size.
    - *
    - * If not, a new SPE.TypedArrayHelper instance will be created.
    - *
    - * @param  {Number} size The size of the typed array to create or update to.
    - */
    -SPE.ShaderAttribute.prototype._ensureTypedArray = function( size ) {
    -    'use strict';
    - -
  • - - -
  • -
    - -
    - -
    -

    Condition that’s most likely to be true at the top: no change.

    - -
    - -
        if ( this.typedArray !== null && this.typedArray.size === size * this.componentSize ) {
    -        return;
    -    }
    - -
  • - - -
  • -
    - -
    - -
    -

    Resize the array if we need to, telling the TypedArrayHelper to -ignore it’s component size when evaluating size.

    - -
    - -
        else if ( this.typedArray !== null && this.typedArray.size !== size ) {
    -        this.typedArray.setSize( size );
    -    }
    - -
  • - - -
  • -
    - -
    - -
    -

    This condition should only occur once in an attribute’s lifecycle.

    - -
    - -
        else if ( this.typedArray === null ) {
    -        this.typedArray = new SPE.TypedArrayHelper( this.arrayType, size, this.componentSize );
    -    }
    -};
    -
    -
    -/**
    - * Creates a THREE.BufferAttribute instance if one doesn't exist already.
    - *
    - * Ensures a typed array is present by calling _ensureTypedArray() first.
    - *
    - * If a buffer attribute exists already, then it will be marked as needing an update.
    - *
    - * @param  {Number} size The size of the typed array to create if one doesn't exist, or resize existing array to.
    - */
    -SPE.ShaderAttribute.prototype._createBufferAttribute = function( size ) {
    -    'use strict';
    - -
  • - - -
  • -
    - -
    - -
    -

    Make sure the typedArray is present and correct.

    - -
    - -
        this._ensureTypedArray( size );
    - -
  • - - -
  • -
    - -
    - -
    -

    Don’t create it if it already exists, but do -flag that it needs updating on the next render -cycle.

    - -
    - -
        if ( this.bufferAttribute !== null ) {
    -        this.bufferAttribute.array = this.typedArray.array;
    -        this.bufferAttribute.needsUpdate = true;
    -        return;
    -    }
    -
    -    this.bufferAttribute = new THREE.BufferAttribute( this.typedArray.array, this.componentSize );
    -    this.bufferAttribute.dynamic = this.dynamicBuffer;
    -};
    -
    -/**
    - * Returns the length of the typed array associated with this attribute.
    - * @return {Number} The length of the typed array. Will be 0 if no typed array has been created yet.
    - */
    -SPE.ShaderAttribute.prototype.getLength = function() {
    -    'use strict';
    -
    -    if ( this.typedArray === null ) {
    -        return 0;
    -    }
    -
    -    return this.typedArray.array.length;
    -};
    -
    -SPE.shaderChunks = {
    - -
  • - - -
  • -
    - -
    - -
    -

    Register color-packing define statements.

    - -
    - -
        defines: [
    -        '#define PACKED_COLOR_SIZE 256.0',
    -        '#define PACKED_COLOR_DIVISOR 255.0'
    -    ].join( '\n' ),
    - -
  • - - -
  • -
    - -
    - -
    -

    All uniforms used by vertex / fragment shaders

    - -
    - -
        uniforms: [
    -        'uniform float deltaTime;',
    -        'uniform float runTime;',
    -        'uniform sampler2D texture;',
    -        'uniform vec4 textureAnimation;',
    -        'uniform float scale;',
    -    ].join( '\n' ),
    - -
  • - - -
  • -
    - -
    - -
    -

    All attributes used by the vertex shader.

    -

    Note that some attributes are squashed into other ones:

    -
      -
    • Drag is acceleration.w
    • -
    - -
    - -
        attributes: [
    -        'attribute vec4 acceleration;',
    -        'attribute vec3 velocity;',
    -        'attribute vec4 rotation;',
    -        'attribute vec3 rotationCenter;',
    -        'attribute vec4 params;',
    -        'attribute vec4 size;',
    -        'attribute vec4 angle;',
    -        'attribute vec4 color;',
    -        'attribute vec4 opacity;'
    -    ].join( '\n' ),
    - -
  • - - -
  • -
    - -
    - -
    - -
    - -
        varyings: [
    -        'varying vec4 vColor;',
    -        '#ifdef SHOULD_ROTATE_TEXTURE',
    -        '    varying float vAngle;',
    -        '#endif',
    -
    -        '#ifdef SHOULD_CALCULATE_SPRITE',
    -        '    varying vec4 vSpriteSheet;',
    -        '#endif'
    -    ].join( '\n' ),
    - -
  • - - -
  • -
    - -
    - -
    -

    Branch-avoiding comparison fns

    - - -
    - -
        branchAvoidanceFunctions: [
    -        'float when_gt(float x, float y) {',
    -        '    return max(sign(x - y), 0.0);',
    -        '}',
    -
    -        'float when_lt(float x, float y) {',
    -        '    return min( max(1.0 - sign(x - y), 0.0), 1.0 );',
    -        '}',
    -
    -        'float when_eq( float x, float y ) {',
    -        '    return 1.0 - abs( sign( x - y ) );',
    -        '}',
    -
    -        'float when_ge(float x, float y) {',
    -        '  return 1.0 - when_lt(x, y);',
    -        '}',
    -
    -        'float when_le(float x, float y) {',
    -        '  return 1.0 - when_gt(x, y);',
    -        '}',
    - -
  • - - -
  • -
    - -
    - -
    -

    Branch-avoiding logical operators -(to be used with above comparison fns)

    - -
    - -
            'float and(float a, float b) {',
    -        '    return a * b;',
    -        '}',
    -
    -        'float or(float a, float b) {',
    -        '    return min(a + b, 1.0);',
    -        '}',
    -    ].join( '\n' ),
    - -
  • - - -
  • - - -
        unpackColor: [
    -        'vec3 unpackColor( in float hex ) {',
    -        '   vec3 c = vec3( 0.0 );',
    -
    -        '   float r = mod( (hex / PACKED_COLOR_SIZE / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );',
    -        '   float g = mod( (hex / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );',
    -        '   float b = mod( hex, PACKED_COLOR_SIZE );',
    -
    -        '   c.r = r / PACKED_COLOR_DIVISOR;',
    -        '   c.g = g / PACKED_COLOR_DIVISOR;',
    -        '   c.b = b / PACKED_COLOR_DIVISOR;',
    -
    -        '   return c;',
    -        '}',
    -    ].join( '\n' ),
    -
    -    unpackRotationAxis: [
    -        'vec3 unpackRotationAxis( in float hex ) {',
    -        '   vec3 c = vec3( 0.0 );',
    -
    -        '   float r = mod( (hex / PACKED_COLOR_SIZE / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );',
    -        '   float g = mod( (hex / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );',
    -        '   float b = mod( hex, PACKED_COLOR_SIZE );',
    -
    -        '   c.r = r / PACKED_COLOR_DIVISOR;',
    -        '   c.g = g / PACKED_COLOR_DIVISOR;',
    -        '   c.b = b / PACKED_COLOR_DIVISOR;',
    -
    -        '   c *= vec3( 2.0 );',
    -        '   c -= vec3( 1.0 );',
    -
    -        '   return c;',
    -        '}',
    -    ].join( '\n' ),
    -
    -    floatOverLifetime: [
    -        'float getFloatOverLifetime( in float positionInTime, in vec4 attr ) {',
    -        '    highp float value = 0.0;',
    -        '    float deltaAge = positionInTime * float( VALUE_OVER_LIFETIME_LENGTH - 1 );',
    -        '    float fIndex = 0.0;',
    -        '    float shouldApplyValue = 0.0;',
    - -
  • - - -
  • -
    - -
    - -
    -

    This might look a little odd, but it’s faster in the testing I’ve done than using branches. -Uses basic maths to avoid branching.

    -

    Take a look at the branch-avoidance functions defined above, -and be sure to check out The Orange Duck site where I got this -from (link above).

    - -
    - -
  • - - -
  • -
    - -
    - -
    -

    Fix for static emitters (age is always zero).

    - -
    - -
            '    value += attr[ 0 ] * when_eq( deltaAge, 0.0 );',
    -        '',
    -        '    for( int i = 0; i < VALUE_OVER_LIFETIME_LENGTH - 1; ++i ) {',
    -        '       fIndex = float( i );',
    -        '       shouldApplyValue = and( when_gt( deltaAge, fIndex ), when_le( deltaAge, fIndex + 1.0 ) );',
    -        '       value += shouldApplyValue * mix( attr[ i ], attr[ i + 1 ], deltaAge - fIndex );',
    -        '    }',
    -        '',
    -        '    return value;',
    -        '}',
    -    ].join( '\n' ),
    -
    -    colorOverLifetime: [
    -        'vec3 getColorOverLifetime( in float positionInTime, in vec3 color1, in vec3 color2, in vec3 color3, in vec3 color4 ) {',
    -        '    vec3 value = vec3( 0.0 );',
    -        '    value.x = getFloatOverLifetime( positionInTime, vec4( color1.x, color2.x, color3.x, color4.x ) );',
    -        '    value.y = getFloatOverLifetime( positionInTime, vec4( color1.y, color2.y, color3.y, color4.y ) );',
    -        '    value.z = getFloatOverLifetime( positionInTime, vec4( color1.z, color2.z, color3.z, color4.z ) );',
    -        '    return value;',
    -        '}',
    -    ].join( '\n' ),
    -
    -    paramFetchingFunctions: [
    -        'float getAlive() {',
    -        '   return params.x;',
    -        '}',
    -
    -        'float getAge() {',
    -        '   return params.y;',
    -        '}',
    -
    -        'float getMaxAge() {',
    -        '   return params.z;',
    -        '}',
    -
    -        'float getWiggle() {',
    -        '   return params.w;',
    -        '}',
    -    ].join( '\n' ),
    -
    -    forceFetchingFunctions: [
    -        'vec4 getPosition( in float age ) {',
    -        '   return modelViewMatrix * vec4( position, 1.0 );',
    -        '}',
    -
    -        'vec3 getVelocity( in float age ) {',
    -        '   return velocity * age;',
    -        '}',
    -
    -        'vec3 getAcceleration( in float age ) {',
    -        '   return acceleration.xyz * age;',
    -        '}',
    -    ].join( '\n' ),
    -
    -
    -    rotationFunctions: [
    - -
  • - - -
  • - - -
            '#ifdef SHOULD_ROTATE_PARTICLES',
    -        '   mat4 getRotationMatrix( in vec3 axis, in float angle) {',
    -        '       axis = normalize(axis);',
    -        '       float s = sin(angle);',
    -        '       float c = cos(angle);',
    -        '       float oc = 1.0 - c;',
    -        '',
    -        '       return mat4(oc * axis.x * axis.x + c,           oc * axis.x * axis.y - axis.z * s,  oc * axis.z * axis.x + axis.y * s,  0.0,',
    -        '                   oc * axis.x * axis.y + axis.z * s,  oc * axis.y * axis.y + c,           oc * axis.y * axis.z - axis.x * s,  0.0,',
    -        '                   oc * axis.z * axis.x - axis.y * s,  oc * axis.y * axis.z + axis.x * s,  oc * axis.z * axis.z + c,           0.0,',
    -        '                   0.0,                                0.0,                                0.0,                                1.0);',
    -        '   }',
    -        '',
    -        '   vec3 getRotation( in vec3 pos, in float positionInTime ) {',
    -        '      if( rotation.y == 0.0 ) {',
    -        '           return pos;',
    -        '      }',
    -        '',
    -        '      vec3 axis = unpackRotationAxis( rotation.x );',
    -        '      vec3 center = rotationCenter;',
    -        '      vec3 translated;',
    -        '      mat4 rotationMatrix;',
    -
    -        '      float angle = 0.0;',
    -        '      angle += when_eq( rotation.z, 0.0 ) * rotation.y;',
    -        '      angle += when_gt( rotation.z, 0.0 ) * mix( 0.0, rotation.y, positionInTime );',
    -        '      translated = rotationCenter - pos;',
    -        '      rotationMatrix = getRotationMatrix( axis, angle );',
    -        '      return center - vec3( rotationMatrix * vec4( translated, 0.0 ) );',
    -        '   }',
    -        '#endif'
    -    ].join( '\n' ),
    - -
  • - - -
  • -
    - -
    - -
    -

    Fragment chunks

    - -
    - -
        rotateTexture: [
    -        '    vec2 vUv = vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y );',
    -        '',
    -        '    #ifdef SHOULD_ROTATE_TEXTURE',
    -        '       float x = gl_PointCoord.x - 0.5;',
    -        '       float y = 1.0 - gl_PointCoord.y - 0.5;',
    -        '       float c = cos( -vAngle );',
    -        '       float s = sin( -vAngle );',
    -
    -        '       vUv = vec2( c * x + s * y + 0.5, c * y - s * x + 0.5 );',
    -        '    #endif',
    -        '',
    - -
  • - - -
  • -
    - -
    - -
    -

    Spritesheets overwrite angle calculations.

    - -
    - -
            '    #ifdef SHOULD_CALCULATE_SPRITE',
    -        '        float framesX = vSpriteSheet.x;',
    -        '        float framesY = vSpriteSheet.y;',
    -        '        float columnNorm = vSpriteSheet.z;',
    -        '        float rowNorm = vSpriteSheet.w;',
    -
    -        '        vUv.x = gl_PointCoord.x * framesX + columnNorm;',
    -        '        vUv.y = 1.0 - (gl_PointCoord.y * framesY + rowNorm);',
    -        '    #endif',
    -
    -        '',
    -        '    vec4 rotatedTexture = texture2D( texture, vUv );',
    -    ].join( '\n' )
    -};
    -
    -SPE.shaders = {
    -    vertex: [
    -        SPE.shaderChunks.defines,
    -        SPE.shaderChunks.uniforms,
    -        SPE.shaderChunks.attributes,
    -        SPE.shaderChunks.varyings,
    -
    -        THREE.ShaderChunk.common,
    -        THREE.ShaderChunk.logdepthbuf_pars_vertex,
    -
    -        SPE.shaderChunks.branchAvoidanceFunctions,
    -        SPE.shaderChunks.unpackColor,
    -        SPE.shaderChunks.unpackRotationAxis,
    -        SPE.shaderChunks.floatOverLifetime,
    -        SPE.shaderChunks.colorOverLifetime,
    -        SPE.shaderChunks.paramFetchingFunctions,
    -        SPE.shaderChunks.forceFetchingFunctions,
    -        SPE.shaderChunks.rotationFunctions,
    -
    -
    -        'void main() {',
    - -
  • - - -
  • -
    - -
    - -
    -

    Setup…

    - -
    - -
            '    highp float age = getAge();',
    -        '    highp float alive = getAlive();',
    -        '    highp float maxAge = getMaxAge();',
    -        '    highp float positionInTime = (age / maxAge);',
    -        '    highp float isAlive = when_gt( alive, 0.0 );',
    -
    -        '    #ifdef SHOULD_WIGGLE_PARTICLES',
    -        '        float wiggleAmount = positionInTime * getWiggle();',
    -        '        float wiggleSin = isAlive * sin( wiggleAmount );',
    -        '        float wiggleCos = isAlive * cos( wiggleAmount );',
    -        '    #endif',
    - -
  • - - -
  • -
    - -
    - -
    -

    Forces

    - -
    - -
  • - - -
  • -
    - -
    - -
    -

    Get forces & position

    - -
    - -
            '    vec3 vel = getVelocity( age );',
    -        '    vec3 accel = getAcceleration( age );',
    -        '    vec3 force = vec3( 0.0 );',
    -        '    vec3 pos = vec3( position );',
    - -
  • - - -
  • -
    - -
    - -
    -

    Calculate the required drag to apply to the forces.

    - -
    - -
            '    float drag = 1.0 - (positionInTime * 0.5) * acceleration.w;',
    - -
  • - - -
  • -
    - -
    - -
    -

    Integrate forces…

    - -
    - -
            '    force += vel;',
    -        '    force *= drag;',
    -        '    force += accel * age;',
    -        '    pos += force;',
    - -
  • - - -
  • -
    - -
    - -
    -

    Wiggly wiggly wiggle!

    - -
    - -
            '    #ifdef SHOULD_WIGGLE_PARTICLES',
    -        '        pos.x += wiggleSin;',
    -        '        pos.y += wiggleCos;',
    -        '        pos.z += wiggleSin;',
    -        '    #endif',
    - -
  • - - -
  • -
    - -
    - -
    -

    Rotate the emitter around it’s central point

    - -
    - -
            '    #ifdef SHOULD_ROTATE_PARTICLES',
    -        '        pos = getRotation( pos, positionInTime );',
    -        '    #endif',
    - -
  • - - -
  • -
    - -
    - -
    -

    Convert pos to a world-space value

    - -
    - -
            '    vec4 mvPos = modelViewMatrix * vec4( pos, 1.0 );',
    - -
  • - - -
  • -
    - -
    - -
    -

    Determine point size.

    - -
    - -
            '    highp float pointSize = getFloatOverLifetime( positionInTime, size ) * isAlive;',
    - -
  • - - -
  • -
    - -
    - -
    -

    Determine perspective

    - -
    - -
            '    #ifdef HAS_PERSPECTIVE',
    -        '        float perspective = scale / length( mvPos.xyz );',
    -        '    #else',
    -        '        float perspective = 1.0;',
    -        '    #endif',
    - -
  • - - -
  • -
    - -
    - -
    -

    Apply perpective to pointSize value

    - -
    - -
            '    float pointSizePerspective = pointSize * perspective;',
    - -
  • - - -
  • -
    - -
    - -
    -

    Appearance

    - -
    - -
  • - - -
  • -
    - -
    - -
    -

    Determine color and opacity for this particle

    - -
    - -
            '    #ifdef COLORIZE',
    -        '       vec3 c = isAlive * getColorOverLifetime(',
    -        '           positionInTime,',
    -        '           unpackColor( color.x ),',
    -        '           unpackColor( color.y ),',
    -        '           unpackColor( color.z ),',
    -        '           unpackColor( color.w )',
    -        '       );',
    -        '    #else',
    -        '       vec3 c = vec3(1.0);',
    -        '    #endif',
    -
    -        '    float o = isAlive * getFloatOverLifetime( positionInTime, opacity );',
    - -
  • - - -
  • -
    - -
    - -
    -

    Assign color to vColor varying.

    - -
    - -
            '    vColor = vec4( c, o );',
    - -
  • - - -
  • -
    - -
    - -
    -

    Determine angle

    - -
    - -
            '    #ifdef SHOULD_ROTATE_TEXTURE',
    -        '        vAngle = isAlive * getFloatOverLifetime( positionInTime, angle );',
    -        '    #endif',
    - -
  • - - -
  • -
    - -
    - -
    -

    If this particle is using a sprite-sheet as a texture, we’ll have to figure out -what frame of the texture the particle is using at it’s current position in time.

    - -
    - -
            '    #ifdef SHOULD_CALCULATE_SPRITE',
    -        '        float framesX = textureAnimation.x;',
    -        '        float framesY = textureAnimation.y;',
    -        '        float loopCount = textureAnimation.w;',
    -        '        float totalFrames = textureAnimation.z;',
    -        '        float frameNumber = mod( (positionInTime * loopCount) * totalFrames, totalFrames );',
    -
    -        '        float column = floor(mod( frameNumber, framesX ));',
    -        '        float row = floor( (frameNumber - column) / framesX );',
    -
    -        '        float columnNorm = column / framesX;',
    -        '        float rowNorm = row / framesY;',
    -
    -        '        vSpriteSheet.x = 1.0 / framesX;',
    -        '        vSpriteSheet.y = 1.0 / framesY;',
    -        '        vSpriteSheet.z = columnNorm;',
    -        '        vSpriteSheet.w = rowNorm;',
    -        '    #endif',
    - -
  • - - -
  • -
    - -
    - -
    -

    Write values

    - -
    - -
  • - - -
  • -
    - -
    - -
    -

    Set PointSize according to size at current point in time.

    - -
    - -
            '    gl_PointSize = pointSizePerspective;',
    -        '    gl_Position = projectionMatrix * mvPos;',
    -
    -        THREE.ShaderChunk.logdepthbuf_vertex,
    -
    -        '}'
    -    ].join( '\n' ),
    -
    -    fragment: [
    -        SPE.shaderChunks.uniforms,
    -
    -        THREE.ShaderChunk.common,
    -        THREE.ShaderChunk.fog_pars_fragment,
    -        THREE.ShaderChunk.logdepthbuf_pars_fragment,
    -
    -        SPE.shaderChunks.varyings,
    -
    -        SPE.shaderChunks.branchAvoidanceFunctions,
    -
    -        'void main() {',
    -        '    vec3 outgoingLight = vColor.xyz;',
    -        '    ',
    -        '    #ifdef ALPHATEST',
    -        '       if ( vColor.w < float(ALPHATEST) ) discard;',
    -        '    #endif',
    -
    -        SPE.shaderChunks.rotateTexture,
    -
    -        THREE.ShaderChunk.logdepthbuf_fragment,
    -
    -        '    outgoingLight = vColor.xyz * rotatedTexture.xyz;',
    -
    -        THREE.ShaderChunk.fog_fragment,
    -
    -        '    gl_FragColor = vec4( outgoingLight.xyz, rotatedTexture.w * vColor.w );',
    -        '}'
    -    ].join( '\n' )
    -};
    -
    -/**
    - * A bunch of utility functions used throughout the library.
    - * @namespace
    - * @type {Object}
    - */
    -SPE.utils = {
    -    /**
    -     * A map of types used by `SPE.utils.ensureTypedArg` and
    -     * `SPE.utils.ensureArrayTypedArg` to compare types against.
    -     *
    -     * @enum {String}
    -     */
    -    types: {
    -        /**
    -         * Boolean type.
    -         * @type {String}
    -         */
    -        BOOLEAN: 'boolean',
    -
    -        /**
    -         * String type.
    -         * @type {String}
    -         */
    -        STRING: 'string',
    -
    -        /**
    -         * Number type.
    -         * @type {String}
    -         */
    -        NUMBER: 'number',
    -
    -        /**
    -         * Object type.
    -         * @type {String}
    -         */
    -        OBJECT: 'object'
    -    },
    -
    -    /**
    -     * Given a value, a type, and a default value to fallback to,
    -     * ensure the given argument adheres to the type requesting,
    -     * returning the default value if type check is false.
    -     *
    -     * @param  {(boolean|string|number|object)} arg          The value to perform a type-check on.
    -     * @param  {String} type         The type the `arg` argument should adhere to.
    -     * @param  {(boolean|string|number|object)} defaultValue A default value to fallback on if the type check fails.
    -     * @return {(boolean|string|number|object)}              The given value if type check passes, or the default value if it fails.
    -     */
    -    ensureTypedArg: function( arg, type, defaultValue ) {
    -        'use strict';
    -
    -        if ( typeof arg === type ) {
    -            return arg;
    -        }
    -        else {
    -            return defaultValue;
    -        }
    -    },
    -
    -    /**
    -     * Given an array of values, a type, and a default value,
    -     * ensure the given array's contents ALL adhere to the provided type,
    -     * returning the default value if type check fails.
    -     *
    -     * If the given value to check isn't an Array, delegates to SPE.utils.ensureTypedArg.
    -     *
    -     * @param  {Array|boolean|string|number|object} arg          The array of values to check type of.
    -     * @param  {String} type         The type that should be adhered to.
    -     * @param  {(boolean|string|number|object)} defaultValue A default fallback value.
    -     * @return {(boolean|string|number|object)}              The given value if type check passes, or the default value if it fails.
    -     */
    -    ensureArrayTypedArg: function( arg, type, defaultValue ) {
    -        'use strict';
    - -
  • - - -
  • -
    - -
    - -
    -

    If the argument being checked is an array, loop through -it and ensure all the values are of the correct type, -falling back to the defaultValue if any aren’t.

    - -
    - -
            if ( Array.isArray( arg ) ) {
    -            for ( var i = arg.length - 1; i >= 0; --i ) {
    -                if ( typeof arg[ i ] !== type ) {
    -                    return defaultValue;
    -                }
    -            }
    -
    -            return arg;
    -        }
    - -
  • - - -
  • -
    - -
    - -
    -

    If the arg isn’t an array then just fallback to -checking the type.

    - -
    - -
            return this.ensureTypedArg( arg, type, defaultValue );
    -    },
    -
    -    /**
    -     * Ensures the given value is an instance of a constructor function.
    -     *
    -     * @param  {Object} arg          The value to check instance of.
    -     * @param  {Function} instance     The constructor of the instance to check against.
    -     * @param  {Object} defaultValue A default fallback value if instance check fails
    -     * @return {Object}              The given value if type check passes, or the default value if it fails.
    -     */
    -    ensureInstanceOf: function( arg, instance, defaultValue ) {
    -        'use strict';
    -
    -        if ( instance !== undefined && arg instanceof instance ) {
    -            return arg;
    -        }
    -        else {
    -            return defaultValue;
    -        }
    -    },
    -
    -    /**
    -     * Given an array of values, ensure the instances of all items in the array
    -     * matches the given instance constructor falling back to a default value if
    -     * the check fails.
    -     *
    -     * If given value isn't an Array, delegates to `SPE.utils.ensureInstanceOf`.
    -     *
    -     * @param  {Array|Object} arg          The value to perform the instanceof check on.
    -     * @param  {Function} instance     The constructor of the instance to check against.
    -     * @param  {Object} defaultValue A default fallback value if instance check fails
    -     * @return {Object}              The given value if type check passes, or the default value if it fails.
    -     */
    -    ensureArrayInstanceOf: function( arg, instance, defaultValue ) {
    -        'use strict';
    - -
  • - - -
  • -
    - -
    - -
    -

    If the argument being checked is an array, loop through -it and ensure all the values are of the correct type, -falling back to the defaultValue if any aren’t.

    - -
    - -
            if ( Array.isArray( arg ) ) {
    -            for ( var i = arg.length - 1; i >= 0; --i ) {
    -                if ( instance !== undefined && arg[ i ] instanceof instance === false ) {
    -                    return defaultValue;
    -                }
    -            }
    -
    -            return arg;
    -        }
    - -
  • - - -
  • -
    - -
    - -
    -

    If the arg isn’t an array then just fallback to -checking the type.

    - -
    - -
            return this.ensureInstanceOf( arg, instance, defaultValue );
    -    },
    -
    -    /**
    -     * Ensures that any "value-over-lifetime" properties of an emitter are
    -     * of the correct length (as dictated by `SPE.valueOverLifetimeLength`).
    -     *
    -     * Delegates to `SPE.utils.interpolateArray` for array resizing.
    -     *
    -     * If properties aren't arrays, then property values are put into one.
    -     *
    -     * @param  {Object} property  The property of an SPE.Emitter instance to check compliance of.
    -     * @param  {Number} minLength The minimum length of the array to create.
    -     * @param  {Number} maxLength The maximum length of the array to create.
    -     */
    -    ensureValueOverLifetimeCompliance: function( property, minLength, maxLength ) {
    -        'use strict';
    -
    -        minLength = minLength || 3;
    -        maxLength = maxLength || 3;
    - -
  • - - -
  • -
    - -
    - -
    -

    First, ensure both properties are arrays.

    - -
    - -
            if ( Array.isArray( property._value ) === false ) {
    -            property._value = [ property._value ];
    -        }
    -
    -        if ( Array.isArray( property._spread ) === false ) {
    -            property._spread = [ property._spread ];
    -        }
    -
    -        var valueLength = this.clamp( property._value.length, minLength, maxLength ),
    -            spreadLength = this.clamp( property._spread.length, minLength, maxLength ),
    -            desiredLength = Math.max( valueLength, spreadLength );
    -
    -        if ( property._value.length !== desiredLength ) {
    -            property._value = this.interpolateArray( property._value, desiredLength );
    -        }
    -
    -        if ( property._spread.length !== desiredLength ) {
    -            property._spread = this.interpolateArray( property._spread, desiredLength );
    -        }
    -    },
    -
    -    /**
    -     * Performs linear interpolation (lerp) on an array.
    -     *
    -     * For example, lerping [1, 10], with a `newLength` of 10 will produce [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].
    -     *
    -     * Delegates to `SPE.utils.lerpTypeAgnostic` to perform the actual
    -     * interpolation.
    -     *
    -     * @param  {Array} srcArray  The array to lerp.
    -     * @param  {Number} newLength The length the array should be interpolated to.
    -     * @return {Array}           The interpolated array.
    -     */
    -    interpolateArray: function( srcArray, newLength ) {
    -        'use strict';
    -
    -        var sourceLength = srcArray.length,
    -            newArray = [ typeof srcArray[ 0 ].clone === 'function' ? srcArray[ 0 ].clone() : srcArray[ 0 ] ],
    -            factor = ( sourceLength - 1 ) / ( newLength - 1 );
    -
    -
    -        for ( var i = 1; i < newLength - 1; ++i ) {
    -            var f = i * factor,
    -                before = Math.floor( f ),
    -                after = Math.ceil( f ),
    -                delta = f - before;
    -
    -            newArray[ i ] = this.lerpTypeAgnostic( srcArray[ before ], srcArray[ after ], delta );
    -        }
    -
    -        newArray.push(
    -            typeof srcArray[ sourceLength - 1 ].clone === 'function' ?
    -            srcArray[ sourceLength - 1 ].clone() :
    -            srcArray[ sourceLength - 1 ]
    -        );
    -
    -        return newArray;
    -    },
    -
    -    /**
    -     * Clamp a number to between the given min and max values.
    -     * @param  {Number} value The number to clamp.
    -     * @param  {Number} min   The minimum value.
    -     * @param  {Number} max   The maximum value.
    -     * @return {Number}       The clamped number.
    -     */
    -    clamp: function( value, min, max ) {
    -        'use strict';
    -
    -        return Math.max( min, Math.min( value, max ) );
    -    },
    -
    -    /**
    -     * If the given value is less than the epsilon value, then return
    -     * a randomised epsilon value if specified, or just the epsilon value if not.
    -     * Works for negative numbers as well as positive.
    -     *
    -     * @param  {Number} value     The value to perform the operation on.
    -     * @param  {Boolean} randomise Whether the value should be randomised.
    -     * @return {Number}           The result of the operation.
    -     */
    -    zeroToEpsilon: function( value, randomise ) {
    -        'use strict';
    -
    -        var epsilon = 0.00001,
    -            result = value;
    -
    -        result = randomise ? Math.random() * epsilon * 10 : epsilon;
    -
    -        if ( value < 0 && value > -epsilon ) {
    -            result = -result;
    -        }
    - -
  • - - -
  • -
    - -
    - -
    -

    if ( value === 0 ) { - result = randomise ? Math.random() epsilon 10 : epsilon; -} -else if ( value > 0 && value < epsilon ) { - result = randomise ? Math.random() epsilon 10 : epsilon; -} -else if ( value < 0 && value > -epsilon ) { - result = -( randomise ? Math.random() epsilon 10 : epsilon ); -}

    - -
    - -
    -        return result;
    -    },
    -
    -    /**
    -     * Linearly interpolates two values of various types. The given values
    -     * must be of the same type for the interpolation to work.
    -     * @param  {(number|Object)} start The start value of the lerp.
    -     * @param  {(number|object)} end   The end value of the lerp.
    -     * @param  {Number} delta The delta posiiton of the lerp operation. Ideally between 0 and 1 (inclusive).
    -     * @return {(number|object|undefined)}       The result of the operation. Result will be undefined if
    -     *                                               the start and end arguments aren't a supported type, or
    -     *                                               if their types do not match.
    -     */
    -    lerpTypeAgnostic: function( start, end, delta ) {
    -        'use strict';
    -
    -        var types = this.types,
    -            out;
    -
    -        if ( typeof start === types.NUMBER && typeof end === types.NUMBER ) {
    -            return start + ( ( end - start ) * delta );
    -        }
    -        else if ( start instanceof THREE.Vector2 && end instanceof THREE.Vector2 ) {
    -            out = start.clone();
    -            out.x = this.lerp( start.x, end.x, delta );
    -            out.y = this.lerp( start.y, end.y, delta );
    -            return out;
    -        }
    -        else if ( start instanceof THREE.Vector3 && end instanceof THREE.Vector3 ) {
    -            out = start.clone();
    -            out.x = this.lerp( start.x, end.x, delta );
    -            out.y = this.lerp( start.y, end.y, delta );
    -            out.z = this.lerp( start.z, end.z, delta );
    -            return out;
    -        }
    -        else if ( start instanceof THREE.Vector4 && end instanceof THREE.Vector4 ) {
    -            out = start.clone();
    -            out.x = this.lerp( start.x, end.x, delta );
    -            out.y = this.lerp( start.y, end.y, delta );
    -            out.z = this.lerp( start.z, end.z, delta );
    -            out.w = this.lerp( start.w, end.w, delta );
    -            return out;
    -        }
    -        else if ( start instanceof THREE.Color && end instanceof THREE.Color ) {
    -            out = start.clone();
    -            out.r = this.lerp( start.r, end.r, delta );
    -            out.g = this.lerp( start.g, end.g, delta );
    -            out.b = this.lerp( start.b, end.b, delta );
    -            return out;
    -        }
    -        else {
    -            console.warn( 'Invalid argument types, or argument types do not match:', start, end );
    -        }
    -    },
    -
    -    /**
    -     * Perform a linear interpolation operation on two numbers.
    -     * @param  {Number} start The start value.
    -     * @param  {Number} end   The end value.
    -     * @param  {Number} delta The position to interpolate to.
    -     * @return {Number}       The result of the lerp operation.
    -     */
    -    lerp: function( start, end, delta ) {
    -        'use strict';
    -        return start + ( ( end - start ) * delta );
    -    },
    -
    -    /**
    -     * Rounds a number to a nearest multiple.
    -     *
    -     * @param  {Number} n        The number to round.
    -     * @param  {Number} multiple The multiple to round to.
    -     * @return {Number}          The result of the round operation.
    -     */
    -    roundToNearestMultiple: function( n, multiple ) {
    -        'use strict';
    -
    -        var remainder = 0;
    -
    -        if ( multiple === 0 ) {
    -            return n;
    -        }
    -
    -        remainder = Math.abs( n ) % multiple;
    -
    -        if ( remainder === 0 ) {
    -            return n;
    -        }
    -
    -        if ( n < 0 ) {
    -            return -( Math.abs( n ) - remainder );
    -        }
    -
    -        return n + multiple - remainder;
    -    },
    -
    -    /**
    -     * Check if all items in an array are equal. Uses strict equality.
    -     *
    -     * @param  {Array} array The array of values to check equality of.
    -     * @return {Boolean}       Whether the array's values are all equal or not.
    -     */
    -    arrayValuesAreEqual: function( array ) {
    -        'use strict';
    -
    -        for ( var i = 0; i < array.length - 1; ++i ) {
    -            if ( array[ i ] !== array[ i + 1 ] ) {
    -                return false;
    -            }
    -        }
    -
    -        return true;
    -    },
    - -
  • - - -
  • -
    - -
    - -
    -

    colorsAreEqual: function() { - var colors = Array.prototype.slice.call( arguments ), - numColors = colors.length;

    - -
    - -
  • - - -
  • -
    - -
    - -
    -
    for ( var i = 0, color1, color2; i < numColors - 1; ++i ) {
    -    color1 = colors[ i ];
    -    color2 = colors[ i + 1 ];
    -
    -
    - -
  • - - -
  • -
    - -
    - -
    -
        if (
    -        color1.r !== color2.r ||
    -        color1.g !== color2.g ||
    -        color1.b !== color2.b
    -    ) {
    -        return false
    -    }
    -}
    -
    -
    - -
  • - - -
  • -
    - -
    - -
    -
    return true;
    -

    },

    - -
    - -
    -
    -    /**
    -     * Given a start value and a spread value, create and return a random
    -     * number.
    -     * @param  {Number} base   The start value.
    -     * @param  {Number} spread The size of the random variance to apply.
    -     * @return {Number}        A randomised number.
    -     */
    -    randomFloat: function( base, spread ) {
    -        'use strict';
    -        return base + spread * ( Math.random() - 0.5 );
    -    },
    -
    -
    -
    -    /**
    -     * Given an SPE.ShaderAttribute instance, and various other settings,
    -     * assign values to the attribute's array in a `vec3` format.
    -     *
    -     * @param  {Object} attribute   The instance of SPE.ShaderAttribute to save the result to.
    -     * @param  {Number} index       The offset in the attribute's TypedArray to save the result from.
    -     * @param  {Object} base        THREE.Vector3 instance describing the start value.
    -     * @param  {Object} spread      THREE.Vector3 instance describing the random variance to apply to the start value.
    -     * @param  {Object} spreadClamp THREE.Vector3 instance describing the multiples to clamp the randomness to.
    -     */
    -    randomVector3: function( attribute, index, base, spread, spreadClamp ) {
    -        'use strict';
    -
    -        var x = base.x + ( Math.random() * spread.x - ( spread.x * 0.5 ) ),
    -            y = base.y + ( Math.random() * spread.y - ( spread.y * 0.5 ) ),
    -            z = base.z + ( Math.random() * spread.z - ( spread.z * 0.5 ) );
    - -
  • - - -
  • -
    - -
    - -
    -

    var x = this.randomFloat( base.x, spread.x ), -y = this.randomFloat( base.y, spread.y ), -z = this.randomFloat( base.z, spread.z );

    - -
    - -
    -        if ( spreadClamp ) {
    -            x = -spreadClamp.x * 0.5 + this.roundToNearestMultiple( x, spreadClamp.x );
    -            y = -spreadClamp.y * 0.5 + this.roundToNearestMultiple( y, spreadClamp.y );
    -            z = -spreadClamp.z * 0.5 + this.roundToNearestMultiple( z, spreadClamp.z );
    -        }
    -
    -        attribute.typedArray.setVec3Components( index, x, y, z );
    -    },
    -
    -    /**
    -     * Given an SPE.Shader attribute instance, and various other settings,
    -     * assign Color values to the attribute.
    -     * @param  {Object} attribute The instance of SPE.ShaderAttribute to save the result to.
    -     * @param  {Number} index     The offset in the attribute's TypedArray to save the result from.
    -     * @param  {Object} base      THREE.Color instance describing the start color.
    -     * @param  {Object} spread    THREE.Vector3 instance describing the random variance to apply to the start color.
    -     */
    -    randomColor: function( attribute, index, base, spread ) {
    -        'use strict';
    -
    -        var r = base.r + ( Math.random() * spread.x ),
    -            g = base.g + ( Math.random() * spread.y ),
    -            b = base.b + ( Math.random() * spread.z );
    -
    -        r = this.clamp( r, 0, 1 );
    -        g = this.clamp( g, 0, 1 );
    -        b = this.clamp( b, 0, 1 );
    -
    -
    -        attribute.typedArray.setVec3Components( index, r, g, b );
    -    },
    -
    -
    -    randomColorAsHex: ( function() {
    -        'use strict';
    -
    -        var workingColor = new THREE.Color();
    -
    -        /**
    -         * Assigns a random color value, encoded as a hex value in decimal
    -         * format, to a SPE.ShaderAttribute instance.
    -         * @param  {Object} attribute The instance of SPE.ShaderAttribute to save the result to.
    -         * @param  {Number} index     The offset in the attribute's TypedArray to save the result from.
    -         * @param  {Object} base      THREE.Color instance describing the start color.
    -         * @param  {Object} spread    THREE.Vector3 instance describing the random variance to apply to the start color.
    -         */
    -        return function( attribute, index, base, spread ) {
    -            var numItems = base.length,
    -                colors = [];
    -
    -            for ( var i = 0; i < numItems; ++i ) {
    -                var spreadVector = spread[ i ];
    -
    -                workingColor.copy( base[ i ] );
    -
    -                workingColor.r += ( Math.random() * spreadVector.x ) - ( spreadVector.x * 0.5 );
    -                workingColor.g += ( Math.random() * spreadVector.y ) - ( spreadVector.y * 0.5 );
    -                workingColor.b += ( Math.random() * spreadVector.z ) - ( spreadVector.z * 0.5 );
    -
    -                workingColor.r = this.clamp( workingColor.r, 0, 1 );
    -                workingColor.g = this.clamp( workingColor.g, 0, 1 );
    -                workingColor.b = this.clamp( workingColor.b, 0, 1 );
    -
    -                colors.push( workingColor.getHex() );
    -            }
    -
    -            attribute.typedArray.setVec4Components( index, colors[ 0 ], colors[ 1 ], colors[ 2 ], colors[ 3 ] );
    -        };
    -    }() ),
    -
    -    /**
    -     * Assigns a random vector 3 value to an SPE.ShaderAttribute instance, projecting the
    -     * given values onto a sphere.
    -     *
    -     * @param  {Object} attribute The instance of SPE.ShaderAttribute to save the result to.
    -     * @param  {Number} index     The offset in the attribute's TypedArray to save the result from.
    -     * @param  {Object} base              THREE.Vector3 instance describing the origin of the transform.
    -     * @param  {Number} radius            The radius of the sphere to project onto.
    -     * @param  {Number} radiusSpread      The amount of randomness to apply to the projection result
    -     * @param  {Object} radiusScale       THREE.Vector3 instance describing the scale of each axis of the sphere.
    -     * @param  {Number} radiusSpreadClamp What numeric multiple the projected value should be clamped to.
    -     */
    -    randomVector3OnSphere: function(
    -        attribute, index, base, radius, radiusSpread, radiusScale, radiusSpreadClamp, distributionClamp
    -    ) {
    -        'use strict';
    -
    -        var depth = 2 * Math.random() - 1,
    -            t = 6.2832 * Math.random(),
    -            r = Math.sqrt( 1 - depth * depth ),
    -            rand = this.randomFloat( radius, radiusSpread ),
    -            x = 0,
    -            y = 0,
    -            z = 0;
    -
    -
    -        if ( radiusSpreadClamp ) {
    -            rand = Math.round( rand / radiusSpreadClamp ) * radiusSpreadClamp;
    -        }
    - -
  • - - -
  • -
    - -
    - -
    -

    Set position on sphere

    - -
    - -
            x = r * Math.cos( t ) * rand;
    -        y = r * Math.sin( t ) * rand;
    -        z = depth * rand;
    - -
  • - - -
  • -
    - -
    - -
    -

    Apply radius scale to this position

    - -
    - -
            x *= radiusScale.x;
    -        y *= radiusScale.y;
    -        z *= radiusScale.z;
    - -
  • - - -
  • -
    - -
    - -
    -

    Translate to the base position.

    - -
    - -
            x += base.x;
    -        y += base.y;
    -        z += base.z;
    - -
  • - - -
  • -
    - -
    - -
    -

    Set the values in the typed array.

    - -
    - -
            attribute.typedArray.setVec3Components( index, x, y, z );
    -    },
    -
    -    seededRandom: function( seed ) {
    -        var x = Math.sin( seed ) * 10000;
    -        return x - ( x | 0 );
    -    },
    -
    -
    -
    -    /**
    -     * Assigns a random vector 3 value to an SPE.ShaderAttribute instance, projecting the
    -     * given values onto a 2d-disc.
    -     *
    -     * @param  {Object} attribute The instance of SPE.ShaderAttribute to save the result to.
    -     * @param  {Number} index     The offset in the attribute's TypedArray to save the result from.
    -     * @param  {Object} base              THREE.Vector3 instance describing the origin of the transform.
    -     * @param  {Number} radius            The radius of the sphere to project onto.
    -     * @param  {Number} radiusSpread      The amount of randomness to apply to the projection result
    -     * @param  {Object} radiusScale       THREE.Vector3 instance describing the scale of each axis of the disc. The z-component is ignored.
    -     * @param  {Number} radiusSpreadClamp What numeric multiple the projected value should be clamped to.
    -     */
    -    randomVector3OnDisc: function( attribute, index, base, radius, radiusSpread, radiusScale, radiusSpreadClamp ) {
    -        'use strict';
    -
    -        var t = 6.2832 * Math.random(),
    -            rand = Math.abs( this.randomFloat( radius, radiusSpread ) ),
    -            x = 0,
    -            y = 0,
    -            z = 0;
    -
    -        if ( radiusSpreadClamp ) {
    -            rand = Math.round( rand / radiusSpreadClamp ) * radiusSpreadClamp;
    -        }
    - -
  • - - -
  • -
    - -
    - -
    -

    Set position on sphere

    - -
    - -
            x = Math.cos( t ) * rand;
    -        y = Math.sin( t ) * rand;
    - -
  • - - -
  • -
    - -
    - -
    -

    Apply radius scale to this position

    - -
    - -
            x *= radiusScale.x;
    -        y *= radiusScale.y;
    - -
  • - - -
  • -
    - -
    - -
    -

    Translate to the base position.

    - -
    - -
            x += base.x;
    -        y += base.y;
    -        z += base.z;
    - -
  • - - -
  • -
    - -
    - -
    -

    Set the values in the typed array.

    - -
    - -
            attribute.typedArray.setVec3Components( index, x, y, z );
    -    },
    -
    -    randomDirectionVector3OnSphere: ( function() {
    -        'use strict';
    -
    -        var v = new THREE.Vector3();
    -
    -        /**
    -         * Given an SPE.ShaderAttribute instance, create a direction vector from the given
    -         * position, using `speed` as the magnitude. Values are saved to the attribute.
    -         *
    -         * @param  {Object} attribute       The instance of SPE.ShaderAttribute to save the result to.
    -         * @param  {Number} index           The offset in the attribute's TypedArray to save the result from.
    -         * @param  {Number} posX            The particle's x coordinate.
    -         * @param  {Number} posY            The particle's y coordinate.
    -         * @param  {Number} posZ            The particle's z coordinate.
    -         * @param  {Object} emitterPosition THREE.Vector3 instance describing the emitter's base position.
    -         * @param  {Number} speed           The magnitude to apply to the vector.
    -         * @param  {Number} speedSpread     The amount of randomness to apply to the magnitude.
    -         */
    -        return function( attribute, index, posX, posY, posZ, emitterPosition, speed, speedSpread ) {
    -            v.copy( emitterPosition );
    -
    -            v.x -= posX;
    -            v.y -= posY;
    -            v.z -= posZ;
    -
    -            v.normalize().multiplyScalar( -this.randomFloat( speed, speedSpread ) );
    -
    -            attribute.typedArray.setVec3Components( index, v.x, v.y, v.z );
    -        };
    -    }() ),
    -
    -
    -    randomDirectionVector3OnDisc: ( function() {
    -        'use strict';
    -
    -        var v = new THREE.Vector3();
    -
    -        /**
    -         * Given an SPE.ShaderAttribute instance, create a direction vector from the given
    -         * position, using `speed` as the magnitude. Values are saved to the attribute.
    -         *
    -         * @param  {Object} attribute       The instance of SPE.ShaderAttribute to save the result to.
    -         * @param  {Number} index           The offset in the attribute's TypedArray to save the result from.
    -         * @param  {Number} posX            The particle's x coordinate.
    -         * @param  {Number} posY            The particle's y coordinate.
    -         * @param  {Number} posZ            The particle's z coordinate.
    -         * @param  {Object} emitterPosition THREE.Vector3 instance describing the emitter's base position.
    -         * @param  {Number} speed           The magnitude to apply to the vector.
    -         * @param  {Number} speedSpread     The amount of randomness to apply to the magnitude.
    -         */
    -        return function( attribute, index, posX, posY, posZ, emitterPosition, speed, speedSpread ) {
    -            v.copy( emitterPosition );
    -
    -            v.x -= posX;
    -            v.y -= posY;
    -            v.z -= posZ;
    -
    -            v.normalize().multiplyScalar( -this.randomFloat( speed, speedSpread ) );
    -
    -            attribute.typedArray.setVec3Components( index, v.x, v.y, 0 );
    -        };
    -    }() ),
    -
    -    getPackedRotationAxis: ( function() {
    -        'use strict';
    -
    -        var v = new THREE.Vector3(),
    -            vSpread = new THREE.Vector3(),
    -            c = new THREE.Color(),
    -            addOne = new THREE.Vector3( 1, 1, 1 );
    -
    -        /**
    -         * Given a rotation axis, and a rotation axis spread vector,
    -         * calculate a randomised rotation axis, and pack it into
    -         * a hexadecimal value represented in decimal form.
    -         * @param  {Object} axis       THREE.Vector3 instance describing the rotation axis.
    -         * @param  {Object} axisSpread THREE.Vector3 instance describing the amount of randomness to apply to the rotation axis.
    -         * @return {Number}            The packed rotation axis, with randomness.
    -         */
    -        return function( axis, axisSpread ) {
    -            v.copy( axis ).normalize();
    -            vSpread.copy( axisSpread ).normalize();
    -
    -            v.x += ( -axisSpread.x * 0.5 ) + ( Math.random() * axisSpread.x );
    -            v.y += ( -axisSpread.y * 0.5 ) + ( Math.random() * axisSpread.y );
    -            v.z += ( -axisSpread.z * 0.5 ) + ( Math.random() * axisSpread.z );
    - -
  • - - -
  • -
    - -
    - -
    -

    v.x = Math.abs( v.x ); -v.y = Math.abs( v.y ); -v.z = Math.abs( v.z );

    - -
    - -
    -            v.normalize().add( addOne ).multiplyScalar( 0.5 );
    -
    -            c.setRGB( v.x, v.y, v.z );
    -
    -            return c.getHex();
    -        };
    -    }() )
    -};
    -
    -/**
    - * An SPE.Group instance.
    - * @typedef {Object} Group
    - * @see SPE.Group
    - */
    -
    -/**
    - * A map of options to configure an SPE.Group instance.
    - * @typedef {Object} GroupOptions
    - *
    - * @property {Object} texture An object describing the texture used by the group.
    - *
    - * @property {Object} texture.value An instance of THREE.Texture.
    - *
    - * @property {Object=} texture.frames A THREE.Vector2 instance describing the number
    - *                                    of frames on the x- and y-axis of the given texture.
    - *                                    If not provided, the texture will NOT be treated as
    - *                                    a sprite-sheet and as such will NOT be animated.
    - *
    - * @property {Number} [texture.frameCount=texture.frames.x * texture.frames.y] The total number of frames in the sprite-sheet.
    - *                                                                   Allows for sprite-sheets that don't fill the entire
    - *                                                                   texture.
    - *
    - * @property {Number} texture.loop The number of loops through the sprite-sheet that should
    - *                                 be performed over the course of a single particle's lifetime.
    - *
    - * @property {Number} fixedTimeStep If no `dt` (or `deltaTime`) value is passed to this group's
    - *                                  `tick()` function, this number will be used to move the particle
    - *                                  simulation forward. Value in SECONDS.
    - *
    - * @property {Boolean} hasPerspective Whether the distance a particle is from the camera should affect
    - *                                    the particle's size.
    - *
    - * @property {Boolean} colorize Whether the particles in this group should be rendered with color, or
    - *                              whether the only color of particles will come from the provided texture.
    - *
    - * @property {Number} blending One of Three.js's blending modes to apply to this group's `ShaderMaterial`.
    - *
    - * @property {Boolean} transparent Whether these particle's should be rendered with transparency.
    - *
    - * @property {Number} alphaTest Sets the alpha value to be used when running an alpha test on the `texture.value` property. Value between 0 and 1.
    - *
    - * @property {Boolean} depthWrite Whether rendering the group has any effect on the depth buffer.
    - *
    - * @property {Boolean} depthTest Whether to have depth test enabled when rendering this group.
    - *
    - * @property {Boolean} fog Whether this group's particles should be affected by their scene's fog.
    - *
    - * @property {Number} scale The scale factor to apply to this group's particle sizes. Useful for
    - *                          setting particle sizes to be relative to renderer size.
    - */
    -
    -
    -/**
    - * The SPE.Group class. Creates a new group, containing a material, geometry, and mesh.
    - *
    - * @constructor
    - * @param {GroupOptions} options A map of options to configure the group instance.
    - */
    -SPE.Group = function( options ) {
    -    'use strict';
    -
    -    var utils = SPE.utils,
    -        types = utils.types;
    - -
  • - - -
  • -
    - -
    - -
    -

    Ensure we have a map of options to play with

    - -
    - -
        options = utils.ensureTypedArg( options, types.OBJECT, {} );
    -    options.texture = utils.ensureTypedArg( options.texture, types.OBJECT, {} );
    - -
  • - - -
  • -
    - -
    - -
    -

    Assign a UUID to this instance

    - -
    - -
        this.uuid = THREE.Math.generateUUID();
    - -
  • - - -
  • -
    - -
    - -
    -

    If no deltaTime value is passed to the SPE.Group.tick function, -the value of this property will be used to advance the simulation.

    - -
    - -
        this.fixedTimeStep = utils.ensureTypedArg( options.fixedTimeStep, types.NUMBER, 0.016 );
    - -
  • - - -
  • -
    - -
    - -
    -

    Set properties used in the uniforms map, starting with the -texture stuff.

    - -
    - -
        this.texture = utils.ensureInstanceOf( options.texture.value, THREE.Texture, null );
    -    this.textureFrames = utils.ensureInstanceOf( options.texture.frames, THREE.Vector2, new THREE.Vector2( 1, 1 ) );
    -    this.textureFrameCount = utils.ensureTypedArg( options.texture.frameCount, types.NUMBER, this.textureFrames.x * this.textureFrames.y );
    -    this.textureLoop = utils.ensureTypedArg( options.texture.loop, types.NUMBER, 1 );
    -    this.textureFrames.max( new THREE.Vector2( 1, 1 ) );
    -
    -    this.hasPerspective = utils.ensureTypedArg( options.hasPerspective, types.BOOLEAN, true );
    -    this.colorize = utils.ensureTypedArg( options.colorize, types.BOOLEAN, true );
    -
    -    this.maxParticleCount = utils.ensureTypedArg( options.maxParticleCount, types.NUMBER, null );
    - -
  • - - -
  • -
    - -
    - -
    -

    Set properties used to define the ShaderMaterial’s appearance.

    - -
    - -
        this.blending = utils.ensureTypedArg( options.blending, types.NUMBER, THREE.AdditiveBlending );
    -    this.transparent = utils.ensureTypedArg( options.transparent, types.BOOLEAN, true );
    -    this.alphaTest = parseFloat( utils.ensureTypedArg( options.alphaTest, types.NUMBER, 0.0 ) );
    -    this.depthWrite = utils.ensureTypedArg( options.depthWrite, types.BOOLEAN, false );
    -    this.depthTest = utils.ensureTypedArg( options.depthTest, types.BOOLEAN, true );
    -    this.fog = utils.ensureTypedArg( options.fog, types.BOOLEAN, true );
    -    this.scale = utils.ensureTypedArg( options.scale, types.NUMBER, 300 );
    - -
  • - - -
  • -
    - -
    - -
    -

    Where emitter’s go to curl up in a warm blanket and live -out their days.

    - -
    - -
        this.emitters = [];
    -    this.emitterIDs = [];
    - -
  • - - -
  • -
    - -
    - -
    -

    Create properties for use by the emitter pooling functions.

    - -
    - -
        this._pool = [];
    -    this._poolCreationSettings = null;
    -    this._createNewWhenPoolEmpty = 0;
    - -
  • - - -
  • -
    - -
    - -
    -

    Whether all attributes should be forced to updated -their entire buffer contents on the next tick.

    -

    Used when an emitter is removed.

    - -
    - -
        this._attributesNeedRefresh = false;
    -    this._attributesNeedDynamicReset = false;
    -
    -    this.particleCount = 0;
    - -
  • - - -
  • -
    - -
    - -
    -

    Map of uniforms to be applied to the ShaderMaterial instance.

    - -
    - -
        this.uniforms = {
    -        texture: {
    -            type: 't',
    -            value: this.texture
    -        },
    -        textureAnimation: {
    -            type: 'v4',
    -            value: new THREE.Vector4(
    -                this.textureFrames.x,
    -                this.textureFrames.y,
    -                this.textureFrameCount,
    -                Math.max( Math.abs( this.textureLoop ), 1.0 )
    -            )
    -        },
    -        fogColor: {
    -            type: 'c',
    -            value: null
    -        },
    -        fogNear: {
    -            type: 'f',
    -            value: 10
    -        },
    -        fogFar: {
    -            type: 'f',
    -            value: 200
    -        },
    -        fogDensity: {
    -            type: 'f',
    -            value: 0.5
    -        },
    -        deltaTime: {
    -            type: 'f',
    -            value: 0
    -        },
    -        runTime: {
    -            type: 'f',
    -            value: 0
    -        },
    -        scale: {
    -            type: 'f',
    -            value: this.scale
    -        }
    -    };
    - -
  • - - -
  • -
    - -
    - -
    -

    Add some defines into the mix…

    - -
    - -
        this.defines = {
    -        HAS_PERSPECTIVE: this.hasPerspective,
    -        COLORIZE: this.colorize,
    -        VALUE_OVER_LIFETIME_LENGTH: SPE.valueOverLifetimeLength,
    -
    -        SHOULD_ROTATE_TEXTURE: false,
    -        SHOULD_ROTATE_PARTICLES: false,
    -        SHOULD_WIGGLE_PARTICLES: false,
    -
    -        SHOULD_CALCULATE_SPRITE: this.textureFrames.x > 1 || this.textureFrames.y > 1
    -    };
    - -
  • - - -
  • -
    - -
    - -
    -

    Map of all attributes to be applied to the particles.

    -

    See SPE.ShaderAttribute for a bit more info on this bit.

    - -
    - -
        this.attributes = {
    -        position: new SPE.ShaderAttribute( 'v3', true ),
    -        acceleration: new SPE.ShaderAttribute( 'v4', true ), // w component is drag
    -        velocity: new SPE.ShaderAttribute( 'v3', true ),
    -        rotation: new SPE.ShaderAttribute( 'v4', true ),
    -        rotationCenter: new SPE.ShaderAttribute( 'v3', true ),
    -        params: new SPE.ShaderAttribute( 'v4', true ), // Holds (alive, age, delay, wiggle)
    -        size: new SPE.ShaderAttribute( 'v4', true ),
    -        angle: new SPE.ShaderAttribute( 'v4', true ),
    -        color: new SPE.ShaderAttribute( 'v4', true ),
    -        opacity: new SPE.ShaderAttribute( 'v4', true )
    -    };
    -
    -    this.attributeKeys = Object.keys( this.attributes );
    -    this.attributeCount = this.attributeKeys.length;
    - -
  • - - -
  • -
    - -
    - -
    -

    Create the ShaderMaterial instance that’ll help render the -particles.

    - -
    - -
        this.material = new THREE.ShaderMaterial( {
    -        uniforms: this.uniforms,
    -        vertexShader: SPE.shaders.vertex,
    -        fragmentShader: SPE.shaders.fragment,
    -        blending: this.blending,
    -        transparent: this.transparent,
    -        alphaTest: this.alphaTest,
    -        depthWrite: this.depthWrite,
    -        depthTest: this.depthTest,
    -        defines: this.defines,
    -        fog: this.fog
    -    } );
    - -
  • - - -
  • -
    - -
    - -
    -

    Create the BufferGeometry and Points instances, ensuring -the geometry and material are given to the latter.

    - -
    - -
        this.geometry = new THREE.BufferGeometry();
    -    this.mesh = new THREE.Points( this.geometry, this.material );
    -
    -    if ( this.maxParticleCount === null ) {
    -        console.warn( 'SPE.Group: No maxParticleCount specified. Adding emitters after rendering will probably cause errors.' );
    -    }
    -};
    -
    -SPE.Group.constructor = SPE.Group;
    -
    -
    -SPE.Group.prototype._updateDefines = function() {
    -    'use strict';
    -
    -    var emitters = this.emitters,
    -        i = emitters.length - 1,
    -        emitter,
    -        defines = this.defines;
    -
    -    for ( i; i >= 0; --i ) {
    -        emitter = emitters[ i ];
    - -
  • - - -
  • -
    - -
    - -
    -

    Only do angle calculation if there’s no spritesheet defined.

    -

    Saves calculations being done and then overwritten in the shaders.

    - -
    - -
            if ( !defines.SHOULD_CALCULATE_SPRITE ) {
    -            defines.SHOULD_ROTATE_TEXTURE = defines.SHOULD_ROTATE_TEXTURE || !!Math.max(
    -                Math.max.apply( null, emitter.angle.value ),
    -                Math.max.apply( null, emitter.angle.spread )
    -            );
    -        }
    -
    -        defines.SHOULD_ROTATE_PARTICLES = defines.SHOULD_ROTATE_PARTICLES || !!Math.max(
    -            emitter.rotation.angle,
    -            emitter.rotation.angleSpread
    -        );
    -
    -        defines.SHOULD_WIGGLE_PARTICLES = defines.SHOULD_WIGGLE_PARTICLES || !!Math.max(
    -            emitter.wiggle.value,
    -            emitter.wiggle.spread
    -        );
    -    }
    -
    -    this.material.needsUpdate = true;
    -};
    -
    -SPE.Group.prototype._applyAttributesToGeometry = function() {
    -    'use strict';
    -
    -    var attributes = this.attributes,
    -        geometry = this.geometry,
    -        geometryAttributes = geometry.attributes,
    -        attribute,
    -        geometryAttribute;
    - -
  • - - -
  • -
    - -
    - -
    -

    Loop through all the shader attributes and assign (or re-assign) -typed array buffers to each one.

    - -
    - -
        for ( var attr in attributes ) {
    -        if ( attributes.hasOwnProperty( attr ) ) {
    -            attribute = attributes[ attr ];
    -            geometryAttribute = geometryAttributes[ attr ];
    - -
  • - - -
  • -
    - -
    - -
    -

    Update the array if this attribute exists on the geometry.

    -

    This needs to be done because the attribute’s typed array might have -been resized and reinstantiated, and might now be looking at a -different ArrayBuffer, so reference needs updating.

    - -
    - -
                if ( geometryAttribute ) {
    -                geometryAttribute.array = attribute.typedArray.array;
    -            }
    - -
  • - - -
  • -
    - -
    - -
    -

    // Add the attribute to the geometry if it doesn’t already exist.

    - -
    - -
                else {
    -                geometry.addAttribute( attr, attribute.bufferAttribute );
    -            }
    - -
  • - - -
  • -
    - -
    - -
    -

    Mark the attribute as needing an update the next time a frame is rendered.

    - -
    - -
                attribute.bufferAttribute.needsUpdate = true;
    -        }
    -    }
    - -
  • - - -
  • -
    - -
    - -
    -

    Mark the draw range on the geometry. This will ensure -only the values in the attribute buffers that are -associated with a particle will be used in THREE’s -render cycle.

    - -
    - -
        this.geometry.setDrawRange( 0, this.particleCount );
    -};
    -
    -/**
    - * Adds an SPE.Emitter instance to this group, creating particle values and
    - * assigning them to this group's shader attributes.
    - *
    - * @param {Emitter} emitter The emitter to add to this group.
    - */
    -SPE.Group.prototype.addEmitter = function( emitter ) {
    -    'use strict';
    - -
  • - - -
  • -
    - -
    - -
    -

    Ensure an actual emitter instance is passed here.

    -

    Decided not to throw here, just in case a scene’s -rendering would be paused. Logging an error instead -of stopping execution if exceptions aren’t caught.

    - -
    - -
        if ( emitter instanceof SPE.Emitter === false ) {
    -        console.error( '`emitter` argument must be instance of SPE.Emitter. Was provided with:', emitter );
    -        return;
    -    }
    - -
  • - - -
  • -
    - -
    - -
    -

    If the emitter already exists as a member of this group, then -stop here, we don’t want to add it again.

    - -
    - -
        else if ( this.emitterIDs.indexOf( emitter.uuid ) > -1 ) {
    -        console.error( 'Emitter already exists in this group. Will not add again.' );
    -        return;
    -    }
    - -
  • - - -
  • -
    - -
    - -
    -

    And finally, if the emitter is a member of another group, -don’t add it to this group.

    - -
    - -
        else if ( emitter.group !== null ) {
    -        console.error( 'Emitter already belongs to another group. Will not add to requested group.' );
    -        return;
    -    }
    -
    -    var attributes = this.attributes,
    -        start = this.particleCount,
    -        end = start + emitter.particleCount;
    - -
  • - - -
  • -
    - -
    - -
    -

    Update this group’s particle count.

    - -
    - -
        this.particleCount = end;
    - -
  • - - -
  • -
    - -
    - -
    -

    Emit a warning if the emitter being added will exceed the buffer sizes specified.

    - -
    - -
        if ( this.maxParticleCount !== null && this.particleCount > this.maxParticleCount ) {
    -        console.warn( 'SPE.Group: maxParticleCount exceeded. Requesting', this.particleCount, 'particles, can support only', this.maxParticleCount );
    -    }
    - -
  • - - -
  • -
    - -
    - -
    -

    Set the particlesPerSecond value (PPS) on the emitter. -It’s used to determine how many particles to release -on a per-frame basis.

    - -
    - -
        emitter._calculatePPSValue( emitter.maxAge._value + emitter.maxAge._spread );
    -    emitter._setBufferUpdateRanges( this.attributeKeys );
    - -
  • - - -
  • -
    - -
    - -
    -

    Store the offset value in the TypedArray attributes for this emitter.

    - -
    - -
        emitter._setAttributeOffset( start );
    - -
  • - - -
  • -
    - -
    - -
    -

    Save a reference to this group on the emitter so it knows -where it belongs.

    - -
    - -
        emitter.group = this;
    - -
  • - - -
  • -
    - -
    - -
    -

    Store reference to the attributes on the emitter for -easier access during the emitter’s tick function.

    - -
    - -
        emitter.attributes = this.attributes;
    - -
  • - - -
  • -
    - -
    - -
    -

    Ensure the attributes and their BufferAttributes exist, and their -TypedArrays are of the correct size.

    - -
    - -
        for ( var attr in attributes ) {
    -        if ( attributes.hasOwnProperty( attr ) ) {
    - -
  • - - -
  • -
    - -
    - -
    -

    When creating a buffer, pass through the maxParticle count -if one is specified.

    - -
    - -
                attributes[ attr ]._createBufferAttribute(
    -                this.maxParticleCount !== null ?
    -                this.maxParticleCount :
    -                this.particleCount
    -            );
    -        }
    -    }
    - -
  • - - -
  • -
    - -
    - -
    -

    Loop through each particle this emitter wants to have, and create the attributes values, -storing them in the TypedArrays that each attribute holds.

    - -
    - -
        for ( var i = start; i < end; ++i ) {
    -        emitter._assignPositionValue( i );
    -        emitter._assignForceValue( i, 'velocity' );
    -        emitter._assignForceValue( i, 'acceleration' );
    -        emitter._assignAbsLifetimeValue( i, 'opacity' );
    -        emitter._assignAbsLifetimeValue( i, 'size' );
    -        emitter._assignAngleValue( i );
    -        emitter._assignRotationValue( i );
    -        emitter._assignParamsValue( i );
    -        emitter._assignColorValue( i );
    -    }
    - -
  • - - -
  • -
    - -
    - -
    -

    Update the geometry and make sure the attributes are referencing -the typed arrays properly.

    - -
    - -
        this._applyAttributesToGeometry();
    - -
  • - - -
  • -
    - -
    - -
    -

    Store this emitter in this group’s emitter’s store.

    - -
    - -
        this.emitters.push( emitter );
    -    this.emitterIDs.push( emitter.uuid );
    - -
  • - - -
  • -
    - -
    - -
    -

    Update certain flags to enable shader calculations only if they’re necessary.

    - -
    - -
        this._updateDefines( emitter );
    - -
  • - - -
  • -
    - -
    - -
    -

    Update the material since defines might have changed

    - -
    - -
        this.material.needsUpdate = true;
    -    this.geometry.needsUpdate = true;
    -    this._attributesNeedRefresh = true;
    - -
  • - - -
  • -
    - -
    - -
    -

    Return the group to enable chaining.

    - -
    - -
        return this;
    -};
    -
    -/**
    - * Removes an SPE.Emitter instance from this group. When called,
    - * all particle's belonging to the given emitter will be instantly
    - * removed from the scene.
    - *
    - * @param {Emitter} emitter The emitter to add to this group.
    - */
    -SPE.Group.prototype.removeEmitter = function( emitter ) {
    -    'use strict';
    -
    -    var emitterIndex = this.emitterIDs.indexOf( emitter.uuid );
    - -
  • - - -
  • -
    - -
    - -
    -

    Ensure an actual emitter instance is passed here.

    -

    Decided not to throw here, just in case a scene’s -rendering would be paused. Logging an error instead -of stopping execution if exceptions aren’t caught.

    - -
    - -
        if ( emitter instanceof SPE.Emitter === false ) {
    -        console.error( '`emitter` argument must be instance of SPE.Emitter. Was provided with:', emitter );
    -        return;
    -    }
    - -
  • - - -
  • -
    - -
    - -
    -

    Issue an error if the emitter isn’t a member of this group.

    - -
    - -
        else if ( emitterIndex === -1 ) {
    -        console.error( 'Emitter does not exist in this group. Will not remove.' );
    -        return;
    -    }
    - -
  • - - -
  • -
    - -
    - -
    -

    Kill all particles by marking them as dead -and their age as 0.

    - -
    - -
        var start = emitter.attributeOffset,
    -        end = start + emitter.particleCount,
    -        params = this.attributes.params.typedArray;
    - -
  • - - -
  • -
    - -
    - -
    -

    Set alive and age to zero.

    - -
    - -
        for ( var i = start; i < end; ++i ) {
    -        params.array[ i * 4 ] = 0.0;
    -        params.array[ i * 4 + 1 ] = 0.0;
    -    }
    - -
  • - - -
  • -
    - -
    - -
    -

    Remove the emitter from this group’s “store”.

    - -
    - -
        this.emitters.splice( emitterIndex, 1 );
    -    this.emitterIDs.splice( emitterIndex, 1 );
    - -
  • - - -
  • -
    - -
    - -
    -

    Remove this emitter’s attribute values from all shader attributes. -The .splice() call here also marks each attribute’s buffer -as needing to update it’s entire contents.

    - -
    - -
        for ( var attr in this.attributes ) {
    -        if ( this.attributes.hasOwnProperty( attr ) ) {
    -            this.attributes[ attr ].splice( start, end );
    -        }
    -    }
    - -
  • - - -
  • -
    - -
    - -
    -

    Ensure this group’s particle count is correct.

    - -
    - -
        this.particleCount -= emitter.particleCount;
    - -
  • - - -
  • -
    - -
    - -
    -

    Call the emitter’s remove method.

    - -
    - -
        emitter._onRemove();
    - -
  • - - -
  • -
    - -
    - -
    -

    Set a flag to indicate that the attribute buffers should -be updated in their entirety on the next frame.

    - -
    - -
        this._attributesNeedRefresh = true;
    -};
    -
    -
    -/**
    - * Fetch a single emitter instance from the pool.
    - * If there are no objects in the pool, a new emitter will be
    - * created if specified.
    - *
    - * @return {Emitter|null}
    - */
    -SPE.Group.prototype.getFromPool = function() {
    -    'use strict';
    -
    -    var pool = this._pool,
    -        createNew = this._createNewWhenPoolEmpty;
    -
    -    if ( pool.length ) {
    -        return pool.pop();
    -    }
    -    else if ( createNew ) {
    -        return new SPE.Emitter( this._poolCreationSettings );
    -    }
    -
    -    return null;
    -};
    -
    -
    -/**
    - * Release an emitter into the pool.
    - *
    - * @param  {ShaderParticleEmitter} emitter
    - * @return {Group} This group instance.
    - */
    -SPE.Group.prototype.releaseIntoPool = function( emitter ) {
    -    'use strict';
    -
    -    if ( emitter instanceof SPE.Emitter === false ) {
    -        console.error( 'Argument is not instanceof SPE.Emitter:', emitter );
    -        return;
    -    }
    -
    -    emitter.reset();
    -    this._pool.unshift( emitter );
    -
    -    return this;
    -};
    -
    -
    -/**
    - * Get the pool array
    - *
    - * @return {Array}
    - */
    -SPE.Group.prototype.getPool = function() {
    -    'use strict';
    -    return this._pool;
    -};
    -
    -
    -/**
    - * Add a pool of emitters to this particle group
    - *
    - * @param {Number} numEmitters      The number of emitters to add to the pool.
    - * @param {EmitterOptions|Array} emitterOptions  An object, or array of objects, describing the options to pass to each emitter.
    - * @param {Boolean} createNew       Should a new emitter be created if the pool runs out?
    - * @return {Group} This group instance.
    - */
    -SPE.Group.prototype.addPool = function( numEmitters, emitterOptions, createNew ) {
    -    'use strict';
    -
    -    var emitter;
    - -
  • - - -
  • -
    - -
    - -
    -

    Save relevant settings and flags.

    - -
    - -
        this._poolCreationSettings = emitterOptions;
    -    this._createNewWhenPoolEmpty = !!createNew;
    - -
  • - - -
  • -
    - -
    - -
    -

    Create the emitters, add them to this group and the pool.

    - -
    - -
        for ( var i = 0; i < numEmitters; ++i ) {
    -        if ( Array.isArray( emitterOptions ) ) {
    -            emitter = new SPE.Emitter( emitterOptions[ i ] );
    -        }
    -        else {
    -            emitter = new SPE.Emitter( emitterOptions );
    -        }
    -        this.addEmitter( emitter );
    -        this.releaseIntoPool( emitter );
    -    }
    -
    -    return this;
    -};
    -
    -
    -
    -SPE.Group.prototype._triggerSingleEmitter = function( pos ) {
    -    'use strict';
    -
    -    var emitter = this.getFromPool(),
    -        self = this;
    -
    -    if ( emitter === null ) {
    -        console.log( 'SPE.Group pool ran out.' );
    -        return;
    -    }
    - -
  • - - -
  • -
    - -
    - -
    -

    TODO:

    -
      -
    • Make sure buffers are update with thus new position.
    • -
    - -
    - -
        if ( pos instanceof THREE.Vector3 ) {
    -        emitter.position.value.copy( pos );
    - -
  • - - -
  • -
    - -
    - -
    -

    Trigger the setter for this property to force an -update to the emitter’s position attribute.

    - -
    - -
            emitter.position.value = emitter.position.value;
    -    }
    -
    -    emitter.enable();
    -
    -    setTimeout( function() {
    -        emitter.disable();
    -        self.releaseIntoPool( emitter );
    -    }, ( emitter.maxAge.value + emitter.maxAge.spread ) * 1000 );
    -
    -    return this;
    -};
    -
    -
    -/**
    - * Set a given number of emitters as alive, with an optional position
    - * vector3 to move them to.
    - *
    - * @param  {Number} numEmitters The number of emitters to activate
    - * @param  {Object} [position=undefined] A THREE.Vector3 instance describing the position to activate the emitter(s) at.
    - * @return {Group} This group instance.
    - */
    -SPE.Group.prototype.triggerPoolEmitter = function( numEmitters, position ) {
    -    'use strict';
    -
    -    if ( typeof numEmitters === 'number' && numEmitters > 1 ) {
    -        for ( var i = 0; i < numEmitters; ++i ) {
    -            this._triggerSingleEmitter( position );
    -        }
    -    }
    -    else {
    -        this._triggerSingleEmitter( position );
    -    }
    -
    -    return this;
    -};
    -
    -
    -
    -SPE.Group.prototype._updateUniforms = function( dt ) {
    -    'use strict';
    -
    -    this.uniforms.runTime.value += dt;
    -    this.uniforms.deltaTime.value = dt;
    -};
    -
    -SPE.Group.prototype._resetBufferRanges = function() {
    -    'use strict';
    -
    -    var keys = this.attributeKeys,
    -        i = this.attributeCount - 1,
    -        attrs = this.attributes;
    -
    -    for ( i; i >= 0; --i ) {
    -        attrs[ keys[ i ] ].resetUpdateRange();
    -    }
    -};
    -
    -
    -SPE.Group.prototype._updateBuffers = function( emitter ) {
    -    'use strict';
    -
    -    var keys = this.attributeKeys,
    -        i = this.attributeCount - 1,
    -        attrs = this.attributes,
    -        emitterRanges = emitter.bufferUpdateRanges,
    -        key,
    -        emitterAttr,
    -        attr;
    -
    -    for ( i; i >= 0; --i ) {
    -        key = keys[ i ];
    -        emitterAttr = emitterRanges[ key ];
    -        attr = attrs[ key ];
    -        attr.setUpdateRange( emitterAttr.min, emitterAttr.max );
    -        attr.flagUpdate();
    -    }
    -};
    -
    -
    -/**
    - * Simulate all the emitter's belonging to this group, updating
    - * attribute values along the way.
    - * @param  {Number} [dt=Group's `fixedTimeStep` value] The number of seconds to simulate the group's emitters for (deltaTime)
    - */
    -SPE.Group.prototype.tick = function( dt ) {
    -    'use strict';
    -
    -    var emitters = this.emitters,
    -        numEmitters = emitters.length,
    -        deltaTime = dt || this.fixedTimeStep,
    -        keys = this.attributeKeys,
    -        i,
    -        attrs = this.attributes;
    - -
  • - - -
  • -
    - -
    - -
    -

    Update uniform values.

    - -
    - -
        this._updateUniforms( deltaTime );
    - -
  • - - -
  • -
    - -
    - -
    -

    Reset buffer update ranges on the shader attributes.

    - -
    - -
        this._resetBufferRanges();
    - -
  • - - -
  • -
    - -
    - -
    -

    If nothing needs updating, then stop here.

    - -
    - -
        if (
    -        numEmitters === 0 &&
    -        this._attributesNeedRefresh === false &&
    -        this._attributesNeedDynamicReset === false
    -    ) {
    -        return;
    -    }
    - -
  • - - -
  • -
    - -
    - -
    -

    Loop through each emitter in this group and -simulate it, then update the shader attribute -buffers.

    - -
    - -
        for ( var i = 0, emitter; i < numEmitters; ++i ) {
    -        emitter = emitters[ i ];
    -        emitter.tick( deltaTime );
    -        this._updateBuffers( emitter );
    -    }
    - -
  • - - -
  • -
    - -
    - -
    -

    If the shader attributes have been refreshed, -then the dynamic properties of each buffer -attribute will need to be reset back to -what they should be.

    - -
    - -
        if ( this._attributesNeedDynamicReset === true ) {
    -        i = this.attributeCount - 1;
    -
    -        for ( i; i >= 0; --i ) {
    -            attrs[ keys[ i ] ].resetDynamic();
    -        }
    -
    -        this._attributesNeedDynamicReset = false;
    -    }
    - -
  • - - -
  • -
    - -
    - -
    -

    If this group’s shader attributes need a full refresh -then mark each attribute’s buffer attribute as -needing so.

    - -
    - -
        if ( this._attributesNeedRefresh === true ) {
    -        i = this.attributeCount - 1;
    -
    -        for ( i; i >= 0; --i ) {
    -            attrs[ keys[ i ] ].forceUpdateAll();
    -        }
    -
    -        this._attributesNeedRefresh = false;
    -        this._attributesNeedDynamicReset = true;
    -    }
    -};
    -
    -
    -/**
    - * Dipose the geometry and material for the group.
    - *
    - * @return {Group} Group instance.
    - */
    -SPE.Group.prototype.dispose = function() {
    -    'use strict';
    -    this.geometry.dispose();
    -    this.material.dispose();
    -    return this;
    -};
    -
    -/**
    - * An SPE.Emitter instance.
    - * @typedef {Object} Emitter
    - * @see SPE.Emitter
    - */
    -
    -/**
    - * A map of options to configure an SPE.Emitter instance.
    - *
    - * @typedef {Object} EmitterOptions
    - *
    - * @property {distribution} [type=BOX] The default distribution this emitter should use to control
    - *                         its particle's spawn position and force behaviour.
    - *                         Must be an SPE.distributions.* value.
    - *
    - *
    - * @property {Number} [particleCount=100] The total number of particles this emitter will hold. NOTE: this is not the number
    - *                                  of particles emitted in a second, or anything like that. The number of particles
    - *                                  emitted per-second is calculated by particleCount / maxAge (approximately!)
    - *
    - * @property {Number|null} [duration=null] The duration in seconds that this emitter should live for. If not specified, the emitter
    - *                                         will emit particles indefinitely.
    - *                                         NOTE: When an emitter is older than a specified duration, the emitter is NOT removed from
    - *                                         it's group, but rather is just marked as dead, allowing it to be reanimated at a later time
    - *                                         using `SPE.Emitter.prototype.enable()`.
    - *
    - * @property {Boolean} [isStatic=false] Whether this emitter should be not be simulated (true).
    - * @property {Boolean} [activeMultiplier=1] A value between 0 and 1 describing what percentage of this emitter's particlesPerSecond should be
    - *                                          emitted, where 0 is 0%, and 1 is 100%.
    - *                                          For example, having an emitter with 100 particles, a maxAge of 2, yields a particlesPerSecond
    - *                                          value of 50. Setting `activeMultiplier` to 0.5, then, will only emit 25 particles per second (0.5 = 50%).
    - *                                          Values greater than 1 will emulate a burst of particles, causing the emitter to run out of particles
    - *                                          before it's next activation cycle.
    - *
    - * @property {Boolean} [direction=1] The direction of the emitter. If value is `1`, emitter will start at beginning of particle's lifecycle.
    - *                                   If value is `-1`, emitter will start at end of particle's lifecycle and work it's way backwards.
    - *
    - * @property {Object} [maxAge={}] An object describing the particle's maximum age in seconds.
    - * @property {Number} [maxAge.value=2] A number between 0 and 1 describing the amount of maxAge to apply to all particles.
    - * @property {Number} [maxAge.spread=0] A number describing the maxAge variance on a per-particle basis.
    - *
    - *
    - * @property {Object} [position={}] An object describing this emitter's position.
    - * @property {Object} [position.value=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's base position.
    - * @property {Object} [position.spread=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's position variance on a per-particle basis.
    - *                                                          Note that when using a SPHERE or DISC distribution, only the x-component
    - *                                                          of this vector is used.
    - * @property {Object} [position.spreadClamp=new THREE.Vector3()] A THREE.Vector3 instance describing the numeric multiples the particle's should
    - *                                                               be spread out over.
    - *                                                               Note that when using a SPHERE or DISC distribution, only the x-component
    - *                                                               of this vector is used.
    - * @property {Number} [position.radius=10] This emitter's base radius.
    - * @property {Object} [position.radiusScale=new THREE.Vector3()] A THREE.Vector3 instance describing the radius's scale in all three axes. Allows a SPHERE or DISC to be squashed or stretched.
    - * @property {distribution} [position.distribution=value of the `type` option.] A specific distribution to use when radiusing particles. Overrides the `type` option.
    - * @property {Boolean} [position.randomise=false] When a particle is re-spawned, whether it's position should be re-randomised or not. Can incur a performance hit.
    - *
    - *
    - * @property {Object} [velocity={}] An object describing this particle velocity.
    - * @property {Object} [velocity.value=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's base velocity.
    - * @property {Object} [velocity.spread=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's velocity variance on a per-particle basis.
    - *                                                          Note that when using a SPHERE or DISC distribution, only the x-component
    - *                                                          of this vector is used.
    - * @property {distribution} [velocity.distribution=value of the `type` option.] A specific distribution to use when calculating a particle's velocity. Overrides the `type` option.
    - * @property {Boolean} [velocity.randomise=false] When a particle is re-spawned, whether it's velocity should be re-randomised or not. Can incur a performance hit.
    - *
    - *
    - * @property {Object} [acceleration={}] An object describing this particle's acceleration.
    - * @property {Object} [acceleration.value=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's base acceleration.
    - * @property {Object} [acceleration.spread=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's acceleration variance on a per-particle basis.
    - *                           Note that when using a SPHERE or DISC distribution, only the x-component
    - *                           of this vector is used.
    - * @property {distribution} [acceleration.distribution=value of the `type` option.] A specific distribution to use when calculating a particle's acceleration. Overrides the `type` option.
    - * @property {Boolean} [acceleration.randomise=false] When a particle is re-spawned, whether it's acceleration should be re-randomised or not. Can incur a performance hit.
    - *
    - *
    - * @property {Object} [drag={}] An object describing this particle drag. Drag is applied to both velocity and acceleration values.
    - * @property {Number} [drag.value=0] A number between 0 and 1 describing the amount of drag to apply to all particles.
    - * @property {Number} [drag.spread=0] A number describing the drag variance on a per-particle basis.
    - * @property {Boolean} [drag.randomise=false] When a particle is re-spawned, whether it's drag should be re-randomised or not. Can incur a performance hit.
    - *
    - *
    - * @property {Object} [wiggle={}] This is quite a fun one! The values of this object will determine whether a particle will wiggle, or jiggle, or wave,
    - *                                or shimmy, or waggle, or... Well you get the idea. The wiggle is calculated over-time, meaning that a particle will
    - *                                start off with no wiggle, and end up wiggling about with the distance of the `value` specified by the time it dies.
    - *                                It's quite handy to simulate fire embers, or similar effects where the particle's position should slightly change over
    - *                                time, and such change isn't easily controlled by rotation, velocity, or acceleration. The wiggle is a combination of sin and cos calculations, so is circular in nature.
    - * @property {Number} [wiggle.value=0] A number describing the amount of wiggle to apply to all particles. It's measured in distance.
    - * @property {Number} [wiggle.spread=0] A number describing the wiggle variance on a per-particle basis.
    - *
    - *
    - * @property {Object} [rotation={}] An object describing this emitter's rotation. It can either be static, or set to rotate from 0radians to the value of `rotation.value`
    - *                                  over a particle's lifetime. Rotation values affect both a particle's position and the forces applied to it.
    - * @property {Object} [rotation.axis=new THREE.Vector3(0, 1, 0)] A THREE.Vector3 instance describing this emitter's axis of rotation.
    - * @property {Object} [rotation.axisSpread=new THREE.Vector3()] A THREE.Vector3 instance describing the amount of variance to apply to the axis of rotation on
    - *                                                              a per-particle basis.
    - * @property {Number} [rotation.angle=0] The angle of rotation, given in radians. If `rotation.static` is true, the emitter will start off rotated at this angle, and stay as such.
    - *                                       Otherwise, the particles will rotate from 0radians to this value over their lifetimes.
    - * @property {Number} [rotation.angleSpread=0] The amount of variance in each particle's rotation angle.
    - * @property {Boolean} [rotation.static=false] Whether the rotation should be static or not.
    - * @property {Object} [rotation.center=The value of `position.value`] A THREE.Vector3 instance describing the center point of rotation.
    - * @property {Boolean} [rotation.randomise=false] When a particle is re-spawned, whether it's rotation should be re-randomised or not. Can incur a performance hit.
    - *
    - *
    - * @property {Object} [color={}] An object describing a particle's color. This property is a "value-over-lifetime" property, meaning an array of values and spreads can be
    - *                               given to describe specific value changes over a particle's lifetime.
    - *                               Depending on the value of SPE.valueOverLifetimeLength, if arrays of THREE.Color instances are given, then the array will be interpolated to
    - *                               have a length matching the value of SPE.valueOverLifetimeLength.
    - * @property {Object} [color.value=new THREE.Color()] Either a single THREE.Color instance, or an array of THREE.Color instances to describe the color of a particle over it's lifetime.
    - * @property {Object} [color.spread=new THREE.Vector3()] Either a single THREE.Vector3 instance, or an array of THREE.Vector3 instances to describe the color variance of a particle over it's lifetime.
    - * @property {Boolean} [color.randomise=false] When a particle is re-spawned, whether it's color should be re-randomised or not. Can incur a performance hit.
    - *
    - *
    - * @property {Object} [opacity={}] An object describing a particle's opacity. This property is a "value-over-lifetime" property, meaning an array of values and spreads can be
    - *                               given to describe specific value changes over a particle's lifetime.
    - *                               Depending on the value of SPE.valueOverLifetimeLength, if arrays of numbers are given, then the array will be interpolated to
    - *                               have a length matching the value of SPE.valueOverLifetimeLength.
    - * @property {Number} [opacity.value=1] Either a single number, or an array of numbers to describe the opacity of a particle over it's lifetime.
    - * @property {Number} [opacity.spread=0] Either a single number, or an array of numbers to describe the opacity variance of a particle over it's lifetime.
    - * @property {Boolean} [opacity.randomise=false] When a particle is re-spawned, whether it's opacity should be re-randomised or not. Can incur a performance hit.
    - *
    - *
    - * @property {Object} [size={}] An object describing a particle's size. This property is a "value-over-lifetime" property, meaning an array of values and spreads can be
    - *                               given to describe specific value changes over a particle's lifetime.
    - *                               Depending on the value of SPE.valueOverLifetimeLength, if arrays of numbers are given, then the array will be interpolated to
    - *                               have a length matching the value of SPE.valueOverLifetimeLength.
    - * @property {Number} [size.value=1] Either a single number, or an array of numbers to describe the size of a particle over it's lifetime.
    - * @property {Number} [size.spread=0] Either a single number, or an array of numbers to describe the size variance of a particle over it's lifetime.
    - * @property {Boolean} [size.randomise=false] When a particle is re-spawned, whether it's size should be re-randomised or not. Can incur a performance hit.
    - *
    - *
    - * @property {Object} [angle={}] An object describing a particle's angle. The angle is a 2d-rotation, measured in radians, applied to the particle's texture.
    - *                               NOTE: if a particle's texture is a sprite-sheet, this value IS IGNORED.
    - *                               This property is a "value-over-lifetime" property, meaning an array of values and spreads can be
    - *                               given to describe specific value changes over a particle's lifetime.
    - *                               Depending on the value of SPE.valueOverLifetimeLength, if arrays of numbers are given, then the array will be interpolated to
    - *                               have a length matching the value of SPE.valueOverLifetimeLength.
    - * @property {Number} [angle.value=0] Either a single number, or an array of numbers to describe the angle of a particle over it's lifetime.
    - * @property {Number} [angle.spread=0] Either a single number, or an array of numbers to describe the angle variance of a particle over it's lifetime.
    - * @property {Boolean} [angle.randomise=false] When a particle is re-spawned, whether it's angle should be re-randomised or not. Can incur a performance hit.
    - *
    - */
    -
    -/**
    - * The SPE.Emitter class.
    - *
    - * @constructor
    - *
    - * @param {EmitterOptions} options A map of options to configure the emitter.
    - */
    -SPE.Emitter = function( options ) {
    -    'use strict';
    -
    -    var utils = SPE.utils,
    -        types = utils.types,
    -        lifetimeLength = SPE.valueOverLifetimeLength;
    - -
  • - - -
  • -
    - -
    - -
    -

    Ensure we have a map of options to play with, -and that each option is in the correct format.

    - -
    - -
        options = utils.ensureTypedArg( options, types.OBJECT, {} );
    -    options.position = utils.ensureTypedArg( options.position, types.OBJECT, {} );
    -    options.velocity = utils.ensureTypedArg( options.velocity, types.OBJECT, {} );
    -    options.acceleration = utils.ensureTypedArg( options.acceleration, types.OBJECT, {} );
    -    options.radius = utils.ensureTypedArg( options.radius, types.OBJECT, {} );
    -    options.drag = utils.ensureTypedArg( options.drag, types.OBJECT, {} );
    -    options.rotation = utils.ensureTypedArg( options.rotation, types.OBJECT, {} );
    -    options.color = utils.ensureTypedArg( options.color, types.OBJECT, {} );
    -    options.opacity = utils.ensureTypedArg( options.opacity, types.OBJECT, {} );
    -    options.size = utils.ensureTypedArg( options.size, types.OBJECT, {} );
    -    options.angle = utils.ensureTypedArg( options.angle, types.OBJECT, {} );
    -    options.wiggle = utils.ensureTypedArg( options.wiggle, types.OBJECT, {} );
    -    options.maxAge = utils.ensureTypedArg( options.maxAge, types.OBJECT, {} );
    -
    -    if ( options.onParticleSpawn ) {
    -        console.warn( 'onParticleSpawn has been removed. Please set properties directly to alter values at runtime.' );
    -    }
    -
    -    this.uuid = THREE.Math.generateUUID();
    -
    -    this.type = utils.ensureTypedArg( options.type, types.NUMBER, SPE.distributions.BOX );
    - -
  • - - -
  • -
    - -
    - -
    -

    Start assigning properties…kicking it off with props that DON’T support values over -lifetimes.

    -

    Btw, values over lifetimes are just the new way of referring to Start, Middle, and *End.

    - -
    - -
        this.position = {
    -        _value: utils.ensureInstanceOf( options.position.value, THREE.Vector3, new THREE.Vector3() ),
    -        _spread: utils.ensureInstanceOf( options.position.spread, THREE.Vector3, new THREE.Vector3() ),
    -        _spreadClamp: utils.ensureInstanceOf( options.position.spreadClamp, THREE.Vector3, new THREE.Vector3() ),
    -        _distribution: utils.ensureTypedArg( options.position.distribution, types.NUMBER, this.type ),
    -        _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false ),
    -        _radius: utils.ensureTypedArg( options.position.radius, types.NUMBER, 10 ),
    -        _radiusScale: utils.ensureInstanceOf( options.position.radiusScale, THREE.Vector3, new THREE.Vector3( 1, 1, 1 ) ),
    -        _distributionClamp: utils.ensureTypedArg( options.position.distributionClamp, types.NUMBER, 0 ),
    -    };
    -
    -    this.velocity = {
    -        _value: utils.ensureInstanceOf( options.velocity.value, THREE.Vector3, new THREE.Vector3() ),
    -        _spread: utils.ensureInstanceOf( options.velocity.spread, THREE.Vector3, new THREE.Vector3() ),
    -        _distribution: utils.ensureTypedArg( options.velocity.distribution, types.NUMBER, this.type ),
    -        _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )
    -    };
    -
    -    this.acceleration = {
    -        _value: utils.ensureInstanceOf( options.acceleration.value, THREE.Vector3, new THREE.Vector3() ),
    -        _spread: utils.ensureInstanceOf( options.acceleration.spread, THREE.Vector3, new THREE.Vector3() ),
    -        _distribution: utils.ensureTypedArg( options.acceleration.distribution, types.NUMBER, this.type ),
    -        _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )
    -    };
    -
    -    this.drag = {
    -        _value: utils.ensureTypedArg( options.drag.value, types.NUMBER, 0 ),
    -        _spread: utils.ensureTypedArg( options.drag.spread, types.NUMBER, 0 ),
    -        _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )
    -    };
    -
    -    this.wiggle = {
    -        _value: utils.ensureTypedArg( options.wiggle.value, types.NUMBER, 0 ),
    -        _spread: utils.ensureTypedArg( options.wiggle.spread, types.NUMBER, 0 )
    -    };
    -
    -    this.rotation = {
    -        _axis: utils.ensureInstanceOf( options.rotation.axis, THREE.Vector3, new THREE.Vector3( 0.0, 1.0, 0.0 ) ),
    -        _axisSpread: utils.ensureInstanceOf( options.rotation.axisSpread, THREE.Vector3, new THREE.Vector3() ),
    -        _angle: utils.ensureTypedArg( options.rotation.angle, types.NUMBER, 0 ),
    -        _angleSpread: utils.ensureTypedArg( options.rotation.angleSpread, types.NUMBER, 0 ),
    -        _static: utils.ensureTypedArg( options.rotation.static, types.BOOLEAN, false ),
    -        _center: utils.ensureInstanceOf( options.rotation.center, THREE.Vector3, this.position._value.clone() ),
    -        _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )
    -    };
    -
    -
    -    this.maxAge = {
    -        _value: utils.ensureTypedArg( options.maxAge.value, types.NUMBER, 2 ),
    -        _spread: utils.ensureTypedArg( options.maxAge.spread, types.NUMBER, 0 )
    -    };
    - -
  • - - -
  • -
    - -
    - -
    -

    The following properties can support either single values, or an array of values that change -the property over a particle’s lifetime (value over lifetime).

    - -
    - -
        this.color = {
    -        _value: utils.ensureArrayInstanceOf( options.color.value, THREE.Color, new THREE.Color() ),
    -        _spread: utils.ensureArrayInstanceOf( options.color.spread, THREE.Vector3, new THREE.Vector3() ),
    -        _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )
    -    };
    -
    -    this.opacity = {
    -        _value: utils.ensureArrayTypedArg( options.opacity.value, types.NUMBER, 1 ),
    -        _spread: utils.ensureArrayTypedArg( options.opacity.spread, types.NUMBER, 0 ),
    -        _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )
    -    };
    -
    -    this.size = {
    -        _value: utils.ensureArrayTypedArg( options.size.value, types.NUMBER, 1 ),
    -        _spread: utils.ensureArrayTypedArg( options.size.spread, types.NUMBER, 0 ),
    -        _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )
    -    };
    -
    -    this.angle = {
    -        _value: utils.ensureArrayTypedArg( options.angle.value, types.NUMBER, 0 ),
    -        _spread: utils.ensureArrayTypedArg( options.angle.spread, types.NUMBER, 0 ),
    -        _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )
    -    };
    - -
  • - - -
  • -
    - -
    - -
    -

    Assign renaining option values.

    - -
    - -
        this.particleCount = utils.ensureTypedArg( options.particleCount, types.NUMBER, 100 );
    -    this.duration = utils.ensureTypedArg( options.duration, types.NUMBER, null );
    -    this.isStatic = utils.ensureTypedArg( options.isStatic, types.BOOLEAN, false );
    -    this.activeMultiplier = utils.ensureTypedArg( options.activeMultiplier, types.NUMBER, 1 );
    -    this.direction = utils.ensureTypedArg( options.direction, types.NUMBER, 1 );
    - -
  • - - -
  • -
    - -
    - -
    -

    Whether this emitter is alive or not.

    - -
    - -
        this.alive = utils.ensureTypedArg( options.alive, types.BOOLEAN, true );
    - -
  • - - -
  • -
    - -
    - -
    -

    The following properties are set internally and are not -user-controllable.

    - -
    - -
        this.particlesPerSecond = 0;
    - -
  • - - -
  • -
    - -
    - -
    -

    The current particle index for which particles should -be marked as active on the next update cycle.

    - -
    - -
        this.activationIndex = 0;
    - -
  • - - -
  • -
    - -
    - -
    -

    The offset in the typed arrays this emitter’s -particle’s values will start at

    - -
    - -
        this.attributeOffset = 0;
    - -
  • - - -
  • -
    - -
    - -
    -

    The end of the range in the attribute buffers

    - -
    - -
        this.attributeEnd = 0;
    - -
  • - - -
  • -
    - -
    - -
    -

    Holds the time the emitter has been alive for.

    - -
    - -
        this.age = 0.0;
    - -
  • - - -
  • -
    - -
    - -
    -

    Holds the number of currently-alive particles

    - -
    - -
        this.activeParticleCount = 0.0;
    - -
  • - - -
  • -
    - -
    - -
    -

    Holds a reference to this emitter’s group once -it’s added to one.

    - -
    - -
        this.group = null;
    - -
  • - - -
  • -
    - -
    - -
    -

    Holds a reference to this emitter’s group’s attributes object -for easier access.

    - -
    - -
        this.attributes = null;
    - -
  • - - -
  • -
    - -
    - -
    -

    Holds a reference to the params attribute’s typed array -for quicker access.

    - -
    - -
        this.paramsArray = null;
    - -
  • - - -
  • -
    - -
    - -
    -

    A set of flags to determine whether particular properties -should be re-randomised when a particle is reset.

    -

    If a randomise property is given, this is preferred. -Otherwise, it looks at whether a spread value has been -given.

    -

    It allows randomization to be turned off as desired. If -all randomization is turned off, then I’d expect a performance -boost as no attribute buffers (excluding the params) -would have to be re-passed to the GPU each frame (since nothing -except the params attribute would have changed).

    - -
    - -
        this.resetFlags = {
    - -
  • - - -
  • -
    - -
    - -
    -

    params: utils.ensureTypedArg( options.maxAge.randomise, types.BOOLEAN, !!options.maxAge.spread ) || - utils.ensureTypedArg( options.wiggle.randomise, types.BOOLEAN, !!options.wiggle.spread ),

    - -
    - -
            position: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false ) ||
    -            utils.ensureTypedArg( options.radius.randomise, types.BOOLEAN, false ),
    -        velocity: utils.ensureTypedArg( options.velocity.randomise, types.BOOLEAN, false ),
    -        acceleration: utils.ensureTypedArg( options.acceleration.randomise, types.BOOLEAN, false ) ||
    -            utils.ensureTypedArg( options.drag.randomise, types.BOOLEAN, false ),
    -        rotation: utils.ensureTypedArg( options.rotation.randomise, types.BOOLEAN, false ),
    -        rotationCenter: utils.ensureTypedArg( options.rotation.randomise, types.BOOLEAN, false ),
    -        size: utils.ensureTypedArg( options.size.randomise, types.BOOLEAN, false ),
    -        color: utils.ensureTypedArg( options.color.randomise, types.BOOLEAN, false ),
    -        opacity: utils.ensureTypedArg( options.opacity.randomise, types.BOOLEAN, false ),
    -        angle: utils.ensureTypedArg( options.angle.randomise, types.BOOLEAN, false )
    -    };
    -
    -    this.updateFlags = {};
    -    this.updateCounts = {};
    - -
  • - - -
  • -
    - -
    - -
    -

    A map to indicate which emitter parameters should update -which attribute.

    - -
    - -
        this.updateMap = {
    -        maxAge: 'params',
    -        position: 'position',
    -        velocity: 'velocity',
    -        acceleration: 'acceleration',
    -        drag: 'acceleration',
    -        wiggle: 'params',
    -        rotation: 'rotation',
    -        size: 'size',
    -        color: 'color',
    -        opacity: 'opacity',
    -        angle: 'angle'
    -    };
    -
    -    for ( var i in this.updateMap ) {
    -        if ( this.updateMap.hasOwnProperty( i ) ) {
    -            this.updateCounts[ this.updateMap[ i ] ] = 0.0;
    -            this.updateFlags[ this.updateMap[ i ] ] = false;
    -            this._createGetterSetters( this[ i ], i );
    -        }
    -    }
    -
    -    this.bufferUpdateRanges = {};
    -    this.attributeKeys = null;
    -    this.attributeCount = 0;
    - -
  • - - -
  • -
    - -
    - -
    -

    Ensure that the value-over-lifetime property objects above -have value and spread properties that are of the same length.

    -

    Also, for now, make sure they have a length of 3 (min/max arguments here).

    - -
    - -
        utils.ensureValueOverLifetimeCompliance( this.color, lifetimeLength, lifetimeLength );
    -    utils.ensureValueOverLifetimeCompliance( this.opacity, lifetimeLength, lifetimeLength );
    -    utils.ensureValueOverLifetimeCompliance( this.size, lifetimeLength, lifetimeLength );
    -    utils.ensureValueOverLifetimeCompliance( this.angle, lifetimeLength, lifetimeLength );
    -};
    -
    -SPE.Emitter.constructor = SPE.Emitter;
    -
    -SPE.Emitter.prototype._createGetterSetters = function( propObj, propName ) {
    -    'use strict';
    -
    -    var self = this;
    -
    -    for ( var i in propObj ) {
    -        if ( propObj.hasOwnProperty( i ) ) {
    -
    -            var name = i.replace( '_', '' );
    -
    -            Object.defineProperty( propObj, name, {
    -                get: ( function( prop ) {
    -                    return function() {
    -                        return this[ prop ];
    -                    };
    -                }( i ) ),
    -
    -                set: ( function( prop ) {
    -                    return function( value ) {
    -                        var mapName = self.updateMap[ propName ],
    -                            prevValue = this[ prop ],
    -                            length = SPE.valueOverLifetimeLength;
    -
    -                        if ( prop === '_rotationCenter' ) {
    -                            self.updateFlags.rotationCenter = true;
    -                            self.updateCounts.rotationCenter = 0.0;
    -                        }
    -                        else if ( prop === '_randomise' ) {
    -                            self.resetFlags[ mapName ] = value;
    -                        }
    -                        else {
    -                            self.updateFlags[ mapName ] = true;
    -                            self.updateCounts[ mapName ] = 0.0;
    -                        }
    -
    -                        self.group._updateDefines();
    -
    -                        this[ prop ] = value;
    - -
  • - - -
  • -
    - -
    - -
    -

    If the previous value was an array, then make -sure the provided value is interpolated correctly.

    - -
    - -
                            if ( Array.isArray( prevValue ) ) {
    -                            SPE.utils.ensureValueOverLifetimeCompliance( self[ propName ], length, length );
    -                        }
    -                    };
    -                }( i ) )
    -            } );
    -        }
    -    }
    -};
    -
    -SPE.Emitter.prototype._setBufferUpdateRanges = function( keys ) {
    -    'use strict';
    -
    -    this.attributeKeys = keys;
    -    this.attributeCount = keys.length;
    -
    -    for ( var i = this.attributeCount - 1; i >= 0; --i ) {
    -        this.bufferUpdateRanges[ keys[ i ] ] = {
    -            min: Number.POSITIVE_INFINITY,
    -            max: Number.NEGATIVE_INFINITY
    -        };
    -    }
    -};
    -
    -SPE.Emitter.prototype._calculatePPSValue = function( groupMaxAge ) {
    -    'use strict';
    -
    -    var particleCount = this.particleCount;
    - -
  • - - -
  • -
    - -
    - -
    -

    Calculate the particlesPerSecond value for this emitter. It’s used -when determining which particles should die and which should live to -see another day. Or be born, for that matter. The “God” property.

    - -
    - -
        if ( this.duration ) {
    -        this.particlesPerSecond = particleCount / ( groupMaxAge < this.duration ? groupMaxAge : this.duration );
    -    }
    -    else {
    -        this.particlesPerSecond = particleCount / groupMaxAge;
    -    }
    -};
    -
    -SPE.Emitter.prototype._setAttributeOffset = function( startIndex ) {
    -    this.attributeOffset = startIndex;
    -    this.activationIndex = startIndex;
    -    this.activationEnd = startIndex + this.particleCount;
    -};
    -
    -
    -SPE.Emitter.prototype._assignValue = function( prop, index ) {
    -    'use strict';
    -
    -    switch ( prop ) {
    -        case 'position':
    -            this._assignPositionValue( index );
    -            break;
    -
    -        case 'velocity':
    -        case 'acceleration':
    -            this._assignForceValue( index, prop );
    -            break;
    -
    -        case 'size':
    -        case 'opacity':
    -            this._assignAbsLifetimeValue( index, prop );
    -            break;
    -
    -        case 'angle':
    -            this._assignAngleValue( index );
    -            break;
    -
    -        case 'params':
    -            this._assignParamsValue( index );
    -            break;
    -
    -        case 'rotation':
    -            this._assignRotationValue( index );
    -            break;
    -
    -        case 'color':
    -            this._assignColorValue( index );
    -            break;
    -    }
    -};
    -
    -SPE.Emitter.prototype._assignPositionValue = function( index ) {
    -    'use strict';
    -
    -    var distributions = SPE.distributions,
    -        utils = SPE.utils,
    -        prop = this.position,
    -        attr = this.attributes.position,
    -        value = prop._value,
    -        spread = prop._spread,
    -        distribution = prop._distribution;
    -
    -    switch ( distribution ) {
    -        case distributions.BOX:
    -            utils.randomVector3( attr, index, value, spread, prop._spreadClamp );
    -            break;
    -
    -        case distributions.SPHERE:
    -            utils.randomVector3OnSphere( attr, index, value, prop._radius, prop._spread.x, prop._radiusScale, prop._spreadClamp.x, prop._distributionClamp || this.particleCount );
    -            break;
    -
    -        case distributions.DISC:
    -            utils.randomVector3OnDisc( attr, index, value, prop._radius, prop._spread.x, prop._radiusScale, prop._spreadClamp.x );
    -            break;
    -    }
    -};
    -
    -SPE.Emitter.prototype._assignForceValue = function( index, attrName ) {
    -    'use strict';
    -
    -    var distributions = SPE.distributions,
    -        utils = SPE.utils,
    -        prop = this[ attrName ],
    -        value = prop._value,
    -        spread = prop._spread,
    -        distribution = prop._distribution,
    -        pos,
    -        positionX,
    -        positionY,
    -        positionZ,
    -        i;
    -
    -    switch ( distribution ) {
    -        case distributions.BOX:
    -            utils.randomVector3( this.attributes[ attrName ], index, value, spread );
    -            break;
    -
    -        case distributions.SPHERE:
    -            pos = this.attributes.position.typedArray.array;
    -            i = index * 3;
    - -
  • - - -
  • -
    - -
    - -
    -

    Ensure position values aren’t zero, otherwise no force will be -applied. -positionX = utils.zeroToEpsilon( pos[ i ], true ); -positionY = utils.zeroToEpsilon( pos[ i + 1 ], true ); -positionZ = utils.zeroToEpsilon( pos[ i + 2 ], true );

    - -
    - -
                positionX = pos[ i ];
    -            positionY = pos[ i + 1 ];
    -            positionZ = pos[ i + 2 ];
    -
    -            utils.randomDirectionVector3OnSphere(
    -                this.attributes[ attrName ], index,
    -                positionX, positionY, positionZ,
    -                this.position._value,
    -                prop._value.x,
    -                prop._spread.x
    -            );
    -            break;
    -
    -        case distributions.DISC:
    -            pos = this.attributes.position.typedArray.array;
    -            i = index * 3;
    - -
  • - - -
  • -
    - -
    - -
    -

    Ensure position values aren’t zero, otherwise no force will be -applied. -positionX = utils.zeroToEpsilon( pos[ i ], true ); -positionY = utils.zeroToEpsilon( pos[ i + 1 ], true ); -positionZ = utils.zeroToEpsilon( pos[ i + 2 ], true );

    - -
    - -
                positionX = pos[ i ];
    -            positionY = pos[ i + 1 ];
    -            positionZ = pos[ i + 2 ];
    -
    -            utils.randomDirectionVector3OnDisc(
    -                this.attributes[ attrName ], index,
    -                positionX, positionY, positionZ,
    -                this.position._value,
    -                prop._value.x,
    -                prop._spread.x
    -            );
    -            break;
    -    }
    -
    -    if ( attrName === 'acceleration' ) {
    -        var drag = utils.clamp( utils.randomFloat( this.drag._value, this.drag._spread ), 0, 1 );
    -        this.attributes.acceleration.typedArray.array[ index * 4 + 3 ] = drag;
    -    }
    -};
    -
    -SPE.Emitter.prototype._assignAbsLifetimeValue = function( index, propName ) {
    -    'use strict';
    -
    -    var array = this.attributes[ propName ].typedArray,
    -        prop = this[ propName ],
    -        utils = SPE.utils,
    -        value;
    -
    -    if ( utils.arrayValuesAreEqual( prop._value ) && utils.arrayValuesAreEqual( prop._spread ) ) {
    -        value = Math.abs( utils.randomFloat( prop._value[ 0 ], prop._spread[ 0 ] ) );
    -        array.setVec4Components( index, value, value, value, value );
    -    }
    -    else {
    -        array.setVec4Components( index,
    -            Math.abs( utils.randomFloat( prop._value[ 0 ], prop._spread[ 0 ] ) ),
    -            Math.abs( utils.randomFloat( prop._value[ 1 ], prop._spread[ 1 ] ) ),
    -            Math.abs( utils.randomFloat( prop._value[ 2 ], prop._spread[ 2 ] ) ),
    -            Math.abs( utils.randomFloat( prop._value[ 3 ], prop._spread[ 3 ] ) )
    -        );
    -    }
    -};
    -
    -SPE.Emitter.prototype._assignAngleValue = function( index ) {
    -    'use strict';
    -
    -    var array = this.attributes.angle.typedArray,
    -        prop = this.angle,
    -        utils = SPE.utils,
    -        value;
    -
    -    if ( utils.arrayValuesAreEqual( prop._value ) && utils.arrayValuesAreEqual( prop._spread ) ) {
    -        value = utils.randomFloat( prop._value[ 0 ], prop._spread[ 0 ] );
    -        array.setVec4Components( index, value, value, value, value );
    -    }
    -    else {
    -        array.setVec4Components( index,
    -            utils.randomFloat( prop._value[ 0 ], prop._spread[ 0 ] ),
    -            utils.randomFloat( prop._value[ 1 ], prop._spread[ 1 ] ),
    -            utils.randomFloat( prop._value[ 2 ], prop._spread[ 2 ] ),
    -            utils.randomFloat( prop._value[ 3 ], prop._spread[ 3 ] )
    -        );
    -    }
    -};
    -
    -SPE.Emitter.prototype._assignParamsValue = function( index ) {
    -    'use strict';
    -
    -    this.attributes.params.typedArray.setVec4Components( index,
    -        this.isStatic ? 1 : 0,
    -        0.0,
    -        Math.abs( SPE.utils.randomFloat( this.maxAge._value, this.maxAge._spread ) ),
    -        SPE.utils.randomFloat( this.wiggle._value, this.wiggle._spread )
    -    );
    -};
    -
    -SPE.Emitter.prototype._assignRotationValue = function( index ) {
    -    'use strict';
    -
    -    this.attributes.rotation.typedArray.setVec3Components( index,
    -        SPE.utils.getPackedRotationAxis( this.rotation._axis, this.rotation._axisSpread ),
    -        SPE.utils.randomFloat( this.rotation._angle, this.rotation._angleSpread ),
    -        this.rotation._static ? 0 : 1
    -    );
    -
    -    this.attributes.rotationCenter.typedArray.setVec3( index, this.rotation._center );
    -};
    -
    -SPE.Emitter.prototype._assignColorValue = function( index ) {
    -    'use strict';
    -    SPE.utils.randomColorAsHex( this.attributes.color, index, this.color._value, this.color._spread );
    -};
    -
    -SPE.Emitter.prototype._resetParticle = function( index ) {
    -    'use strict';
    -
    -    var resetFlags = this.resetFlags,
    -        updateFlags = this.updateFlags,
    -        updateCounts = this.updateCounts,
    -        keys = this.attributeKeys,
    -        key,
    -        updateFlag;
    -
    -    for ( var i = this.attributeCount - 1; i >= 0; --i ) {
    -        key = keys[ i ];
    -        updateFlag = updateFlags[ key ];
    -
    -        if ( resetFlags[ key ] === true || updateFlag === true ) {
    -            this._assignValue( key, index );
    -            this._updateAttributeUpdateRange( key, index );
    -
    -            if ( updateFlag === true && updateCounts[ key ] === this.particleCount ) {
    -                updateFlags[ key ] = false;
    -                updateCounts[ key ] = 0.0;
    -            }
    -            else if ( updateFlag == true ) {
    -                ++updateCounts[ key ];
    -            }
    -        }
    -    }
    -};
    -
    -SPE.Emitter.prototype._updateAttributeUpdateRange = function( attr, i ) {
    -    'use strict';
    -
    -    var ranges = this.bufferUpdateRanges[ attr ];
    -
    -    ranges.min = Math.min( i, ranges.min );
    -    ranges.max = Math.max( i, ranges.max );
    -};
    -
    -SPE.Emitter.prototype._resetBufferRanges = function() {
    -    'use strict';
    -
    -    var ranges = this.bufferUpdateRanges,
    -        keys = this.bufferUpdateKeys,
    -        i = this.bufferUpdateCount - 1,
    -        key;
    -
    -    for ( i; i >= 0; --i ) {
    -        key = keys[ i ];
    -        ranges[ key ].min = Number.POSITIVE_INFINITY;
    -        ranges[ key ].max = Number.NEGATIVE_INFINITY;
    -    }
    -};
    -
    -SPE.Emitter.prototype._onRemove = function() {
    -    'use strict';
    - -
  • - - -
  • -
    - -
    - -
    -

    Reset any properties of the emitter that were set by -a group when it was added.

    - -
    - -
        this.particlesPerSecond = 0;
    -    this.attributeOffset = 0;
    -    this.activationIndex = 0;
    -    this.activeParticleCount = 0;
    -    this.group = null;
    -    this.attributes = null;
    -    this.paramsArray = null;
    -    this.age = 0.0;
    -};
    -
    -SPE.Emitter.prototype._decrementParticleCount = function() {
    -    'use strict';
    -    --this.activeParticleCount;
    - -
  • - - -
  • -
    - -
    - -
    -

    TODO:

    -
      -
    • Trigger event if count === 0.
    • -
    - -
    - -
    };
    -
    -SPE.Emitter.prototype._incrementParticleCount = function() {
    -    'use strict';
    -    ++this.activeParticleCount;
    - -
  • - - -
  • -
    - -
    - -
    -

    TODO:

    -
      -
    • Trigger event if count === this.particleCount.
    • -
    - -
    - -
    };
    -
    -SPE.Emitter.prototype._checkParticleAges = function( start, end, params, dt ) {
    -    'use strict';
    -    for ( var i = end - 1, index, maxAge, age, alive; i >= start; --i ) {
    -        index = i * 4;
    -
    -        alive = params[ index ];
    -
    -        if ( alive === 0.0 ) {
    -            continue;
    -        }
    - -
  • - - -
  • -
    - -
    - -
    -

    Increment age

    - -
    - -
            age = params[ index + 1 ];
    -        maxAge = params[ index + 2 ];
    -
    -        if ( this.direction === 1 ) {
    -            age += dt;
    -
    -            if ( age >= maxAge ) {
    -                age = 0.0;
    -                alive = 0.0;
    -                this._decrementParticleCount();
    -            }
    -        }
    -        else {
    -            age -= dt;
    -
    -            if ( age <= 0.0 ) {
    -                age = maxAge;
    -                alive = 0.0;
    -                this._decrementParticleCount();
    -            }
    -        }
    -
    -        params[ index ] = alive;
    -        params[ index + 1 ] = age;
    -
    -        this._updateAttributeUpdateRange( 'params', i );
    -    }
    -};
    -
    -SPE.Emitter.prototype._activateParticles = function( activationStart, activationEnd, params, dtPerParticle ) {
    -    'use strict';
    -    var direction = this.direction;
    -
    -    for ( var i = activationStart, index, dtValue; i < activationEnd; ++i ) {
    -        index = i * 4;
    - -
  • - - -
  • -
    - -
    - -
    -

    Don’t re-activate particles that aren’t dead yet. -if ( params[ index ] !== 0.0 && ( this.particleCount !== 1 || this.activeMultiplier !== 1 ) ) { - continue; -}

    - -
    - -
    -        if ( params[ index ] != 0.0 && this.particleCount !== 1 ) {
    -            continue;
    -        }
    - -
  • - - -
  • -
    - -
    - -
    -

    Increment the active particle count.

    - -
    - -
            this._incrementParticleCount();
    - -
  • - - -
  • -
    - -
    - -
    -

    Mark the particle as alive.

    - -
    - -
            params[ index ] = 1.0;
    - -
  • - - -
  • -
    - -
    - -
    -

    Reset the particle

    - -
    - -
            this._resetParticle( i );
    - -
  • - - -
  • -
    - -
    - -
    -

    Move each particle being activated to -it’s actual position in time.

    -

    This stops particles being ‘clumped’ together -when frame rates are on the lower side of 60fps -or not constant (a very real possibility!)

    - -
    - -
            dtValue = dtPerParticle * ( i - activationStart )
    -        params[ index + 1 ] = direction === -1 ? params[ index + 2 ] - dtValue : dtValue;
    -
    -        this._updateAttributeUpdateRange( 'params', i );
    -    }
    -};
    -
    -/**
    - * Simulates one frame's worth of particles, updating particles
    - * that are already alive, and marking ones that are currently dead
    - * but should be alive as alive.
    - *
    - * If the emitter is marked as static, then this function will do nothing.
    - *
    - * @param  {Number} dt The number of seconds to simulate (deltaTime)
    - */
    -SPE.Emitter.prototype.tick = function( dt ) {
    -    'use strict';
    -
    -    if ( this.isStatic ) {
    -        return;
    -    }
    -
    -    if ( this.paramsArray === null ) {
    -        this.paramsArray = this.attributes.params.typedArray.array;
    -    }
    -
    -    var start = this.attributeOffset,
    -        end = start + this.particleCount,
    -        params = this.paramsArray, // vec3( alive, age, maxAge, wiggle )
    -        ppsDt = this.particlesPerSecond * this.activeMultiplier * dt,
    -        activationIndex = this.activationIndex;
    - -
  • - - -
  • -
    - -
    - -
    -

    Reset the buffer update indices.

    - -
    - -
        this._resetBufferRanges();
    - -
  • - - -
  • -
    - -
    - -
    -

    Increment age for those particles that are alive, -and kill off any particles whose age is over the limit.

    - -
    - -
        this._checkParticleAges( start, end, params, dt );
    - -
  • - - -
  • -
    - -
    - -
    -

    If the emitter is dead, reset the age of the emitter to zero, -ready to go again if required

    - -
    - -
        if ( this.alive === false ) {
    -        this.age = 0.0;
    -        return;
    -    }
    - -
  • - - -
  • -
    - -
    - -
    -

    If the emitter has a specified lifetime and we’ve exceeded it, -mark the emitter as dead.

    - -
    - -
        if ( this.duration !== null && this.age > this.duration ) {
    -        this.alive = false;
    -        this.age = 0.0;
    -        return;
    -    }
    -
    -
    -    var activationStart = this.particleCount === 1 ? activationIndex : ( activationIndex | 0 ),
    -        activationEnd = Math.min( activationStart + ppsDt, this.activationEnd ),
    -        activationCount = activationEnd - this.activationIndex | 0,
    -        dtPerParticle = activationCount > 0 ? dt / activationCount : 0;
    -
    -    this._activateParticles( activationStart, activationEnd, params, dtPerParticle );
    - -
  • - - -
  • -
    - -
    - -
    -

    Move the activation window forward, soldier.

    - -
    - -
        this.activationIndex += ppsDt;
    -
    -    if ( this.activationIndex > end ) {
    -        this.activationIndex = start;
    -    }
    - -
  • - - -
  • -
    - -
    - -
    -

    Increment the age of the emitter.

    - -
    - -
        this.age += dt;
    -};
    -
    -/**
    - * Resets all the emitter's particles to their start positions
    - * and marks the particles as dead if the `force` argument is
    - * true.
    - *
    - * @param  {Boolean} [force=undefined] If true, all particles will be marked as dead instantly.
    - * @return {Emitter}       This emitter instance.
    - */
    -SPE.Emitter.prototype.reset = function( force ) {
    -    'use strict';
    -
    -    this.age = 0.0;
    -    this.alive = false;
    -
    -    if ( force === true ) {
    -        var start = this.attributeOffset,
    -            end = start + this.particleCount,
    -            array = this.paramsArray,
    -            attr = this.attributes.params.bufferAttribute;
    -
    -        for ( var i = end - 1, index; i >= start; --i ) {
    -            index = i * 4;
    -
    -            array[ index ] = 0.0;
    -            array[ index + 1 ] = 0.0;
    -        }
    -
    -        attr.updateRange.offset = 0;
    -        attr.updateRange.count = -1;
    -        attr.needsUpdate = true;
    -    }
    -
    -    return this;
    -};
    -
    -/**
    - * Enables the emitter. If not already enabled, the emitter
    - * will start emitting particles.
    - *
    - * @return {Emitter} This emitter instance.
    - */
    -SPE.Emitter.prototype.enable = function() {
    -    'use strict';
    -    this.alive = true;
    -    return this;
    -};
    -
    -/**
    - * Disables th emitter, but does not instantly remove it's
    - * particles fromt the scene. When called, the emitter will be
    - * 'switched off' and just stop emitting. Any particle's alive will
    - * be allowed to finish their lifecycle.
    - *
    - * @return {Emitter} This emitter instance.
    - */
    -SPE.Emitter.prototype.disable = function() {
    -    'use strict';
    -
    -    this.alive = false;
    -    return this;
    -};
    -
    -/**
    - * Remove this emitter from it's parent group (if it has been added to one).
    - * Delgates to SPE.group.prototype.removeEmitter().
    - *
    - * When called, all particle's belonging to this emitter will be instantly
    - * removed from the scene.
    - *
    - * @return {Emitter} This emitter instance.
    - *
    - * @see SPE.Group.prototype.removeEmitter
    - */
    -SPE.Emitter.prototype.remove = function() {
    -    'use strict';
    -    if ( this.group !== null ) {
    -        this.group.removeEmitter( this );
    -    }
    -    else {
    -        console.error( 'Emitter does not belong to a group, cannot remove.' );
    -    }
    -
    -    return this;
    -};
    - -
  • - -
-
- - diff --git a/docs/source/docco.css b/docs/source/docco.css index a2899ac..b60f6fa 100644 --- a/docs/source/docco.css +++ b/docs/source/docco.css @@ -21,11 +21,11 @@ } @font-face { - font-family: 'novecento-bold'; - src: url('public/fonts/novecento-bold.eot'); - src: url('public/fonts/novecento-bold.eot?#iefix') format('embedded-opentype'), - url('public/fonts/novecento-bold.woff') format('woff'), - url('public/fonts/novecento-bold.ttf') format('truetype'); + font-family: 'roboto-black'; + src: url('public/fonts/roboto-black.eot'); + src: url('public/fonts/roboto-black.eot?#iefix') format('embedded-opentype'), + url('public/fonts/roboto-black.woff') format('woff'), + url('public/fonts/roboto-black.ttf') format('truetype'); font-weight: normal; font-style: normal; } @@ -67,7 +67,7 @@ h1, h2, h3, h4, h5, h6 { color: #112233; line-height: 1em; font-weight: normal; - font-family: "novecento-bold"; + font-family: "roboto-black"; text-transform: uppercase; margin: 30px 0 15px 0; } @@ -75,6 +75,9 @@ h1, h2, h3, h4, h5, h6 { h1 { margin-top: 40px; } +h2 { + font-size: 1.26em; +} hr { border: 0; @@ -180,9 +183,18 @@ ul.sections > li > div { display: block; } +#jump_page_wrapper{ + position: fixed; + right: 0; + top: 0; + bottom: 0; +} + #jump_page { padding: 5px 0 3px; margin: 0 0 25px 25px; + max-height: 100%; + overflow: auto; } #jump_page .source { diff --git a/docs/source/public/fonts/novecento-bold.eot b/docs/source/public/fonts/novecento-bold.eot deleted file mode 100644 index 98a9a7fbabb1083aca5143bc306e228886ff2eeb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18190 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 F001ed(;ol; diff --git a/docs/source/public/fonts/roboto-black.eot b/docs/source/public/fonts/roboto-black.eot new file mode 100755 index 0000000000000000000000000000000000000000..571ed49125910cd6b6b5a9259134dd2e8d37766e GIT binary patch literal 20702 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
~sO?6glRGZi8n^KyFR=i1erVaJp>l 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

  • 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 X3&Ic#E%XNK++i8SYTo{GJ<-1bF1c8o literal 0 HcmV?d00001 diff --git a/docs/source/public/fonts/roboto-black.woff b/docs/source/public/fonts/roboto-black.woff new file mode 100755 index 0000000000000000000000000000000000000000..642e5b60f7c662ab5bcf67cfc1089f50bfdd7b00 GIT binary patch literal 24536 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‚ZNmb*+jwao2 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 E0ADU%yZ`_I literal 0 HcmV?d00001 diff --git a/docs/source/src/constants/distributions.html b/docs/source/src/constants/distributions.html new file mode 100644 index 0000000..95ee096 --- /dev/null +++ b/docs/source/src/constants/distributions.html @@ -0,0 +1,145 @@ + + + + + distributions.js + + + + + +

    +
    + + + +
      + +
    • +
      +

      distributions.js

      +
      +
    • + + + +
    • +
      + +
      + +
      + +
      + +
      /**
      + * A map of supported distribution types used
      + * by SPE.Emitter instances.
      + *
      + * These distribution types can be applied to
      + * an emitter globally, which will affect the
      + * `position`, `velocity`, and `acceleration`
      + * value calculations for an emitter, or they
      + * can be applied on a per-property basis.
      + *
      + * @enum {Number}
      + */
      +export default {
      +
      +	/**
      +	 * Values will be distributed within a box.
      +	 * @type {Number}
      +	 */
      +	BOX: 1,
      +
      +	/**
      +	 * Values will be distributed on a sphere.
      +	 * @type {Number}
      +	 */
      +	SPHERE: 2,
      +
      +	/**
      +	 * Values will be distributed on a 2d-disc shape.
      +	 * @type {Number}
      +	 */
      +	DISC: 3,
      +
      +	/**
      +	 * Values will be distributed along a line.
      +	 * @type {Number}
      +	 */
      +	LINE: 4,
      +};
      + +
    • + +
    +
    + + diff --git a/docs/source/src/constants/globals.html b/docs/source/src/constants/globals.html new file mode 100644 index 0000000..3699de2 --- /dev/null +++ b/docs/source/src/constants/globals.html @@ -0,0 +1,110 @@ + + + + + globals.js + + + + + + + + diff --git a/docs/source/src/constants/typeSizeMap.html b/docs/source/src/constants/typeSizeMap.html new file mode 100644 index 0000000..065dd87 --- /dev/null +++ b/docs/source/src/constants/typeSizeMap.html @@ -0,0 +1,155 @@ + + + + + typeSizeMap.js + + + + + +
    +
    + + + +
      + +
    • +
      +

      typeSizeMap.js

      +
      +
    • + + + +
    • +
      + +
      + +
      + +
      + +
      /**
      + * A map of uniform types to their component size.
      + * @enum {Number}
      + */
      +export default {
      +
      +	/**
      +	 * Float
      +	 * @type {Number}
      +	 */
      +	f: 1,
      +
      +	/**
      +	 * Vec2
      +	 * @type {Number}
      +	 */
      +	v2: 2,
      +
      +	/**
      +	 * Vec3
      +	 * @type {Number}
      +	 */
      +	v3: 3,
      +
      +	/**
      +	 * Vec4
      +	 * @type {Number}
      +	 */
      +	v4: 4,
      +
      +	/**
      +	 * Color
      +	 * @type {Number}
      +	 */
      +	c: 3,
      +
      +	/**
      +	 * Mat3
      +	 * @type {Number}
      +	 */
      +	m3: 9,
      +
      +	/**
      +	 * Mat4
      +	 * @type {Number}
      +	 */
      +	m4: 16,
      +};
      + +
    • + +
    +
    + + diff --git a/docs/source/src/constants/valueTypes.html b/docs/source/src/constants/valueTypes.html new file mode 100644 index 0000000..521aef5 --- /dev/null +++ b/docs/source/src/constants/valueTypes.html @@ -0,0 +1,133 @@ + + + + + valueTypes.js + + + + + +
    +
    + + + +
      + +
    • +
      +

      valueTypes.js

      +
      +
    • + + + +
    • +
      + +
      + +
      + +
      + +
      export default {
      +
      +	/**
      +	 * Boolean type.
      +	 * @type {String}
      +	 */
      +	BOOLEAN: 'boolean',
      +
      +	/**
      +	 * String type.
      +	 * @type {String}
      +	 */
      +	STRING: 'string',
      +
      +	/**
      +	 * Number type.
      +	 * @type {String}
      +	 */
      +	NUMBER: 'number',
      +
      +	/**
      +	 * Object type.
      +	 * @type {String}
      +	 */
      +	OBJECT: 'object',
      +};
      + +
    • + +
    +
    + + diff --git a/docs/source/src/core/Emitter.html b/docs/source/src/core/Emitter.html new file mode 100644 index 0000000..5d197d5 --- /dev/null +++ b/docs/source/src/core/Emitter.html @@ -0,0 +1,1500 @@ + + + + + Emitter.js + + + + + +
    +
    + + + +
      + +
    • +
      +

      Emitter.js

      +
      +
    • + + + +
    • +
      + +
      + +
      + +
      + +
      import * as THREE from 'three';
      +import valueTypes from '@/constants/valueTypes';
      +import distributions from '@/constants/distributions';
      +import globals from '@/constants/globals';
      +import utils from './utils';
      +
      +const HAS_OWN = Object.prototype.hasOwnProperty;
      +
      +/**
      + * An SPE.Emitter instance.
      + * @typedef {Object} Emitter
      + * @see SPE.Emitter
      + */
      +
      +/**
      + * A map of options to configure an SPE.Emitter instance.
      + *
      + * @typedef {Object} EmitterOptions
      + *
      + * @property {distribution} [type=BOX] The default distribution this emitter should use to control
      + *                         its particle's spawn position and force behaviour.
      + *                         Must be an distributions.* value.
      + *
      + *
      + * @property {Number} [particleCount=100] The total number of particles this emitter will hold. NOTE: this is not the number
      + *                                  of particles emitted in a second, or anything like that. The number of particles
      + *                                  emitted per-second is calculated by particleCount / maxAge (approximately!)
      + *
      + * @property {Number|null} [duration=null] The duration in seconds that this emitter should live for. If not specified, the emitter
      + *                                         will emit particles indefinitely.
      + *                                         NOTE: When an emitter is older than a specified duration, the emitter is NOT removed from
      + *                                         it's group, but rather is just marked as dead, allowing it to be reanimated at a later time
      + *                                         using `SPE.Emitter.prototype.enable()`.
      + *
      + * @property {Boolean} [isStatic=false] Whether this emitter should be not be simulated (true).
      + * @property {Boolean} [activeMultiplier=1] A value between 0 and 1 describing what percentage of this emitter's particlesPerSecond should be
      + *                                          emitted, where 0 is 0%, and 1 is 100%.
      + *                                          For example, having an emitter with 100 particles, a maxAge of 2, yields a particlesPerSecond
      + *                                          value of 50. Setting `activeMultiplier` to 0.5, then, will only emit 25 particles per second (0.5 = 50%).
      + *                                          Values greater than 1 will emulate a burst of particles, causing the emitter to run out of particles
      + *                                          before it's next activation cycle.
      + *
      + * @property {Boolean} [direction=1] The direction of the emitter. If value is `1`, emitter will start at beginning of particle's lifecycle.
      + *                                   If value is `-1`, emitter will start at end of particle's lifecycle and work it's way backwards.
      + *
      + * @property {Object} [maxAge={}] An object describing the particle's maximum age in seconds.
      + * @property {Number} [maxAge.value=2] A number between 0 and 1 describing the amount of maxAge to apply to all particles.
      + * @property {Number} [maxAge.spread=0] A number describing the maxAge variance on a per-particle basis.
      + *
      + *
      + * @property {Object} [position={}] An object describing this emitter's position.
      + * @property {Object} [position.value=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's base position.
      + * @property {Object} [position.spread=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's position variance on a per-particle basis.
      + *                                                          Note that when using a SPHERE or DISC distribution, only the x-component
      + *                                                          of this vector is used.
      + *                                                          When using a LINE distribution, this value is the endpoint of the LINE.
      + * @property {Object} [position.spreadClamp=new THREE.Vector3()] A THREE.Vector3 instance describing the numeric multiples the particle's should
      + *                                                               be spread out over.
      + *                                                               Note that when using a SPHERE or DISC distribution, only the x-component
      + *                                                               of this vector is used.
      + *                                                               When using a LINE distribution, this property is ignored.
      + * @property {Number} [position.radius=10] This emitter's base radius.
      + * @property {Object} [position.radiusScale=new THREE.Vector3()] A THREE.Vector3 instance describing the radius's scale in all three axes. Allows a SPHERE or DISC to be squashed or stretched.
      + * @property {distribution} [position.distribution=value of the `type` option.] A specific distribution to use when radiusing particles. Overrides the `type` option.
      + * @property {Boolean} [position.randomise=false] When a particle is re-spawned, whether it's position should be re-randomised or not. Can incur a performance hit.
      + *
      + *
      + * @property {Object} [velocity={}] An object describing this particle velocity.
      + * @property {Object} [velocity.value=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's base velocity.
      + * @property {Object} [velocity.spread=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's velocity variance on a per-particle basis.
      + *                                                          Note that when using a SPHERE or DISC distribution, only the x-component
      + *                                                          of this vector is used.
      + * @property {distribution} [velocity.distribution=value of the `type` option.] A specific distribution to use when calculating a particle's velocity. Overrides the `type` option.
      + * @property {Boolean} [velocity.randomise=false] When a particle is re-spawned, whether it's velocity should be re-randomised or not. Can incur a performance hit.
      + *
      + *
      + * @property {Object} [acceleration={}] An object describing this particle's acceleration.
      + * @property {Object} [acceleration.value=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's base acceleration.
      + * @property {Object} [acceleration.spread=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's acceleration variance on a per-particle basis.
      + *                           Note that when using a SPHERE or DISC distribution, only the x-component
      + *                           of this vector is used.
      + * @property {distribution} [acceleration.distribution=value of the `type` option.] A specific distribution to use when calculating a particle's acceleration. Overrides the `type` option.
      + * @property {Boolean} [acceleration.randomise=false] When a particle is re-spawned, whether it's acceleration should be re-randomised or not. Can incur a performance hit.
      + *
      + *
      + * @property {Object} [drag={}] An object describing this particle drag. Drag is applied to both velocity and acceleration values.
      + * @property {Number} [drag.value=0] A number between 0 and 1 describing the amount of drag to apply to all particles.
      + * @property {Number} [drag.spread=0] A number describing the drag variance on a per-particle basis.
      + * @property {Boolean} [drag.randomise=false] When a particle is re-spawned, whether it's drag should be re-randomised or not. Can incur a performance hit.
      + *
      + *
      + * @property {Object} [wiggle={}] This is quite a fun one! The values of this object will determine whether a particle will wiggle, or jiggle, or wave,
      + *                                or shimmy, or waggle, or... Well you get the idea. The wiggle is calculated over-time, meaning that a particle will
      + *                                start off with no wiggle, and end up wiggling about with the distance of the `value` specified by the time it dies.
      + *                                It's quite handy to simulate fire embers, or similar effects where the particle's position should slightly change over
      + *                                time, and such change isn't easily controlled by rotation, velocity, or acceleration. The wiggle is a combination of sin and cos calculations, so is circular in nature.
      + * @property {Number} [wiggle.value=0] A number describing the amount of wiggle to apply to all particles. It's measured in distance.
      + * @property {Number} [wiggle.spread=0] A number describing the wiggle variance on a per-particle basis.
      + *
      + *
      + * @property {Object} [rotation={}] An object describing this emitter's rotation. It can either be static, or set to rotate from 0radians to the value of `rotation.value`
      + *                                  over a particle's lifetime. Rotation values affect both a particle's position and the forces applied to it.
      + * @property {Object} [rotation.axis=new THREE.Vector3(0, 1, 0)] A THREE.Vector3 instance describing this emitter's axis of rotation.
      + * @property {Object} [rotation.axisSpread=new THREE.Vector3()] A THREE.Vector3 instance describing the amount of variance to apply to the axis of rotation on
      + *                                                              a per-particle basis.
      + * @property {Number} [rotation.angle=0] The angle of rotation, given in radians. If `rotation.static` is true, the emitter will start off rotated at this angle, and stay as such.
      + *                                       Otherwise, the particles will rotate from 0radians to this value over their lifetimes.
      + * @property {Number} [rotation.angleSpread=0] The amount of variance in each particle's rotation angle.
      + * @property {Boolean} [rotation.static=false] Whether the rotation should be static or not.
      + * @property {Object} [rotation.center=The value of `position.value`] A THREE.Vector3 instance describing the center point of rotation.
      + * @property {Boolean} [rotation.randomise=false] When a particle is re-spawned, whether it's rotation should be re-randomised or not. Can incur a performance hit.
      + *
      + *
      + * @property {Object} [color={}] An object describing a particle's color. This property is a "value-over-lifetime" property, meaning an array of values and spreads can be
      + *                               given to describe specific value changes over a particle's lifetime.
      + *                               Depending on the value of SPE.valueOverLifetimeLength, if arrays of THREE.Color instances are given, then the array will be interpolated to
      + *                               have a length matching the value of SPE.valueOverLifetimeLength.
      + * @property {Object} [color.value=new THREE.Color()] Either a single THREE.Color instance, or an array of THREE.Color instances to describe the color of a particle over it's lifetime.
      + * @property {Object} [color.spread=new THREE.Vector3()] Either a single THREE.Vector3 instance, or an array of THREE.Vector3 instances to describe the color variance of a particle over it's lifetime.
      + * @property {Boolean} [color.randomise=false] When a particle is re-spawned, whether it's color should be re-randomised or not. Can incur a performance hit.
      + *
      + *
      + * @property {Object} [opacity={}] An object describing a particle's opacity. This property is a "value-over-lifetime" property, meaning an array of values and spreads can be
      + *                               given to describe specific value changes over a particle's lifetime.
      + *                               Depending on the value of SPE.valueOverLifetimeLength, if arrays of numbers are given, then the array will be interpolated to
      + *                               have a length matching the value of SPE.valueOverLifetimeLength.
      + * @property {Number} [opacity.value=1] Either a single number, or an array of numbers to describe the opacity of a particle over it's lifetime.
      + * @property {Number} [opacity.spread=0] Either a single number, or an array of numbers to describe the opacity variance of a particle over it's lifetime.
      + * @property {Boolean} [opacity.randomise=false] When a particle is re-spawned, whether it's opacity should be re-randomised or not. Can incur a performance hit.
      + *
      + *
      + * @property {Object} [size={}] An object describing a particle's size. This property is a "value-over-lifetime" property, meaning an array of values and spreads can be
      + *                               given to describe specific value changes over a particle's lifetime.
      + *                               Depending on the value of SPE.valueOverLifetimeLength, if arrays of numbers are given, then the array will be interpolated to
      + *                               have a length matching the value of SPE.valueOverLifetimeLength.
      + * @property {Number} [size.value=1] Either a single number, or an array of numbers to describe the size of a particle over it's lifetime.
      + * @property {Number} [size.spread=0] Either a single number, or an array of numbers to describe the size variance of a particle over it's lifetime.
      + * @property {Boolean} [size.randomise=false] When a particle is re-spawned, whether it's size should be re-randomised or not. Can incur a performance hit.
      + *
      + *
      + * @property {Object} [angle={}] An object describing a particle's angle. The angle is a 2d-rotation, measured in radians, applied to the particle's texture.
      + *                               NOTE: if a particle's texture is a sprite-sheet, this value IS IGNORED.
      + *                               This property is a "value-over-lifetime" property, meaning an array of values and spreads can be
      + *                               given to describe specific value changes over a particle's lifetime.
      + *                               Depending on the value of SPE.valueOverLifetimeLength, if arrays of numbers are given, then the array will be interpolated to
      + *                               have a length matching the value of SPE.valueOverLifetimeLength.
      + * @property {Number} [angle.value=0] Either a single number, or an array of numbers to describe the angle of a particle over it's lifetime.
      + * @property {Number} [angle.spread=0] Either a single number, or an array of numbers to describe the angle variance of a particle over it's lifetime.
      + * @property {Boolean} [angle.randomise=false] When a particle is re-spawned, whether it's angle should be re-randomised or not. Can incur a performance hit.
      + *
      + */
      +
      +/**
      + * The SPE.Emitter class.
      + *
      + * @constructor
      + *
      + * @param {EmitterOptions} options A map of options to configure the emitter.
      + */
      +export default class Emitter {
      +	constructor( opts ) {
      + +
    • + + +
    • +
      + +
      + +
      +

      Ensure we have a map of options to play with, +and that each option is in the correct format.

      + +
      + +
      		const options = utils.ensureTypedArg( opts, valueTypes.OBJECT, {} );
      +		options.position = utils.ensureTypedArg( options.position, valueTypes.OBJECT, {} );
      +		options.velocity = utils.ensureTypedArg( options.velocity, valueTypes.OBJECT, {} );
      +		options.acceleration = utils.ensureTypedArg( options.acceleration, valueTypes.OBJECT, {} );
      +		options.radius = utils.ensureTypedArg( options.radius, valueTypes.OBJECT, {} );
      +		options.drag = utils.ensureTypedArg( options.drag, valueTypes.OBJECT, {} );
      +		options.rotation = utils.ensureTypedArg( options.rotation, valueTypes.OBJECT, {} );
      +		options.color = utils.ensureTypedArg( options.color, valueTypes.OBJECT, {} );
      +		options.opacity = utils.ensureTypedArg( options.opacity, valueTypes.OBJECT, {} );
      +		options.size = utils.ensureTypedArg( options.size, valueTypes.OBJECT, {} );
      +		options.angle = utils.ensureTypedArg( options.angle, valueTypes.OBJECT, {} );
      +		options.wiggle = utils.ensureTypedArg( options.wiggle, valueTypes.OBJECT, {} );
      +		options.maxAge = utils.ensureTypedArg( options.maxAge, valueTypes.OBJECT, {} );
      +
      +		if ( options.onParticleSpawn ) {
      +			console.warn( 'onParticleSpawn has been removed. Please set properties directly to alter values at runtime.' );
      +		}
      +
      +		this.uuid = THREE.Math.generateUUID();
      +
      +		this.type = utils.ensureTypedArg( options.type, valueTypes.NUMBER, distributions.BOX );
      + +
    • + + +
    • +
      + +
      + +
      +

      Start assigning properties…kicking it off with props that DON’T support values over +lifetimes.

      +

      Btw, values over lifetimes are just the new way of referring to *Start, *Middle, and *End.

      + +
      + +
      		this.position = {
      +			_value: utils.ensureInstanceOf( options.position.value, THREE.Vector3, new THREE.Vector3() ),
      +			_spread: utils.ensureInstanceOf( options.position.spread, THREE.Vector3, new THREE.Vector3() ),
      +			_spreadClamp: utils.ensureInstanceOf( options.position.spreadClamp, THREE.Vector3, new THREE.Vector3() ),
      +			_distribution: utils.ensureTypedArg( options.position.distribution, valueTypes.NUMBER, this.type ),
      +			_randomise: utils.ensureTypedArg( options.position.randomise, valueTypes.BOOLEAN, false ),
      +			_radius: utils.ensureTypedArg( options.position.radius, valueTypes.NUMBER, 10 ),
      +			_radiusScale: utils.ensureInstanceOf( options.position.radiusScale, THREE.Vector3, new THREE.Vector3( 1, 1, 1 ) ),
      +			_distributionClamp: utils.ensureTypedArg( options.position.distributionClamp, valueTypes.NUMBER, 0 ),
      +		};
      +
      +		this.velocity = {
      +			_value: utils.ensureInstanceOf( options.velocity.value, THREE.Vector3, new THREE.Vector3() ),
      +			_spread: utils.ensureInstanceOf( options.velocity.spread, THREE.Vector3, new THREE.Vector3() ),
      +			_distribution: utils.ensureTypedArg( options.velocity.distribution, valueTypes.NUMBER, this.type ),
      +			_randomise: utils.ensureTypedArg( options.position.randomise, valueTypes.BOOLEAN, false ),
      +		};
      +
      +		this.acceleration = {
      +			_value: utils.ensureInstanceOf( options.acceleration.value, THREE.Vector3, new THREE.Vector3() ),
      +			_spread: utils.ensureInstanceOf( options.acceleration.spread, THREE.Vector3, new THREE.Vector3() ),
      +			_distribution: utils.ensureTypedArg( options.acceleration.distribution, valueTypes.NUMBER, this.type ),
      +			_randomise: utils.ensureTypedArg( options.position.randomise, valueTypes.BOOLEAN, false ),
      +		};
      +
      +		this.drag = {
      +			_value: utils.ensureTypedArg( options.drag.value, valueTypes.NUMBER, 0 ),
      +			_spread: utils.ensureTypedArg( options.drag.spread, valueTypes.NUMBER, 0 ),
      +			_randomise: utils.ensureTypedArg( options.position.randomise, valueTypes.BOOLEAN, false ),
      +		};
      +
      +		this.wiggle = {
      +			_value: utils.ensureTypedArg( options.wiggle.value, valueTypes.NUMBER, 0 ),
      +			_spread: utils.ensureTypedArg( options.wiggle.spread, valueTypes.NUMBER, 0 ),
      +		};
      +
      +		this.rotation = {
      +			_axis: utils.ensureInstanceOf( options.rotation.axis, THREE.Vector3, new THREE.Vector3( 0.0, 1.0, 0.0 ) ),
      +			_axisSpread: utils.ensureInstanceOf( options.rotation.axisSpread, THREE.Vector3, new THREE.Vector3() ),
      +			_angle: utils.ensureTypedArg( options.rotation.angle, valueTypes.NUMBER, 0 ),
      +			_angleSpread: utils.ensureTypedArg( options.rotation.angleSpread, valueTypes.NUMBER, 0 ),
      +			_static: utils.ensureTypedArg( options.rotation.static, valueTypes.BOOLEAN, false ),
      +			_center: utils.ensureInstanceOf( options.rotation.center, THREE.Vector3, this.position._value.clone() ),
      +			_randomise: utils.ensureTypedArg( options.position.randomise, valueTypes.BOOLEAN, false ),
      +		};
      +
      +
      +		this.maxAge = {
      +			_value: utils.ensureTypedArg( options.maxAge.value, valueTypes.NUMBER, 2 ),
      +			_spread: utils.ensureTypedArg( options.maxAge.spread, valueTypes.NUMBER, 0 ),
      +		};
      + +
    • + + +
    • +
      + +
      + +
      +

      The following properties can support either single values, or an array of values that change +the property over a particle’s lifetime (value over lifetime).

      + +
      + +
      		this.color = {
      +			_value: utils.ensureArrayInstanceOf( options.color.value, THREE.Color, new THREE.Color() ),
      +			_spread: utils.ensureArrayInstanceOf( options.color.spread, THREE.Vector3, new THREE.Vector3() ),
      +			_randomise: utils.ensureTypedArg( options.position.randomise, valueTypes.BOOLEAN, false ),
      +		};
      +
      +		this.opacity = {
      +			_value: utils.ensureArrayTypedArg( options.opacity.value, valueTypes.NUMBER, 1 ),
      +			_spread: utils.ensureArrayTypedArg( options.opacity.spread, valueTypes.NUMBER, 0 ),
      +			_randomise: utils.ensureTypedArg( options.position.randomise, valueTypes.BOOLEAN, false ),
      +		};
      +
      +		this.size = {
      +			_value: utils.ensureArrayTypedArg( options.size.value, valueTypes.NUMBER, 1 ),
      +			_spread: utils.ensureArrayTypedArg( options.size.spread, valueTypes.NUMBER, 0 ),
      +			_randomise: utils.ensureTypedArg( options.position.randomise, valueTypes.BOOLEAN, false ),
      +		};
      +
      +		this.angle = {
      +			_value: utils.ensureArrayTypedArg( options.angle.value, valueTypes.NUMBER, 0 ),
      +			_spread: utils.ensureArrayTypedArg( options.angle.spread, valueTypes.NUMBER, 0 ),
      +			_randomise: utils.ensureTypedArg( options.position.randomise, valueTypes.BOOLEAN, false ),
      +		};
      + +
    • + + +
    • +
      + +
      + +
      +

      Assign renaining option values.

      + +
      + +
      		this.particleCount = utils.ensureTypedArg( options.particleCount, valueTypes.NUMBER, 100 );
      +		this.duration = utils.ensureTypedArg( options.duration, valueTypes.NUMBER, null );
      +		this.isStatic = utils.ensureTypedArg( options.isStatic, valueTypes.BOOLEAN, false );
      +		this.activeMultiplier = utils.ensureTypedArg( options.activeMultiplier, valueTypes.NUMBER, 1 );
      +		this.direction = utils.ensureTypedArg( options.direction, valueTypes.NUMBER, 1 );
      + +
    • + + +
    • +
      + +
      + +
      +

      Whether this emitter is alive or not.

      + +
      + +
      		this.alive = utils.ensureTypedArg( options.alive, valueTypes.BOOLEAN, true );
      + +
    • + + +
    • +
      + +
      + +
      +

      The following properties are set internally and are not +user-controllable.

      + +
      + +
      		this.particlesPerSecond = 0;
      + +
    • + + +
    • +
      + +
      + +
      +

      The current particle index for which particles should +be marked as active on the next update cycle.

      + +
      + +
      		this.activationIndex = 0;
      + +
    • + + +
    • +
      + +
      + +
      +

      The offset in the typed arrays this emitter’s +particle’s values will start at

      + +
      + +
      		this.attributeOffset = 0;
      + +
    • + + +
    • +
      + +
      + +
      +

      The end of the range in the attribute buffers

      + +
      + +
      		this.attributeEnd = 0;
      + +
    • + + +
    • +
      + +
      + +
      +

      Holds the time the emitter has been alive for.

      + +
      + +
      		this.age = 0.0;
      + +
    • + + +
    • +
      + +
      + +
      +

      Holds the number of currently-alive particles

      + +
      + +
      		this.activeParticleCount = 0.0;
      + +
    • + + +
    • +
      + +
      + +
      +

      Holds a reference to this emitter’s group once +it’s added to one.

      + +
      + +
      		this.group = null;
      + +
    • + + +
    • +
      + +
      + +
      +

      Holds a reference to this emitter’s group’s attributes object +for easier access.

      + +
      + +
      		this.attributes = null;
      + +
    • + + +
    • +
      + +
      + +
      +

      Holds a reference to the params attribute’s typed array +for quicker access.

      + +
      + +
      		this.paramsArray = null;
      + +
    • + + +
    • +
      + +
      + +
      +

      A set of flags to determine whether particular properties +should be re-randomised when a particle is reset.

      +

      If a randomise property is given, this is preferred. +Otherwise, it looks at whether a spread value has been +given.

      +

      It allows randomization to be turned off as desired. If +all randomization is turned off, then I’d expect a performance +boost as no attribute buffers (excluding the params) +would have to be re-passed to the GPU each frame (since nothing +except the params attribute would have changed).

      + +
      + +
      		this.resetFlags = {
      + +
    • + + +
    • +
      + +
      + +
      +

      params: utils.ensureTypedArg( options.maxAge.randomise, valueTypes.BOOLEAN, !!options.maxAge.spread ) || + utils.ensureTypedArg( options.wiggle.randomise, valueTypes.BOOLEAN, !!options.wiggle.spread ),

      + +
      + +
      			position: utils.ensureTypedArg( options.position.randomise, valueTypes.BOOLEAN, false ) ||
      +				utils.ensureTypedArg( options.radius.randomise, valueTypes.BOOLEAN, false ),
      +			velocity: utils.ensureTypedArg( options.velocity.randomise, valueTypes.BOOLEAN, false ),
      +			acceleration: utils.ensureTypedArg( options.acceleration.randomise, valueTypes.BOOLEAN, false ) ||
      +				utils.ensureTypedArg( options.drag.randomise, valueTypes.BOOLEAN, false ),
      +			rotation: utils.ensureTypedArg( options.rotation.randomise, valueTypes.BOOLEAN, false ),
      +			rotationCenter: utils.ensureTypedArg( options.rotation.randomise, valueTypes.BOOLEAN, false ),
      +			size: utils.ensureTypedArg( options.size.randomise, valueTypes.BOOLEAN, false ),
      +			color: utils.ensureTypedArg( options.color.randomise, valueTypes.BOOLEAN, false ),
      +			opacity: utils.ensureTypedArg( options.opacity.randomise, valueTypes.BOOLEAN, false ),
      +			angle: utils.ensureTypedArg( options.angle.randomise, valueTypes.BOOLEAN, false ),
      +		};
      +
      +		this.updateFlags = {};
      +		this.updateCounts = {};
      + +
    • + + +
    • +
      + +
      + +
      +

      A map to indicate which emitter parameters should update +which attribute.

      + +
      + +
      		this.updateMap = {
      +			maxAge: 'params',
      +			position: 'position',
      +			velocity: 'velocity',
      +			acceleration: 'acceleration',
      +			drag: 'acceleration',
      +			wiggle: 'params',
      +			rotation: 'rotation',
      +			size: 'size',
      +			color: 'color',
      +			opacity: 'opacity',
      +			angle: 'angle',
      +		};
      +
      +		for ( const i in this.updateMap ) {
      +			if ( HAS_OWN.call( this.updateMap, i ) ) {
      +				this.updateCounts[ this.updateMap[ i ] ] = 0.0;
      +				this.updateFlags[ this.updateMap[ i ] ] = false;
      +				this._createGetterSetters( this[ i ], i );
      +			}
      +		}
      +
      +		this.bufferUpdateRanges = {};
      +		this.attributeKeys = null;
      +		this.attributeCount = 0;
      + +
    • + + +
    • +
      + +
      + +
      +

      Ensure that the value-over-lifetime property objects above +have value and spread properties that are of the same length.

      +

      Also, for now, make sure they have a length of 3 (min/max arguments here).

      + +
      + +
      		utils.ensureValueOverLifetimeCompliance( this.color, globals.valueOverLifetimeLength, globals.valueOverLifetimeLength );
      +		utils.ensureValueOverLifetimeCompliance( this.opacity, globals.valueOverLifetimeLength, globals.valueOverLifetimeLength );
      +		utils.ensureValueOverLifetimeCompliance( this.size, globals.valueOverLifetimeLength, globals.valueOverLifetimeLength );
      +		utils.ensureValueOverLifetimeCompliance( this.angle, globals.valueOverLifetimeLength, globals.valueOverLifetimeLength );
      +	}
      +
      +	_createGetterSetters( propObj, propName ) {
      +		const self = this;
      +
      +		for ( const i in propObj ) {
      +			if ( HAS_OWN.call( propObj, i ) ) {
      +
      +				const name = i.replace( '_', '' );
      +
      +				Object.defineProperty( propObj, name, {
      +					get: ( function( prop ) {
      +						return function() {
      +							return this[ prop ];
      +						};
      +					}( i ) ),
      +
      +					set: ( function( prop ) {
      +						return function( value ) {
      +							const mapName = self.updateMap[ propName ],
      +								prevValue = this[ prop ],
      +								length = globals.valueOverLifetimeLength;
      +
      +							if ( prop === '_rotationCenter' ) {
      +								self.updateFlags.rotationCenter = true;
      +								self.updateCounts.rotationCenter = 0.0;
      +							}
      +							else if ( prop === '_randomise' ) {
      +								self.resetFlags[ mapName ] = value;
      +							}
      +							else {
      +								self.updateFlags[ mapName ] = true;
      +								self.updateCounts[ mapName ] = 0.0;
      +							}
      +
      +							self.group._updateDefines();
      +
      +							this[ prop ] = value;
      + +
    • + + +
    • +
      + +
      + +
      +

      If the previous value was an array, then make +sure the provided value is interpolated correctly.

      + +
      + +
      							if ( Array.isArray( prevValue ) ) {
      +								utils.ensureValueOverLifetimeCompliance( self[ propName ], length, length );
      +							}
      +						};
      +					}( i ) ),
      +				} );
      +			}
      +		}
      +	}
      +
      +	_setBufferUpdateRanges( keys ) {
      +		this.attributeKeys = keys;
      +		this.attributeCount = keys.length;
      +
      +		for ( let i = this.attributeCount - 1; i >= 0; --i ) {
      +			this.bufferUpdateRanges[ keys[ i ] ] = {
      +				min: Number.POSITIVE_INFINITY,
      +				max: Number.NEGATIVE_INFINITY,
      +			};
      +		}
      +	}
      +
      +	_calculatePPSValue( groupMaxAge ) {
      +		const particleCount = this.particleCount;
      + +
    • + + +
    • +
      + +
      + +
      +

      Calculate the particlesPerSecond value for this emitter. It’s used +when determining which particles should die and which should live to +see another day. Or be born, for that matter. The “God” property.

      + +
      + +
      		if ( this.duration ) {
      +			this.particlesPerSecond = particleCount / ( groupMaxAge < this.duration ? groupMaxAge : this.duration );
      +		}
      +		else {
      +			this.particlesPerSecond = particleCount / groupMaxAge;
      +		}
      +	}
      +
      +	_setAttributeOffset( startIndex ) {
      +		this.attributeOffset = startIndex;
      +		this.activationIndex = startIndex;
      +		this.activationEnd = startIndex + this.particleCount;
      +	}
      +
      +
      +	_assignValue( prop, index ) {
      +		switch ( prop ) {
      +			case 'position':
      +				this._assignPositionValue( index );
      +				break;
      +
      +			case 'velocity':
      +			case 'acceleration':
      +				this._assignForceValue( index, prop );
      +				break;
      +
      +			case 'size':
      +			case 'opacity':
      +				this._assignAbsLifetimeValue( index, prop );
      +				break;
      +
      +			case 'angle':
      +				this._assignAngleValue( index );
      +				break;
      +
      +			case 'params':
      +				this._assignParamsValue( index );
      +				break;
      +
      +			case 'rotation':
      +				this._assignRotationValue( index );
      +				break;
      +
      +			case 'color':
      +				this._assignColorValue( index );
      +				break;
      +		}
      +	}
      +
      +	_assignPositionValue( index ) {
      +		const prop = this.position,
      +			attr = this.attributes.position,
      +			value = prop._value,
      +			spread = prop._spread,
      +			distribution = prop._distribution;
      +
      +		switch ( distribution ) {
      +			case distributions.BOX:
      +				utils.randomVector3( attr, index, value, spread, prop._spreadClamp );
      +				break;
      +
      +			case distributions.SPHERE:
      +				utils.randomVector3OnSphere( attr, index, value, prop._radius, prop._spread.x, prop._radiusScale, prop._spreadClamp.x, prop._distributionClamp || this.particleCount );
      +				break;
      +
      +			case distributions.DISC:
      +				utils.randomVector3OnDisc( attr, index, value, prop._radius, prop._spread.x, prop._radiusScale, prop._spreadClamp.x );
      +				break;
      +
      +			case distributions.LINE:
      +				utils.randomVector3OnLine( attr, index, value, spread );
      +				break;
      +		}
      +	}
      +
      +	_assignForceValue( index, attrName ) {
      +		const prop = this[ attrName ],
      +			value = prop._value,
      +			spread = prop._spread,
      +			distribution = prop._distribution;
      +		let pos,
      +			positionX,
      +			positionY,
      +			positionZ,
      +			i;
      +
      +		switch ( distribution ) {
      +			case distributions.BOX:
      +				utils.randomVector3( this.attributes[ attrName ], index, value, spread );
      +				break;
      +
      +			case distributions.SPHERE:
      +				pos = this.attributes.position.typedArray.array;
      +				i = index * 3;
      + +
    • + + +
    • +
      + +
      + +
      +

      Ensure position values aren’t zero, otherwise no force will be +applied. +positionX = utils.zeroToEpsilon( pos[ i ], true ); +positionY = utils.zeroToEpsilon( pos[ i + 1 ], true ); +positionZ = utils.zeroToEpsilon( pos[ i + 2 ], true );

      + +
      + +
      				positionX = pos[ i ];
      +				positionY = pos[ i + 1 ];
      +				positionZ = pos[ i + 2 ];
      +
      +				utils.randomDirectionVector3OnSphere(
      +					this.attributes[ attrName ], index,
      +					positionX, positionY, positionZ,
      +					this.position._value,
      +					prop._value.x,
      +					prop._spread.x
      +				);
      +				break;
      +
      +			case distributions.DISC:
      +				pos = this.attributes.position.typedArray.array;
      +				i = index * 3;
      + +
    • + + +
    • +
      + +
      + +
      +

      Ensure position values aren’t zero, otherwise no force will be +applied. +positionX = utils.zeroToEpsilon( pos[ i ], true ); +positionY = utils.zeroToEpsilon( pos[ i + 1 ], true ); +positionZ = utils.zeroToEpsilon( pos[ i + 2 ], true );

      + +
      + +
      				positionX = pos[ i ];
      +				positionY = pos[ i + 1 ];
      +				positionZ = pos[ i + 2 ];
      +
      +				utils.randomDirectionVector3OnDisc(
      +					this.attributes[ attrName ], index,
      +					positionX, positionY, positionZ,
      +					this.position._value,
      +					prop._value.x,
      +					prop._spread.x
      +				);
      +				break;
      +
      +			case distributions.LINE:
      +				utils.randomVector3OnLine( this.attributes[ attrName ], index, value, spread );
      +				break;
      +		}
      +
      +		if ( attrName === 'acceleration' ) {
      +			const drag = utils.clamp( utils.randomFloat( this.drag._value, this.drag._spread ), 0, 1 );
      +			this.attributes.acceleration.typedArray.array[ index * 4 + 3 ] = drag;
      +		}
      +	}
      +
      +	_assignAbsLifetimeValue( index, propName ) {
      +		const array = this.attributes[ propName ].typedArray,
      +			prop = this[ propName ];
      +
      +		if ( utils.arrayValuesAreEqual( prop._value ) && utils.arrayValuesAreEqual( prop._spread ) ) {
      +			const value = Math.abs( utils.randomFloat( prop._value[ 0 ], prop._spread[ 0 ] ) );
      +			array.setVec4Components( index, value, value, value, value );
      +		}
      +		else {
      +			array.setVec4Components( index,
      +				Math.abs( utils.randomFloat( prop._value[ 0 ], prop._spread[ 0 ] ) ),
      +				Math.abs( utils.randomFloat( prop._value[ 1 ], prop._spread[ 1 ] ) ),
      +				Math.abs( utils.randomFloat( prop._value[ 2 ], prop._spread[ 2 ] ) ),
      +				Math.abs( utils.randomFloat( prop._value[ 3 ], prop._spread[ 3 ] ) )
      +			);
      +		}
      +	}
      +
      +	_assignAngleValue( index ) {
      +		const array = this.attributes.angle.typedArray,
      +			prop = this.angle;
      +
      +		if ( utils.arrayValuesAreEqual( prop._value ) && utils.arrayValuesAreEqual( prop._spread ) ) {
      +			const value = utils.randomFloat( prop._value[ 0 ], prop._spread[ 0 ] );
      +			array.setVec4Components( index, value, value, value, value );
      +		}
      +		else {
      +			array.setVec4Components( index,
      +				utils.randomFloat( prop._value[ 0 ], prop._spread[ 0 ] ),
      +				utils.randomFloat( prop._value[ 1 ], prop._spread[ 1 ] ),
      +				utils.randomFloat( prop._value[ 2 ], prop._spread[ 2 ] ),
      +				utils.randomFloat( prop._value[ 3 ], prop._spread[ 3 ] )
      +			);
      +		}
      +	}
      +
      +	_assignParamsValue( index ) {
      +		this.attributes.params.typedArray.setVec4Components( index,
      +			this.isStatic ? 1 : 0,
      +			0.0,
      +			Math.abs( utils.randomFloat( this.maxAge._value, this.maxAge._spread ) ),
      +			utils.randomFloat( this.wiggle._value, this.wiggle._spread )
      +		);
      +	}
      +
      +	_assignRotationValue( index ) {
      +		this.attributes.rotation.typedArray.setVec3Components( index,
      +			utils.getPackedRotationAxis( this.rotation._axis, this.rotation._axisSpread ),
      +			utils.randomFloat( this.rotation._angle, this.rotation._angleSpread ),
      +			this.rotation._static ? 0 : 1
      +		);
      +
      +		this.attributes.rotationCenter.typedArray.setVec3( index, this.rotation._center );
      +	}
      +
      +	_assignColorValue( index ) {
      +		utils.randomColorAsHex( this.attributes.color, index, this.color._value, this.color._spread );
      +	}
      +
      +	_resetParticle( index ) {
      +		const resetFlags = this.resetFlags,
      +			updateFlags = this.updateFlags,
      +			updateCounts = this.updateCounts,
      +			keys = this.attributeKeys;
      +		let key,
      +			updateFlag;
      +
      +		for ( let i = this.attributeCount - 1; i >= 0; --i ) {
      +			key = keys[ i ];
      +			updateFlag = updateFlags[ key ];
      +
      +			if ( resetFlags[ key ] === true || updateFlag === true ) {
      +				this._assignValue( key, index );
      +				this._updateAttributeUpdateRange( key, index );
      +
      +				if ( updateFlag === true && updateCounts[ key ] === this.particleCount ) {
      +					updateFlags[ key ] = false;
      +					updateCounts[ key ] = 0.0;
      +				}
      +				else if ( updateFlag == true ) {
      +					++updateCounts[ key ];
      +				}
      +			}
      +		}
      +	}
      +
      +	_updateAttributeUpdateRange( attr, i ) {
      +		var ranges = this.bufferUpdateRanges[ attr ];
      +
      +		ranges.min = Math.min( i, ranges.min );
      +		ranges.max = Math.max( i, ranges.max );
      +	}
      +
      +	_resetBufferRanges() {
      +		const ranges = this.bufferUpdateRanges,
      +			keys = this.bufferUpdateKeys;
      +		let i = this.bufferUpdateCount - 1,
      +			key;
      +
      +		for ( i; i >= 0; --i ) {
      +			key = keys[ i ];
      +			ranges[ key ].min = Number.POSITIVE_INFINITY;
      +			ranges[ key ].max = Number.NEGATIVE_INFINITY;
      +		}
      +	}
      +
      +	_onRemove() {
      + +
    • + + +
    • +
      + +
      + +
      +

      Reset any properties of the emitter that were set by +a group when it was added.

      + +
      + +
      		this.particlesPerSecond = 0;
      +		this.attributeOffset = 0;
      +		this.activationIndex = 0;
      +		this.activeParticleCount = 0;
      +		this.group = null;
      +		this.attributes = null;
      +		this.paramsArray = null;
      +		this.age = 0.0;
      +	}
      +
      +	_decrementParticleCount() {
      +		--this.activeParticleCount;
      + +
    • + + +
    • +
      + +
      + +
      +

      TODO:

      +
        +
      • Trigger event if count === 0.
      • +
      + +
      + +
      	}
      +
      +	_incrementParticleCount() {
      +		'use strict';
      +		++this.activeParticleCount;
      + +
    • + + +
    • +
      + +
      + +
      +

      TODO:

      +
        +
      • Trigger event if count === this.particleCount.
      • +
      + +
      + +
      	}
      +
      +	_checkParticleAges( start, end, params, dt ) {
      +		for ( let i = end - 1, index, maxAge, age, alive; i >= start; --i ) {
      +			index = i * 4;
      +
      +			alive = params[ index ];
      +
      +			if ( alive === 0.0 ) {
      +				continue;
      +			}
      + +
    • + + +
    • +
      + +
      + +
      +

      Increment age

      + +
      + +
      			age = params[ index + 1 ];
      +			maxAge = params[ index + 2 ];
      +
      +			if ( this.direction === 1 ) {
      +				age += dt;
      +
      +				if ( age >= maxAge ) {
      +					age = 0.0;
      +					alive = 0.0;
      +					this._decrementParticleCount();
      +				}
      +			}
      +			else {
      +				age -= dt;
      +
      +				if ( age <= 0.0 ) {
      +					age = maxAge;
      +					alive = 0.0;
      +					this._decrementParticleCount();
      +				}
      +			}
      +
      +			params[ index ] = alive;
      +			params[ index + 1 ] = age;
      +
      +			this._updateAttributeUpdateRange( 'params', i );
      +		}
      +	}
      +
      +	_activateParticles( activationStart, activationEnd, params, dtPerParticle ) {
      +		const direction = this.direction;
      +
      +		for ( let i = activationStart, index, dtValue; i < activationEnd; ++i ) {
      +			index = i * 4;
      + +
    • + + +
    • +
      + +
      + +
      +

      Don’t re-activate particles that aren’t dead yet. +if ( params[ index ] !== 0.0 && ( this.particleCount !== 1 || this.activeMultiplier !== 1 ) ) { + continue; +}

      + +
      + +
      +			if ( params[ index ] != 0.0 && this.particleCount !== 1 ) {
      +				continue;
      +			}
      + +
    • + + +
    • +
      + +
      + +
      +

      Increment the active particle count.

      + +
      + +
      			this._incrementParticleCount();
      + +
    • + + +
    • +
      + +
      + +
      +

      Mark the particle as alive.

      + +
      + +
      			params[ index ] = 1.0;
      + +
    • + + +
    • +
      + +
      + +
      +

      Reset the particle

      + +
      + +
      			this._resetParticle( i );
      + +
    • + + +
    • +
      + +
      + +
      +

      Move each particle being activated to +it’s actual position in time.

      +

      This stops particles being ‘clumped’ together +when frame rates are on the lower side of 60fps +or not constant (a very real possibility!)

      + +
      + +
      			dtValue = dtPerParticle * ( i - activationStart );
      +			params[ index + 1 ] = direction === -1 ? params[ index + 2 ] - dtValue : dtValue;
      +
      +			this._updateAttributeUpdateRange( 'params', i );
      +		}
      +	}
      +
      +	/**
      +	 * Simulates one frame's worth of particles, updating particles
      +	 * that are already alive, and marking ones that are currently dead
      +	 * but should be alive as alive.
      +	 *
      +	 * If the emitter is marked as static, then this function will do nothing.
      +	 *
      +	 * @param  {Number} dt The number of seconds to simulate (deltaTime)
      +	 */
      +	tick( dt ) {
      +		if ( this.isStatic ) {
      +			return;
      +		}
      +
      +		if ( this.paramsArray === null ) {
      +			this.paramsArray = this.attributes.params.typedArray.array;
      +		}
      +
      +		const start = this.attributeOffset,
      +			end = start + this.particleCount,
      +			params = this.paramsArray, // vec3( alive, age, maxAge, wiggle )
      +			ppsDt = this.particlesPerSecond * this.activeMultiplier * dt,
      +			activationIndex = this.activationIndex;
      + +
    • + + +
    • +
      + +
      + +
      +

      Reset the buffer update indices.

      + +
      + +
      		this._resetBufferRanges();
      + +
    • + + +
    • +
      + +
      + +
      +

      Increment age for those particles that are alive, +and kill off any particles whose age is over the limit.

      + +
      + +
      		this._checkParticleAges( start, end, params, dt );
      + +
    • + + +
    • +
      + +
      + +
      +

      If the emitter is dead, reset the age of the emitter to zero, +ready to go again if required

      + +
      + +
      		if ( this.alive === false ) {
      +			this.age = 0.0;
      +			return;
      +		}
      + +
    • + + +
    • +
      + +
      + +
      +

      If the emitter has a specified lifetime and we’ve exceeded it, +mark the emitter as dead.

      + +
      + +
      		if ( this.duration !== null && this.age > this.duration ) {
      +			this.alive = false;
      +			this.age = 0.0;
      +			return;
      +		}
      +
      +
      +		const activationStart = this.particleCount === 1 ? activationIndex : ( activationIndex | 0 ),
      +			activationEnd = Math.min( activationStart + ppsDt, this.activationEnd ),
      +			activationCount = activationEnd - this.activationIndex | 0,
      +			dtPerParticle = activationCount > 0 ? dt / activationCount : 0;
      +
      +		this._activateParticles( activationStart, activationEnd, params, dtPerParticle );
      + +
    • + + +
    • +
      + +
      + +
      +

      Move the activation window forward, soldier.

      + +
      + +
      		this.activationIndex += ppsDt;
      +
      +		if ( this.activationIndex > end ) {
      +			this.activationIndex = start;
      +		}
      + +
    • + + +
    • +
      + +
      + +
      +

      Increment the age of the emitter.

      + +
      + +
      		this.age += dt;
      +	}
      +
      +	/**
      +	 * Resets all the emitter's particles to their start positions
      +	 * and marks the particles as dead if the `force` argument is
      +	 * true.
      +	 *
      +	 * @param  {Boolean} [force=undefined] If true, all particles will be marked as dead instantly.
      +	 * @return {Emitter}       This emitter instance.
      +	 */
      +	reset( force ) {
      +		this.age = 0.0;
      +		this.alive = false;
      +
      +		if ( force === true ) {
      +			const start = this.attributeOffset,
      +				end = start + this.particleCount,
      +				array = this.paramsArray,
      +				attr = this.attributes.params.bufferAttribute;
      +
      +			for ( let i = end - 1, index; i >= start; --i ) {
      +				index = i * 4;
      +
      +				array[ index ] = 0.0;
      +				array[ index + 1 ] = 0.0;
      +			}
      +
      +			attr.updateRange.offset = 0;
      +			attr.updateRange.count = -1;
      +			attr.needsUpdate = true;
      +		}
      +
      +		return this;
      +	}
      +
      +	/**
      +	 * Enables the emitter. If not already enabled, the emitter
      +	 * will start emitting particles.
      +	 *
      +	 * @return {Emitter} This emitter instance.
      +	 */
      +	enable() {
      +		this.alive = true;
      +		return this;
      +	}
      +
      +	/**
      +	 * Disables th emitter, but does not instantly remove it's
      +	 * particles fromt the scene. When called, the emitter will be
      +	 * 'switched off' and just stop emitting. Any particle's alive will
      +	 * be allowed to finish their lifecycle.
      +	 *
      +	 * @return {Emitter} This emitter instance.
      +	 */
      +	disable() {
      +		this.alive = false;
      +		return this;
      +	}
      +
      +	/**
      +	 * Remove this emitter from it's parent group (if it has been added to one).
      +	 * Delgates to Group.prototype.removeEmitter().
      +	 *
      +	 * When called, all particle's belonging to this emitter will be instantly
      +	 * removed from the scene.
      +	 *
      +	 * @return {Emitter} This emitter instance.
      +	 *
      +	 * @see Group.prototype.removeEmitter
      +	 */
      +	remove() {
      +		if ( this.group !== null ) {
      +			this.group.removeEmitter( this );
      +		}
      +		else {
      +			console.error( 'Emitter does not belong to a group, cannot remove.' );
      +		}
      +
      +		return this;
      +	}
      +}
      + +
    • + +
    +
    + + diff --git a/docs/source/src/core/Group.html b/docs/source/src/core/Group.html new file mode 100644 index 0000000..6ddc3f4 --- /dev/null +++ b/docs/source/src/core/Group.html @@ -0,0 +1,1485 @@ + + + + + Group.js + + + + + +
    +
    + + + +
      + +
    • +
      +

      Group.js

      +
      +
    • + + + +
    • +
      + +
      + +
      + +
      + +
      import * as THREE from 'three';
      +import valueTypes from '@/constants/valueTypes';
      +import globals from '@/constants/globals';
      +import utils from './utils';
      +import ShaderAttribute from '@/helpers/ShaderAttribute';
      +import shaders from '@/shaders/shaders';
      +import Emitter from './Emitter';
      +
      +const HAS_OWN = Object.prototype.hasOwnProperty;
      +
      +/**
      + * An SPE.Group instance.
      + * @typedef {Object} Group
      + * @see SPE.Group
      + */
      +
      +/**
      + * A map of options to configure an SPE.Group instance.
      + * @typedef {Object} GroupOptions
      + *
      + * @property {Object} texture An object describing the texture used by the group.
      + *
      + * @property {Object} texture.value An instance of THREE.Texture.
      + *
      + * @property {Object=} texture.frames A THREE.Vector2 instance describing the number
      + *                                    of frames on the x- and y-axis of the given texture.
      + *                                    If not provided, the texture will NOT be treated as
      + *                                    a sprite-sheet and as such will NOT be animated.
      + *
      + * @property {Number} [texture.frameCount=texture.frames.x * texture.frames.y] The total number of frames in the sprite-sheet.
      + *                                                                   Allows for sprite-sheets that don't fill the entire
      + *                                                                   texture.
      + *
      + * @property {Number} texture.loop The number of loops through the sprite-sheet that should
      + *                                 be performed over the course of a single particle's lifetime.
      + *
      + * @property {Number} fixedTimeStep If no `dt` (or `deltaTime`) value is passed to this group's
      + *                                  `tick()` function, this number will be used to move the particle
      + *                                  simulation forward. Value in SECONDS.
      + *
      + * @property {Boolean} hasPerspective Whether the distance a particle is from the camera should affect
      + *                                    the particle's size.
      + *
      + * @property {Boolean} colorize Whether the particles in this group should be rendered with color, or
      + *                              whether the only color of particles will come from the provided texture.
      + *
      + * @property {Number} blending One of Three.js's blending modes to apply to this group's `ShaderMaterial`.
      + *
      + * @property {Boolean} transparent Whether these particle's should be rendered with transparency.
      + *
      + * @property {Number} alphaTest Sets the alpha value to be used when running an alpha test on the `texture.value` property. Value between 0 and 1.
      + *
      + * @property {Boolean} depthWrite Whether rendering the group has any effect on the depth buffer.
      + *
      + * @property {Boolean} depthTest Whether to have depth test enabled when rendering this group.
      + *
      + * @property {Boolean} fog Whether this group's particles should be affected by their scene's fog.
      + *
      + * @property {Number} scale The scale factor to apply to this group's particle sizes. Useful for
      + *                          setting particle sizes to be relative to renderer size.
      + */
      +
      +
      +/**
      + * The SPE.Group class. Creates a new group, containing a material, geometry, and mesh.
      + *
      + * @constructor
      + * @param {GroupOptions} options A map of options to configure the group instance.
      + */
      +export default class Group {
      +	constructor( opts ) {
      + +
    • + + +
    • +
      + +
      + +
      +

      Ensure we have a map of options to play with

      + +
      + +
      		const options = utils.ensureTypedArg( opts, valueTypes.OBJECT, {} );
      +		options.texture = utils.ensureTypedArg( options.texture, valueTypes.OBJECT, {} );
      + +
    • + + +
    • +
      + +
      + +
      +

      Assign a UUID to this instance

      + +
      + +
      		this.uuid = THREE.Math.generateUUID();
      + +
    • + + +
    • +
      + +
      + +
      +

      If no deltaTime value is passed to the SPE.Group.tick function, +the value of this property will be used to advance the simulation.

      + +
      + +
      		this.fixedTimeStep = utils.ensureTypedArg( options.fixedTimeStep, valueTypes.NUMBER, 0.016 );
      + +
    • + + +
    • +
      + +
      + +
      +

      Set properties used in the uniforms map, starting with the +texture stuff.

      + +
      + +
      		this.texture = utils.ensureInstanceOf( options.texture.value, THREE.Texture, null );
      +		this.textureFrames = utils.ensureInstanceOf( options.texture.frames, THREE.Vector2, new THREE.Vector2( 1, 1 ) );
      +		this.textureFrameCount = utils.ensureTypedArg( options.texture.frameCount, valueTypes.NUMBER, this.textureFrames.x * this.textureFrames.y );
      +		this.textureLoop = utils.ensureTypedArg( options.texture.loop, valueTypes.NUMBER, 1 );
      +		this.textureFrames.max( new THREE.Vector2( 1, 1 ) );
      +
      +		this.hasPerspective = utils.ensureTypedArg( options.hasPerspective, valueTypes.BOOLEAN, true );
      +		this.colorize = utils.ensureTypedArg( options.colorize, valueTypes.BOOLEAN, true );
      +
      +		this.maxParticleCount = utils.ensureTypedArg( options.maxParticleCount, valueTypes.NUMBER, null );
      + +
    • + + +
    • +
      + +
      + +
      +

      Set properties used to define the ShaderMaterial’s appearance.

      + +
      + +
      		this.blending = utils.ensureTypedArg( options.blending, valueTypes.NUMBER, THREE.AdditiveBlending );
      +		this.transparent = utils.ensureTypedArg( options.transparent, valueTypes.BOOLEAN, true );
      +		this.alphaTest = parseFloat( utils.ensureTypedArg( options.alphaTest, valueTypes.NUMBER, 0.0 ) );
      +		this.depthWrite = utils.ensureTypedArg( options.depthWrite, valueTypes.BOOLEAN, false );
      +		this.depthTest = utils.ensureTypedArg( options.depthTest, valueTypes.BOOLEAN, true );
      +		this.fog = utils.ensureTypedArg( options.fog, valueTypes.BOOLEAN, true );
      +		this.scale = utils.ensureTypedArg( options.scale, valueTypes.NUMBER, 300 );
      + +
    • + + +
    • +
      + +
      + +
      +

      Where emitter’s go to curl up in a warm blanket and live +out their days.

      + +
      + +
      		this.emitters = [];
      +		this.emitterIDs = [];
      + +
    • + + +
    • +
      + +
      + +
      +

      Create properties for use by the emitter pooling functions.

      + +
      + +
      		this._pool = [];
      +		this._poolCreationSettings = null;
      +		this._createNewWhenPoolEmpty = 0;
      + +
    • + + +
    • +
      + +
      + +
      +

      Whether all attributes should be forced to updated +their entire buffer contents on the next tick.

      +

      Used when an emitter is removed.

      + +
      + +
      		this._attributesNeedRefresh = false;
      +		this._attributesNeedDynamicReset = false;
      +
      +		this.particleCount = 0;
      + +
    • + + +
    • +
      + +
      + +
      +

      Map of uniforms to be applied to the ShaderMaterial instance.

      + +
      + +
      		this.uniforms = {
      +			tex: {
      +				type: 't',
      +				value: this.texture,
      +			},
      +			textureAnimation: {
      +				type: 'v4',
      +				value: new THREE.Vector4(
      +					this.textureFrames.x,
      +					this.textureFrames.y,
      +					this.textureFrameCount,
      +					Math.max( Math.abs( this.textureLoop ), 1.0 )
      +				),
      +			},
      +			fogColor: {
      +				type: 'c',
      +				value: this.fog ? new THREE.Color() : null,
      +			},
      +			fogNear: {
      +				type: 'f',
      +				value: 10,
      +			},
      +			fogFar: {
      +				type: 'f',
      +				value: 200,
      +			},
      +			fogDensity: {
      +				type: 'f',
      +				value: 0.5,
      +			},
      +			deltaTime: {
      +				type: 'f',
      +				value: 0,
      +			},
      +			runTime: {
      +				type: 'f',
      +				value: 0,
      +			},
      +			scale: {
      +				type: 'f',
      +				value: this.scale,
      +			},
      +		};
      + +
    • + + +
    • +
      + +
      + +
      +

      Add some defines into the mix…

      + +
      + +
      		this.defines = {
      +			HAS_PERSPECTIVE: this.hasPerspective,
      +			COLORIZE: this.colorize,
      +			VALUE_OVER_LIFETIME_LENGTH: globals.valueOverLifetimeLength,
      +
      +			SHOULD_ROTATE_TEXTURE: false,
      +			SHOULD_ROTATE_PARTICLES: false,
      +			SHOULD_WIGGLE_PARTICLES: false,
      +
      +			SHOULD_CALCULATE_SPRITE: this.textureFrames.x > 1 || this.textureFrames.y > 1,
      +		};
      + +
    • + + +
    • +
      + +
      + +
      +

      Map of all attributes to be applied to the particles.

      +

      See ShaderAttribute for a bit more info on this.

      + +
      + +
      		this.attributes = {
      +			position: new ShaderAttribute( 'v3', true ),
      +			acceleration: new ShaderAttribute( 'v4', true ), // w component is drag
      +			velocity: new ShaderAttribute( 'v3', true ),
      +			rotation: new ShaderAttribute( 'v4', true ),
      +			rotationCenter: new ShaderAttribute( 'v3', true ),
      +			params: new ShaderAttribute( 'v4', true ), // Holds (alive, age, delay, wiggle)
      +			size: new ShaderAttribute( 'v4', true ),
      +			angle: new ShaderAttribute( 'v4', true ),
      +			color: new ShaderAttribute( 'v4', true ),
      +			opacity: new ShaderAttribute( 'v4', true ),
      +		};
      +
      +		this.attributeKeys = Object.keys( this.attributes );
      +		this.attributeCount = this.attributeKeys.length;
      + +
    • + + +
    • +
      + +
      + +
      +

      Create the ShaderMaterial instance that’ll help render the +particles.

      + +
      + +
      		this.material = new THREE.ShaderMaterial( {
      +			uniforms: this.uniforms,
      +			vertexShader: shaders.vertex,
      +			fragmentShader: shaders.fragment,
      +			blending: this.blending,
      +			transparent: this.transparent,
      +			alphaTest: this.alphaTest,
      +			depthWrite: this.depthWrite,
      +			depthTest: this.depthTest,
      +			defines: this.defines,
      +			fog: this.fog,
      +		} );
      + +
    • + + +
    • +
      + +
      + +
      +

      Create the BufferGeometry and Points instances, ensuring +the geometry and material are given to the latter.

      + +
      + +
      		this.geometry = new THREE.BufferGeometry();
      +		this.mesh = new THREE.Points( this.geometry, this.material );
      +
      +		if ( this.maxParticleCount === null ) {
      +			console.warn( 'SPE.Group: No maxParticleCount specified. Adding emitters after rendering will probably cause errors.' );
      +		}
      +	}
      +
      +	_updateDefines() {
      +		const emitters = this.emitters,
      +			defines = this.defines;
      +		let emitter,
      +			i = emitters.length - 1;
      +
      +		for ( i; i >= 0; --i ) {
      +			emitter = emitters[ i ];
      + +
    • + + +
    • +
      + +
      + +
      +

      Only do angle calculation if there’s no spritesheet defined.

      +

      Saves calculations being done and then overwritten in the shaders.

      + +
      + +
      			if ( !defines.SHOULD_CALCULATE_SPRITE ) {
      +				defines.SHOULD_ROTATE_TEXTURE = defines.SHOULD_ROTATE_TEXTURE || !!Math.max(
      +					Math.max.apply( null, emitter.angle.value ),
      +					Math.max.apply( null, emitter.angle.spread )
      +				);
      +			}
      +
      +			defines.SHOULD_ROTATE_PARTICLES = defines.SHOULD_ROTATE_PARTICLES || !!Math.max(
      +				emitter.rotation.angle,
      +				emitter.rotation.angleSpread
      +			);
      +
      +			defines.SHOULD_WIGGLE_PARTICLES = defines.SHOULD_WIGGLE_PARTICLES || !!Math.max(
      +				emitter.wiggle.value,
      +				emitter.wiggle.spread
      +			);
      +		}
      +
      +		this.material.needsUpdate = true;
      +	}
      +
      +	_applyAttributesToGeometry() {
      +		const attributes = this.attributes,
      +			geometry = this.geometry,
      +			geometryAttributes = geometry.attributes;
      +		let attribute,
      +			geometryAttribute;
      + +
    • + + +
    • +
      + +
      + +
      +

      Loop through all the shader attributes and assign (or re-assign) +typed array buffers to each one.

      + +
      + +
      		for ( const attr in attributes ) {
      +			if ( HAS_OWN.call( attributes, attr ) ) {
      +				attribute = attributes[ attr ];
      +				geometryAttribute = geometryAttributes[ attr ];
      + +
    • + + +
    • +
      + +
      + +
      +

      Update the array if this attribute exists on the geometry.

      +

      This needs to be done because the attribute’s typed array might have +been resized and reinstantiated, and might now be looking at a +different ArrayBuffer, so reference needs updating.

      + +
      + +
      				if ( geometryAttribute ) {
      +					geometryAttribute.array = attribute.typedArray.array;
      +				}
      + +
    • + + +
    • +
      + +
      + +
      +

      // Add the attribute to the geometry if it doesn’t already exist.

      + +
      + +
      				else {
      +					geometry.addAttribute( attr, attribute.bufferAttribute );
      +				}
      + +
    • + + +
    • +
      + +
      + +
      +

      Mark the attribute as needing an update the next time a frame is rendered.

      + +
      + +
      				attribute.bufferAttribute.needsUpdate = true;
      +			}
      +		}
      + +
    • + + +
    • +
      + +
      + +
      +

      Mark the draw range on the geometry. This will ensure +only the values in the attribute buffers that are +associated with a particle will be used in THREE’s +render cycle.

      + +
      + +
      		this.geometry.setDrawRange( 0, this.particleCount );
      +	}
      +
      +	/**
      +     * Adds an SPE.Emitter instance to this group, creating particle values and
      +     * assigning them to this group's shader attributes.
      +     *
      +     * @param {Emitter} emitter The emitter to add to this group.
      +     */
      +	addEmitter( emitter ) {
      + +
    • + + +
    • +
      + +
      + +
      +

      Ensure an actual emitter instance is passed here.

      +

      Decided not to throw here, just in case a scene’s +rendering would be paused. Logging an error instead +of stopping execution if exceptions aren’t caught.

      + +
      + +
      		if ( emitter instanceof Emitter === false ) {
      +			console.error( '`emitter` argument must be instance of Emitter. Was provided with:', emitter );
      +			return;
      +		}
      + +
    • + + +
    • +
      + +
      + +
      +

      If the emitter already exists as a member of this group, then +stop here, we don’t want to add it again.

      + +
      + +
      		else if ( this.emitterIDs.indexOf( emitter.uuid ) > -1 ) {
      +			console.error( 'Emitter already exists in this group. Will not add again.' );
      +			return;
      +		}
      + +
    • + + +
    • +
      + +
      + +
      +

      And finally, if the emitter is a member of another group, +don’t add it to this group.

      + +
      + +
      		else if ( emitter.group !== null ) {
      +			console.error( 'Emitter already belongs to another group. Will not add to requested group.' );
      +			return;
      +		}
      +
      +		const attributes = this.attributes,
      +			start = this.particleCount,
      +			end = start + emitter.particleCount;
      + +
    • + + +
    • +
      + +
      + +
      +

      Update this group’s particle count.

      + +
      + +
      		this.particleCount = end;
      + +
    • + + +
    • +
      + +
      + +
      +

      Emit a warning if the emitter being added will exceed the buffer sizes specified.

      + +
      + +
      		if ( this.maxParticleCount !== null && this.particleCount > this.maxParticleCount ) {
      +			console.warn( 'SPE.Group: maxParticleCount exceeded. Requesting', this.particleCount, 'particles, can support only', this.maxParticleCount );
      +		}
      + +
    • + + +
    • +
      + +
      + +
      +

      Set the particlesPerSecond value (PPS) on the emitter. +It’s used to determine how many particles to release +on a per-frame basis.

      + +
      + +
      		emitter._calculatePPSValue( emitter.maxAge._value + emitter.maxAge._spread );
      +		emitter._setBufferUpdateRanges( this.attributeKeys );
      + +
    • + + +
    • +
      + +
      + +
      +

      Store the offset value in the TypedArray attributes for this emitter.

      + +
      + +
      		emitter._setAttributeOffset( start );
      + +
    • + + +
    • +
      + +
      + +
      +

      Save a reference to this group on the emitter so it knows +where it belongs.

      + +
      + +
      		emitter.group = this;
      + +
    • + + +
    • +
      + +
      + +
      +

      Store reference to the attributes on the emitter for +easier access during the emitter’s tick function.

      + +
      + +
      		emitter.attributes = this.attributes;
      + +
    • + + +
    • +
      + +
      + +
      +

      Ensure the attributes and their BufferAttributes exist, and their +TypedArrays are of the correct size.

      + +
      + +
      		for ( const attr in attributes ) {
      +			if ( HAS_OWN.call( attributes, attr ) ) {
      + +
    • + + +
    • +
      + +
      + +
      +

      When creating a buffer, pass through the maxParticle count +if one is specified.

      + +
      + +
      				attributes[ attr ]._createBufferAttribute(
      +					this.maxParticleCount !== null ?
      +						this.maxParticleCount :
      +						this.particleCount
      +				);
      +			}
      +		}
      + +
    • + + +
    • +
      + +
      + +
      +

      Loop through each particle this emitter wants to have, and create the attributes values, +storing them in the TypedArrays that each attribute holds.

      + +
      + +
      		for ( let i = start; i < end; ++i ) {
      +			emitter._assignPositionValue( i );
      +			emitter._assignForceValue( i, 'velocity' );
      +			emitter._assignForceValue( i, 'acceleration' );
      +			emitter._assignAbsLifetimeValue( i, 'opacity' );
      +			emitter._assignAbsLifetimeValue( i, 'size' );
      +			emitter._assignAngleValue( i );
      +			emitter._assignRotationValue( i );
      +			emitter._assignParamsValue( i );
      +			emitter._assignColorValue( i );
      +		}
      + +
    • + + +
    • +
      + +
      + +
      +

      Update the geometry and make sure the attributes are referencing +the typed arrays properly.

      + +
      + +
      		this._applyAttributesToGeometry();
      + +
    • + + +
    • +
      + +
      + +
      +

      Store this emitter in this group’s emitter’s store.

      + +
      + +
      		this.emitters.push( emitter );
      +		this.emitterIDs.push( emitter.uuid );
      + +
    • + + +
    • +
      + +
      + +
      +

      Update certain flags to enable shader calculations only if they’re necessary.

      + +
      + +
      		this._updateDefines( emitter );
      + +
    • + + +
    • +
      + +
      + +
      +

      Update the material since defines might have changed

      + +
      + +
      		this.material.needsUpdate = true;
      +		this.geometry.needsUpdate = true;
      +		this._attributesNeedRefresh = true;
      + +
    • + + +
    • +
      + +
      + +
      +

      Return the group to enable chaining.

      + +
      + +
      		return this;
      +	}
      +
      +	/**
      +     * Removes an Emitter instance from this group. When called,
      +     * all particle's belonging to the given emitter will be instantly
      +     * removed from the scene.
      +     *
      +     * @param {Emitter} emitter The emitter to add to this group.
      +     */
      +	removeEmitter( emitter ) {
      +		const emitterIndex = this.emitterIDs.indexOf( emitter.uuid );
      + +
    • + + +
    • +
      + +
      + +
      +

      Ensure an actual emitter instance is passed here.

      +

      Decided not to throw here, just in case a scene’s +rendering would be paused. Logging an error instead +of stopping execution if exceptions aren’t caught.

      + +
      + +
      		if ( emitter instanceof Emitter === false ) {
      +			console.error( '`emitter` argument must be instance of SPE.Emitter. Was provided with:', emitter );
      +			return;
      +		}
      + +
    • + + +
    • +
      + +
      + +
      +

      Issue an error if the emitter isn’t a member of this group.

      + +
      + +
      		else if ( emitterIndex === -1 ) {
      +			console.error( 'Emitter does not exist in this group. Will not remove.' );
      +			return;
      +		}
      + +
    • + + +
    • +
      + +
      + +
      +

      Kill all particles by marking them as dead +and their age as 0.

      + +
      + +
      		const start = emitter.attributeOffset,
      +			end = start + emitter.particleCount,
      +			params = this.attributes.params.typedArray;
      + +
    • + + +
    • +
      + +
      + +
      +

      Set alive and age to zero.

      + +
      + +
      		for ( let i = start; i < end; ++i ) {
      +			params.array[ i * 4 ] = 0.0;
      +			params.array[ i * 4 + 1 ] = 0.0;
      +		}
      + +
    • + + +
    • +
      + +
      + +
      +

      Remove the emitter from this group’s “store”.

      + +
      + +
      		this.emitters.splice( emitterIndex, 1 );
      +		this.emitterIDs.splice( emitterIndex, 1 );
      + +
    • + + +
    • +
      + +
      + +
      +

      Remove this emitter’s attribute values from all shader attributes. +The .splice() call here also marks each attribute’s buffer +as needing to update it’s entire contents.

      + +
      + +
      		for ( const attr in this.attributes ) {
      +			if ( HAS_OWN.call( this.attributes, attr ) ) {
      +				this.attributes[ attr ].splice( start, end );
      +			}
      +		}
      + +
    • + + +
    • +
      + +
      + +
      +

      Ensure this group’s particle count is correct.

      + +
      + +
      		this.particleCount -= emitter.particleCount;
      + +
    • + + +
    • +
      + +
      + +
      +

      Call the emitter’s remove method.

      + +
      + +
      		emitter._onRemove();
      + +
    • + + +
    • +
      + +
      + +
      +

      Set a flag to indicate that the attribute buffers should +be updated in their entirety on the next frame.

      + +
      + +
      		this._attributesNeedRefresh = true;
      +	}
      +
      +
      +	/**
      +     * Fetch a single emitter instance from the pool.
      +     * If there are no objects in the pool, a new emitter will be
      +     * created if specified.
      +     *
      +     * @return {Emitter|null}
      +     */
      +	getFromPool() {
      +		const pool = this._pool,
      +			createNew = this._createNewWhenPoolEmpty;
      +
      +		if ( pool.length ) {
      +			return pool.pop();
      +		}
      +		else if ( createNew ) {
      +			const emitter = new Emitter( this._poolCreationSettings );
      +
      +			this.addEmitter( emitter );
      +
      +			return emitter;
      +		}
      +
      +		return null;
      +	}
      +
      +
      +	/**
      +     * Release an emitter into the pool.
      +     *
      +     * @param  {ShaderParticleEmitter} emitter
      +     * @return {Group} This group instance.
      +     */
      +	releaseIntoPool( emitter ) {
      +		if ( emitter instanceof Emitter === false ) {
      +			console.error( 'Argument is not instanceof Emitter:', emitter );
      +			return;
      +		}
      +
      +		emitter.reset();
      +		this._pool.unshift( emitter );
      +
      +		return this;
      +	}
      +
      +
      +	/**
      +     * Get the pool array
      +     *
      +     * @return {Array}
      +     */
      +	getPool() {
      +		return this._pool;
      +	}
      +
      +
      +	/**
      +     * Add a pool of emitters to this particle group
      +     *
      +     * @param {Number} numEmitters      The number of emitters to add to the pool.
      +     * @param {EmitterOptions|Array} emitterOptions  An object, or array of objects, describing the options to pass to each emitter.
      +     * @param {Boolean} createNew       Should a new emitter be created if the pool runs out?
      +     * @return {Group} This group instance.
      +     */
      +	addPool( numEmitters, emitterOptions, createNew ) {
      + +
    • + + +
    • +
      + +
      + +
      +

      Save relevant settings and flags.

      + +
      + +
      		this._poolCreationSettings = emitterOptions;
      +		this._createNewWhenPoolEmpty = !!createNew;
      + +
    • + + +
    • +
      + +
      + +
      +

      Create the emitters, add them to this group and the pool.

      + +
      + +
      		for ( let i = 0; i < numEmitters; ++i ) {
      +			let args;
      +
      +			if ( Array.isArray( emitterOptions ) ) {
      +				args = emitterOptions[ i ];
      +			}
      +			else {
      +				args = emitterOptions;
      +			}
      +
      +			const emitter = new Emitter( args );
      +
      +			this.addEmitter( emitter );
      +			this.releaseIntoPool( emitter );
      +		}
      +
      +		return this;
      +	}
      +
      +
      +
      +	_triggerSingleEmitter( pos ) {
      +		const emitter = this.getFromPool();
      +
      +		if ( emitter === null ) {
      +			console.log( 'Group pool ran out.' );
      +			return;
      +		}
      + +
    • + + +
    • +
      + +
      + +
      +

      TODO:

      +
        +
      • Make sure buffers are update with the new position.
      • +
      + +
      + +
      		if ( pos instanceof THREE.Vector3 ) {
      +			emitter.position.value.copy( pos );
      + +
    • + + +
    • +
      + +
      + +
      +

      Trigger the setter for this property to force an +update to the emitter’s position attribute.

      + +
      + +
      			emitter.position.value = emitter.position.value; // eslint-disable-line
      +		}
      +
      +		emitter.enable();
      +
      +		setTimeout( () => {
      +			emitter.disable();
      +			this.releaseIntoPool( emitter );
      +		}, ( Math.max( emitter.duration, ( emitter.maxAge.value + emitter.maxAge.spread ) ) ) * 1000 );
      +
      +		return this;
      +	}
      +
      +
      +	/**
      +     * Set a given number of emitters as alive, with an optional position
      +     * vector3 to move them to.
      +     *
      +     * @param  {Number} numEmitters The number of emitters to activate
      +     * @param  {Object} [position=undefined] A THREE.Vector3 instance describing the position to activate the emitter(s) at.
      +     * @return {Group} This group instance.
      +     */
      +	triggerPoolEmitter( numEmitters, position ) {
      +		if ( typeof numEmitters === 'number' && numEmitters > 1 ) {
      +			for ( let i = 0; i < numEmitters; ++i ) {
      +				this._triggerSingleEmitter( position );
      +			}
      +		}
      +		else {
      +			this._triggerSingleEmitter( position );
      +		}
      +
      +		return this;
      +	}
      +
      +
      +
      +	_updateUniforms( dt ) {
      +		this.uniforms.runTime.value += dt;
      +		this.uniforms.deltaTime.value = dt;
      +	}
      +
      +	_resetBufferRanges() {
      +		const keys = this.attributeKeys,
      +			attrs = this.attributes;
      +		let i = this.attributeCount - 1;
      +
      +		for ( i; i >= 0; --i ) {
      +			attrs[ keys[ i ] ].resetUpdateRange();
      +		}
      +	}
      +
      +
      +	_updateBuffers( emitter ) {
      +		const keys = this.attributeKeys,
      +			attrs = this.attributes,
      +			emitterRanges = emitter.bufferUpdateRanges;
      +		let key,
      +			emitterAttr,
      +			attr;
      +
      +		for ( let i = this.attributeCount - 1; i >= 0; --i ) {
      +			key = keys[ i ];
      +			emitterAttr = emitterRanges[ key ];
      +			attr = attrs[ key ];
      +			attr.setUpdateRange( emitterAttr.min, emitterAttr.max );
      +			attr.flagUpdate();
      +		}
      +	}
      +
      +
      +	/**
      +     * Simulate all the emitter's belonging to this group, updating
      +     * attribute values along the way.
      +     * @param  {Number} [dt=Group's `fixedTimeStep` value] The number of seconds to simulate the group's emitters for (deltaTime)
      +     */
      +	tick( dt ) {
      +		const emitters = this.emitters,
      +			numEmitters = emitters.length,
      +			deltaTime = dt || this.fixedTimeStep,
      +			keys = this.attributeKeys,
      +			attrs = this.attributes;
      + +
    • + + +
    • +
      + +
      + +
      +

      Update uniform values.

      + +
      + +
      		this._updateUniforms( deltaTime );
      + +
    • + + +
    • +
      + +
      + +
      +

      Reset buffer update ranges on the shader attributes.

      + +
      + +
      		this._resetBufferRanges();
      + +
    • + + +
    • +
      + +
      + +
      +

      If nothing needs updating, then stop here.

      + +
      + +
      		if (
      +			numEmitters === 0 &&
      +            this._attributesNeedRefresh === false &&
      +            this._attributesNeedDynamicReset === false
      +		) {
      +			return;
      +		}
      + +
    • + + +
    • +
      + +
      + +
      +

      Loop through each emitter in this group and +simulate it, then update the shader attribute +buffers.

      + +
      + +
      		for ( let i = 0, emitter; i < numEmitters; ++i ) {
      +			emitter = emitters[ i ];
      +			emitter.tick( deltaTime );
      +			this._updateBuffers( emitter );
      +		}
      + +
    • + + +
    • +
      + +
      + +
      +

      If the shader attributes have been refreshed, +then the dynamic properties of each buffer +attribute will need to be reset back to +what they should be.

      + +
      + +
      		if ( this._attributesNeedDynamicReset === true ) {
      +			for ( let i = this.attributeCount - 1; i >= 0; --i ) {
      +				attrs[ keys[ i ] ].resetDynamic();
      +			}
      +
      +			this._attributesNeedDynamicReset = false;
      +		}
      + +
    • + + +
    • +
      + +
      + +
      +

      If this group’s shader attributes need a full refresh +then mark each attribute’s buffer attribute as +needing so.

      + +
      + +
      		if ( this._attributesNeedRefresh === true ) {
      +			for ( let i = this.attributeCount - 1; i >= 0; --i ) {
      +				attrs[ keys[ i ] ].forceUpdateAll();
      +			}
      +
      +			this._attributesNeedRefresh = false;
      +			this._attributesNeedDynamicReset = true;
      +		}
      +	}
      +
      +
      +	/**
      +     * Dipose the geometry and material for the group.
      +     *
      +     * @return {Group} Group instance.
      +     */
      +	dispose() {
      +		this.geometry.dispose();
      +		this.material.dispose();
      +		return this;
      +	}
      +}
      + +
    • + +
    +
    + + diff --git a/docs/source/src/core/utils.html b/docs/source/src/core/utils.html new file mode 100644 index 0000000..e6ecc0d --- /dev/null +++ b/docs/source/src/core/utils.html @@ -0,0 +1,1046 @@ + + + + + utils.js + + + + + +
    +
    + + + +
      + +
    • +
      +

      utils.js

      +
      +
    • + + + +
    • +
      + +
      + +
      + +
      + +
      import * as THREE from 'three';
      +import valueTypes from '@/constants/valueTypes';
      +
      +/**
      + * A bunch of utility functions used throughout the library.
      + * @namespace
      + * @type {Object}
      + */
      +export default {
      +
      +	/**
      +     * Given a value, a type, and a default value to fallback to,
      +     * ensure the given argument adheres to the type requesting,
      +     * returning the default value if type check is false.
      +     *
      +     * @param  {(boolean|string|number|object)} arg          The value to perform a type-check on.
      +     * @param  {String} type         The type the `arg` argument should adhere to.
      +     * @param  {(boolean|string|number|object)} defaultValue A default value to fallback on if the type check fails.
      +     * @return {(boolean|string|number|object)}              The given value if type check passes, or the default value if it fails.
      +     */
      +	ensureTypedArg( arg, type, defaultValue ) {
      +		'use strict';
      +
      +		if ( typeof arg === type ) {
      +			return arg;
      +		}
      +		else {
      +			return defaultValue;
      +		}
      +	},
      +
      +	/**
      +     * Given an array of values, a type, and a default value,
      +     * ensure the given array's contents ALL adhere to the provided type,
      +     * returning the default value if type check fails.
      +     *
      +     * If the given value to check isn't an Array, delegates to SPE.utils.ensureTypedArg.
      +     *
      +     * @param  {Array|boolean|string|number|object} arg          The array of values to check type of.
      +     * @param  {String} type         The type that should be adhered to.
      +     * @param  {(boolean|string|number|object)} defaultValue A default fallback value.
      +     * @return {(boolean|string|number|object)}              The given value if type check passes, or the default value if it fails.
      +     */
      +	ensureArrayTypedArg( arg, type, defaultValue ) {
      +		'use strict';
      + +
    • + + +
    • +
      + +
      + +
      +

      If the argument being checked is an array, loop through +it and ensure all the values are of the correct type, +falling back to the defaultValue if any aren’t.

      + +
      + +
      		if ( Array.isArray( arg ) ) {
      +			for ( var i = arg.length - 1; i >= 0; --i ) {
      +				if ( typeof arg[ i ] !== type ) {
      +					return defaultValue;
      +				}
      +			}
      +
      +			return arg;
      +		}
      + +
    • + + +
    • +
      + +
      + +
      +

      If the arg isn’t an array then just fallback to +checking the type.

      + +
      + +
      		return this.ensureTypedArg( arg, type, defaultValue );
      +	},
      +
      +	/**
      +     * Ensures the given value is an instance of a constructor function.
      +     *
      +     * @param  {Object} arg          The value to check instance of.
      +     * @param  {Function} instance     The constructor of the instance to check against.
      +     * @param  {Object} defaultValue A default fallback value if instance check fails
      +     * @return {Object}              The given value if type check passes, or the default value if it fails.
      +     */
      +	ensureInstanceOf( arg, instance, defaultValue ) {
      +		'use strict';
      +
      +		if ( instance !== undefined && arg instanceof instance ) {
      +			return arg;
      +		}
      +		else {
      +			return defaultValue;
      +		}
      +	},
      +
      +	/**
      +     * Given an array of values, ensure the instances of all items in the array
      +     * matches the given instance constructor falling back to a default value if
      +     * the check fails.
      +     *
      +     * If given value isn't an Array, delegates to `SPE.utils.ensureInstanceOf`.
      +     *
      +     * @param  {Array|Object} arg          The value to perform the instanceof check on.
      +     * @param  {Function} instance     The constructor of the instance to check against.
      +     * @param  {Object} defaultValue A default fallback value if instance check fails
      +     * @return {Object}              The given value if type check passes, or the default value if it fails.
      +     */
      +	ensureArrayInstanceOf( arg, instance, defaultValue ) {
      +		'use strict';
      + +
    • + + +
    • +
      + +
      + +
      +

      If the argument being checked is an array, loop through +it and ensure all the values are of the correct type, +falling back to the defaultValue if any aren’t.

      + +
      + +
      		if ( Array.isArray( arg ) ) {
      +			for ( var i = arg.length - 1; i >= 0; --i ) {
      +				if ( instance !== undefined && arg[ i ] instanceof instance === false ) {
      +					return defaultValue;
      +				}
      +			}
      +
      +			return arg;
      +		}
      + +
    • + + +
    • +
      + +
      + +
      +

      If the arg isn’t an array then just fallback to +checking the type.

      + +
      + +
      		return this.ensureInstanceOf( arg, instance, defaultValue );
      +	},
      +
      +	/**
      +     * Ensures that any "value-over-lifetime" properties of an emitter are
      +     * of the correct length (as dictated by `SPE.valueOverLifetimeLength`).
      +     *
      +     * Delegates to `SPE.utils.interpolateArray` for array resizing.
      +     *
      +     * If properties aren't arrays, then property values are put into one.
      +     *
      +     * @param  {Object} property  The property of an SPE.Emitter instance to check compliance of.
      +     * @param  {Number} minLength The minimum length of the array to create.
      +     * @param  {Number} maxLength The maximum length of the array to create.
      +     */
      +	ensureValueOverLifetimeCompliance( property, minLength, maxLength ) {
      +		'use strict';
      +
      +		minLength = minLength || 3;
      +		maxLength = maxLength || 3;
      + +
    • + + +
    • +
      + +
      + +
      +

      First, ensure both properties are arrays.

      + +
      + +
      		if ( Array.isArray( property._value ) === false ) {
      +			property._value = [ property._value ];
      +		}
      +
      +		if ( Array.isArray( property._spread ) === false ) {
      +			property._spread = [ property._spread ];
      +		}
      +
      +		var valueLength = this.clamp( property._value.length, minLength, maxLength ),
      +			spreadLength = this.clamp( property._spread.length, minLength, maxLength ),
      +			desiredLength = Math.max( valueLength, spreadLength );
      +
      +		if ( property._value.length !== desiredLength ) {
      +			property._value = this.interpolateArray( property._value, desiredLength );
      +		}
      +
      +		if ( property._spread.length !== desiredLength ) {
      +			property._spread = this.interpolateArray( property._spread, desiredLength );
      +		}
      +	},
      +
      +	/**
      +     * Performs linear interpolation (lerp) on an array.
      +     *
      +     * For example, lerping [1, 10], with a `newLength` of 10 will produce [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].
      +     *
      +     * Delegates to `SPE.utils.lerpTypeAgnostic` to perform the actual
      +     * interpolation.
      +     *
      +     * @param  {Array} srcArray  The array to lerp.
      +     * @param  {Number} newLength The length the array should be interpolated to.
      +     * @return {Array}           The interpolated array.
      +     */
      +	interpolateArray( srcArray, newLength ) {
      +		'use strict';
      +
      +		var sourceLength = srcArray.length,
      +			newArray = [ typeof srcArray[ 0 ].clone === 'function' ? srcArray[ 0 ].clone() : srcArray[ 0 ] ],
      +			factor = ( sourceLength - 1 ) / ( newLength - 1 );
      +
      +
      +		for ( var i = 1; i < newLength - 1; ++i ) {
      +			var f = i * factor,
      +				before = Math.floor( f ),
      +				after = Math.ceil( f ),
      +				delta = f - before;
      +
      +			newArray[ i ] = this.lerpTypeAgnostic( srcArray[ before ], srcArray[ after ], delta );
      +		}
      +
      +		newArray.push(
      +			typeof srcArray[ sourceLength - 1 ].clone === 'function' ?
      +				srcArray[ sourceLength - 1 ].clone() :
      +				srcArray[ sourceLength - 1 ]
      +		);
      +
      +		return newArray;
      +	},
      +
      +	/**
      +     * Clamp a number to between the given min and max values.
      +     * @param  {Number} value The number to clamp.
      +     * @param  {Number} min   The minimum value.
      +     * @param  {Number} max   The maximum value.
      +     * @return {Number}       The clamped number.
      +     */
      +	clamp( value, min, max ) {
      +		'use strict';
      +
      +		return Math.max( min, Math.min( value, max ) );
      +	},
      +
      +	/**
      +     * If the given value is less than the epsilon value, then return
      +     * a randomised epsilon value if specified, or just the epsilon value if not.
      +     * Works for negative numbers as well as positive.
      +     *
      +     * @param  {Number} value     The value to perform the operation on.
      +     * @param  {Boolean} randomise Whether the value should be randomised.
      +     * @return {Number}           The result of the operation.
      +     */
      +	zeroToEpsilon( value, randomise ) {
      +		'use strict';
      +
      +		var epsilon = 0.00001,
      +			result = value;
      +
      +		result = randomise ? Math.random() * epsilon * 10 : epsilon;
      +
      +		if ( value < 0 && value > -epsilon ) {
      +			result = -result;
      +		}
      + +
    • + + +
    • +
      + +
      + +
      +

      if ( value === 0 ) { + result = randomise ? Math.random() * epsilon * 10 : epsilon; +} +else if ( value > 0 && value < epsilon ) { + result = randomise ? Math.random() * epsilon * 10 : epsilon; +} +else if ( value < 0 && value > -epsilon ) { + result = -( randomise ? Math.random() * epsilon * 10 : epsilon ); +}

      + +
      + +
      +		return result;
      +	},
      +
      +	/**
      +     * Linearly interpolates two values of various valueTypes. The given values
      +     * must be of the same type for the interpolation to work.
      +     * @param  {(number|Object)} start The start value of the lerp.
      +     * @param  {(number|object)} end   The end value of the lerp.
      +     * @param  {Number} delta The delta posiiton of the lerp operation. Ideally between 0 and 1 (inclusive).
      +     * @return {(number|object|undefined)}       The result of the operation. Result will be undefined if
      +     *                                               the start and end arguments aren't a supported type, or
      +     *                                               if their types do not match.
      +     */
      +	lerpTypeAgnostic( start, end, delta ) {
      +		'use strict';
      +
      +		var out;
      +
      +		if ( typeof start === valueTypes.NUMBER && typeof end === valueTypes.NUMBER ) {
      +			return start + ( ( end - start ) * delta );
      +		}
      +		else if ( start instanceof THREE.Vector2 && end instanceof THREE.Vector2 ) {
      +			out = start.clone();
      +			out.x = this.lerp( start.x, end.x, delta );
      +			out.y = this.lerp( start.y, end.y, delta );
      +			return out;
      +		}
      +		else if ( start instanceof THREE.Vector3 && end instanceof THREE.Vector3 ) {
      +			out = start.clone();
      +			out.x = this.lerp( start.x, end.x, delta );
      +			out.y = this.lerp( start.y, end.y, delta );
      +			out.z = this.lerp( start.z, end.z, delta );
      +			return out;
      +		}
      +		else if ( start instanceof THREE.Vector4 && end instanceof THREE.Vector4 ) {
      +			out = start.clone();
      +			out.x = this.lerp( start.x, end.x, delta );
      +			out.y = this.lerp( start.y, end.y, delta );
      +			out.z = this.lerp( start.z, end.z, delta );
      +			out.w = this.lerp( start.w, end.w, delta );
      +			return out;
      +		}
      +		else if ( start instanceof THREE.Color && end instanceof THREE.Color ) {
      +			out = start.clone();
      +			out.r = this.lerp( start.r, end.r, delta );
      +			out.g = this.lerp( start.g, end.g, delta );
      +			out.b = this.lerp( start.b, end.b, delta );
      +			return out;
      +		}
      +		else {
      +			console.warn( 'Invalid argument types, or argument types do not match:', start, end );
      +		}
      +	},
      +
      +	/**
      +     * Perform a linear interpolation operation on two numbers.
      +     * @param  {Number} start The start value.
      +     * @param  {Number} end   The end value.
      +     * @param  {Number} delta The position to interpolate to.
      +     * @return {Number}       The result of the lerp operation.
      +     */
      +	lerp( start, end, delta ) {
      +		'use strict';
      +		return start + ( ( end - start ) * delta );
      +	},
      +
      +	/**
      +     * Rounds a number to a nearest multiple.
      +     *
      +     * @param  {Number} n        The number to round.
      +     * @param  {Number} multiple The multiple to round to.
      +     * @return {Number}          The result of the round operation.
      +     */
      +	roundToNearestMultiple( n, multiple ) {
      +		'use strict';
      +
      +		var remainder = 0;
      +
      +		if ( multiple === 0 ) {
      +			return n;
      +		}
      +
      +		remainder = Math.abs( n ) % multiple;
      +
      +		if ( remainder === 0 ) {
      +			return n;
      +		}
      +
      +		if ( n < 0 ) {
      +			return -( Math.abs( n ) - remainder );
      +		}
      +
      +		return n + multiple - remainder;
      +	},
      +
      +	/**
      +     * Check if all items in an array are equal. Uses strict equality.
      +     *
      +     * @param  {Array} array The array of values to check equality of.
      +     * @return {Boolean}       Whether the array's values are all equal or not.
      +     */
      +	arrayValuesAreEqual( array ) {
      +		'use strict';
      +
      +		for ( var i = 0; i < array.length - 1; ++i ) {
      +			if ( array[ i ] !== array[ i + 1 ] ) {
      +				return false;
      +			}
      +		}
      +
      +		return true;
      +	},
      + +
    • + + +
    • +
      + +
      + +
      +

      colorsAreEqual: function() { + var colors = Array.prototype.slice.call( arguments ), + numColors = colors.length;

      + +
      + +
    • + + +
    • +
      + +
      + +
      +
      for ( var i = 0, color1, color2; i < numColors - 1; ++i ) {
      +    color1 = colors[ i ];
      +    color2 = colors[ i + 1 ];
      + +
      + +
    • + + +
    • +
      + +
      + +
      +
          if (
      +        color1.r !== color2.r ||
      +        color1.g !== color2.g ||
      +        color1.b !== color2.b
      +    ) {
      +        return false
      +    }
      +}
      + +
      + +
    • + + +
    • +
      + +
      + +
      +
      return true;
      +

      },

      + +
      + +
      +
      +	/**
      +     * Given a start value and a spread value, create and return a random
      +     * number.
      +     * @param  {Number} base   The start value.
      +     * @param  {Number} spread The size of the random variance to apply.
      +     * @return {Number}        A randomised number.
      +     */
      +	randomFloat( base, spread ) {
      +		'use strict';
      +		return base + spread * ( Math.random() - 0.5 );
      +	},
      +
      +
      +
      +	/**
      +     * Given an SPE.ShaderAttribute instance, and various other settings,
      +     * assign values to the attribute's array in a `vec3` format.
      +     *
      +     * @param  {Object} attribute   The instance of SPE.ShaderAttribute to save the result to.
      +     * @param  {Number} index       The offset in the attribute's TypedArray to save the result from.
      +     * @param  {Object} base        THREE.Vector3 instance describing the start value.
      +     * @param  {Object} spread      THREE.Vector3 instance describing the random variance to apply to the start value.
      +     * @param  {Object} spreadClamp THREE.Vector3 instance describing the multiples to clamp the randomness to.
      +     */
      +	randomVector3( attribute, index, base, spread, spreadClamp ) {
      +		'use strict';
      +
      +		var x = base.x + ( Math.random() * spread.x - ( spread.x * 0.5 ) ),
      +			y = base.y + ( Math.random() * spread.y - ( spread.y * 0.5 ) ),
      +			z = base.z + ( Math.random() * spread.z - ( spread.z * 0.5 ) );
      + +
    • + + +
    • +
      + +
      + +
      +

      var x = this.randomFloat( base.x, spread.x ), +y = this.randomFloat( base.y, spread.y ), +z = this.randomFloat( base.z, spread.z );

      + +
      + +
      +		if ( spreadClamp ) {
      +			x = -spreadClamp.x * 0.5 + this.roundToNearestMultiple( x, spreadClamp.x );
      +			y = -spreadClamp.y * 0.5 + this.roundToNearestMultiple( y, spreadClamp.y );
      +			z = -spreadClamp.z * 0.5 + this.roundToNearestMultiple( z, spreadClamp.z );
      +		}
      +
      +		attribute.typedArray.setVec3Components( index, x, y, z );
      +	},
      +
      +	/**
      +     * Given an SPE.Shader attribute instance, and various other settings,
      +     * assign Color values to the attribute.
      +     * @param  {Object} attribute The instance of SPE.ShaderAttribute to save the result to.
      +     * @param  {Number} index     The offset in the attribute's TypedArray to save the result from.
      +     * @param  {Object} base      THREE.Color instance describing the start color.
      +     * @param  {Object} spread    THREE.Vector3 instance describing the random variance to apply to the start color.
      +     */
      +	randomColor( attribute, index, base, spread ) {
      +		'use strict';
      +
      +		var r = base.r + ( Math.random() * spread.x ),
      +			g = base.g + ( Math.random() * spread.y ),
      +			b = base.b + ( Math.random() * spread.z );
      +
      +		r = this.clamp( r, 0, 1 );
      +		g = this.clamp( g, 0, 1 );
      +		b = this.clamp( b, 0, 1 );
      +
      +
      +		attribute.typedArray.setVec3Components( index, r, g, b );
      +	},
      +
      +
      +	randomColorAsHex: ( function() {
      +		'use strict';
      +
      +		var workingColor = new THREE.Color();
      +
      +		/**
      +         * Assigns a random color value, encoded as a hex value in decimal
      +         * format, to a SPE.ShaderAttribute instance.
      +         * @param  {Object} attribute The instance of SPE.ShaderAttribute to save the result to.
      +         * @param  {Number} index     The offset in the attribute's TypedArray to save the result from.
      +         * @param  {Object} base      THREE.Color instance describing the start color.
      +         * @param  {Object} spread    THREE.Vector3 instance describing the random variance to apply to the start color.
      +         */
      +		return function( attribute, index, base, spread ) {
      +			var numItems = base.length,
      +				colors = [];
      +
      +			for ( var i = 0; i < numItems; ++i ) {
      +				var spreadVector = spread[ i ];
      +
      +				workingColor.copy( base[ i ] );
      +
      +				workingColor.r += ( Math.random() * spreadVector.x ) - ( spreadVector.x * 0.5 );
      +				workingColor.g += ( Math.random() * spreadVector.y ) - ( spreadVector.y * 0.5 );
      +				workingColor.b += ( Math.random() * spreadVector.z ) - ( spreadVector.z * 0.5 );
      +
      +				workingColor.r = this.clamp( workingColor.r, 0, 1 );
      +				workingColor.g = this.clamp( workingColor.g, 0, 1 );
      +				workingColor.b = this.clamp( workingColor.b, 0, 1 );
      +
      +				colors.push( workingColor.getHex() );
      +			}
      +
      +			attribute.typedArray.setVec4Components( index, colors[ 0 ], colors[ 1 ], colors[ 2 ], colors[ 3 ] );
      +		};
      +	}() ),
      +
      +	/**
      +     * Given an SPE.ShaderAttribute instance, and various other settings,
      +     * assign values to the attribute's array in a `vec3` format.
      +     *
      +     * @param  {Object} attribute   The instance of SPE.ShaderAttribute to save the result to.
      +     * @param  {Number} index       The offset in the attribute's TypedArray to save the result from.
      +     * @param  {Object} start       THREE.Vector3 instance describing the start line position.
      +     * @param  {Object} end         THREE.Vector3 instance describing the end line position.
      +     */
      +	randomVector3OnLine( attribute, index, start, end ) {
      +		'use strict';
      +		var pos = start.clone();
      +
      +		pos.lerp( end, Math.random() );
      +
      +		attribute.typedArray.setVec3Components( index, pos.x, pos.y, pos.z );
      +	},
      +
      +	/**
      +     * Given an SPE.Shader attribute instance, and various other settings,
      +     * assign Color values to the attribute.
      +     * @param  {Object} attribute The instance of SPE.ShaderAttribute to save the result to.
      +     * @param  {Number} index     The offset in the attribute's TypedArray to save the result from.
      +     * @param  {Object} base      THREE.Color instance describing the start color.
      +     * @param  {Object} spread    THREE.Vector3 instance describing the random variance to apply to the start color.
      +     */
      +
      +	/**
      +     * Assigns a random vector 3 value to an SPE.ShaderAttribute instance, projecting the
      +     * given values onto a sphere.
      +     *
      +     * @param  {Object} attribute The instance of SPE.ShaderAttribute to save the result to.
      +     * @param  {Number} index     The offset in the attribute's TypedArray to save the result from.
      +     * @param  {Object} base              THREE.Vector3 instance describing the origin of the transform.
      +     * @param  {Number} radius            The radius of the sphere to project onto.
      +     * @param  {Number} radiusSpread      The amount of randomness to apply to the projection result
      +     * @param  {Object} radiusScale       THREE.Vector3 instance describing the scale of each axis of the sphere.
      +     * @param  {Number} radiusSpreadClamp What numeric multiple the projected value should be clamped to.
      +     */
      +	randomVector3OnSphere(
      +		attribute, index, base, radius, radiusSpread, radiusScale, radiusSpreadClamp, distributionClamp
      +	) {
      +		'use strict';
      +
      +		var depth = 2 * Math.random() - 1,
      +			t = 6.2832 * Math.random(),
      +			r = Math.sqrt( 1 - depth * depth ),
      +			rand = this.randomFloat( radius, radiusSpread ),
      +			x = 0,
      +			y = 0,
      +			z = 0;
      +
      +
      +		if ( radiusSpreadClamp ) {
      +			rand = Math.round( rand / radiusSpreadClamp ) * radiusSpreadClamp;
      +		}
      + +
    • + + +
    • +
      + +
      + +
      +

      Set position on sphere

      + +
      + +
      		x = r * Math.cos( t ) * rand;
      +		y = r * Math.sin( t ) * rand;
      +		z = depth * rand;
      + +
    • + + +
    • +
      + +
      + +
      +

      Apply radius scale to this position

      + +
      + +
      		x *= radiusScale.x;
      +		y *= radiusScale.y;
      +		z *= radiusScale.z;
      + +
    • + + +
    • +
      + +
      + +
      +

      Translate to the base position.

      + +
      + +
      		x += base.x;
      +		y += base.y;
      +		z += base.z;
      + +
    • + + +
    • +
      + +
      + +
      +

      Set the values in the typed array.

      + +
      + +
      		attribute.typedArray.setVec3Components( index, x, y, z );
      +	},
      +
      +	seededRandom( seed ) {
      +		var x = Math.sin( seed ) * 10000;
      +		return x - ( x | 0 );
      +	},
      +
      +
      +
      +	/**
      +     * Assigns a random vector 3 value to an SPE.ShaderAttribute instance, projecting the
      +     * given values onto a 2d-disc.
      +     *
      +     * @param  {Object} attribute The instance of SPE.ShaderAttribute to save the result to.
      +     * @param  {Number} index     The offset in the attribute's TypedArray to save the result from.
      +     * @param  {Object} base              THREE.Vector3 instance describing the origin of the transform.
      +     * @param  {Number} radius            The radius of the sphere to project onto.
      +     * @param  {Number} radiusSpread      The amount of randomness to apply to the projection result
      +     * @param  {Object} radiusScale       THREE.Vector3 instance describing the scale of each axis of the disc. The z-component is ignored.
      +     * @param  {Number} radiusSpreadClamp What numeric multiple the projected value should be clamped to.
      +     */
      +	randomVector3OnDisc( attribute, index, base, radius, radiusSpread, radiusScale, radiusSpreadClamp ) {
      +		'use strict';
      +
      +		var t = 6.2832 * Math.random(),
      +			rand = Math.abs( this.randomFloat( radius, radiusSpread ) ),
      +			x = 0,
      +			y = 0,
      +			z = 0;
      +
      +		if ( radiusSpreadClamp ) {
      +			rand = Math.round( rand / radiusSpreadClamp ) * radiusSpreadClamp;
      +		}
      + +
    • + + +
    • +
      + +
      + +
      +

      Set position on sphere

      + +
      + +
      		x = Math.cos( t ) * rand;
      +		y = Math.sin( t ) * rand;
      + +
    • + + +
    • +
      + +
      + +
      +

      Apply radius scale to this position

      + +
      + +
      		x *= radiusScale.x;
      +		y *= radiusScale.y;
      + +
    • + + +
    • +
      + +
      + +
      +

      Translate to the base position.

      + +
      + +
      		x += base.x;
      +		y += base.y;
      +		z += base.z;
      + +
    • + + +
    • +
      + +
      + +
      +

      Set the values in the typed array.

      + +
      + +
      		attribute.typedArray.setVec3Components( index, x, y, z );
      +	},
      +
      +	randomDirectionVector3OnSphere: ( function() {
      +		'use strict';
      +
      +		var v = new THREE.Vector3();
      +
      +		/**
      +         * Given an SPE.ShaderAttribute instance, create a direction vector from the given
      +         * position, using `speed` as the magnitude. Values are saved to the attribute.
      +         *
      +         * @param  {Object} attribute       The instance of SPE.ShaderAttribute to save the result to.
      +         * @param  {Number} index           The offset in the attribute's TypedArray to save the result from.
      +         * @param  {Number} posX            The particle's x coordinate.
      +         * @param  {Number} posY            The particle's y coordinate.
      +         * @param  {Number} posZ            The particle's z coordinate.
      +         * @param  {Object} emitterPosition THREE.Vector3 instance describing the emitter's base position.
      +         * @param  {Number} speed           The magnitude to apply to the vector.
      +         * @param  {Number} speedSpread     The amount of randomness to apply to the magnitude.
      +         */
      +		return function( attribute, index, posX, posY, posZ, emitterPosition, speed, speedSpread ) {
      +			v.copy( emitterPosition );
      +
      +			v.x -= posX;
      +			v.y -= posY;
      +			v.z -= posZ;
      +
      +			v.normalize().multiplyScalar( -this.randomFloat( speed, speedSpread ) );
      +
      +			attribute.typedArray.setVec3Components( index, v.x, v.y, v.z );
      +		};
      +	}() ),
      +
      +
      +	randomDirectionVector3OnDisc: ( function() {
      +		'use strict';
      +
      +		var v = new THREE.Vector3();
      +
      +		/**
      +         * Given an SPE.ShaderAttribute instance, create a direction vector from the given
      +         * position, using `speed` as the magnitude. Values are saved to the attribute.
      +         *
      +         * @param  {Object} attribute       The instance of SPE.ShaderAttribute to save the result to.
      +         * @param  {Number} index           The offset in the attribute's TypedArray to save the result from.
      +         * @param  {Number} posX            The particle's x coordinate.
      +         * @param  {Number} posY            The particle's y coordinate.
      +         * @param  {Number} posZ            The particle's z coordinate.
      +         * @param  {Object} emitterPosition THREE.Vector3 instance describing the emitter's base position.
      +         * @param  {Number} speed           The magnitude to apply to the vector.
      +         * @param  {Number} speedSpread     The amount of randomness to apply to the magnitude.
      +         */
      +		return function( attribute, index, posX, posY, posZ, emitterPosition, speed, speedSpread ) {
      +			v.copy( emitterPosition );
      +
      +			v.x -= posX;
      +			v.y -= posY;
      +			v.z -= posZ;
      +
      +			v.normalize().multiplyScalar( -this.randomFloat( speed, speedSpread ) );
      +
      +			attribute.typedArray.setVec3Components( index, v.x, v.y, 0 );
      +		};
      +	}() ),
      +
      +	getPackedRotationAxis: ( function() {
      +		'use strict';
      +
      +		var v = new THREE.Vector3(),
      +			vSpread = new THREE.Vector3(),
      +			c = new THREE.Color(),
      +			addOne = new THREE.Vector3( 1, 1, 1 );
      +
      +		/**
      +         * Given a rotation axis, and a rotation axis spread vector,
      +         * calculate a randomised rotation axis, and pack it into
      +         * a hexadecimal value represented in decimal form.
      +         * @param  {Object} axis       THREE.Vector3 instance describing the rotation axis.
      +         * @param  {Object} axisSpread THREE.Vector3 instance describing the amount of randomness to apply to the rotation axis.
      +         * @return {Number}            The packed rotation axis, with randomness.
      +         */
      +		return function( axis, axisSpread ) {
      +			v.copy( axis ).normalize();
      +			vSpread.copy( axisSpread ).normalize();
      +
      +			v.x += ( -axisSpread.x * 0.5 ) + ( Math.random() * axisSpread.x );
      +			v.y += ( -axisSpread.y * 0.5 ) + ( Math.random() * axisSpread.y );
      +			v.z += ( -axisSpread.z * 0.5 ) + ( Math.random() * axisSpread.z );
      + +
    • + + +
    • +
      + +
      + +
      +

      v.x = Math.abs( v.x ); +v.y = Math.abs( v.y ); +v.z = Math.abs( v.z );

      + +
      + +
      +			v.normalize().add( addOne )
      +				.multiplyScalar( 0.5 );
      +
      +			c.setRGB( v.x, v.y, v.z );
      +
      +			return c.getHex();
      +		};
      +	}() ),
      +};
      + +
    • + +
    +
    + + diff --git a/docs/source/src/helpers/ShaderAttribute.html b/docs/source/src/helpers/ShaderAttribute.html new file mode 100644 index 0000000..4f0df50 --- /dev/null +++ b/docs/source/src/helpers/ShaderAttribute.html @@ -0,0 +1,363 @@ + + + + + ShaderAttribute.js + + + + + +
    +
    + + + +
      + +
    • +
      +

      ShaderAttribute.js

      +
      +
    • + + + +
    • +
      + +
      + +
      + +
      + +
      import * as THREE from 'three';
      +import TypedArrayHelper from './TypedArrayHelper';
      +import typeSizeMap from '@/constants/typeSizeMap';
      +
      +const HAS_OWN = Object.prototype.hasOwnProperty;
      +
      +/**
      + * A helper to handle creating and updating a THREE.BufferAttribute instance.
      + *
      + * @author  Luke Moody
      + * @constructor
      + * @param {String} type          The buffer attribute type. See `typeSizeMap`` for valid values.
      + * @param {Boolean=} dynamicBuffer Whether this buffer attribute should be marked as dynamic or not.
      + * @param {Function=} arrayType     A reference to a TypedArray constructor. Defaults to Float32Array if none provided.
      + */
      +export default class ShaderAttribute {
      +	constructor( type, dynamicBuffer, arrayType ) {
      +		this.type = typeof type === 'string' && HAS_OWN.call( typeSizeMap, type ) ? type : 'f';
      +		this.componentSize = typeSizeMap[ this.type ];
      +		this.arrayType = arrayType || Float32Array;
      +		this.typedArray = null;
      +		this.bufferAttribute = null;
      +		this.dynamicBuffer = !!dynamicBuffer;
      +
      +		this.updateMin = 0;
      +		this.updateMax = 0;
      +	}
      +
      +	/**
      +	 * Calculate the minimum and maximum update range for this buffer attribute using
      +	 * component size independant min and max values.
      +	 *
      +	 * @param {Number} min The start of the range to mark as needing an update.
      +	 * @param {Number} max The end of the range to mark as needing an update.
      +	 */
      +	setUpdateRange( min, max ) {
      +		this.updateMin = Math.min( min * this.componentSize, this.updateMin * this.componentSize );
      +		this.updateMax = Math.max( max * this.componentSize, this.updateMax * this.componentSize );
      +	}
      +
      +	/**
      +	 * Calculate the number of indices that this attribute should mark as needing
      +	 * updating. Also marks the attribute as needing an update.
      +	 */
      +	flagUpdate() {
      +		const attr = this.bufferAttribute,
      +			range = attr.updateRange;
      +
      +		range.offset = this.updateMin;
      +		range.count = Math.min( ( this.updateMax - this.updateMin ) + this.componentSize, this.typedArray.array.length );
      +		attr.needsUpdate = true;
      +	}
      +
      +
      +
      +	/**
      +	 * Reset the index update counts for this attribute
      +	 */
      +	resetUpdateRange() {
      +		this.updateMin = 0;
      +		this.updateMax = 0;
      +	}
      +
      +	resetDynamic() {
      +		this.bufferAttribute.usage = this.dynamicBuffer ?
      +			THREE.DynamicDrawUsage :
      +			THREE.StaticDrawUsage;
      +	}
      +
      +	/**
      +	 * Perform a splice operation on this attribute's buffer.
      +	 * @param  {Number} start The start index of the splice. Will be multiplied by the number of components for this attribute.
      +	 * @param  {Number} end The end index of the splice. Will be multiplied by the number of components for this attribute.
      +	 */
      +	splice( start, end ) {
      +		this.typedArray.splice( start, end );
      + +
    • + + +
    • +
      + +
      + +
      +

      Reset the reference to the attribute’s typed array +since it has probably changed.

      + +
      + +
      		this.forceUpdateAll();
      +	}
      +
      +	forceUpdateAll() {
      +		this.bufferAttribute.array = this.typedArray.array;
      +		this.bufferAttribute.updateRange.offset = 0;
      +		this.bufferAttribute.updateRange.count = -1;
      +
      +		this.bufferAttribute.usage = THREE.StaticDrawUsage;
      +		this.bufferAttribute.needsUpdate = true;
      +	}
      +
      +	/**
      +	 * Make sure this attribute has a typed array associated with it.
      +	 *
      +	 * If it does, then it will ensure the typed array is of the correct size.
      +	 *
      +	 * If not, a new `TypedArrayHelper` instance will be created.
      +	 *
      +	 * @param  {Number} size The size of the typed array to create or update to.
      +	 */
      +	_ensureTypedArray( size ) {
      + +
    • + + +
    • +
      + +
      + +
      +

      Condition that’s most likely to be true at the top: no change.

      + +
      + +
      		if ( this.typedArray !== null && this.typedArray.size === size * this.componentSize ) {
      +			return;
      +		}
      + +
    • + + +
    • +
      + +
      + +
      +

      Resize the array if we need to, telling the TypedArrayHelper to +ignore it’s component size when evaluating size.

      + +
      + +
      		else if ( this.typedArray !== null && this.typedArray.size !== size ) {
      +			this.typedArray.setSize( size );
      +		}
      + +
    • + + +
    • +
      + +
      + +
      +

      This condition should only occur once in an attribute’s lifecycle.

      + +
      + +
      		else if ( this.typedArray === null ) {
      +			this.typedArray = new TypedArrayHelper( this.arrayType, size, this.componentSize );
      +		}
      +	}
      +
      +
      +	/**
      +	 * Creates a THREE.BufferAttribute instance if one doesn't exist already.
      +	 *
      +	 * Ensures a typed array is present by calling _ensureTypedArray() first.
      +	 *
      +	 * If a buffer attribute exists already, then it will be marked as needing an update.
      +	 *
      +	 * @param  {Number} size The size of the typed array to create if one doesn't exist, or resize existing array to.
      +	 */
      +	_createBufferAttribute( size ) {
      + +
    • + + +
    • +
      + +
      + +
      +

      Make sure the typedArray is present and correct.

      + +
      + +
      		this._ensureTypedArray( size );
      + +
    • + + +
    • +
      + +
      + +
      +

      Don’t create it if it already exists, but do +flag that it needs updating on the next render +cycle.

      + +
      + +
      		if ( this.bufferAttribute !== null ) {
      +			this.bufferAttribute.array = this.typedArray.array;
      + +
    • + + +
    • +
      + +
      + +
      +

      Since THREE.js version 81, dynamic count calculation was removed +so I need to do it manually here.

      +

      In the next minor release, I may well remove this check and force +dependency on THREE r81+.

      + +
      + +
      			if ( parseFloat( THREE.REVISION ) >= 81 ) {
      +				this.bufferAttribute.count = this.bufferAttribute.array.length / this.bufferAttribute.itemSize;
      +			}
      +
      +			this.bufferAttribute.needsUpdate = true;
      +			return;
      +		}
      +
      +		this.bufferAttribute = new THREE.BufferAttribute( this.typedArray.array, this.componentSize );
      +
      +		this.bufferAttribute.usage = this.dynamicBuffer ?
      +			THREE.DynamicDrawUsage :
      +			THREE.StaticDrawUsage;
      +	}
      +
      +	/**
      +	 * Returns the length of the typed array associated with this attribute.
      +	 * @return {Number} The length of the typed array. Will be 0 if no typed array has been created yet.
      +	 */
      +	getLength() {
      +		if ( this.typedArray === null ) {
      +			return 0;
      +		}
      +
      +		return this.typedArray.array.length;
      +	}
      +
      +}
      + +
    • + +
    +
    + + diff --git a/docs/source/src/helpers/TypedArrayHelper.html b/docs/source/src/helpers/TypedArrayHelper.html new file mode 100644 index 0000000..f083e88 --- /dev/null +++ b/docs/source/src/helpers/TypedArrayHelper.html @@ -0,0 +1,416 @@ + + + + + TypedArrayHelper.js + + + + + +
    +
    + + + +
      + +
    • +
      +

      TypedArrayHelper.js

      +
      +
    • + + + +
    • +
      + +
      + +
      + +
      + +
      /**
      + * A helper class for TypedArrays.
      + *
      + * Allows for easy resizing, assignment of various component-based
      + * types (Vector2s, Vector3s, Vector4s, Mat3s, Mat4s),
      + * as well as Colors (where components are `r`, `g`, `b`),
      + * Numbers, and setting from other TypedArrays.
      + *
      + * @author Luke Moody
      + * @constructor
      + * @param {Function} TypedArrayConstructor The constructor to use (Float32Array, Uint8Array, etc.)
      + * @param {Number} size                 The size of the array to create
      + * @param {Number} componentSize        The number of components per-value (ie. 3 for a vec3, 9 for a Mat3, etc.)
      + * @param {Number} indexOffset          The index in the array from which to start assigning values. Default `0` if none provided
      + */
      +export default class TypedArrayHelper {
      +	constructor( TypedArrayConstructor, size, componentSize, indexOffset ) {
      +		this.componentSize = componentSize || 1;
      +		this.size = ( size || 1 );
      +		this.TypedArrayConstructor = TypedArrayConstructor || Float32Array;
      +		this.array = new TypedArrayConstructor( size * this.componentSize );
      +		this.indexOffset = indexOffset || 0;
      +	}
      +
      +	/**
      +	 * Sets the size of the internal array.
      +	 *
      +	 * Delegates to `this.shrink` or `this.grow` depending on size
      +	 * argument's relation to the current size of the internal array.
      +	 *
      +	 * Note that if the array is to be shrunk, data will be lost.
      +	 *
      +	 * @param {Number} size The new size of the array.
      +	 */
      +	setSize( s, noComponentMultiply ) {
      +		const currentArraySize = this.array.length;
      +		let size = s;
      +
      +		if ( !noComponentMultiply ) {
      +			size = size * this.componentSize;
      +		}
      +
      +		if ( size < currentArraySize ) {
      +			return this.shrink( size );
      +		}
      +		else if ( size > currentArraySize ) {
      +			return this.grow( size );
      +		}
      +		else {
      +			console.info( 'TypedArray is already of size:', size + '.', 'Will not resize.' );
      +		}
      +	}
      +
      +	/**
      +	 * Shrinks the internal array.
      +	 *
      +	 * @param  {Number} size The new size of the typed array. Must be smaller than `this.array.length`.
      +	 * @return {SPE.TypedArrayHelper}      Instance of this class.
      +	 */
      +	shrink( size ) {
      +		this.array = this.array.subarray( 0, size );
      +		this.size = size;
      +		return this;
      +	}
      +
      +	/**
      +	 * Grows the internal array.
      +	 * @param  {Number} size The new size of the typed array. Must be larger than `this.array.length`.
      +	 * @return {SPE.TypedArrayHelper}      Instance of this class.
      +	 */
      +	grow( size ) {
      +		const newArray = new this.TypedArrayConstructor( size );
      +
      +		newArray.set( this.array );
      +		this.array = newArray;
      +		this.size = size;
      +
      +		return this;
      +	}
      +
      +
      +	/**
      +	 * Perform a splice operation on this array's buffer.
      +	 * @param  {Number} start The start index of the splice. Will be multiplied by the number of components for this attribute.
      +	 * @param  {Number} end The end index of the splice. Will be multiplied by the number of components for this attribute.
      +	 * @returns {Object} The SPE.TypedArrayHelper instance.
      +	 */
      +	splice( start, end ) {
      +		const _start = start * this.componentSize,
      +			_end = end * this.componentSize;
      +
      +		const data = [],
      +			array = this.array,
      +			size = array.length;
      +
      +		for ( let i = 0; i < size; ++i ) {
      +			if ( i < _start || i >= _end ) {
      +				data.push( array[ i ] );
      +			}
      + +
    • + + +
    • +
      + +
      + +
      +

      array[ i ] = 0;

      + +
      + +
      		}
      +
      +		this.setFromArray( 0, data );
      +
      +		return this;
      +	}
      +
      +
      +	/**
      +	 * Copies from the given TypedArray into this one, using the index argument
      +	 * as the start position. Alias for `TypedArray.set`. Will automatically resize
      +	 * if the given source array is of a larger size than the internal array.
      +	 *
      +	 * @param {Number} index      The start position from which to copy into this array.
      +	 * @param {TypedArray} array The array from which to copy; the source array.
      +	 * @return {SPE.TypedArrayHelper} Instance of this class.
      +	 */
      +	setFromArray( index, array ) {
      +		const sourceArraySize = array.length,
      +			newSize = index + sourceArraySize;
      +
      +		if ( newSize > this.array.length ) {
      +			this.grow( newSize );
      +		}
      +		else if ( newSize < this.array.length ) {
      +			this.shrink( newSize );
      +		}
      +
      +		this.array.set( array, this.indexOffset + index );
      +
      +		return this;
      +	}
      +
      +	/**
      +	 * Set a Vector2 value at `index`.
      +	 *
      +	 * @param {Number} index The index at which to set the vec2 values from.
      +	 * @param {Vector2} vec2  Any object that has `x` and `y` properties.
      +	 * @return {SPE.TypedArrayHelper} Instance of this class.
      +	 */
      +	setVec2( index, vec2 ) {
      +		return this.setVec2Components( index, vec2.x, vec2.y );
      +	}
      +
      +	/**
      +	 * Set a Vector2 value using raw components.
      +	 *
      +	 * @param {Number} index The index at which to set the vec2 values from.
      +	 * @param {Number} x     The Vec2's `x` component.
      +	 * @param {Number} y     The Vec2's `y` component.
      +	 * @return {SPE.TypedArrayHelper} Instance of this class.
      +	 */
      +	setVec2Components( index, x, y ) {
      +		const array = this.array,
      +			i = this.indexOffset + ( index * this.componentSize );
      +
      +		array[ i ] = x;
      +		array[ i + 1 ] = y;
      +		return this;
      +	}
      +
      +	/**
      +	 * Set a Vector3 value at `index`.
      +	 *
      +	 * @param {Number} index The index at which to set the vec3 values from.
      +	 * @param {Vector3} vec2  Any object that has `x`, `y`, and `z` properties.
      +	 * @return {SPE.TypedArrayHelper} Instance of this class.
      +	 */
      +	setVec3( index, vec3 ) {
      +		return this.setVec3Components( index, vec3.x, vec3.y, vec3.z );
      +	}
      +
      +	/**
      +	 * Set a Vector3 value using raw components.
      +	 *
      +	 * @param {Number} index The index at which to set the vec3 values from.
      +	 * @param {Number} x     The Vec3's `x` component.
      +	 * @param {Number} y     The Vec3's `y` component.
      +	 * @param {Number} z     The Vec3's `z` component.
      +	 * @return {SPE.TypedArrayHelper} Instance of this class.
      +	 */
      +	setVec3Components( index, x, y, z ) {
      +		const array = this.array,
      +			i = this.indexOffset + ( index * this.componentSize );
      +
      +		array[ i ] = x;
      +		array[ i + 1 ] = y;
      +		array[ i + 2 ] = z;
      +		return this;
      +	}
      +
      +	/**
      +	 * Set a Vector4 value at `index`.
      +	 *
      +	 * @param {Number} index The index at which to set the vec4 values from.
      +	 * @param {Vector4} vec2  Any object that has `x`, `y`, `z`, and `w` properties.
      +	 * @return {SPE.TypedArrayHelper} Instance of this class.
      +	 */
      +	setVec4( index, vec4 ) {
      +		return this.setVec4Components( index, vec4.x, vec4.y, vec4.z, vec4.w );
      +	}
      +
      +	/**
      +	 * Set a Vector4 value using raw components.
      +	 *
      +	 * @param {Number} index The index at which to set the vec4 values from.
      +	 * @param {Number} x     The Vec4's `x` component.
      +	 * @param {Number} y     The Vec4's `y` component.
      +	 * @param {Number} z     The Vec4's `z` component.
      +	 * @param {Number} w     The Vec4's `w` component.
      +	 * @return {SPE.TypedArrayHelper} Instance of this class.
      +	 */
      +	setVec4Components( index, x, y, z, w ) {
      +		const array = this.array,
      +			i = this.indexOffset + ( index * this.componentSize );
      +
      +		array[ i ] = x;
      +		array[ i + 1 ] = y;
      +		array[ i + 2 ] = z;
      +		array[ i + 3 ] = w;
      +		return this;
      +	}
      +
      +	/**
      +	 * Set a Matrix3 value at `index`.
      +	 *
      +	 * @param {Number} index The index at which to set the matrix values from.
      +	 * @param {Matrix3} mat3 The 3x3 matrix to set from. Must have a TypedArray property named `elements` to copy from.
      +	 * @return {SPE.TypedArrayHelper} Instance of this class.
      +	 */
      +	setMat3( index, mat3 ) {
      +		return this.setFromArray( this.indexOffset + ( index * this.componentSize ), mat3.elements );
      +	}
      +
      +	/**
      +	 * Set a Matrix4 value at `index`.
      +	 *
      +	 * @param {Number} index The index at which to set the matrix values from.
      +	 * @param {Matrix4} mat3 The 4x4 matrix to set from. Must have a TypedArray property named `elements` to copy from.
      +	 * @return {SPE.TypedArrayHelper} Instance of this class.
      +	 */
      +	setMat4( index, mat4 ) {
      +		return this.setFromArray( this.indexOffset + ( index * this.componentSize ), mat4.elements );
      +	}
      +
      +	/**
      +	 * Set a Color value at `index`.
      +	 *
      +	 * @param {Number} index The index at which to set the vec3 values from.
      +	 * @param {Color} color  Any object that has `r`, `g`, and `b` properties.
      +	 * @return {SPE.TypedArrayHelper} Instance of this class.
      +	 */
      +	setColor( index, color ) {
      +		return this.setVec3Components( index, color.r, color.g, color.b );
      +	}
      +
      +	/**
      +	 * Set a Number value at `index`.
      +	 *
      +	 * @param {Number} index The index at which to set the vec3 values from.
      +	 * @param {Number} numericValue  The number to assign to this index in the array.
      +	 * @return {SPE.TypedArrayHelper} Instance of this class.
      +	 */
      +	setNumber( index, numericValue ) {
      +		this.array[ this.indexOffset + ( index * this.componentSize ) ] = numericValue;
      +		return this;
      +	}
      +
      +	/**
      +	 * Returns the value of the array at the given index, taking into account
      +	 * the `indexOffset` property of this class.
      +	 *
      +	 * Note that this function ignores the component size and will just return a
      +	 * single value.
      +	 *
      +	 * @param  {Number} index The index in the array to fetch.
      +	 * @return {Number}       The value at the given index.
      +	 */
      +	getValueAtIndex( index ) {
      +		return this.array[ this.indexOffset + index ];
      +	}
      +
      +	/**
      +	 * Returns the component value of the array at the given index, taking into account
      +	 * the `indexOffset` property of this class.
      +	 *
      +	 * If the componentSize is set to 3, then it will return a new TypedArray
      +	 * of length 3.
      +	 *
      +	 * @param  {Number} index The index in the array to fetch.
      +	 * @return {TypedArray}       The component value at the given index.
      +	 */
      +	getComponentValueAtIndex( index ) {
      +		return this.array.subarray( this.indexOffset + ( index * this.componentSize ) );
      +	}
      +}
      + +
    • + +
    +
    + + diff --git a/docs/source/src/shaders/shaderChunks.html b/docs/source/src/shaders/shaderChunks.html new file mode 100644 index 0000000..b9939e7 --- /dev/null +++ b/docs/source/src/shaders/shaderChunks.html @@ -0,0 +1,508 @@ + + + + + shaderChunks.js + + + + + +
    +
    + + + +
      + +
    • +
      +

      shaderChunks.js

      +
      +
    • + + + +
    • +
      + +
      + +
      + +
      + +
      export default {
      + +
    • + + +
    • +
      + +
      + +
      +

      Register color-packing define statements.

      + +
      + +
      	defines: [
      +		'#define PACKED_COLOR_SIZE 256.0',
      +		'#define PACKED_COLOR_DIVISOR 255.0',
      +	].join( '\n' ),
      + +
    • + + +
    • +
      + +
      + +
      +

      All uniforms used by vertex / fragment shaders

      + +
      + +
      	uniforms: [
      +		'uniform float deltaTime;',
      +		'uniform float runTime;',
      +		'uniform sampler2D tex;',
      +		'uniform vec4 textureAnimation;',
      +		'uniform float scale;',
      +	].join( '\n' ),
      + +
    • + + +
    • +
      + +
      + +
      +

      All attributes used by the vertex shader.

      +

      Note that some attributes are squashed into other ones:

      +
        +
      • Drag is acceleration.w
      • +
      + +
      + +
      	attributes: [
      +		'attribute vec4 acceleration;',
      +		'attribute vec3 velocity;',
      +		'attribute vec4 rotation;',
      +		'attribute vec3 rotationCenter;',
      +		'attribute vec4 params;',
      +		'attribute vec4 size;',
      +		'attribute vec4 angle;',
      +		'attribute vec4 color;',
      +		'attribute vec4 opacity;',
      +	].join( '\n' ),
      + +
    • + + +
    • +
      + +
      + +
      + +
      + +
      	varyings: [
      +		'varying vec4 vColor;',
      +		'#ifdef SHOULD_ROTATE_TEXTURE',
      +		'    varying float vAngle;',
      +		'#endif',
      +
      +		'#ifdef SHOULD_CALCULATE_SPRITE',
      +		'    varying vec4 vSpriteSheet;',
      +		'#endif',
      +	].join( '\n' ),
      + +
    • + + +
    • +
      + +
      + +
      +

      Branch-avoiding comparison fns

      + + +
      + +
      	branchAvoidanceFunctions: [
      +		'float when_gt(float x, float y) {',
      +		'    return max(sign(x - y), 0.0);',
      +		'}',
      +
      +		'float when_lt(float x, float y) {',
      +		'    return min( max(1.0 - sign(x - y), 0.0), 1.0 );',
      +		'}',
      +
      +		'float when_eq( float x, float y ) {',
      +		'    return 1.0 - abs( sign( x - y ) );',
      +		'}',
      +
      +		'float when_ge(float x, float y) {',
      +		'  return 1.0 - when_lt(x, y);',
      +		'}',
      +
      +		'float when_le(float x, float y) {',
      +		'  return 1.0 - when_gt(x, y);',
      +		'}',
      + +
    • + + +
    • +
      + +
      + +
      +

      Branch-avoiding logical operators +(to be used with above comparison fns)

      + +
      + +
      		'float and(float a, float b) {',
      +		'    return a * b;',
      +		'}',
      +
      +		'float or(float a, float b) {',
      +		'    return min(a + b, 1.0);',
      +		'}',
      +	].join( '\n' ),
      + +
    • + + +
    • + + +
      	unpackColor: [
      +		'vec3 unpackColor( in float hex ) {',
      +		'   vec3 c = vec3( 0.0 );',
      +
      +		'   float r = mod( (hex / PACKED_COLOR_SIZE / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );',
      +		'   float g = mod( (hex / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );',
      +		'   float b = mod( hex, PACKED_COLOR_SIZE );',
      +
      +		'   c.r = r / PACKED_COLOR_DIVISOR;',
      +		'   c.g = g / PACKED_COLOR_DIVISOR;',
      +		'   c.b = b / PACKED_COLOR_DIVISOR;',
      +
      +		'   return c;',
      +		'}',
      +	].join( '\n' ),
      +
      +	unpackRotationAxis: [
      +		'vec3 unpackRotationAxis( in float hex ) {',
      +		'   vec3 c = vec3( 0.0 );',
      +
      +		'   float r = mod( (hex / PACKED_COLOR_SIZE / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );',
      +		'   float g = mod( (hex / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );',
      +		'   float b = mod( hex, PACKED_COLOR_SIZE );',
      +
      +		'   c.r = r / PACKED_COLOR_DIVISOR;',
      +		'   c.g = g / PACKED_COLOR_DIVISOR;',
      +		'   c.b = b / PACKED_COLOR_DIVISOR;',
      +
      +		'   c *= vec3( 2.0 );',
      +		'   c -= vec3( 1.0 );',
      +
      +		'   return c;',
      +		'}',
      +	].join( '\n' ),
      +
      +	floatOverLifetime: [
      +		'float getFloatOverLifetime( in float positionInTime, in vec4 attr ) {',
      +		'    highp float value = 0.0;',
      +		'    float deltaAge = positionInTime * float( VALUE_OVER_LIFETIME_LENGTH - 1 );',
      +		'    float fIndex = 0.0;',
      +		'    float shouldApplyValue = 0.0;',
      + +
    • + + +
    • +
      + +
      + +
      +

      This might look a little odd, but it’s faster in the testing I’ve done than using branches. +Uses basic maths to avoid branching.

      +

      Take a look at the branch-avoidance functions defined above, +and be sure to check out The Orange Duck site where I got this +from (link above).

      + +
      + +
    • + + +
    • +
      + +
      + +
      +

      Fix for static emitters (age is always zero).

      + +
      + +
      		'    value += attr[ 0 ] * when_eq( deltaAge, 0.0 );',
      +		'',
      +		'    for( int i = 0; i < VALUE_OVER_LIFETIME_LENGTH - 1; ++i ) {',
      +		'       fIndex = float( i );',
      +		'       shouldApplyValue = and( when_gt( deltaAge, fIndex ), when_le( deltaAge, fIndex + 1.0 ) );',
      +		'       value += shouldApplyValue * mix( attr[ i ], attr[ i + 1 ], deltaAge - fIndex );',
      +		'    }',
      +		'',
      +		'    return value;',
      +		'}',
      +	].join( '\n' ),
      +
      +	colorOverLifetime: [
      +		'vec3 getColorOverLifetime( in float positionInTime, in vec3 color1, in vec3 color2, in vec3 color3, in vec3 color4 ) {',
      +		'    vec3 value = vec3( 0.0 );',
      +		'    value.x = getFloatOverLifetime( positionInTime, vec4( color1.x, color2.x, color3.x, color4.x ) );',
      +		'    value.y = getFloatOverLifetime( positionInTime, vec4( color1.y, color2.y, color3.y, color4.y ) );',
      +		'    value.z = getFloatOverLifetime( positionInTime, vec4( color1.z, color2.z, color3.z, color4.z ) );',
      +		'    return value;',
      +		'}',
      +	].join( '\n' ),
      +
      +	paramFetchingFunctions: [
      +		'float getAlive() {',
      +		'   return params.x;',
      +		'}',
      +
      +		'float getAge() {',
      +		'   return params.y;',
      +		'}',
      +
      +		'float getMaxAge() {',
      +		'   return params.z;',
      +		'}',
      +
      +		'float getWiggle() {',
      +		'   return params.w;',
      +		'}',
      +	].join( '\n' ),
      +
      +	forceFetchingFunctions: [
      +		'vec4 getPosition( in float age ) {',
      +		'   return modelViewMatrix * vec4( position, 1.0 );',
      +		'}',
      +
      +		'vec3 getVelocity( in float age ) {',
      +		'   return velocity * age;',
      +		'}',
      +
      +		'vec3 getAcceleration( in float age ) {',
      +		'   return acceleration.xyz * age;',
      +		'}',
      +	].join( '\n' ),
      +
      +
      +	rotationFunctions: [
      + +
    • + + +
    • + + +
      		'#ifdef SHOULD_ROTATE_PARTICLES',
      +		'   mat4 getRotationMatrix( in vec3 axis, in float angle) {',
      +		'       axis = normalize(axis);',
      +		'       float s = sin(angle);',
      +		'       float c = cos(angle);',
      +		'       float oc = 1.0 - c;',
      +		'',
      +		'       return mat4(oc * axis.x * axis.x + c,           oc * axis.x * axis.y - axis.z * s,  oc * axis.z * axis.x + axis.y * s,  0.0,',
      +		'                   oc * axis.x * axis.y + axis.z * s,  oc * axis.y * axis.y + c,           oc * axis.y * axis.z - axis.x * s,  0.0,',
      +		'                   oc * axis.z * axis.x - axis.y * s,  oc * axis.y * axis.z + axis.x * s,  oc * axis.z * axis.z + c,           0.0,',
      +		'                   0.0,                                0.0,                                0.0,                                1.0);',
      +		'   }',
      +		'',
      +		'   vec3 getRotation( in vec3 pos, in float positionInTime ) {',
      +		'      if( rotation.y == 0.0 ) {',
      +		'           return pos;',
      +		'      }',
      +		'',
      +		'      vec3 axis = unpackRotationAxis( rotation.x );',
      +		'      vec3 center = rotationCenter;',
      +		'      vec3 translated;',
      +		'      mat4 rotationMatrix;',
      +
      +		'      float angle = 0.0;',
      +		'      angle += when_eq( rotation.z, 0.0 ) * rotation.y;',
      +		'      angle += when_gt( rotation.z, 0.0 ) * mix( 0.0, rotation.y, positionInTime );',
      +		'      translated = rotationCenter - pos;',
      +		'      rotationMatrix = getRotationMatrix( axis, angle );',
      +		'      return center - vec3( rotationMatrix * vec4( translated, 0.0 ) );',
      +		'   }',
      +		'#endif',
      +	].join( '\n' ),
      + +
    • + + +
    • +
      + +
      + +
      +

      Fragment chunks

      + +
      + +
      	rotateTexture: [
      +		'    vec2 vUv = vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y );',
      +		'',
      +		'    #ifdef SHOULD_ROTATE_TEXTURE',
      +		'       float x = gl_PointCoord.x - 0.5;',
      +		'       float y = 1.0 - gl_PointCoord.y - 0.5;',
      +		'       float c = cos( -vAngle );',
      +		'       float s = sin( -vAngle );',
      +
      +		'       vUv = vec2( c * x + s * y + 0.5, c * y - s * x + 0.5 );',
      +		'    #endif',
      +		'',
      + +
    • + + +
    • +
      + +
      + +
      +

      Spritesheets overwrite angle calculations.

      + +
      + +
      		'    #ifdef SHOULD_CALCULATE_SPRITE',
      +		'        float framesX = vSpriteSheet.x;',
      +		'        float framesY = vSpriteSheet.y;',
      +		'        float columnNorm = vSpriteSheet.z;',
      +		'        float rowNorm = vSpriteSheet.w;',
      +
      +		'        vUv.x = gl_PointCoord.x * framesX + columnNorm;',
      +		'        vUv.y = 1.0 - (gl_PointCoord.y * framesY + rowNorm);',
      +		'    #endif',
      +
      +		'',
      +		'    vec4 rotatedTexture = texture2D( tex, vUv );',
      +	].join( '\n' ),
      +};
      + +
    • + +
    +
    + + diff --git a/docs/source/src/shaders/shaders.html b/docs/source/src/shaders/shaders.html new file mode 100644 index 0000000..226f42f --- /dev/null +++ b/docs/source/src/shaders/shaders.html @@ -0,0 +1,491 @@ + + + + + shaders.js + + + + + +
    +
    + + + +
      + +
    • +
      +

      shaders.js

      +
      +
    • + + + +
    • +
      + +
      + +
      + +
      + +
      import * as THREE from 'three';
      +import shaderChunks from './shaderChunks';
      +
      +export default {
      +	vertex: [
      +		shaderChunks.defines,
      +		shaderChunks.uniforms,
      +		shaderChunks.attributes,
      +		shaderChunks.varyings,
      +
      +		THREE.ShaderChunk.common,
      +		THREE.ShaderChunk.logdepthbuf_pars_vertex,
      +		THREE.ShaderChunk.fog_pars_vertex,
      +
      +		shaderChunks.branchAvoidanceFunctions,
      +		shaderChunks.unpackColor,
      +		shaderChunks.unpackRotationAxis,
      +		shaderChunks.floatOverLifetime,
      +		shaderChunks.colorOverLifetime,
      +		shaderChunks.paramFetchingFunctions,
      +		shaderChunks.forceFetchingFunctions,
      +		shaderChunks.rotationFunctions,
      +
      +		'void main() {',
      + +
    • + + +
    • +
      + +
      + +
      +

      Setup…

      + +
      + +
      		'    highp float age = getAge();',
      +		'    highp float alive = getAlive();',
      +		'    highp float maxAge = getMaxAge();',
      +		'    highp float positionInTime = (age / maxAge);',
      +		'    highp float isAlive = when_gt( alive, 0.0 );',
      +
      +		'    #ifdef SHOULD_WIGGLE_PARTICLES',
      +		'        float wiggleAmount = positionInTime * getWiggle();',
      +		'        float wiggleSin = isAlive * sin( wiggleAmount );',
      +		'        float wiggleCos = isAlive * cos( wiggleAmount );',
      +		'    #endif',
      + +
    • + + +
    • +
      + +
      + +
      +

      Forces

      + +
      + +
    • + + +
    • +
      + +
      + +
      +

      Get forces & position

      + +
      + +
      		'    vec3 vel = getVelocity( age );',
      +		'    vec3 accel = getAcceleration( age );',
      +		'    vec3 force = vec3( 0.0 );',
      +		'    vec3 pos = vec3( position );',
      + +
    • + + +
    • +
      + +
      + +
      +

      Calculate the required drag to apply to the forces.

      + +
      + +
      		'    float drag = 1.0 - (positionInTime * 0.5) * acceleration.w;',
      + +
    • + + +
    • +
      + +
      + +
      +

      Integrate forces…

      + +
      + +
      		'    force += vel;',
      +		'    force *= drag;',
      +		'    force += accel * age;',
      +		'    pos += force;',
      + +
    • + + +
    • +
      + +
      + +
      +

      Wiggly wiggly wiggle!

      + +
      + +
      		'    #ifdef SHOULD_WIGGLE_PARTICLES',
      +		'        pos.x += wiggleSin;',
      +		'        pos.y += wiggleCos;',
      +		'        pos.z += wiggleSin;',
      +		'    #endif',
      + +
    • + + +
    • +
      + +
      + +
      +

      Rotate the emitter around it’s central point

      + +
      + +
      		'    #ifdef SHOULD_ROTATE_PARTICLES',
      +		'        pos = getRotation( pos, positionInTime );',
      +		'    #endif',
      + +
    • + + +
    • +
      + +
      + +
      +

      Convert pos to a world-space value

      + +
      + +
      		'    vec4 mvPosition = modelViewMatrix * vec4( pos, 1.0 );',
      + +
    • + + +
    • +
      + +
      + +
      +

      Determine point size.

      + +
      + +
      		'    highp float pointSize = getFloatOverLifetime( positionInTime, size ) * isAlive;',
      + +
    • + + +
    • +
      + +
      + +
      +

      Determine perspective

      + +
      + +
      		'    #ifdef HAS_PERSPECTIVE',
      +		'        float perspective = scale / length( mvPosition.xyz );',
      +		'    #else',
      +		'        float perspective = 1.0;',
      +		'    #endif',
      + +
    • + + +
    • +
      + +
      + +
      +

      Apply perpective to pointSize value

      + +
      + +
      		'    float pointSizePerspective = pointSize * perspective;',
      + +
    • + + +
    • +
      + +
      + +
      +

      Appearance

      + +
      + +
    • + + +
    • +
      + +
      + +
      +

      Determine color and opacity for this particle

      + +
      + +
      		'    #ifdef COLORIZE',
      +		'       vec3 c = isAlive * getColorOverLifetime(',
      +		'           positionInTime,',
      +		'           unpackColor( color.x ),',
      +		'           unpackColor( color.y ),',
      +		'           unpackColor( color.z ),',
      +		'           unpackColor( color.w )',
      +		'       );',
      +		'    #else',
      +		'       vec3 c = vec3(1.0);',
      +		'    #endif',
      +
      +		'    float o = isAlive * getFloatOverLifetime( positionInTime, opacity );',
      + +
    • + + +
    • +
      + +
      + +
      +

      Assign color to vColor varying.

      + +
      + +
      		'    vColor = vec4( c, o );',
      + +
    • + + +
    • +
      + +
      + +
      +

      Determine angle

      + +
      + +
      		'    #ifdef SHOULD_ROTATE_TEXTURE',
      +		'        vAngle = isAlive * getFloatOverLifetime( positionInTime, angle );',
      +		'    #endif',
      + +
    • + + +
    • +
      + +
      + +
      +

      If this particle is using a sprite-sheet as a texture, we’ll have to figure out +what frame of the texture the particle is using at it’s current position in time.

      + +
      + +
      		'    #ifdef SHOULD_CALCULATE_SPRITE',
      +		'        float framesX = textureAnimation.x;',
      +		'        float framesY = textureAnimation.y;',
      +		'        float loopCount = textureAnimation.w;',
      +		'        float totalFrames = textureAnimation.z;',
      +		'        float frameNumber = mod( (positionInTime * loopCount) * totalFrames, totalFrames );',
      +
      +		'        float column = floor(mod( frameNumber, framesX ));',
      +		'        float row = floor( (frameNumber - column) / framesX );',
      +
      +		'        float columnNorm = column / framesX;',
      +		'        float rowNorm = row / framesY;',
      +
      +		'        vSpriteSheet.x = 1.0 / framesX;',
      +		'        vSpriteSheet.y = 1.0 / framesY;',
      +		'        vSpriteSheet.z = columnNorm;',
      +		'        vSpriteSheet.w = rowNorm;',
      +		'    #endif',
      + +
    • + + +
    • +
      + +
      + +
      +

      Write values

      + +
      + +
    • + + +
    • +
      + +
      + +
      +

      Set PointSize according to size at current point in time.

      + +
      + +
      		'    gl_PointSize = pointSizePerspective;',
      +		'    gl_Position = projectionMatrix * mvPosition;',
      +
      +		THREE.ShaderChunk.logdepthbuf_vertex,
      +		THREE.ShaderChunk.fog_vertex,
      +
      +		'}',
      +	].join( '\n' ),
      +
      +	fragment: [
      +		shaderChunks.uniforms,
      +
      +		THREE.ShaderChunk.common,
      +		THREE.ShaderChunk.fog_pars_fragment,
      +		THREE.ShaderChunk.logdepthbuf_pars_fragment,
      +
      +		shaderChunks.varyings,
      +
      +		shaderChunks.branchAvoidanceFunctions,
      +
      +		'void main() {',
      +		'    vec3 outgoingLight = vColor.xyz;',
      +		'    ',
      +		'    #ifdef ALPHATEST',
      +		'       if ( vColor.w < float(ALPHATEST) ) discard;',
      +		'    #endif',
      +
      +		shaderChunks.rotateTexture,
      +
      +		THREE.ShaderChunk.logdepthbuf_fragment,
      +
      +		'    outgoingLight = vColor.xyz * rotatedTexture.xyz;',
      +		'    gl_FragColor = vec4( outgoingLight.xyz, rotatedTexture.w * vColor.w );',
      +
      +		THREE.ShaderChunk.fog_fragment,
      +
      +		'}',
      +	].join( '\n' ),
      +};
      + +
    • + +
    +
    + + diff --git a/examples/activeMultiplier.html b/examples/activeMultiplier.html index f0722b6..8e929b3 100644 --- a/examples/activeMultiplier.html +++ b/examples/activeMultiplier.html @@ -13,9 +13,9 @@ - + - + + - + + - + + - + + - + + - + + - + + - + + - + + - + + - + + - + - + - + diff --git a/docs/api/constants_typeSizeMap.js.html b/docs/api/constants_typeSizeMap.js.html index d8e1d3a..d75f91f 100644 --- a/docs/api/constants_typeSizeMap.js.html +++ b/docs/api/constants_typeSizeMap.js.html @@ -91,7 +91,7 @@

    Home

    Namespaces

    • diff --git a/docs/api/constants_valueTypes.js.html b/docs/api/constants_valueTypes.js.html index 4f9aaae..f388594 100644 --- a/docs/api/constants_valueTypes.js.html +++ b/docs/api/constants_valueTypes.js.html @@ -68,7 +68,7 @@

      Home

      Namespaces

      • diff --git a/docs/api/core_Emitter.js.html b/docs/api/core_Emitter.js.html index aa12c5d..df6b6c5 100644 --- a/docs/api/core_Emitter.js.html +++ b/docs/api/core_Emitter.js.html @@ -996,7 +996,7 @@

        Home

        Namespaces

        • diff --git a/docs/api/core_Group.js.html b/docs/api/core_Group.js.html index 37b4d4f..82f9402 100644 --- a/docs/api/core_Group.js.html +++ b/docs/api/core_Group.js.html @@ -766,7 +766,7 @@

          Home

          Namespaces

          • diff --git a/docs/api/core_utils.js.html b/docs/api/core_utils.js.html index f072e86..6aad58f 100644 --- a/docs/api/core_utils.js.html +++ b/docs/api/core_utils.js.html @@ -746,7 +746,7 @@

            Home

            Namespaces

            • diff --git a/docs/api/global.html b/docs/api/global.html index 6a0e5da..4ac78c4 100644 --- a/docs/api/global.html +++ b/docs/api/global.html @@ -9424,7 +9424,7 @@

              Home

              Namespaces

              • diff --git a/docs/api/helpers_ShaderAttribute.js.html b/docs/api/helpers_ShaderAttribute.js.html index 43390b6..791d230 100644 --- a/docs/api/helpers_ShaderAttribute.js.html +++ b/docs/api/helpers_ShaderAttribute.js.html @@ -214,7 +214,7 @@

                Home

                Namespaces

                • diff --git a/docs/api/helpers_TypedArrayHelper.js.html b/docs/api/helpers_TypedArrayHelper.js.html index 230919e..3e56912 100644 --- a/docs/api/helpers_TypedArrayHelper.js.html +++ b/docs/api/helpers_TypedArrayHelper.js.html @@ -337,7 +337,7 @@

                  Home

                  Namespaces

                  • diff --git a/docs/api/index.html b/docs/api/index.html index 1bb4a95..acae45a 100644 --- a/docs/api/index.html +++ b/docs/api/index.html @@ -56,7 +56,7 @@

                    Home

                    Namespaces

                    • diff --git a/docs/api/module.html#.exports b/docs/api/module.html#.exports index 87d654c..541a8b5 100644 --- a/docs/api/module.html#.exports +++ b/docs/api/module.html#.exports @@ -5065,7 +5065,7 @@ Works for negative numbers as well as positive.
                      - Documentation generated by JSDoc 3.6.4 on Sun Jun 28 2020 15:22:31 GMT+0100 (British Summer Time) + Documentation generated by JSDoc 3.6.4 on Sun Jun 28 2020 15:31:45 GMT+0100 (British Summer Time)
                      diff --git a/webpack.config.js b/webpack.config.js index 1d5ca5c..48b04ee 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -4,7 +4,7 @@ const packageJSON = require( './package.json' ); const licenseBanner = '/* ' + packageJSON.name + ' ' + packageJSON.version + '\n' + ' * ' + '\n' + - ' * (c) 2015 Luke Moody (http://www.github.com/squarefeet)' + '\n' + + ' * (c) 2020 Luke Moody (http://www.github.com/squarefeet)' + '\n' + ' * Originally based on Lee Stemkoski\'s original work (https://github.com/stemkoski/stemkoski.github.com/blob/master/Three.js/js/ParticleEngine.js).' + '\n' + ' *' + '\n' + ' * ' + packageJSON.name + ' may be freely distributed under the MIT license (See LICENSE at root of this repository.)' + '\n */\n';