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 92ee19c..0000000 --- a/build/SPE.js +++ /dev/null @@ -1,3588 +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.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; - - // 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; -}; - -/** - * 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 - // - 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( 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, - 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 = { - 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 ) { - 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 dcc9d56..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.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"), -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={ -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..e8f82a9 --- /dev/null +++ b/dist/SPE.js @@ -0,0 +1,12 @@ +/*! + * /* shader-particle-engine 2.0.0 + * * + * * (c) 2020 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/ChangeLog.md b/docs/ChangeLog.md index 0013619..4caa338 100644 --- a/docs/ChangeLog.md +++ b/docs/ChangeLog.md @@ -1,6 +1,21 @@ Change Log ========== +v2.0.0 +------ +* Replaced Grunt with Webpack. +* Moved to import/export. +* Full module support. + * `Group`, `Emitter`, `distribution`, and `globals` are now the only exposed objects. +* `SPE.valueOverLifetimeLength` is now `SPE.globals.valueOverLifetimeLength` (or accessed by importing `globals`) +* Distribution files are now located in `dist`, changed from `build`. +* `SPE.min.js` has been removed. Please use `SPE.js` instead - this is minified. + +v1.1.0 +------ +* Updated `texture` uniform name to `tex`. +* Moved to use BufferAttribute.usage instead of BufferAttribute.dynamic. + v1.0.6 ------ * #110: Added `three` as an NPM dependency. 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..4841495 --- /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:31:45 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..d75f91f --- /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:31:45 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..f388594 --- /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:31:45 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..df6b6c5 --- /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:31:45 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..82f9402 --- /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:31:45 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..6aad58f --- /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:31:45 GMT+0100 (British Summer Time) +
+ + + + + diff --git a/docs/api/global.html b/docs/api/global.html index 7d2320f..4ac78c4 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:31:45 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..791d230 --- /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:31:45 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..3e56912 --- /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:31:45 GMT+0100 (British Summer Time) +
+ + + + + diff --git a/docs/api/index.html b/docs/api/index.html index df42ea5..acae45a 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:31:45 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..541a8b5 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:31:45 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 98a9a7f..0000000 Binary files a/docs/source/public/fonts/novecento-bold.eot and /dev/null differ diff --git a/docs/source/public/fonts/novecento-bold.ttf b/docs/source/public/fonts/novecento-bold.ttf deleted file mode 100644 index 2af39b0..0000000 Binary files a/docs/source/public/fonts/novecento-bold.ttf and /dev/null differ diff --git a/docs/source/public/fonts/novecento-bold.woff b/docs/source/public/fonts/novecento-bold.woff deleted file mode 100644 index de558b5..0000000 Binary files a/docs/source/public/fonts/novecento-bold.woff and /dev/null differ diff --git a/docs/source/public/fonts/roboto-black.eot b/docs/source/public/fonts/roboto-black.eot new file mode 100755 index 0000000..571ed49 Binary files /dev/null and b/docs/source/public/fonts/roboto-black.eot differ diff --git a/docs/source/public/fonts/roboto-black.ttf b/docs/source/public/fonts/roboto-black.ttf new file mode 100755 index 0000000..e0300b3 Binary files /dev/null and b/docs/source/public/fonts/roboto-black.ttf differ diff --git a/docs/source/public/fonts/roboto-black.woff b/docs/source/public/fonts/roboto-black.woff new file mode 100755 index 0000000..642e5b6 Binary files /dev/null and b/docs/source/public/fonts/roboto-black.woff differ 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 @@ - + - + + - + + - + + - + + - + + - + + - + + - + + - + + - + + - + + - + - + - +