diff --git a/.gitignore b/.gitignore index 3fd1631..0b1c050 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ gh-pages node_modules/ npm-debug.log package-lock.json + +.idea diff --git a/README.md b/README.md index c64d9d3..6bb11ef 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ Install and use by directly including the [browser files](dist). A-Frame Particle System Component Example - + diff --git a/dist/aframe-particle-system-component.js b/dist/aframe-particle-system-component.js index 508b281..c9e9c2a 100644 --- a/dist/aframe-particle-system-component.js +++ b/dist/aframe-particle-system-component.js @@ -1,3971 +1,3970 @@ -/******/ (function(modules) { // webpackBootstrap -/******/ // The module cache -/******/ var installedModules = {}; - -/******/ // The require function -/******/ function __webpack_require__(moduleId) { - -/******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) -/******/ return installedModules[moduleId].exports; - -/******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ exports: {}, -/******/ id: moduleId, -/******/ loaded: false -/******/ }; - -/******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); - -/******/ // Flag the module as loaded -/******/ module.loaded = true; - -/******/ // Return the exports of the module -/******/ return module.exports; -/******/ } - - -/******/ // expose the modules object (__webpack_modules__) -/******/ __webpack_require__.m = modules; - -/******/ // expose the module cache -/******/ __webpack_require__.c = installedModules; - -/******/ // __webpack_public_path__ -/******/ __webpack_require__.p = ""; - -/******/ // Load entry module and return exports -/******/ return __webpack_require__(0); -/******/ }) -/************************************************************************/ -/******/ ([ -/* 0 */ -/***/ (function(module, exports, __webpack_require__) { - - /** - * Particles component for A-Frame. - * - * ShaderParticleEngine by Squarefeet (https://github.com/squarefeet). - */ - - var SPE = __webpack_require__(1); - - if (typeof AFRAME === 'undefined') { - throw new Error('Component attempted to register before AFRAME was available.'); - } - - AFRAME.registerComponent('particle-system', { - - schema: { - preset: { - type: 'string', - default: '', - oneOf: ['default', 'dust', 'snow', 'rain'] - }, - maxAge: { - type: 'number', - default: 6 - }, - positionSpread: { - type: 'vec3', - default: { x: 0, y: 0, z: 0 } - }, - type: { - type: 'number', - default: SPE.distributions.BOX - }, - rotationAxis: { - type: 'string', - default: 'x' - }, - rotationAngle: { - type: 'number', - default: 0 - }, - rotationAngleSpread: { - type: 'number', - default: 0 - }, - accelerationValue: { - type: 'vec3', - default: { x: 0, y: -10, z: 0 } - }, - accelerationSpread: { - type: 'vec3', - default: { x: 10, y: 0, z: 10 } - }, - velocityValue: { - type: 'vec3', - default: { x: 0, y: 25, z: 0 } - }, - velocitySpread: { - type: 'vec3', - default: { x: 10, y: 7.5, z: 10 } - }, - dragValue: { - type: 'number', - default: 0 - }, - dragSpread: { - type: 'number', - default: 0 - }, - dragRandomise: { - type: 'boolean', - default: false - }, - color: { - type: 'array', - default: [ '#0000FF', '#FF0000' ] - }, - size: { - type: 'array', - default: [ '1' ] - }, - sizeSpread: { - type: 'array', - default: [ '0' ] - }, - direction: { - type: 'number', - default: 1 - }, - duration: { - type: 'number', - default: Infinity - }, - particleCount: { - type: 'number', - default: 1000 - }, - texture: { - type: 'asset', - default: 'https://cdn.rawgit.com/IdeaSpaceVR/aframe-particle-system-component/master/dist/images/star2.png' - }, - randomise: { - type: 'boolean', - default: false - }, - opacity: { - type: 'array', - default: [ '1' ] - }, - opacitySpread: { - type: 'array', - default: [ '0' ] - }, - maxParticleCount: { - type: 'number', - default: 250000 - }, - blending: { - type: 'number', - default: THREE.AdditiveBlending, - oneOf: [THREE.NoBlending,THREE.NormalBlending,THREE.AdditiveBlending,THREE.SubtractiveBlending,THREE.MultiplyBlending] - }, - enabled: { - type:'boolean', - default:true - } - }, - - - init: function() { - - this.presets = {}; - - /* preset settings can be overwritten */ - - this.presets['dust'] = { - maxAge: 20, - positionSpread: {x:100,y:100,z:100}, - rotationAngle: 3.14, - accelerationValue: {x: 0, y: 0, z: 0}, - accelerationSpread: {x: 0, y: 0, z: 0}, - velocityValue: {x: 1, y: 0.3, z: 1}, - velocitySpread: {x: 0.5, y: 1, z: 0.5}, - color: ['#FFFFFF'], - particleCount: 100, - texture: 'https://cdn.rawgit.com/IdeaSpaceVR/aframe-particle-system-component/master/dist/images/smokeparticle.png' - }; - - - this.presets['snow'] = { - maxAge: 20, - positionSpread: {x:100,y:100,z:100}, - rotationAngle: 3.14, - accelerationValue: {x: 0, y: 0, z: 0}, - accelerationSpread: {x: 0.2, y: 0, z: 0.2}, - velocityValue: {x: 0, y: 8, z: 0}, - velocitySpread: {x: 2, y: 0, z: 2}, - color: ['#FFFFFF'], - particleCount: 200, - texture: 'https://cdn.rawgit.com/IdeaSpaceVR/aframe-particle-system-component/master/dist/images/smokeparticle.png' - }; - - - this.presets['rain'] = { - maxAge: 1, - positionSpread: {x:100,y:100,z:100}, - rotationAngle: 3.14, - accelerationValue: {x: 0, y: 3, z: 0}, - accelerationSpread: {x: 2, y: 1, z: 2}, - velocityValue: {x: 0, y: 75, z: 0}, - velocitySpread: {x: 10, y: 50, z: 10}, - color: ['#FFFFFF'], - size: 0.4, - texture: 'https://cdn.rawgit.com/IdeaSpaceVR/aframe-particle-system-component/master/dist/images/raindrop.png' - }; - - - }, - - - update: function (oldData) { - - // Remove old particle group. - if (this.particleGroup) { - this.el.removeObject3D('particle-system'); - } - - // Set the selected preset, if any, or use an empty object to keep schema defaults - this.preset = this.presets[this.data.preset] || {}; - - // Get custom, preset, or default data for each property defined in the schema - for (var key in this.data) { - this.data[key] = this.applyPreset(key); - } - - this.initParticleSystem(this.data); - - if(this.data.enabled === true) { - this.startParticles() - } else { - this.stopParticles() - } - }, - - - applyPreset: function (key) { - // !this.attrValue[key] = the user did not set a custom value - // this.preset[key] = there exists a value for this key in the selected preset - if (!this.attrValue[key] && this.preset[key]) { - return this.preset[key]; - } else { - // Otherwise stick to the user or schema default value - return this.data[key]; - } - }, - - - tick: function(time, dt) { - - this.particleGroup.tick(dt / 1000); - }, - - - remove: function() { - - // Remove particle system. - if (!this.particleGroup) { return; } - this.el.removeObject3D('particle-system'); - }, - - startParticles: function() { - this.particleGroup.emitters.forEach(function(em) { em.enable() }); - }, - - stopParticles: function() { - this.particleGroup.emitters.forEach(function(em) { em.disable() }); - }, - - - initParticleSystem: function(settings) { - - var loader = new THREE.TextureLoader(); - var particle_texture = loader.load( - settings.texture, - function (texture) { - return texture; - }, - function (xhr) { - console.log((xhr.loaded / xhr.total * 100) + '% loaded'); - }, - function (xhr) { - console.log('An error occurred'); - } - ); - - this.particleGroup = new SPE.Group({ - texture: { - value: particle_texture - }, - maxParticleCount: settings.maxParticleCount, - blending: settings.blending - }); - - var emitter = new SPE.Emitter({ - maxAge: { - value: settings.maxAge - }, - type: { - value: settings.type - }, - position: { - spread: new THREE.Vector3(settings.positionSpread.x, settings.positionSpread.y, settings.positionSpread.z), - randomise: settings.randomise - //spreadClamp: new THREE.Vector3( 2, 2, 2 ), - //radius: 4 - }, - rotation: { - axis: (settings.rotationAxis=='x'?new THREE.Vector3(1, 0, 0):(settings.rotationAxis=='y'?new THREE.Vector3(0, 1, 0):(settings.rotationAxis=='z'?new THREE.Vector3(0, 0, 1):new THREE.Vector3(0, 1, 0)))), - angle: settings.rotationAngle, - angleSpread: settings.rotationAngleSpread, - static: true - }, - acceleration: { - value: new THREE.Vector3(settings.accelerationValue.x, settings.accelerationValue.y, settings.accelerationValue.z), - spread: new THREE.Vector3(settings.accelerationSpread.x, settings.accelerationSpread.y, settings.accelerationSpread.z) - }, - velocity: { - value: new THREE.Vector3(settings.velocityValue.x, settings.velocityValue.y, settings.velocityValue.z), - spread: new THREE.Vector3(settings.velocitySpread.x, settings.velocitySpread.y, settings.velocitySpread.z) - }, - drag: { - value: new THREE.Vector3(settings.dragValue.x, settings.dragValue.y, settings.dragValue.z), - spread: new THREE.Vector3(settings.dragSpread.x, settings.dragSpread.y, settings.dragSpread.z), - randomise: settings.dragRandomise - }, - color: { - value: settings.color.map(function(c) { return new THREE.Color(c); }) - }, - size: { value: settings.size.map(function (s) { return parseFloat(s); }), - spread: settings.sizeSpread.map(function (s) { return parseFloat(s); }) }, - - /*wiggle: { value: 4, spread: 2 }, //settings.wiggle,*/ - /*drag: { - value: settings.drag - },*/ - direction: { - value: settings.direction - }, - duration: settings.duration, - opacity: { value: settings.opacity.map(function (o) { return parseFloat(o); }), - spread: settings.opacitySpread.map(function (o) { return parseFloat(o); }) }, - particleCount: settings.particleCount - }); - - this.particleGroup.addEmitter(emitter); - this.particleGroup.mesh.frustumCulled = false; - this.el.setObject3D('particle-system', this.particleGroup.mesh); - } - }); - - -/***/ }), -/* 1 */ -/***/ (function(module, exports, __webpack_require__) { - - var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;/* 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.) - */ +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ({ + +/***/ "./lib/SPE.js": +/*!********************!*\ + !*** ./lib/SPE.js ***! + \********************/ +/***/ ((module, exports, __webpack_require__) => { + +var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;/* 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 ( true ) { + !(__WEBPACK_AMD_DEFINE_FACTORY__ = (SPE), + __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? + (__WEBPACK_AMD_DEFINE_FACTORY__.call(exports, __webpack_require__, exports, module)) : + __WEBPACK_AMD_DEFINE_FACTORY__), + __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); +} +else {} + + +/** + * 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 = { /** - * @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. + * Float + * @type {Number} */ + f: 1, /** - * Namespace for Shader Particle Engine. - * - * All SPE-related code sits under this namespace. - * - * @type {Object} - * @namespace + * Vec2 + * @type {Number} */ - 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 ( true ) { - !(__WEBPACK_AMD_DEFINE_FACTORY__ = (SPE), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.call(exports, __webpack_require__, exports, module)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); - } - else if ( typeof exports !== 'undefined' && typeof module !== 'undefined' ) { - module.exports = SPE; - } - + v2: 2, /** - * 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 + * Vec3 + * @type {Number} */ - 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; + v3: 3, /** - * 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. + * Vec4 + * @type {Number} */ - 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.' ); - } - }; + v4: 4, /** - * 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. + * Color + * @type {Number} */ - SPE.TypedArrayHelper.prototype.shrink = function( size ) { - 'use strict'; - - this.array = this.array.subarray( 0, size ); - this.size = size; - return this; - }; + c: 3, /** - * 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. + * Mat3 + * @type {Number} */ - 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; - }; - + m3: 9, /** - * 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. + * Mat4 + * @type {Number} */ - SPE.TypedArrayHelper.prototype.splice = function( start, end ) { - 'use strict'; - start *= this.componentSize; - end *= this.componentSize; - - var data = [], - array = this.array, - size = array.length; - - for ( var i = 0; i < size; ++i ) { - if ( i < start || i >= end ) { - data.push( array[ i ] ); - } - // array[ i ] = 0; - } - - this.setFromArray( 0, data ); - - return this; - }; - - - /** - * Copies from the given TypedArray into this one, using the index argument - * as the start position. Alias for `TypedArray.set`. Will automatically resize - * if the given source array is of a larger size than the internal array. - * - * @param {Number} index The start position from which to copy into this array. - * @param {TypedArray} array The array from which to copy; the source array. - * @return {SPE.TypedArrayHelper} Instance of this class. - */ - SPE.TypedArrayHelper.prototype.setFromArray = function( index, array ) { - 'use strict'; - - var sourceArraySize = array.length, - newSize = index + sourceArraySize; - - if ( newSize > this.array.length ) { - this.grow( newSize ); - } - else if ( newSize < this.array.length ) { - this.shrink( newSize ); - } - - this.array.set( array, this.indexOffset + index ); - - return this; - }; - - /** - * Set a Vector2 value at `index`. - * - * @param {Number} index The index at which to set the vec2 values from. - * @param {Vector2} vec2 Any object that has `x` and `y` properties. - * @return {SPE.TypedArrayHelper} Instance of this class. - */ - SPE.TypedArrayHelper.prototype.setVec2 = function( index, vec2 ) { - 'use strict'; - - return this.setVec2Components( index, vec2.x, vec2.y ); - }; - - /** - * Set a Vector2 value using raw components. - * - * @param {Number} index The index at which to set the vec2 values from. - * @param {Number} x The Vec2's `x` component. - * @param {Number} y The Vec2's `y` component. - * @return {SPE.TypedArrayHelper} Instance of this class. - */ - SPE.TypedArrayHelper.prototype.setVec2Components = function( index, x, y ) { - 'use strict'; - - var array = this.array, - i = this.indexOffset + ( index * this.componentSize ); - - array[ i ] = x; - array[ i + 1 ] = y; - return this; - }; - - /** - * Set a Vector3 value at `index`. - * - * @param {Number} index The index at which to set the vec3 values from. - * @param {Vector3} vec2 Any object that has `x`, `y`, and `z` properties. - * @return {SPE.TypedArrayHelper} Instance of this class. - */ - SPE.TypedArrayHelper.prototype.setVec3 = function( index, vec3 ) { - 'use strict'; - - return this.setVec3Components( index, vec3.x, vec3.y, vec3.z ); - }; - - /** - * Set a Vector3 value using raw components. - * - * @param {Number} index The index at which to set the vec3 values from. - * @param {Number} x The Vec3's `x` component. - * @param {Number} y The Vec3's `y` component. - * @param {Number} z The Vec3's `z` component. - * @return {SPE.TypedArrayHelper} Instance of this class. - */ - SPE.TypedArrayHelper.prototype.setVec3Components = function( index, x, y, z ) { - 'use strict'; - - var array = this.array, - i = this.indexOffset + ( index * this.componentSize ); - - array[ i ] = x; - array[ i + 1 ] = y; - array[ i + 2 ] = z; - return this; - }; - - /** - * Set a Vector4 value at `index`. - * - * @param {Number} index The index at which to set the vec4 values from. - * @param {Vector4} vec2 Any object that has `x`, `y`, `z`, and `w` properties. - * @return {SPE.TypedArrayHelper} Instance of this class. - */ - SPE.TypedArrayHelper.prototype.setVec4 = function( index, vec4 ) { - 'use strict'; - - return this.setVec4Components( index, vec4.x, vec4.y, vec4.z, vec4.w ); - }; - - /** - * Set a Vector4 value using raw components. - * - * @param {Number} index The index at which to set the vec4 values from. - * @param {Number} x The Vec4's `x` component. - * @param {Number} y The Vec4's `y` component. - * @param {Number} z The Vec4's `z` component. - * @param {Number} w The Vec4's `w` component. - * @return {SPE.TypedArrayHelper} Instance of this class. - */ - SPE.TypedArrayHelper.prototype.setVec4Components = function( index, x, y, z, w ) { - 'use strict'; - - var array = this.array, - i = this.indexOffset + ( index * this.componentSize ); - - array[ i ] = x; - array[ i + 1 ] = y; - array[ i + 2 ] = z; - array[ i + 3 ] = w; - return this; - }; - - /** - * Set a Matrix3 value at `index`. - * - * @param {Number} index The index at which to set the matrix values from. - * @param {Matrix3} mat3 The 3x3 matrix to set from. Must have a TypedArray property named `elements` to copy from. - * @return {SPE.TypedArrayHelper} Instance of this class. - */ - SPE.TypedArrayHelper.prototype.setMat3 = function( index, mat3 ) { - 'use strict'; - - return this.setFromArray( this.indexOffset + ( index * this.componentSize ), mat3.elements ); - }; - - /** - * Set a Matrix4 value at `index`. - * - * @param {Number} index The index at which to set the matrix values from. - * @param {Matrix4} mat3 The 4x4 matrix to set from. Must have a TypedArray property named `elements` to copy from. - * @return {SPE.TypedArrayHelper} Instance of this class. - */ - SPE.TypedArrayHelper.prototype.setMat4 = function( index, mat4 ) { - 'use strict'; - - return this.setFromArray( this.indexOffset + ( index * this.componentSize ), mat4.elements ); - }; - - /** - * Set a Color value at `index`. - * - * @param {Number} index The index at which to set the vec3 values from. - * @param {Color} color Any object that has `r`, `g`, and `b` properties. - * @return {SPE.TypedArrayHelper} Instance of this class. - */ - SPE.TypedArrayHelper.prototype.setColor = function( index, color ) { - 'use strict'; - - return this.setVec3Components( index, color.r, color.g, color.b ); - }; - - /** - * Set a Number value at `index`. - * - * @param {Number} index The index at which to set the vec3 values from. - * @param {Number} numericValue The number to assign to this index in the array. - * @return {SPE.TypedArrayHelper} Instance of this class. - */ - SPE.TypedArrayHelper.prototype.setNumber = function( index, numericValue ) { - 'use strict'; - - this.array[ this.indexOffset + ( index * this.componentSize ) ] = numericValue; - return this; - }; - - /** - * Returns the value of the array at the given index, taking into account - * the `indexOffset` property of this class. - * - * Note that this function ignores the component size and will just return a - * single value. - * - * @param {Number} index The index in the array to fetch. - * @return {Number} The value at the given index. - */ - SPE.TypedArrayHelper.prototype.getValueAtIndex = function( index ) { - 'use strict'; - - return this.array[ this.indexOffset + index ]; - }; - - /** - * Returns the component value of the array at the given index, taking into account - * the `indexOffset` property of this class. - * - * If the componentSize is set to 3, then it will return a new TypedArray - * of length 3. - * - * @param {Number} index The index in the array to fetch. - * @return {TypedArray} The component value at the given index. - */ - SPE.TypedArrayHelper.prototype.getComponentValueAtIndex = function( index ) { - 'use strict'; - - return this.array.subarray( this.indexOffset + ( index * this.componentSize ) ); - }; - - /** - * A helper to handle creating and updating a THREE.BufferAttribute instance. - * - * @author Luke Moody - * @constructor - * @param {String} type The buffer attribute type. See SPE.ShaderAttribute.typeSizeMap for valid values. - * @param {Boolean=} dynamicBuffer Whether this buffer attribute should be marked as dynamic or not. - * @param {Function=} arrayType A reference to a TypedArray constructor. Defaults to Float32Array if none provided. - */ - SPE.ShaderAttribute = function( type, dynamicBuffer, arrayType ) { - 'use strict'; - - var typeMap = SPE.ShaderAttribute.typeSizeMap; - - this.type = typeof type === 'string' && typeMap.hasOwnProperty( type ) ? type : 'f'; - this.componentSize = typeMap[ this.type ]; - this.arrayType = arrayType || Float32Array; - this.typedArray = null; - this.bufferAttribute = null; - this.dynamicBuffer = !!dynamicBuffer; - - this.updateMin = 0; - this.updateMax = 0; - }; - - SPE.ShaderAttribute.constructor = SPE.ShaderAttribute; - - /** - * A map of uniform types to their component size. - * @enum {Number} - */ - SPE.ShaderAttribute.typeSizeMap = { - /** - * Float - * @type {Number} - */ - f: 1, - - /** - * Vec2 - * @type {Number} - */ - v2: 2, - - /** - * Vec3 - * @type {Number} - */ - v3: 3, - - /** - * Vec4 - * @type {Number} - */ - v4: 4, - - /** - * Color - * @type {Number} - */ - c: 3, - - /** - * Mat3 - * @type {Number} - */ - m3: 9, - - /** - * Mat4 - * @type {Number} - */ - m4: 16 - }; - - /** - * Calculate the minimum and maximum update range for this buffer attribute using - * component size independant min and max values. - * - * @param {Number} min The start of the range to mark as needing an update. - * @param {Number} max The end of the range to mark as needing an update. - */ - SPE.ShaderAttribute.prototype.setUpdateRange = function( min, max ) { - 'use strict'; - - this.updateMin = Math.min( min * this.componentSize, this.updateMin * this.componentSize ); - this.updateMax = Math.max( max * this.componentSize, this.updateMax * this.componentSize ); - }; - - /** - * Calculate the number of indices that this attribute should mark as needing - * updating. Also marks the attribute as needing an update. - */ - SPE.ShaderAttribute.prototype.flagUpdate = function() { - 'use strict'; - - var attr = this.bufferAttribute, - range = attr.updateRange; - - range.offset = this.updateMin; - range.count = Math.min( ( this.updateMax - this.updateMin ) + this.componentSize, this.typedArray.array.length ); - // console.log( range.offset, range.count, this.typedArray.array.length ); - // console.log( 'flagUpdate:', range.offset, range.count ); - attr.needsUpdate = true; - }; - - - - /** - * Reset the index update counts for this attribute - */ - SPE.ShaderAttribute.prototype.resetUpdateRange = function() { - 'use strict'; - - this.updateMin = 0; - this.updateMax = 0; - }; - - SPE.ShaderAttribute.prototype.resetDynamic = function() { - 'use strict'; - this.bufferAttribute.usage = this.dynamicBuffer ? - THREE.DynamicDrawUsage : - THREE.StaticDrawUsage; - }; - - /** - * Perform a splice operation on this attribute's buffer. - * @param {Number} start The start index of the splice. Will be multiplied by the number of components for this attribute. - * @param {Number} end The end index of the splice. Will be multiplied by the number of components for this attribute. - */ - SPE.ShaderAttribute.prototype.splice = function( start, end ) { - 'use strict'; - - this.typedArray.splice( start, end ); - - // Reset the reference to the attribute's typed array - // since it has probably changed. - this.forceUpdateAll(); - }; + m4: 16 +}; + +/** + * Calculate the minimum and maximum update range for this buffer attribute using + * component size independant min and max values. + * + * @param {Number} min The start of the range to mark as needing an update. + * @param {Number} max The end of the range to mark as needing an update. + */ +SPE.ShaderAttribute.prototype.setUpdateRange = function( min, max ) { + 'use strict'; + + this.updateMin = Math.min( min * this.componentSize, this.updateMin * this.componentSize ); + this.updateMax = Math.max( max * this.componentSize, this.updateMax * this.componentSize ); +}; + +/** + * Calculate the number of indices that this attribute should mark as needing + * updating. Also marks the attribute as needing an update. + */ +SPE.ShaderAttribute.prototype.flagUpdate = function() { + 'use strict'; + + var attr = this.bufferAttribute, + range = attr.updateRange; + + range.offset = this.updateMin; + range.count = Math.min( ( this.updateMax - this.updateMin ) + this.componentSize, this.typedArray.array.length ); + // console.log( range.offset, range.count, this.typedArray.array.length ); + // console.log( 'flagUpdate:', range.offset, range.count ); + attr.needsUpdate = true; +}; + + + +/** + * Reset the index update counts for this attribute + */ +SPE.ShaderAttribute.prototype.resetUpdateRange = function() { + 'use strict'; + + this.updateMin = 0; + this.updateMax = 0; +}; + +SPE.ShaderAttribute.prototype.resetDynamic = function() { + 'use strict'; + this.bufferAttribute.usage = this.dynamicBuffer ? + THREE.DynamicDrawUsage : + THREE.StaticDrawUsage; +}; + +/** + * Perform a splice operation on this attribute's buffer. + * @param {Number} start The start index of the splice. Will be multiplied by the number of components for this attribute. + * @param {Number} end The end index of the splice. Will be multiplied by the number of components for this attribute. + */ +SPE.ShaderAttribute.prototype.splice = function( start, end ) { + 'use strict'; + + this.typedArray.splice( start, end ); + + // Reset the reference to the attribute's typed array + // since it has probably changed. + this.forceUpdateAll(); +}; + +SPE.ShaderAttribute.prototype.forceUpdateAll = function() { + 'use strict'; + + this.bufferAttribute.array = this.typedArray.array; + this.bufferAttribute.updateRange.offset = 0; + this.bufferAttribute.updateRange.count = -1; + // this.bufferAttribute.dynamic = false; + // this.bufferAttribute.usage = this.dynamicBuffer ? + // THREE.DynamicDrawUsage : + // THREE.StaticDrawUsage; + + this.bufferAttribute.usage = THREE.StaticDrawUsage; + this.bufferAttribute.needsUpdate = true; +}; + +/** + * Make sure this attribute has a typed array associated with it. + * + * If it does, then it will ensure the typed array is of the correct size. + * + * If not, a new SPE.TypedArrayHelper instance will be created. + * + * @param {Number} size The size of the typed array to create or update to. + */ +SPE.ShaderAttribute.prototype._ensureTypedArray = function( size ) { + 'use strict'; + + // Condition that's most likely to be true at the top: no change. + if ( this.typedArray !== null && this.typedArray.size === size * this.componentSize ) { + return; + } - SPE.ShaderAttribute.prototype.forceUpdateAll = function() { - 'use strict'; + // 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.updateRange.offset = 0; - this.bufferAttribute.updateRange.count = -1; - // this.bufferAttribute.dynamic = false; - // this.bufferAttribute.usage = this.dynamicBuffer ? - // THREE.DynamicDrawUsage : - // THREE.StaticDrawUsage; - - this.bufferAttribute.usage = THREE.StaticDrawUsage; - this.bufferAttribute.needsUpdate = true; - }; - - /** - * Make sure this attribute has a typed array associated with it. - * - * If it does, then it will ensure the typed array is of the correct size. - * - * If not, a new SPE.TypedArrayHelper instance will be created. - * - * @param {Number} size The size of the typed array to create or update to. - */ - SPE.ShaderAttribute.prototype._ensureTypedArray = function( size ) { - 'use strict'; - // Condition that's most likely to be true at the top: no change. - if ( this.typedArray !== null && this.typedArray.size === size * this.componentSize ) { - return; + // 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; } - // Resize the array if we need to, telling the TypedArrayHelper to - // ignore it's component size when evaluating size. - else if ( this.typedArray !== null && this.typedArray.size !== size ) { - this.typedArray.setSize( size ); - } - - // This condition should only occur once in an attribute's lifecycle. - else if ( this.typedArray === null ) { - this.typedArray = new SPE.TypedArrayHelper( this.arrayType, size, this.componentSize ); - } - }; - - - /** - * Creates a THREE.BufferAttribute instance if one doesn't exist already. - * - * Ensures a typed array is present by calling _ensureTypedArray() first. - * - * If a buffer attribute exists already, then it will be marked as needing an update. - * - * @param {Number} size The size of the typed array to create if one doesn't exist, or resize existing array to. - */ - SPE.ShaderAttribute.prototype._createBufferAttribute = function( size ) { - 'use strict'; - - // Make sure the typedArray is present and correct. - this._ensureTypedArray( size ); - - // Don't create it if it already exists, but do - // flag that it needs updating on the next render - // cycle. - if ( this.bufferAttribute !== null ) { - this.bufferAttribute.array = this.typedArray.array; - - // Since THREE.js version 81, dynamic count calculation was removed - // so I need to do it manually here. - // - // In the next minor release, I may well remove this check and force - // dependency on THREE r81+. - if ( parseFloat( THREE.REVISION ) >= 81 ) { - this.bufferAttribute.count = this.bufferAttribute.array.length / this.bufferAttribute.itemSize; - } - - this.bufferAttribute.needsUpdate = true; - return; - } - - this.bufferAttribute = new THREE.BufferAttribute( this.typedArray.array, this.componentSize ); - // this.bufferAttribute.dynamic = this.dynamicBuffer; - this.bufferAttribute.usage = this.dynamicBuffer ? - THREE.DynamicDrawUsage : - THREE.StaticDrawUsage; - }; - - /** - * Returns the length of the typed array associated with this attribute. - * @return {Number} The length of the typed array. Will be 0 if no typed array has been created yet. - */ - SPE.ShaderAttribute.prototype.getLength = function() { - 'use strict'; - - if ( this.typedArray === null ) { - return 0; - } - - return this.typedArray.array.length; - }; - - - SPE.shaderChunks = { - // Register color-packing define statements. - defines: [ - '#define PACKED_COLOR_SIZE 256.0', - '#define PACKED_COLOR_DIVISOR 255.0' - ].join( '\n' ), - - // All uniforms used by vertex / fragment shaders - uniforms: [ - 'uniform float deltaTime;', - 'uniform float runTime;', - 'uniform sampler2D tex;', - 'uniform vec4 textureAnimation;', - 'uniform float scale;', - ].join( '\n' ), - - // All attributes used by the vertex shader. - // - // Note that some attributes are squashed into other ones: - // - // * Drag is acceleration.w - attributes: [ - 'attribute vec4 acceleration;', - 'attribute vec3 velocity;', - 'attribute vec4 rotation;', - 'attribute vec3 rotationCenter;', - 'attribute vec4 params;', - 'attribute vec4 size;', - 'attribute vec4 angle;', - 'attribute vec4 color;', - 'attribute vec4 opacity;' - ].join( '\n' ), - - // - varyings: [ - 'varying vec4 vColor;', - '#ifdef SHOULD_ROTATE_TEXTURE', - ' varying float vAngle;', - '#endif', - - '#ifdef SHOULD_CALCULATE_SPRITE', - ' varying vec4 vSpriteSheet;', - '#endif' - ].join( '\n' ), - - - // Branch-avoiding comparison fns - // - http://theorangeduck.com/page/avoiding-shader-conditionals - branchAvoidanceFunctions: [ - 'float when_gt(float x, float y) {', - ' return max(sign(x - y), 0.0);', - '}', - - 'float when_lt(float x, float y) {', - ' return min( max(1.0 - sign(x - y), 0.0), 1.0 );', - '}', - - 'float when_eq( float x, float y ) {', - ' return 1.0 - abs( sign( x - y ) );', - '}', - - 'float when_ge(float x, float y) {', - ' return 1.0 - when_lt(x, y);', - '}', - - 'float when_le(float x, float y) {', - ' return 1.0 - when_gt(x, y);', - '}', - - // Branch-avoiding logical operators - // (to be used with above comparison fns) - 'float and(float a, float b) {', - ' return a * b;', - '}', - - 'float or(float a, float b) {', - ' return min(a + b, 1.0);', - '}', - ].join( '\n' ), - - - // From: - // - http://stackoverflow.com/a/12553149 - // - https://stackoverflow.com/questions/22895237/hexadecimal-to-rgb-values-in-webgl-shader - unpackColor: [ - 'vec3 unpackColor( in float hex ) {', - ' vec3 c = vec3( 0.0 );', - - ' float r = mod( (hex / PACKED_COLOR_SIZE / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );', - ' float g = mod( (hex / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );', - ' float b = mod( hex, PACKED_COLOR_SIZE );', - - ' c.r = r / PACKED_COLOR_DIVISOR;', - ' c.g = g / PACKED_COLOR_DIVISOR;', - ' c.b = b / PACKED_COLOR_DIVISOR;', - - ' return c;', - '}', - ].join( '\n' ), - - unpackRotationAxis: [ - 'vec3 unpackRotationAxis( in float hex ) {', - ' vec3 c = vec3( 0.0 );', - - ' float r = mod( (hex / PACKED_COLOR_SIZE / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );', - ' float g = mod( (hex / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );', - ' float b = mod( hex, PACKED_COLOR_SIZE );', - - ' c.r = r / PACKED_COLOR_DIVISOR;', - ' c.g = g / PACKED_COLOR_DIVISOR;', - ' c.b = b / PACKED_COLOR_DIVISOR;', - - ' c *= vec3( 2.0 );', - ' c -= vec3( 1.0 );', - - ' return c;', - '}', - ].join( '\n' ), - - floatOverLifetime: [ - 'float getFloatOverLifetime( in float positionInTime, in vec4 attr ) {', - ' highp float value = 0.0;', - ' float deltaAge = positionInTime * float( VALUE_OVER_LIFETIME_LENGTH - 1 );', - ' float fIndex = 0.0;', - ' float shouldApplyValue = 0.0;', - - // This might look a little odd, but it's faster in the testing I've done than using branches. - // Uses basic maths to avoid branching. - // - // Take a look at the branch-avoidance functions defined above, - // and be sure to check out The Orange Duck site where I got this - // from (link above). - - // Fix for static emitters (age is always zero). - ' value += attr[ 0 ] * when_eq( deltaAge, 0.0 );', - '', - ' for( int i = 0; i < VALUE_OVER_LIFETIME_LENGTH - 1; ++i ) {', - ' fIndex = float( i );', - ' shouldApplyValue = and( when_gt( deltaAge, fIndex ), when_le( deltaAge, fIndex + 1.0 ) );', - ' value += shouldApplyValue * mix( attr[ i ], attr[ i + 1 ], deltaAge - fIndex );', - ' }', - '', - ' return value;', - '}', - ].join( '\n' ), - - colorOverLifetime: [ - 'vec3 getColorOverLifetime( in float positionInTime, in vec3 color1, in vec3 color2, in vec3 color3, in vec3 color4 ) {', - ' vec3 value = vec3( 0.0 );', - ' value.x = getFloatOverLifetime( positionInTime, vec4( color1.x, color2.x, color3.x, color4.x ) );', - ' value.y = getFloatOverLifetime( positionInTime, vec4( color1.y, color2.y, color3.y, color4.y ) );', - ' value.z = getFloatOverLifetime( positionInTime, vec4( color1.z, color2.z, color3.z, color4.z ) );', - ' return value;', - '}', - ].join( '\n' ), - - paramFetchingFunctions: [ - 'float getAlive() {', - ' return params.x;', - '}', - - 'float getAge() {', - ' return params.y;', - '}', - - 'float getMaxAge() {', - ' return params.z;', - '}', - - 'float getWiggle() {', - ' return params.w;', - '}', - ].join( '\n' ), - - forceFetchingFunctions: [ - 'vec4 getPosition( in float age ) {', - ' return modelViewMatrix * vec4( position, 1.0 );', - '}', - - 'vec3 getVelocity( in float age ) {', - ' return velocity * age;', - '}', - - 'vec3 getAcceleration( in float age ) {', - ' return acceleration.xyz * age;', - '}', - ].join( '\n' ), - - - rotationFunctions: [ - // Huge thanks to: - // - http://www.neilmendoza.com/glsl-rotation-about-an-arbitrary-axis/ - '#ifdef SHOULD_ROTATE_PARTICLES', - ' mat4 getRotationMatrix( in vec3 axis, in float angle) {', - ' axis = normalize(axis);', - ' float s = sin(angle);', - ' float c = cos(angle);', - ' float oc = 1.0 - c;', - '', - ' return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0,', - ' oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0,', - ' oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0,', - ' 0.0, 0.0, 0.0, 1.0);', - ' }', - '', - ' vec3 getRotation( in vec3 pos, in float positionInTime ) {', - ' if( rotation.y == 0.0 ) {', - ' return pos;', - ' }', - '', - ' vec3 axis = unpackRotationAxis( rotation.x );', - ' vec3 center = rotationCenter;', - ' vec3 translated;', - ' mat4 rotationMatrix;', - - ' float angle = 0.0;', - ' angle += when_eq( rotation.z, 0.0 ) * rotation.y;', - ' angle += when_gt( rotation.z, 0.0 ) * mix( 0.0, rotation.y, positionInTime );', - ' translated = rotationCenter - pos;', - ' rotationMatrix = getRotationMatrix( axis, angle );', - ' return center - vec3( rotationMatrix * vec4( translated, 0.0 ) );', - ' }', - '#endif' - ].join( '\n' ), - - - // Fragment chunks - rotateTexture: [ - ' vec2 vUv = vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y );', - '', - ' #ifdef SHOULD_ROTATE_TEXTURE', - ' float x = gl_PointCoord.x - 0.5;', - ' float y = 1.0 - gl_PointCoord.y - 0.5;', - ' float c = cos( -vAngle );', - ' float s = sin( -vAngle );', - - ' vUv = vec2( c * x + s * y + 0.5, c * y - s * x + 0.5 );', - ' #endif', - '', - - // Spritesheets overwrite angle calculations. - ' #ifdef SHOULD_CALCULATE_SPRITE', - ' float framesX = vSpriteSheet.x;', - ' float framesY = vSpriteSheet.y;', - ' float columnNorm = vSpriteSheet.z;', - ' float rowNorm = vSpriteSheet.w;', - - ' vUv.x = gl_PointCoord.x * framesX + columnNorm;', - ' vUv.y = 1.0 - (gl_PointCoord.y * framesY + rowNorm);', - ' #endif', - - '', - ' vec4 rotatedTexture = texture2D( tex, vUv );', - ].join( '\n' ) - }; - - SPE.shaders = { - vertex: [ - SPE.shaderChunks.defines, - SPE.shaderChunks.uniforms, - SPE.shaderChunks.attributes, - SPE.shaderChunks.varyings, - - THREE.ShaderChunk.common, - THREE.ShaderChunk.logdepthbuf_pars_vertex, - THREE.ShaderChunk.fog_pars_vertex, - - SPE.shaderChunks.branchAvoidanceFunctions, - SPE.shaderChunks.unpackColor, - SPE.shaderChunks.unpackRotationAxis, - SPE.shaderChunks.floatOverLifetime, - SPE.shaderChunks.colorOverLifetime, - SPE.shaderChunks.paramFetchingFunctions, - SPE.shaderChunks.forceFetchingFunctions, - SPE.shaderChunks.rotationFunctions, - - - 'void main() {', - - - // - // Setup... - // - ' highp float age = getAge();', - ' highp float alive = getAlive();', - ' highp float maxAge = getMaxAge();', - ' highp float positionInTime = (age / maxAge);', - ' highp float isAlive = when_gt( alive, 0.0 );', - - ' #ifdef SHOULD_WIGGLE_PARTICLES', - ' float wiggleAmount = positionInTime * getWiggle();', - ' float wiggleSin = isAlive * sin( wiggleAmount );', - ' float wiggleCos = isAlive * cos( wiggleAmount );', - ' #endif', - - // - // Forces - // - - // Get forces & position - ' vec3 vel = getVelocity( age );', - ' vec3 accel = getAcceleration( age );', - ' vec3 force = vec3( 0.0 );', - ' vec3 pos = vec3( position );', - - // Calculate the required drag to apply to the forces. - ' float drag = 1.0 - (positionInTime * 0.5) * acceleration.w;', - - // Integrate forces... - ' force += vel;', - ' force *= drag;', - ' force += accel * age;', - ' pos += force;', - - - // Wiggly wiggly wiggle! - ' #ifdef SHOULD_WIGGLE_PARTICLES', - ' pos.x += wiggleSin;', - ' pos.y += wiggleCos;', - ' pos.z += wiggleSin;', - ' #endif', - - - // Rotate the emitter around it's central point - ' #ifdef SHOULD_ROTATE_PARTICLES', - ' pos = getRotation( pos, positionInTime );', - ' #endif', - - // Convert pos to a world-space value - ' vec4 mvPosition = modelViewMatrix * vec4( pos, 1.0 );', - - // Determine point size. - ' highp float pointSize = getFloatOverLifetime( positionInTime, size ) * isAlive;', - - // Determine perspective - ' #ifdef HAS_PERSPECTIVE', - ' float perspective = scale / length( mvPosition.xyz );', - ' #else', - ' float perspective = 1.0;', - ' #endif', - - // Apply perpective to pointSize value - ' float pointSizePerspective = pointSize * perspective;', - - - // - // Appearance - // - - // Determine color and opacity for this particle - ' #ifdef COLORIZE', - ' vec3 c = isAlive * getColorOverLifetime(', - ' positionInTime,', - ' unpackColor( color.x ),', - ' unpackColor( color.y ),', - ' unpackColor( color.z ),', - ' unpackColor( color.w )', - ' );', - ' #else', - ' vec3 c = vec3(1.0);', - ' #endif', - - ' float o = isAlive * getFloatOverLifetime( positionInTime, opacity );', - - // Assign color to vColor varying. - ' vColor = vec4( c, o );', - - // Determine angle - ' #ifdef SHOULD_ROTATE_TEXTURE', - ' vAngle = isAlive * getFloatOverLifetime( positionInTime, angle );', - ' #endif', - - // If this particle is using a sprite-sheet as a texture, we'll have to figure out - // what frame of the texture the particle is using at it's current position in time. - ' #ifdef SHOULD_CALCULATE_SPRITE', - ' float framesX = textureAnimation.x;', - ' float framesY = textureAnimation.y;', - ' float loopCount = textureAnimation.w;', - ' float totalFrames = textureAnimation.z;', - ' float frameNumber = mod( (positionInTime * loopCount) * totalFrames, totalFrames );', - - ' float column = floor(mod( frameNumber, framesX ));', - ' float row = floor( (frameNumber - column) / framesX );', - - ' float columnNorm = column / framesX;', - ' float rowNorm = row / framesY;', - - ' vSpriteSheet.x = 1.0 / framesX;', - ' vSpriteSheet.y = 1.0 / framesY;', - ' vSpriteSheet.z = columnNorm;', - ' vSpriteSheet.w = rowNorm;', - ' #endif', - - // - // Write values - // - - // Set PointSize according to size at current point in time. - ' gl_PointSize = pointSizePerspective;', - ' gl_Position = projectionMatrix * mvPosition;', - - THREE.ShaderChunk.logdepthbuf_vertex, - THREE.ShaderChunk.fog_vertex, - - '}' - ].join( '\n' ), - - fragment: [ - SPE.shaderChunks.uniforms, - - THREE.ShaderChunk.common, - THREE.ShaderChunk.fog_pars_fragment, - THREE.ShaderChunk.logdepthbuf_pars_fragment, - - SPE.shaderChunks.varyings, - - SPE.shaderChunks.branchAvoidanceFunctions, - - 'void main() {', - ' vec3 outgoingLight = vColor.xyz;', - ' ', - ' #ifdef ALPHATEST', - ' if ( vColor.w < float(ALPHATEST) ) discard;', - ' #endif', - - SPE.shaderChunks.rotateTexture, - - THREE.ShaderChunk.logdepthbuf_fragment, - - ' outgoingLight = vColor.xyz * rotatedTexture.xyz;', - ' gl_FragColor = vec4( outgoingLight.xyz, rotatedTexture.w * vColor.w );', - - THREE.ShaderChunk.fog_fragment, - - '}' - ].join( '\n' ) - }; - - - /** - * A bunch of utility functions used throughout the library. - * @namespace - * @type {Object} - */ - SPE.utils = { - /** - * A map of types used by `SPE.utils.ensureTypedArg` and - * `SPE.utils.ensureArrayTypedArg` to compare types against. - * - * @enum {String} - */ - types: { - /** - * Boolean type. - * @type {String} - */ - BOOLEAN: 'boolean', - - /** - * String type. - * @type {String} - */ - STRING: 'string', - - /** - * Number type. - * @type {String} - */ - NUMBER: 'number', - - /** - * Object type. - * @type {String} - */ - OBJECT: 'object' - }, - - /** - * Given a value, a type, and a default value to fallback to, - * ensure the given argument adheres to the type requesting, - * returning the default value if type check is false. - * - * @param {(boolean|string|number|object)} arg The value to perform a type-check on. - * @param {String} type The type the `arg` argument should adhere to. - * @param {(boolean|string|number|object)} defaultValue A default value to fallback on if the type check fails. - * @return {(boolean|string|number|object)} The given value if type check passes, or the default value if it fails. - */ - ensureTypedArg: function( arg, type, defaultValue ) { - 'use strict'; - - if ( typeof arg === type ) { - return arg; - } - else { - return defaultValue; - } - }, - - /** - * Given an array of values, a type, and a default value, - * ensure the given array's contents ALL adhere to the provided type, - * returning the default value if type check fails. - * - * If the given value to check isn't an Array, delegates to SPE.utils.ensureTypedArg. - * - * @param {Array|boolean|string|number|object} arg The array of values to check type of. - * @param {String} type The type that should be adhered to. - * @param {(boolean|string|number|object)} defaultValue A default fallback value. - * @return {(boolean|string|number|object)} The given value if type check passes, or the default value if it fails. - */ - ensureArrayTypedArg: function( arg, type, defaultValue ) { - 'use strict'; - - // If the argument being checked is an array, loop through - // it and ensure all the values are of the correct type, - // falling back to the defaultValue if any aren't. - if ( Array.isArray( arg ) ) { - for ( var i = arg.length - 1; i >= 0; --i ) { - if ( typeof arg[ i ] !== type ) { - return defaultValue; - } - } - - return arg; - } - - // If the arg isn't an array then just fallback to - // checking the type. - return this.ensureTypedArg( arg, type, defaultValue ); - }, - - /** - * Ensures the given value is an instance of a constructor function. - * - * @param {Object} arg The value to check instance of. - * @param {Function} instance The constructor of the instance to check against. - * @param {Object} defaultValue A default fallback value if instance check fails - * @return {Object} The given value if type check passes, or the default value if it fails. - */ - ensureInstanceOf: function( arg, instance, defaultValue ) { - 'use strict'; - - if ( instance !== undefined && arg instanceof instance ) { - return arg; - } - else { - return defaultValue; - } - }, - - /** - * Given an array of values, ensure the instances of all items in the array - * matches the given instance constructor falling back to a default value if - * the check fails. - * - * If given value isn't an Array, delegates to `SPE.utils.ensureInstanceOf`. - * - * @param {Array|Object} arg The value to perform the instanceof check on. - * @param {Function} instance The constructor of the instance to check against. - * @param {Object} defaultValue A default fallback value if instance check fails - * @return {Object} The given value if type check passes, or the default value if it fails. - */ - ensureArrayInstanceOf: function( arg, instance, defaultValue ) { - 'use strict'; - - // If the argument being checked is an array, loop through - // it and ensure all the values are of the correct type, - // falling back to the defaultValue if any aren't. - if ( Array.isArray( arg ) ) { - for ( var i = arg.length - 1; i >= 0; --i ) { - if ( instance !== undefined && arg[ i ] instanceof instance === false ) { - return defaultValue; - } - } - - return arg; - } - - // If the arg isn't an array then just fallback to - // checking the type. - return this.ensureInstanceOf( arg, instance, defaultValue ); - }, - - /** - * Ensures that any "value-over-lifetime" properties of an emitter are - * of the correct length (as dictated by `SPE.valueOverLifetimeLength`). - * - * Delegates to `SPE.utils.interpolateArray` for array resizing. - * - * If properties aren't arrays, then property values are put into one. - * - * @param {Object} property The property of an SPE.Emitter instance to check compliance of. - * @param {Number} minLength The minimum length of the array to create. - * @param {Number} maxLength The maximum length of the array to create. - */ - ensureValueOverLifetimeCompliance: function( property, minLength, maxLength ) { - 'use strict'; - - minLength = minLength || 3; - maxLength = maxLength || 3; - - // First, ensure both properties are arrays. - if ( Array.isArray( property._value ) === false ) { - property._value = [ property._value ]; - } - - if ( Array.isArray( property._spread ) === false ) { - property._spread = [ property._spread ]; - } - - var valueLength = this.clamp( property._value.length, minLength, maxLength ), - spreadLength = this.clamp( property._spread.length, minLength, maxLength ), - desiredLength = Math.max( valueLength, spreadLength ); - - if ( property._value.length !== desiredLength ) { - property._value = this.interpolateArray( property._value, desiredLength ); - } - - if ( property._spread.length !== desiredLength ) { - property._spread = this.interpolateArray( property._spread, desiredLength ); - } - }, - - /** - * Performs linear interpolation (lerp) on an array. - * - * For example, lerping [1, 10], with a `newLength` of 10 will produce [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]. - * - * Delegates to `SPE.utils.lerpTypeAgnostic` to perform the actual - * interpolation. - * - * @param {Array} srcArray The array to lerp. - * @param {Number} newLength The length the array should be interpolated to. - * @return {Array} The interpolated array. - */ - interpolateArray: function( srcArray, newLength ) { - 'use strict'; - - var sourceLength = srcArray.length, - newArray = [ typeof srcArray[ 0 ].clone === 'function' ? srcArray[ 0 ].clone() : srcArray[ 0 ] ], - factor = ( sourceLength - 1 ) / ( newLength - 1 ); - - - for ( var i = 1; i < newLength - 1; ++i ) { - var f = i * factor, - before = Math.floor( f ), - after = Math.ceil( f ), - delta = f - before; - - newArray[ i ] = this.lerpTypeAgnostic( srcArray[ before ], srcArray[ after ], delta ); - } - - newArray.push( - typeof srcArray[ sourceLength - 1 ].clone === 'function' ? - srcArray[ sourceLength - 1 ].clone() : - srcArray[ sourceLength - 1 ] - ); - - return newArray; - }, - - /** - * Clamp a number to between the given min and max values. - * @param {Number} value The number to clamp. - * @param {Number} min The minimum value. - * @param {Number} max The maximum value. - * @return {Number} The clamped number. - */ - clamp: function( value, min, max ) { - 'use strict'; - - return Math.max( min, Math.min( value, max ) ); - }, - - /** - * If the given value is less than the epsilon value, then return - * a randomised epsilon value if specified, or just the epsilon value if not. - * Works for negative numbers as well as positive. - * - * @param {Number} value The value to perform the operation on. - * @param {Boolean} randomise Whether the value should be randomised. - * @return {Number} The result of the operation. - */ - zeroToEpsilon: function( value, randomise ) { - 'use strict'; - - var epsilon = 0.00001, - result = value; - - result = randomise ? Math.random() * epsilon * 10 : epsilon; - - if ( value < 0 && value > -epsilon ) { - result = -result; - } - - // if ( value === 0 ) { - // result = randomise ? Math.random() * epsilon * 10 : epsilon; - // } - // else if ( value > 0 && value < epsilon ) { - // result = randomise ? Math.random() * epsilon * 10 : epsilon; - // } - // else if ( value < 0 && value > -epsilon ) { - // result = -( randomise ? Math.random() * epsilon * 10 : epsilon ); - // } - - return result; - }, - - /** - * Linearly interpolates two values of various types. The given values - * must be of the same type for the interpolation to work. - * @param {(number|Object)} start The start value of the lerp. - * @param {(number|object)} end The end value of the lerp. - * @param {Number} delta The delta posiiton of the lerp operation. Ideally between 0 and 1 (inclusive). - * @return {(number|object|undefined)} The result of the operation. Result will be undefined if - * the start and end arguments aren't a supported type, or - * if their types do not match. - */ - lerpTypeAgnostic: function( start, end, delta ) { - 'use strict'; - - var types = this.types, - out; - - if ( typeof start === types.NUMBER && typeof end === types.NUMBER ) { - return start + ( ( end - start ) * delta ); - } - else if ( start instanceof THREE.Vector2 && end instanceof THREE.Vector2 ) { - out = start.clone(); - out.x = this.lerp( start.x, end.x, delta ); - out.y = this.lerp( start.y, end.y, delta ); - return out; - } - else if ( start instanceof THREE.Vector3 && end instanceof THREE.Vector3 ) { - out = start.clone(); - out.x = this.lerp( start.x, end.x, delta ); - out.y = this.lerp( start.y, end.y, delta ); - out.z = this.lerp( start.z, end.z, delta ); - return out; - } - else if ( start instanceof THREE.Vector4 && end instanceof THREE.Vector4 ) { - out = start.clone(); - out.x = this.lerp( start.x, end.x, delta ); - out.y = this.lerp( start.y, end.y, delta ); - out.z = this.lerp( start.z, end.z, delta ); - out.w = this.lerp( start.w, end.w, delta ); - return out; - } - else if ( start instanceof THREE.Color && end instanceof THREE.Color ) { - out = start.clone(); - out.r = this.lerp( start.r, end.r, delta ); - out.g = this.lerp( start.g, end.g, delta ); - out.b = this.lerp( start.b, end.b, delta ); - return out; - } - else { - console.warn( 'Invalid argument types, or argument types do not match:', start, end ); - } - }, - - /** - * Perform a linear interpolation operation on two numbers. - * @param {Number} start The start value. - * @param {Number} end The end value. - * @param {Number} delta The position to interpolate to. - * @return {Number} The result of the lerp operation. - */ - lerp: function( start, end, delta ) { - 'use strict'; - return start + ( ( end - start ) * delta ); - }, - - /** - * Rounds a number to a nearest multiple. - * - * @param {Number} n The number to round. - * @param {Number} multiple The multiple to round to. - * @return {Number} The result of the round operation. - */ - roundToNearestMultiple: function( n, multiple ) { - 'use strict'; - - var remainder = 0; - - if ( multiple === 0 ) { - return n; - } - - remainder = Math.abs( n ) % multiple; - - if ( remainder === 0 ) { - return n; - } - - if ( n < 0 ) { - return -( Math.abs( n ) - remainder ); - } - - return n + multiple - remainder; - }, - - /** - * Check if all items in an array are equal. Uses strict equality. - * - * @param {Array} array The array of values to check equality of. - * @return {Boolean} Whether the array's values are all equal or not. - */ - arrayValuesAreEqual: function( array ) { - 'use strict'; - - for ( var i = 0; i < array.length - 1; ++i ) { - if ( array[ i ] !== array[ i + 1 ] ) { - return false; - } - } - - return true; - }, - - // colorsAreEqual: function() { - // var colors = Array.prototype.slice.call( arguments ), - // numColors = colors.length; - - // for ( var i = 0, color1, color2; i < numColors - 1; ++i ) { - // color1 = colors[ i ]; - // color2 = colors[ i + 1 ]; - - // if ( - // color1.r !== color2.r || - // color1.g !== color2.g || - // color1.b !== color2.b - // ) { - // return false - // } - // } - - // return true; - // }, - - - /** - * Given a start value and a spread value, create and return a random - * number. - * @param {Number} base The start value. - * @param {Number} spread The size of the random variance to apply. - * @return {Number} A randomised number. - */ - randomFloat: function( base, spread ) { - 'use strict'; - return base + spread * ( Math.random() - 0.5 ); - }, - - - - /** - * Given an SPE.ShaderAttribute instance, and various other settings, - * assign values to the attribute's array in a `vec3` format. - * - * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to. - * @param {Number} index The offset in the attribute's TypedArray to save the result from. - * @param {Object} base THREE.Vector3 instance describing the start value. - * @param {Object} spread THREE.Vector3 instance describing the random variance to apply to the start value. - * @param {Object} spreadClamp THREE.Vector3 instance describing the multiples to clamp the randomness to. - */ - randomVector3: function( attribute, index, base, spread, spreadClamp ) { - 'use strict'; - - var x = base.x + ( Math.random() * spread.x - ( spread.x * 0.5 ) ), - y = base.y + ( Math.random() * spread.y - ( spread.y * 0.5 ) ), - z = base.z + ( Math.random() * spread.z - ( spread.z * 0.5 ) ); - - // var x = this.randomFloat( base.x, spread.x ), - // y = this.randomFloat( base.y, spread.y ), - // z = this.randomFloat( base.z, spread.z ); - - if ( spreadClamp ) { - x = -spreadClamp.x * 0.5 + this.roundToNearestMultiple( x, spreadClamp.x ); - y = -spreadClamp.y * 0.5 + this.roundToNearestMultiple( y, spreadClamp.y ); - z = -spreadClamp.z * 0.5 + this.roundToNearestMultiple( z, spreadClamp.z ); - } - - attribute.typedArray.setVec3Components( index, x, y, z ); - }, - - /** - * Given an SPE.Shader attribute instance, and various other settings, - * assign Color values to the attribute. - * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to. - * @param {Number} index The offset in the attribute's TypedArray to save the result from. - * @param {Object} base THREE.Color instance describing the start color. - * @param {Object} spread THREE.Vector3 instance describing the random variance to apply to the start color. - */ - randomColor: function( attribute, index, base, spread ) { - 'use strict'; - - var r = base.r + ( Math.random() * spread.x ), - g = base.g + ( Math.random() * spread.y ), - b = base.b + ( Math.random() * spread.z ); - - r = this.clamp( r, 0, 1 ); - g = this.clamp( g, 0, 1 ); - b = this.clamp( b, 0, 1 ); - - - attribute.typedArray.setVec3Components( index, r, g, b ); - }, - - - randomColorAsHex: ( function() { - 'use strict'; - - var workingColor = new THREE.Color(); - - /** - * Assigns a random color value, encoded as a hex value in decimal - * format, to a SPE.ShaderAttribute instance. - * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to. - * @param {Number} index The offset in the attribute's TypedArray to save the result from. - * @param {Object} base THREE.Color instance describing the start color. - * @param {Object} spread THREE.Vector3 instance describing the random variance to apply to the start color. - */ - return function( attribute, index, base, spread ) { - var numItems = base.length, - colors = []; - - for ( var i = 0; i < numItems; ++i ) { - var spreadVector = spread[ i ]; - - workingColor.copy( base[ i ] ); - - workingColor.r += ( Math.random() * spreadVector.x ) - ( spreadVector.x * 0.5 ); - workingColor.g += ( Math.random() * spreadVector.y ) - ( spreadVector.y * 0.5 ); - workingColor.b += ( Math.random() * spreadVector.z ) - ( spreadVector.z * 0.5 ); - - workingColor.r = this.clamp( workingColor.r, 0, 1 ); - workingColor.g = this.clamp( workingColor.g, 0, 1 ); - workingColor.b = this.clamp( workingColor.b, 0, 1 ); - - colors.push( workingColor.getHex() ); - } - - attribute.typedArray.setVec4Components( index, colors[ 0 ], colors[ 1 ], colors[ 2 ], colors[ 3 ] ); - }; - }() ), - - /** - * Given an SPE.ShaderAttribute instance, and various other settings, - * assign values to the attribute's array in a `vec3` format. - * - * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to. - * @param {Number} index The offset in the attribute's TypedArray to save the result from. - * @param {Object} start THREE.Vector3 instance describing the start line position. - * @param {Object} end THREE.Vector3 instance describing the end line position. - */ - randomVector3OnLine: function( attribute, index, start, end ) { - 'use strict'; - var pos = start.clone(); - - pos.lerp( end, Math.random() ); - - attribute.typedArray.setVec3Components( index, pos.x, pos.y, pos.z ); - }, - - /** - * Given an SPE.Shader attribute instance, and various other settings, - * assign Color values to the attribute. - * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to. - * @param {Number} index The offset in the attribute's TypedArray to save the result from. - * @param {Object} base THREE.Color instance describing the start color. - * @param {Object} spread THREE.Vector3 instance describing the random variance to apply to the start color. - */ - - /** - * Assigns a random vector 3 value to an SPE.ShaderAttribute instance, projecting the - * given values onto a sphere. - * - * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to. - * @param {Number} index The offset in the attribute's TypedArray to save the result from. - * @param {Object} base THREE.Vector3 instance describing the origin of the transform. - * @param {Number} radius The radius of the sphere to project onto. - * @param {Number} radiusSpread The amount of randomness to apply to the projection result - * @param {Object} radiusScale THREE.Vector3 instance describing the scale of each axis of the sphere. - * @param {Number} radiusSpreadClamp What numeric multiple the projected value should be clamped to. - */ - randomVector3OnSphere: function( - attribute, index, base, radius, radiusSpread, radiusScale, radiusSpreadClamp, distributionClamp - ) { - 'use strict'; - - var depth = 2 * Math.random() - 1, - t = 6.2832 * Math.random(), - r = Math.sqrt( 1 - depth * depth ), - rand = this.randomFloat( radius, radiusSpread ), - x = 0, - y = 0, - z = 0; - - - if ( radiusSpreadClamp ) { - rand = Math.round( rand / radiusSpreadClamp ) * radiusSpreadClamp; - } - - - - // Set position on sphere - x = r * Math.cos( t ) * rand; - y = r * Math.sin( t ) * rand; - z = depth * rand; - - // Apply radius scale to this position - x *= radiusScale.x; - y *= radiusScale.y; - z *= radiusScale.z; - - // Translate to the base position. - x += base.x; - y += base.y; - z += base.z; - - // Set the values in the typed array. - attribute.typedArray.setVec3Components( index, x, y, z ); - }, - - seededRandom: function( seed ) { - var x = Math.sin( seed ) * 10000; - return x - ( x | 0 ); - }, - - - - /** - * Assigns a random vector 3 value to an SPE.ShaderAttribute instance, projecting the - * given values onto a 2d-disc. - * - * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to. - * @param {Number} index The offset in the attribute's TypedArray to save the result from. - * @param {Object} base THREE.Vector3 instance describing the origin of the transform. - * @param {Number} radius The radius of the sphere to project onto. - * @param {Number} radiusSpread The amount of randomness to apply to the projection result - * @param {Object} radiusScale THREE.Vector3 instance describing the scale of each axis of the disc. The z-component is ignored. - * @param {Number} radiusSpreadClamp What numeric multiple the projected value should be clamped to. - */ - randomVector3OnDisc: function( attribute, index, base, radius, radiusSpread, radiusScale, radiusSpreadClamp ) { - 'use strict'; - - var t = 6.2832 * Math.random(), - rand = Math.abs( this.randomFloat( radius, radiusSpread ) ), - x = 0, - y = 0, - z = 0; - - if ( radiusSpreadClamp ) { - rand = Math.round( rand / radiusSpreadClamp ) * radiusSpreadClamp; - } - - // Set position on sphere - x = Math.cos( t ) * rand; - y = Math.sin( t ) * rand; - - // Apply radius scale to this position - x *= radiusScale.x; - y *= radiusScale.y; - - // Translate to the base position. - x += base.x; - y += base.y; - z += base.z; - - // Set the values in the typed array. - attribute.typedArray.setVec3Components( index, x, y, z ); - }, - - randomDirectionVector3OnSphere: ( function() { - 'use strict'; - - var v = new THREE.Vector3(); - - /** - * Given an SPE.ShaderAttribute instance, create a direction vector from the given - * position, using `speed` as the magnitude. Values are saved to the attribute. - * - * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to. - * @param {Number} index The offset in the attribute's TypedArray to save the result from. - * @param {Number} posX The particle's x coordinate. - * @param {Number} posY The particle's y coordinate. - * @param {Number} posZ The particle's z coordinate. - * @param {Object} emitterPosition THREE.Vector3 instance describing the emitter's base position. - * @param {Number} speed The magnitude to apply to the vector. - * @param {Number} speedSpread The amount of randomness to apply to the magnitude. - */ - return function( attribute, index, posX, posY, posZ, emitterPosition, speed, speedSpread ) { - v.copy( emitterPosition ); - - v.x -= posX; - v.y -= posY; - v.z -= posZ; - - v.normalize().multiplyScalar( -this.randomFloat( speed, speedSpread ) ); - - attribute.typedArray.setVec3Components( index, v.x, v.y, v.z ); - }; - }() ), - - - randomDirectionVector3OnDisc: ( function() { - 'use strict'; - - var v = new THREE.Vector3(); - - /** - * Given an SPE.ShaderAttribute instance, create a direction vector from the given - * position, using `speed` as the magnitude. Values are saved to the attribute. - * - * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to. - * @param {Number} index The offset in the attribute's TypedArray to save the result from. - * @param {Number} posX The particle's x coordinate. - * @param {Number} posY The particle's y coordinate. - * @param {Number} posZ The particle's z coordinate. - * @param {Object} emitterPosition THREE.Vector3 instance describing the emitter's base position. - * @param {Number} speed The magnitude to apply to the vector. - * @param {Number} speedSpread The amount of randomness to apply to the magnitude. - */ - return function( attribute, index, posX, posY, posZ, emitterPosition, speed, speedSpread ) { - v.copy( emitterPosition ); - - v.x -= posX; - v.y -= posY; - v.z -= posZ; - - v.normalize().multiplyScalar( -this.randomFloat( speed, speedSpread ) ); - - attribute.typedArray.setVec3Components( index, v.x, v.y, 0 ); - }; - }() ), - - getPackedRotationAxis: ( function() { - 'use strict'; - - var v = new THREE.Vector3(), - vSpread = new THREE.Vector3(), - c = new THREE.Color(), - addOne = new THREE.Vector3( 1, 1, 1 ); - - /** - * Given a rotation axis, and a rotation axis spread vector, - * calculate a randomised rotation axis, and pack it into - * a hexadecimal value represented in decimal form. - * @param {Object} axis THREE.Vector3 instance describing the rotation axis. - * @param {Object} axisSpread THREE.Vector3 instance describing the amount of randomness to apply to the rotation axis. - * @return {Number} The packed rotation axis, with randomness. - */ - return function( axis, axisSpread ) { - v.copy( axis ).normalize(); - vSpread.copy( axisSpread ).normalize(); - - v.x += ( -axisSpread.x * 0.5 ) + ( Math.random() * axisSpread.x ); - v.y += ( -axisSpread.y * 0.5 ) + ( Math.random() * axisSpread.y ); - v.z += ( -axisSpread.z * 0.5 ) + ( Math.random() * axisSpread.z ); - - // v.x = Math.abs( v.x ); - // v.y = Math.abs( v.y ); - // v.z = Math.abs( v.z ); - - v.normalize().add( addOne ).multiplyScalar( 0.5 ); - - c.setRGB( v.x, v.y, v.z ); - - return c.getHex(); - }; - }() ) - }; - - - /** - * An SPE.Group instance. - * @typedef {Object} Group - * @see SPE.Group - */ - - /** - * A map of options to configure an SPE.Group instance. - * @typedef {Object} GroupOptions - * - * @property {Object} texture An object describing the texture used by the group. - * - * @property {Object} texture.value An instance of THREE.Texture. - * - * @property {Object=} texture.frames A THREE.Vector2 instance describing the number - * of frames on the x- and y-axis of the given texture. - * If not provided, the texture will NOT be treated as - * a sprite-sheet and as such will NOT be animated. - * - * @property {Number} [texture.frameCount=texture.frames.x * texture.frames.y] The total number of frames in the sprite-sheet. - * Allows for sprite-sheets that don't fill the entire - * texture. - * - * @property {Number} texture.loop The number of loops through the sprite-sheet that should - * be performed over the course of a single particle's lifetime. - * - * @property {Number} fixedTimeStep If no `dt` (or `deltaTime`) value is passed to this group's - * `tick()` function, this number will be used to move the particle - * simulation forward. Value in SECONDS. - * - * @property {Boolean} hasPerspective Whether the distance a particle is from the camera should affect - * the particle's size. - * - * @property {Boolean} colorize Whether the particles in this group should be rendered with color, or - * whether the only color of particles will come from the provided texture. - * - * @property {Number} blending One of Three.js's blending modes to apply to this group's `ShaderMaterial`. - * - * @property {Boolean} transparent Whether these particle's should be rendered with transparency. - * - * @property {Number} alphaTest Sets the alpha value to be used when running an alpha test on the `texture.value` property. Value between 0 and 1. - * - * @property {Boolean} depthWrite Whether rendering the group has any effect on the depth buffer. - * - * @property {Boolean} depthTest Whether to have depth test enabled when rendering this group. - * - * @property {Boolean} fog Whether this group's particles should be affected by their scene's fog. - * - * @property {Number} scale The scale factor to apply to this group's particle sizes. Useful for - * setting particle sizes to be relative to renderer size. - */ - - - /** - * The SPE.Group class. Creates a new group, containing a material, geometry, and mesh. - * - * @constructor - * @param {GroupOptions} options A map of options to configure the group instance. - */ - SPE.Group = function( options ) { - 'use strict'; - - var utils = SPE.utils, - types = utils.types; - - // Ensure we have a map of options to play with - options = utils.ensureTypedArg( options, types.OBJECT, {} ); - options.texture = utils.ensureTypedArg( options.texture, types.OBJECT, {} ); - - // Assign a UUID to this instance - this.uuid = THREE.Math.generateUUID(); - - // If no `deltaTime` value is passed to the `SPE.Group.tick` function, - // the value of this property will be used to advance the simulation. - this.fixedTimeStep = utils.ensureTypedArg( options.fixedTimeStep, types.NUMBER, 0.016 ); - - // Set properties used in the uniforms map, starting with the - // texture stuff. - this.texture = utils.ensureInstanceOf( options.texture.value, THREE.Texture, null ); - this.textureFrames = utils.ensureInstanceOf( options.texture.frames, THREE.Vector2, new THREE.Vector2( 1, 1 ) ); - this.textureFrameCount = utils.ensureTypedArg( options.texture.frameCount, types.NUMBER, this.textureFrames.x * this.textureFrames.y ); - this.textureLoop = utils.ensureTypedArg( options.texture.loop, types.NUMBER, 1 ); - this.textureFrames.max( new THREE.Vector2( 1, 1 ) ); - - this.hasPerspective = utils.ensureTypedArg( options.hasPerspective, types.BOOLEAN, true ); - this.colorize = utils.ensureTypedArg( options.colorize, types.BOOLEAN, true ); - - this.maxParticleCount = utils.ensureTypedArg( options.maxParticleCount, types.NUMBER, null ); - - - // Set properties used to define the ShaderMaterial's appearance. - this.blending = utils.ensureTypedArg( options.blending, types.NUMBER, THREE.AdditiveBlending ); - this.transparent = utils.ensureTypedArg( options.transparent, types.BOOLEAN, true ); - this.alphaTest = parseFloat( utils.ensureTypedArg( options.alphaTest, types.NUMBER, 0.0 ) ); - this.depthWrite = utils.ensureTypedArg( options.depthWrite, types.BOOLEAN, false ); - this.depthTest = utils.ensureTypedArg( options.depthTest, types.BOOLEAN, true ); - this.fog = utils.ensureTypedArg( options.fog, types.BOOLEAN, true ); - this.scale = utils.ensureTypedArg( options.scale, types.NUMBER, 300 ); - - // Where emitter's go to curl up in a warm blanket and live - // out their days. - this.emitters = []; - this.emitterIDs = []; - - // Create properties for use by the emitter pooling functions. - this._pool = []; - this._poolCreationSettings = null; - this._createNewWhenPoolEmpty = 0; - - // Whether all attributes should be forced to updated - // their entire buffer contents on the next tick. - // - // Used when an emitter is removed. - this._attributesNeedRefresh = false; - this._attributesNeedDynamicReset = false; - - this.particleCount = 0; - - - // Map of uniforms to be applied to the ShaderMaterial instance. - this.uniforms = { - tex: { - type: 't', - value: this.texture - }, - textureAnimation: { - type: 'v4', - value: new THREE.Vector4( - this.textureFrames.x, - this.textureFrames.y, - this.textureFrameCount, - Math.max( Math.abs( this.textureLoop ), 1.0 ) - ) - }, - fogColor: { - type: 'c', - value: this.fog ? new THREE.Color() : null - }, - fogNear: { - type: 'f', - value: 10 - }, - fogFar: { - type: 'f', - value: 200 - }, - fogDensity: { - type: 'f', - value: 0.5 - }, - deltaTime: { - type: 'f', - value: 0 - }, - runTime: { - type: 'f', - value: 0 - }, - scale: { - type: 'f', - value: this.scale - } - }; - - // Add some defines into the mix... - this.defines = { - HAS_PERSPECTIVE: this.hasPerspective, - COLORIZE: this.colorize, - VALUE_OVER_LIFETIME_LENGTH: SPE.valueOverLifetimeLength, - - SHOULD_ROTATE_TEXTURE: false, - SHOULD_ROTATE_PARTICLES: false, - SHOULD_WIGGLE_PARTICLES: false, - - SHOULD_CALCULATE_SPRITE: this.textureFrames.x > 1 || this.textureFrames.y > 1 - }; - - // Map of all attributes to be applied to the particles. - // - // See SPE.ShaderAttribute for a bit more info on this bit. - this.attributes = { - position: new SPE.ShaderAttribute( 'v3', true ), - acceleration: new SPE.ShaderAttribute( 'v4', true ), // w component is drag - velocity: new SPE.ShaderAttribute( 'v3', true ), - rotation: new SPE.ShaderAttribute( 'v4', true ), - rotationCenter: new SPE.ShaderAttribute( 'v3', true ), - params: new SPE.ShaderAttribute( 'v4', true ), // Holds (alive, age, delay, wiggle) - size: new SPE.ShaderAttribute( 'v4', true ), - angle: new SPE.ShaderAttribute( 'v4', true ), - color: new SPE.ShaderAttribute( 'v4', true ), - opacity: new SPE.ShaderAttribute( 'v4', true ) - }; - - this.attributeKeys = Object.keys( this.attributes ); - this.attributeCount = this.attributeKeys.length; - - // Create the ShaderMaterial instance that'll help render the - // particles. - this.material = new THREE.ShaderMaterial( { - uniforms: this.uniforms, - vertexShader: SPE.shaders.vertex, - fragmentShader: SPE.shaders.fragment, - blending: this.blending, - transparent: this.transparent, - alphaTest: this.alphaTest, - depthWrite: this.depthWrite, - depthTest: this.depthTest, - defines: this.defines, - fog: this.fog - } ); - - // Create the BufferGeometry and Points instances, ensuring - // the geometry and material are given to the latter. - this.geometry = new THREE.BufferGeometry(); - this.mesh = new THREE.Points( this.geometry, this.material ); - - if ( this.maxParticleCount === null ) { - console.warn( 'SPE.Group: No maxParticleCount specified. Adding emitters after rendering will probably cause errors.' ); - } - }; - - SPE.Group.constructor = SPE.Group; - - - SPE.Group.prototype._updateDefines = function() { - 'use strict'; - - var emitters = this.emitters, - i = emitters.length - 1, - emitter, - defines = this.defines; - - for ( i; i >= 0; --i ) { - emitter = emitters[ i ]; - - // Only do angle calculation if there's no spritesheet defined. - // - // Saves calculations being done and then overwritten in the shaders. - if ( !defines.SHOULD_CALCULATE_SPRITE ) { - defines.SHOULD_ROTATE_TEXTURE = defines.SHOULD_ROTATE_TEXTURE || !!Math.max( - Math.max.apply( null, emitter.angle.value ), - Math.max.apply( null, emitter.angle.spread ) - ); - } - - defines.SHOULD_ROTATE_PARTICLES = defines.SHOULD_ROTATE_PARTICLES || !!Math.max( - emitter.rotation.angle, - emitter.rotation.angleSpread - ); - - defines.SHOULD_WIGGLE_PARTICLES = defines.SHOULD_WIGGLE_PARTICLES || !!Math.max( - emitter.wiggle.value, - emitter.wiggle.spread - ); - } - - this.material.needsUpdate = true; - }; - - SPE.Group.prototype._applyAttributesToGeometry = function() { - 'use strict'; - - var attributes = this.attributes, - geometry = this.geometry, - geometryAttributes = geometry.attributes, - attribute, - geometryAttribute; - - // Loop through all the shader attributes and assign (or re-assign) - // typed array buffers to each one. - for ( var attr in attributes ) { - if ( attributes.hasOwnProperty( attr ) ) { - attribute = attributes[ attr ]; - geometryAttribute = geometryAttributes[ attr ]; - - // Update the array if this attribute exists on the geometry. - // - // This needs to be done because the attribute's typed array might have - // been resized and reinstantiated, and might now be looking at a - // different ArrayBuffer, so reference needs updating. - if ( geometryAttribute ) { - geometryAttribute.array = attribute.typedArray.array; - } - - // // Add the attribute to the geometry if it doesn't already exist. - else { - geometry.setAttribute( 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; - }; + this.bufferAttribute.needsUpdate = true; + return; + } - /** - * 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.bufferAttribute = new THREE.BufferAttribute( this.typedArray.array, this.componentSize ); + // this.bufferAttribute.dynamic = this.dynamicBuffer; + this.bufferAttribute.usage = this.dynamicBuffer ? + THREE.DynamicDrawUsage : + THREE.StaticDrawUsage; +}; + +/** + * Returns the length of the typed array associated with this attribute. + * @return {Number} The length of the typed array. Will be 0 if no typed array has been created yet. + */ +SPE.ShaderAttribute.prototype.getLength = function() { + 'use strict'; + + if ( this.typedArray === null ) { + return 0; + } - this.alive = false; - return this; - }; + return this.typedArray.array.length; +}; + + +SPE.shaderChunks = { + // Register color-packing define statements. + defines: [ + '#define PACKED_COLOR_SIZE 256.0', + '#define PACKED_COLOR_DIVISOR 255.0' + ].join( '\n' ), + + // All uniforms used by vertex / fragment shaders + uniforms: [ + 'uniform float deltaTime;', + 'uniform float runTime;', + 'uniform sampler2D tex;', + 'uniform vec4 textureAnimation;', + 'uniform float scale;', + ].join( '\n' ), + + // All attributes used by the vertex shader. + // + // Note that some attributes are squashed into other ones: + // + // * Drag is acceleration.w + attributes: [ + 'attribute vec4 acceleration;', + 'attribute vec3 velocity;', + 'attribute vec4 rotation;', + 'attribute vec3 rotationCenter;', + 'attribute vec4 params;', + 'attribute vec4 size;', + 'attribute vec4 angle;', + 'attribute vec4 color;', + 'attribute vec4 opacity;' + ].join( '\n' ), + + // + varyings: [ + 'varying vec4 vColor;', + '#ifdef SHOULD_ROTATE_TEXTURE', + ' varying float vAngle;', + '#endif', + + '#ifdef SHOULD_CALCULATE_SPRITE', + ' varying vec4 vSpriteSheet;', + '#endif' + ].join( '\n' ), + + + // Branch-avoiding comparison fns + // - http://theorangeduck.com/page/avoiding-shader-conditionals + branchAvoidanceFunctions: [ + 'float when_gt(float x, float y) {', + ' return max(sign(x - y), 0.0);', + '}', + + 'float when_lt(float x, float y) {', + ' return min( max(1.0 - sign(x - y), 0.0), 1.0 );', + '}', + + 'float when_eq( float x, float y ) {', + ' return 1.0 - abs( sign( x - y ) );', + '}', + + 'float when_ge(float x, float y) {', + ' return 1.0 - when_lt(x, y);', + '}', + + 'float when_le(float x, float y) {', + ' return 1.0 - when_gt(x, y);', + '}', + + // Branch-avoiding logical operators + // (to be used with above comparison fns) + 'float and(float a, float b) {', + ' return a * b;', + '}', + + 'float or(float a, float b) {', + ' return min(a + b, 1.0);', + '}', + ].join( '\n' ), + + + // From: + // - http://stackoverflow.com/a/12553149 + // - https://stackoverflow.com/questions/22895237/hexadecimal-to-rgb-values-in-webgl-shader + unpackColor: [ + 'vec3 unpackColor( in float hex ) {', + ' vec3 c = vec3( 0.0 );', + + ' float r = mod( (hex / PACKED_COLOR_SIZE / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );', + ' float g = mod( (hex / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );', + ' float b = mod( hex, PACKED_COLOR_SIZE );', + + ' c.r = r / PACKED_COLOR_DIVISOR;', + ' c.g = g / PACKED_COLOR_DIVISOR;', + ' c.b = b / PACKED_COLOR_DIVISOR;', + + ' return c;', + '}', + ].join( '\n' ), + + unpackRotationAxis: [ + 'vec3 unpackRotationAxis( in float hex ) {', + ' vec3 c = vec3( 0.0 );', + + ' float r = mod( (hex / PACKED_COLOR_SIZE / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );', + ' float g = mod( (hex / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );', + ' float b = mod( hex, PACKED_COLOR_SIZE );', + + ' c.r = r / PACKED_COLOR_DIVISOR;', + ' c.g = g / PACKED_COLOR_DIVISOR;', + ' c.b = b / PACKED_COLOR_DIVISOR;', + + ' c *= vec3( 2.0 );', + ' c -= vec3( 1.0 );', + + ' return c;', + '}', + ].join( '\n' ), + + floatOverLifetime: [ + 'float getFloatOverLifetime( in float positionInTime, in vec4 attr ) {', + ' highp float value = 0.0;', + ' float deltaAge = positionInTime * float( VALUE_OVER_LIFETIME_LENGTH - 1 );', + ' float fIndex = 0.0;', + ' float shouldApplyValue = 0.0;', + + // This might look a little odd, but it's faster in the testing I've done than using branches. + // Uses basic maths to avoid branching. + // + // Take a look at the branch-avoidance functions defined above, + // and be sure to check out The Orange Duck site where I got this + // from (link above). + + // Fix for static emitters (age is always zero). + ' value += attr[ 0 ] * when_eq( deltaAge, 0.0 );', + '', + ' for( int i = 0; i < VALUE_OVER_LIFETIME_LENGTH - 1; ++i ) {', + ' fIndex = float( i );', + ' shouldApplyValue = and( when_gt( deltaAge, fIndex ), when_le( deltaAge, fIndex + 1.0 ) );', + ' value += shouldApplyValue * mix( attr[ i ], attr[ i + 1 ], deltaAge - fIndex );', + ' }', + '', + ' return value;', + '}', + ].join( '\n' ), + + colorOverLifetime: [ + 'vec3 getColorOverLifetime( in float positionInTime, in vec3 color1, in vec3 color2, in vec3 color3, in vec3 color4 ) {', + ' vec3 value = vec3( 0.0 );', + ' value.x = getFloatOverLifetime( positionInTime, vec4( color1.x, color2.x, color3.x, color4.x ) );', + ' value.y = getFloatOverLifetime( positionInTime, vec4( color1.y, color2.y, color3.y, color4.y ) );', + ' value.z = getFloatOverLifetime( positionInTime, vec4( color1.z, color2.z, color3.z, color4.z ) );', + ' return value;', + '}', + ].join( '\n' ), + + paramFetchingFunctions: [ + 'float getAlive() {', + ' return params.x;', + '}', + + 'float getAge() {', + ' return params.y;', + '}', + + 'float getMaxAge() {', + ' return params.z;', + '}', + + 'float getWiggle() {', + ' return params.w;', + '}', + ].join( '\n' ), + + forceFetchingFunctions: [ + 'vec4 getPosition( in float age ) {', + ' return modelViewMatrix * vec4( position, 1.0 );', + '}', + + 'vec3 getVelocity( in float age ) {', + ' return velocity * age;', + '}', + + 'vec3 getAcceleration( in float age ) {', + ' return acceleration.xyz * age;', + '}', + ].join( '\n' ), + + + rotationFunctions: [ + // Huge thanks to: + // - http://www.neilmendoza.com/glsl-rotation-about-an-arbitrary-axis/ + '#ifdef SHOULD_ROTATE_PARTICLES', + ' mat4 getRotationMatrix( in vec3 axis, in float angle) {', + ' axis = normalize(axis);', + ' float s = sin(angle);', + ' float c = cos(angle);', + ' float oc = 1.0 - c;', + '', + ' return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0,', + ' oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0,', + ' oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0,', + ' 0.0, 0.0, 0.0, 1.0);', + ' }', + '', + ' vec3 getRotation( in vec3 pos, in float positionInTime ) {', + ' if( rotation.y == 0.0 ) {', + ' return pos;', + ' }', + '', + ' vec3 axis = unpackRotationAxis( rotation.x );', + ' vec3 center = rotationCenter;', + ' vec3 translated;', + ' mat4 rotationMatrix;', + + ' float angle = 0.0;', + ' angle += when_eq( rotation.z, 0.0 ) * rotation.y;', + ' angle += when_gt( rotation.z, 0.0 ) * mix( 0.0, rotation.y, positionInTime );', + ' translated = rotationCenter - pos;', + ' rotationMatrix = getRotationMatrix( axis, angle );', + ' return center - vec3( rotationMatrix * vec4( translated, 0.0 ) );', + ' }', + '#endif' + ].join( '\n' ), + + + // Fragment chunks + rotateTexture: [ + ' vec2 vUv = vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y );', + '', + ' #ifdef SHOULD_ROTATE_TEXTURE', + ' float x = gl_PointCoord.x - 0.5;', + ' float y = 1.0 - gl_PointCoord.y - 0.5;', + ' float c = cos( -vAngle );', + ' float s = sin( -vAngle );', + + ' vUv = vec2( c * x + s * y + 0.5, c * y - s * x + 0.5 );', + ' #endif', + '', + + // Spritesheets overwrite angle calculations. + ' #ifdef SHOULD_CALCULATE_SPRITE', + ' float framesX = vSpriteSheet.x;', + ' float framesY = vSpriteSheet.y;', + ' float columnNorm = vSpriteSheet.z;', + ' float rowNorm = vSpriteSheet.w;', + + ' vUv.x = gl_PointCoord.x * framesX + columnNorm;', + ' vUv.y = 1.0 - (gl_PointCoord.y * framesY + rowNorm);', + ' #endif', + + '', + ' vec4 rotatedTexture = texture2D( tex, vUv );', + ].join( '\n' ) +}; + +SPE.shaders = { + vertex: [ + SPE.shaderChunks.defines, + SPE.shaderChunks.uniforms, + SPE.shaderChunks.attributes, + SPE.shaderChunks.varyings, + + THREE.ShaderChunk.common, + THREE.ShaderChunk.logdepthbuf_pars_vertex, + THREE.ShaderChunk.fog_pars_vertex, + + SPE.shaderChunks.branchAvoidanceFunctions, + SPE.shaderChunks.unpackColor, + SPE.shaderChunks.unpackRotationAxis, + SPE.shaderChunks.floatOverLifetime, + SPE.shaderChunks.colorOverLifetime, + SPE.shaderChunks.paramFetchingFunctions, + SPE.shaderChunks.forceFetchingFunctions, + SPE.shaderChunks.rotationFunctions, + + + 'void main() {', + + + // + // Setup... + // + ' highp float age = getAge();', + ' highp float alive = getAlive();', + ' highp float maxAge = getMaxAge();', + ' highp float positionInTime = (age / maxAge);', + ' highp float isAlive = when_gt( alive, 0.0 );', + + ' #ifdef SHOULD_WIGGLE_PARTICLES', + ' float wiggleAmount = positionInTime * getWiggle();', + ' float wiggleSin = isAlive * sin( wiggleAmount );', + ' float wiggleCos = isAlive * cos( wiggleAmount );', + ' #endif', + + // + // Forces + // + + // Get forces & position + ' vec3 vel = getVelocity( age );', + ' vec3 accel = getAcceleration( age );', + ' vec3 force = vec3( 0.0 );', + ' vec3 pos = vec3( position );', + + // Calculate the required drag to apply to the forces. + ' float drag = 1.0 - (positionInTime * 0.5) * acceleration.w;', + + // Integrate forces... + ' force += vel;', + ' force *= drag;', + ' force += accel * age;', + ' pos += force;', + + + // Wiggly wiggly wiggle! + ' #ifdef SHOULD_WIGGLE_PARTICLES', + ' pos.x += wiggleSin;', + ' pos.y += wiggleCos;', + ' pos.z += wiggleSin;', + ' #endif', + + + // Rotate the emitter around it's central point + ' #ifdef SHOULD_ROTATE_PARTICLES', + ' pos = getRotation( pos, positionInTime );', + ' #endif', + + // Convert pos to a world-space value + ' vec4 mvPosition = modelViewMatrix * vec4( pos, 1.0 );', + + // Determine point size. + ' highp float pointSize = getFloatOverLifetime( positionInTime, size ) * isAlive;', + + // Determine perspective + ' #ifdef HAS_PERSPECTIVE', + ' float perspective = scale / length( mvPosition.xyz );', + ' #else', + ' float perspective = 1.0;', + ' #endif', + + // Apply perpective to pointSize value + ' float pointSizePerspective = pointSize * perspective;', + + + // + // Appearance + // + + // Determine color and opacity for this particle + ' #ifdef COLORIZE', + ' vec3 c = isAlive * getColorOverLifetime(', + ' positionInTime,', + ' unpackColor( color.x ),', + ' unpackColor( color.y ),', + ' unpackColor( color.z ),', + ' unpackColor( color.w )', + ' );', + ' #else', + ' vec3 c = vec3(1.0);', + ' #endif', + + ' float o = isAlive * getFloatOverLifetime( positionInTime, opacity );', + + // Assign color to vColor varying. + ' vColor = vec4( c, o );', + + // Determine angle + ' #ifdef SHOULD_ROTATE_TEXTURE', + ' vAngle = isAlive * getFloatOverLifetime( positionInTime, angle );', + ' #endif', + + // If this particle is using a sprite-sheet as a texture, we'll have to figure out + // what frame of the texture the particle is using at it's current position in time. + ' #ifdef SHOULD_CALCULATE_SPRITE', + ' float framesX = textureAnimation.x;', + ' float framesY = textureAnimation.y;', + ' float loopCount = textureAnimation.w;', + ' float totalFrames = textureAnimation.z;', + ' float frameNumber = mod( (positionInTime * loopCount) * totalFrames, totalFrames );', + + ' float column = floor(mod( frameNumber, framesX ));', + ' float row = floor( (frameNumber - column) / framesX );', + + ' float columnNorm = column / framesX;', + ' float rowNorm = row / framesY;', + + ' vSpriteSheet.x = 1.0 / framesX;', + ' vSpriteSheet.y = 1.0 / framesY;', + ' vSpriteSheet.z = columnNorm;', + ' vSpriteSheet.w = rowNorm;', + ' #endif', + + // + // Write values + // + + // Set PointSize according to size at current point in time. + ' gl_PointSize = pointSizePerspective;', + ' gl_Position = projectionMatrix * mvPosition;', + + THREE.ShaderChunk.logdepthbuf_vertex, + THREE.ShaderChunk.fog_vertex, + + '}' + ].join( '\n' ), + + fragment: [ + SPE.shaderChunks.uniforms, + + THREE.ShaderChunk.common, + THREE.ShaderChunk.fog_pars_fragment, + THREE.ShaderChunk.logdepthbuf_pars_fragment, + + SPE.shaderChunks.varyings, + + SPE.shaderChunks.branchAvoidanceFunctions, + + 'void main() {', + ' vec3 outgoingLight = vColor.xyz;', + ' ', + ' #ifdef ALPHATEST', + ' if ( vColor.w < float(ALPHATEST) ) discard;', + ' #endif', + + SPE.shaderChunks.rotateTexture, + + THREE.ShaderChunk.logdepthbuf_fragment, + + ' outgoingLight = vColor.xyz * rotatedTexture.xyz;', + ' gl_FragColor = vec4( outgoingLight.xyz, rotatedTexture.w * vColor.w );', + + THREE.ShaderChunk.fog_fragment, + + '}' + ].join( '\n' ) +}; + + +/** + * A bunch of utility functions used throughout the library. + * @namespace + * @type {Object} + */ +SPE.utils = { + /** + * A map of types used by `SPE.utils.ensureTypedArg` and + * `SPE.utils.ensureArrayTypedArg` to compare types against. + * + * @enum {String} + */ + types: { + /** + * Boolean type. + * @type {String} + */ + BOOLEAN: 'boolean', + + /** + * String type. + * @type {String} + */ + STRING: 'string', + + /** + * Number type. + * @type {String} + */ + NUMBER: 'number', + + /** + * Object type. + * @type {String} + */ + OBJECT: 'object' + }, + + /** + * Given a value, a type, and a default value to fallback to, + * ensure the given argument adheres to the type requesting, + * returning the default value if type check is false. + * + * @param {(boolean|string|number|object)} arg The value to perform a type-check on. + * @param {String} type The type the `arg` argument should adhere to. + * @param {(boolean|string|number|object)} defaultValue A default value to fallback on if the type check fails. + * @return {(boolean|string|number|object)} The given value if type check passes, or the default value if it fails. + */ + ensureTypedArg: function( arg, type, defaultValue ) { + 'use strict'; + + if ( typeof arg === type ) { + return arg; + } + else { + return defaultValue; + } + }, + + /** + * Given an array of values, a type, and a default value, + * ensure the given array's contents ALL adhere to the provided type, + * returning the default value if type check fails. + * + * If the given value to check isn't an Array, delegates to SPE.utils.ensureTypedArg. + * + * @param {Array|boolean|string|number|object} arg The array of values to check type of. + * @param {String} type The type that should be adhered to. + * @param {(boolean|string|number|object)} defaultValue A default fallback value. + * @return {(boolean|string|number|object)} The given value if type check passes, or the default value if it fails. + */ + ensureArrayTypedArg: function( arg, type, defaultValue ) { + 'use strict'; + + // If the argument being checked is an array, loop through + // it and ensure all the values are of the correct type, + // falling back to the defaultValue if any aren't. + if ( Array.isArray( arg ) ) { + for ( var i = arg.length - 1; i >= 0; --i ) { + if ( typeof arg[ i ] !== type ) { + return defaultValue; + } + } + + return arg; + } + + // If the arg isn't an array then just fallback to + // checking the type. + return this.ensureTypedArg( arg, type, defaultValue ); + }, + + /** + * Ensures the given value is an instance of a constructor function. + * + * @param {Object} arg The value to check instance of. + * @param {Function} instance The constructor of the instance to check against. + * @param {Object} defaultValue A default fallback value if instance check fails + * @return {Object} The given value if type check passes, or the default value if it fails. + */ + ensureInstanceOf: function( arg, instance, defaultValue ) { + 'use strict'; + + if ( instance !== undefined && arg instanceof instance ) { + return arg; + } + else { + return defaultValue; + } + }, + + /** + * Given an array of values, ensure the instances of all items in the array + * matches the given instance constructor falling back to a default value if + * the check fails. + * + * If given value isn't an Array, delegates to `SPE.utils.ensureInstanceOf`. + * + * @param {Array|Object} arg The value to perform the instanceof check on. + * @param {Function} instance The constructor of the instance to check against. + * @param {Object} defaultValue A default fallback value if instance check fails + * @return {Object} The given value if type check passes, or the default value if it fails. + */ + ensureArrayInstanceOf: function( arg, instance, defaultValue ) { + 'use strict'; + + // If the argument being checked is an array, loop through + // it and ensure all the values are of the correct type, + // falling back to the defaultValue if any aren't. + if ( Array.isArray( arg ) ) { + for ( var i = arg.length - 1; i >= 0; --i ) { + if ( instance !== undefined && arg[ i ] instanceof instance === false ) { + return defaultValue; + } + } + + return arg; + } + + // If the arg isn't an array then just fallback to + // checking the type. + return this.ensureInstanceOf( arg, instance, defaultValue ); + }, + + /** + * Ensures that any "value-over-lifetime" properties of an emitter are + * of the correct length (as dictated by `SPE.valueOverLifetimeLength`). + * + * Delegates to `SPE.utils.interpolateArray` for array resizing. + * + * If properties aren't arrays, then property values are put into one. + * + * @param {Object} property The property of an SPE.Emitter instance to check compliance of. + * @param {Number} minLength The minimum length of the array to create. + * @param {Number} maxLength The maximum length of the array to create. + */ + ensureValueOverLifetimeCompliance: function( property, minLength, maxLength ) { + 'use strict'; + + minLength = minLength || 3; + maxLength = maxLength || 3; + + // First, ensure both properties are arrays. + if ( Array.isArray( property._value ) === false ) { + property._value = [ property._value ]; + } + + if ( Array.isArray( property._spread ) === false ) { + property._spread = [ property._spread ]; + } + + var valueLength = this.clamp( property._value.length, minLength, maxLength ), + spreadLength = this.clamp( property._spread.length, minLength, maxLength ), + desiredLength = Math.max( valueLength, spreadLength ); + + if ( property._value.length !== desiredLength ) { + property._value = this.interpolateArray( property._value, desiredLength ); + } + + if ( property._spread.length !== desiredLength ) { + property._spread = this.interpolateArray( property._spread, desiredLength ); + } + }, + + /** + * Performs linear interpolation (lerp) on an array. + * + * For example, lerping [1, 10], with a `newLength` of 10 will produce [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]. + * + * Delegates to `SPE.utils.lerpTypeAgnostic` to perform the actual + * interpolation. + * + * @param {Array} srcArray The array to lerp. + * @param {Number} newLength The length the array should be interpolated to. + * @return {Array} The interpolated array. + */ + interpolateArray: function( srcArray, newLength ) { + 'use strict'; + + var sourceLength = srcArray.length, + newArray = [ typeof srcArray[ 0 ].clone === 'function' ? srcArray[ 0 ].clone() : srcArray[ 0 ] ], + factor = ( sourceLength - 1 ) / ( newLength - 1 ); + + + for ( var i = 1; i < newLength - 1; ++i ) { + var f = i * factor, + before = Math.floor( f ), + after = Math.ceil( f ), + delta = f - before; + + newArray[ i ] = this.lerpTypeAgnostic( srcArray[ before ], srcArray[ after ], delta ); + } + + newArray.push( + typeof srcArray[ sourceLength - 1 ].clone === 'function' ? + srcArray[ sourceLength - 1 ].clone() : + srcArray[ sourceLength - 1 ] + ); + + return newArray; + }, + + /** + * Clamp a number to between the given min and max values. + * @param {Number} value The number to clamp. + * @param {Number} min The minimum value. + * @param {Number} max The maximum value. + * @return {Number} The clamped number. + */ + clamp: function( value, min, max ) { + 'use strict'; + + return Math.max( min, Math.min( value, max ) ); + }, + + /** + * If the given value is less than the epsilon value, then return + * a randomised epsilon value if specified, or just the epsilon value if not. + * Works for negative numbers as well as positive. + * + * @param {Number} value The value to perform the operation on. + * @param {Boolean} randomise Whether the value should be randomised. + * @return {Number} The result of the operation. + */ + zeroToEpsilon: function( value, randomise ) { + 'use strict'; + + var epsilon = 0.00001, + result = value; + + result = randomise ? Math.random() * epsilon * 10 : epsilon; + + if ( value < 0 && value > -epsilon ) { + result = -result; + } + + // if ( value === 0 ) { + // result = randomise ? Math.random() * epsilon * 10 : epsilon; + // } + // else if ( value > 0 && value < epsilon ) { + // result = randomise ? Math.random() * epsilon * 10 : epsilon; + // } + // else if ( value < 0 && value > -epsilon ) { + // result = -( randomise ? Math.random() * epsilon * 10 : epsilon ); + // } + + return result; + }, + + /** + * Linearly interpolates two values of various types. The given values + * must be of the same type for the interpolation to work. + * @param {(number|Object)} start The start value of the lerp. + * @param {(number|object)} end The end value of the lerp. + * @param {Number} delta The delta posiiton of the lerp operation. Ideally between 0 and 1 (inclusive). + * @return {(number|object|undefined)} The result of the operation. Result will be undefined if + * the start and end arguments aren't a supported type, or + * if their types do not match. + */ + lerpTypeAgnostic: function( start, end, delta ) { + 'use strict'; + + var types = this.types, + out; + + if ( typeof start === types.NUMBER && typeof end === types.NUMBER ) { + return start + ( ( end - start ) * delta ); + } + else if ( start instanceof THREE.Vector2 && end instanceof THREE.Vector2 ) { + out = start.clone(); + out.x = this.lerp( start.x, end.x, delta ); + out.y = this.lerp( start.y, end.y, delta ); + return out; + } + else if ( start instanceof THREE.Vector3 && end instanceof THREE.Vector3 ) { + out = start.clone(); + out.x = this.lerp( start.x, end.x, delta ); + out.y = this.lerp( start.y, end.y, delta ); + out.z = this.lerp( start.z, end.z, delta ); + return out; + } + else if ( start instanceof THREE.Vector4 && end instanceof THREE.Vector4 ) { + out = start.clone(); + out.x = this.lerp( start.x, end.x, delta ); + out.y = this.lerp( start.y, end.y, delta ); + out.z = this.lerp( start.z, end.z, delta ); + out.w = this.lerp( start.w, end.w, delta ); + return out; + } + else if ( start instanceof THREE.Color && end instanceof THREE.Color ) { + out = start.clone(); + out.r = this.lerp( start.r, end.r, delta ); + out.g = this.lerp( start.g, end.g, delta ); + out.b = this.lerp( start.b, end.b, delta ); + return out; + } + else { + console.warn( 'Invalid argument types, or argument types do not match:', start, end ); + } + }, + + /** + * Perform a linear interpolation operation on two numbers. + * @param {Number} start The start value. + * @param {Number} end The end value. + * @param {Number} delta The position to interpolate to. + * @return {Number} The result of the lerp operation. + */ + lerp: function( start, end, delta ) { + 'use strict'; + return start + ( ( end - start ) * delta ); + }, + + /** + * Rounds a number to a nearest multiple. + * + * @param {Number} n The number to round. + * @param {Number} multiple The multiple to round to. + * @return {Number} The result of the round operation. + */ + roundToNearestMultiple: function( n, multiple ) { + 'use strict'; + + var remainder = 0; + + if ( multiple === 0 ) { + return n; + } + + remainder = Math.abs( n ) % multiple; + + if ( remainder === 0 ) { + return n; + } + + if ( n < 0 ) { + return -( Math.abs( n ) - remainder ); + } + + return n + multiple - remainder; + }, + + /** + * Check if all items in an array are equal. Uses strict equality. + * + * @param {Array} array The array of values to check equality of. + * @return {Boolean} Whether the array's values are all equal or not. + */ + arrayValuesAreEqual: function( array ) { + 'use strict'; + + for ( var i = 0; i < array.length - 1; ++i ) { + if ( array[ i ] !== array[ i + 1 ] ) { + return false; + } + } + + return true; + }, + + // colorsAreEqual: function() { + // var colors = Array.prototype.slice.call( arguments ), + // numColors = colors.length; + + // for ( var i = 0, color1, color2; i < numColors - 1; ++i ) { + // color1 = colors[ i ]; + // color2 = colors[ i + 1 ]; + + // if ( + // color1.r !== color2.r || + // color1.g !== color2.g || + // color1.b !== color2.b + // ) { + // return false + // } + // } + + // return true; + // }, + + + /** + * Given a start value and a spread value, create and return a random + * number. + * @param {Number} base The start value. + * @param {Number} spread The size of the random variance to apply. + * @return {Number} A randomised number. + */ + randomFloat: function( base, spread ) { + 'use strict'; + return base + spread * ( Math.random() - 0.5 ); + }, + + + + /** + * Given an SPE.ShaderAttribute instance, and various other settings, + * assign values to the attribute's array in a `vec3` format. + * + * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to. + * @param {Number} index The offset in the attribute's TypedArray to save the result from. + * @param {Object} base THREE.Vector3 instance describing the start value. + * @param {Object} spread THREE.Vector3 instance describing the random variance to apply to the start value. + * @param {Object} spreadClamp THREE.Vector3 instance describing the multiples to clamp the randomness to. + */ + randomVector3: function( attribute, index, base, spread, spreadClamp ) { + 'use strict'; + + var x = base.x + ( Math.random() * spread.x - ( spread.x * 0.5 ) ), + y = base.y + ( Math.random() * spread.y - ( spread.y * 0.5 ) ), + z = base.z + ( Math.random() * spread.z - ( spread.z * 0.5 ) ); + + // var x = this.randomFloat( base.x, spread.x ), + // y = this.randomFloat( base.y, spread.y ), + // z = this.randomFloat( base.z, spread.z ); + + if ( spreadClamp ) { + x = -spreadClamp.x * 0.5 + this.roundToNearestMultiple( x, spreadClamp.x ); + y = -spreadClamp.y * 0.5 + this.roundToNearestMultiple( y, spreadClamp.y ); + z = -spreadClamp.z * 0.5 + this.roundToNearestMultiple( z, spreadClamp.z ); + } + + attribute.typedArray.setVec3Components( index, x, y, z ); + }, + + /** + * Given an SPE.Shader attribute instance, and various other settings, + * assign Color values to the attribute. + * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to. + * @param {Number} index The offset in the attribute's TypedArray to save the result from. + * @param {Object} base THREE.Color instance describing the start color. + * @param {Object} spread THREE.Vector3 instance describing the random variance to apply to the start color. + */ + randomColor: function( attribute, index, base, spread ) { + 'use strict'; + + var r = base.r + ( Math.random() * spread.x ), + g = base.g + ( Math.random() * spread.y ), + b = base.b + ( Math.random() * spread.z ); + + r = this.clamp( r, 0, 1 ); + g = this.clamp( g, 0, 1 ); + b = this.clamp( b, 0, 1 ); + + + attribute.typedArray.setVec3Components( index, r, g, b ); + }, + + + randomColorAsHex: ( function() { + 'use strict'; + + var workingColor = new THREE.Color(); + + /** + * Assigns a random color value, encoded as a hex value in decimal + * format, to a SPE.ShaderAttribute instance. + * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to. + * @param {Number} index The offset in the attribute's TypedArray to save the result from. + * @param {Object} base THREE.Color instance describing the start color. + * @param {Object} spread THREE.Vector3 instance describing the random variance to apply to the start color. + */ + return function( attribute, index, base, spread ) { + var numItems = base.length, + colors = []; + + for ( var i = 0; i < numItems; ++i ) { + var spreadVector = spread[ i ]; + + workingColor.copy( base[ i ] ); + + workingColor.r += ( Math.random() * spreadVector.x ) - ( spreadVector.x * 0.5 ); + workingColor.g += ( Math.random() * spreadVector.y ) - ( spreadVector.y * 0.5 ); + workingColor.b += ( Math.random() * spreadVector.z ) - ( spreadVector.z * 0.5 ); + + workingColor.r = this.clamp( workingColor.r, 0, 1 ); + workingColor.g = this.clamp( workingColor.g, 0, 1 ); + workingColor.b = this.clamp( workingColor.b, 0, 1 ); + + colors.push( workingColor.getHex() ); + } + + attribute.typedArray.setVec4Components( index, colors[ 0 ], colors[ 1 ], colors[ 2 ], colors[ 3 ] ); + }; + }() ), + + /** + * Given an SPE.ShaderAttribute instance, and various other settings, + * assign values to the attribute's array in a `vec3` format. + * + * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to. + * @param {Number} index The offset in the attribute's TypedArray to save the result from. + * @param {Object} start THREE.Vector3 instance describing the start line position. + * @param {Object} end THREE.Vector3 instance describing the end line position. + */ + randomVector3OnLine: function( attribute, index, start, end ) { + 'use strict'; + var pos = start.clone(); + + pos.lerp( end, Math.random() ); + + attribute.typedArray.setVec3Components( index, pos.x, pos.y, pos.z ); + }, + + /** + * Given an SPE.Shader attribute instance, and various other settings, + * assign Color values to the attribute. + * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to. + * @param {Number} index The offset in the attribute's TypedArray to save the result from. + * @param {Object} base THREE.Color instance describing the start color. + * @param {Object} spread THREE.Vector3 instance describing the random variance to apply to the start color. + */ + + /** + * Assigns a random vector 3 value to an SPE.ShaderAttribute instance, projecting the + * given values onto a sphere. + * + * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to. + * @param {Number} index The offset in the attribute's TypedArray to save the result from. + * @param {Object} base THREE.Vector3 instance describing the origin of the transform. + * @param {Number} radius The radius of the sphere to project onto. + * @param {Number} radiusSpread The amount of randomness to apply to the projection result + * @param {Object} radiusScale THREE.Vector3 instance describing the scale of each axis of the sphere. + * @param {Number} radiusSpreadClamp What numeric multiple the projected value should be clamped to. + */ + randomVector3OnSphere: function( + attribute, index, base, radius, radiusSpread, radiusScale, radiusSpreadClamp, distributionClamp + ) { + 'use strict'; + + var depth = 2 * Math.random() - 1, + t = 6.2832 * Math.random(), + r = Math.sqrt( 1 - depth * depth ), + rand = this.randomFloat( radius, radiusSpread ), + x = 0, + y = 0, + z = 0; + + + if ( radiusSpreadClamp ) { + rand = Math.round( rand / radiusSpreadClamp ) * radiusSpreadClamp; + } + + + + // Set position on sphere + x = r * Math.cos( t ) * rand; + y = r * Math.sin( t ) * rand; + z = depth * rand; + + // Apply radius scale to this position + x *= radiusScale.x; + y *= radiusScale.y; + z *= radiusScale.z; + + // Translate to the base position. + x += base.x; + y += base.y; + z += base.z; + + // Set the values in the typed array. + attribute.typedArray.setVec3Components( index, x, y, z ); + }, + + seededRandom: function( seed ) { + var x = Math.sin( seed ) * 10000; + return x - ( x | 0 ); + }, + + + + /** + * Assigns a random vector 3 value to an SPE.ShaderAttribute instance, projecting the + * given values onto a 2d-disc. + * + * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to. + * @param {Number} index The offset in the attribute's TypedArray to save the result from. + * @param {Object} base THREE.Vector3 instance describing the origin of the transform. + * @param {Number} radius The radius of the sphere to project onto. + * @param {Number} radiusSpread The amount of randomness to apply to the projection result + * @param {Object} radiusScale THREE.Vector3 instance describing the scale of each axis of the disc. The z-component is ignored. + * @param {Number} radiusSpreadClamp What numeric multiple the projected value should be clamped to. + */ + randomVector3OnDisc: function( attribute, index, base, radius, radiusSpread, radiusScale, radiusSpreadClamp ) { + 'use strict'; + + var t = 6.2832 * Math.random(), + rand = Math.abs( this.randomFloat( radius, radiusSpread ) ), + x = 0, + y = 0, + z = 0; + + if ( radiusSpreadClamp ) { + rand = Math.round( rand / radiusSpreadClamp ) * radiusSpreadClamp; + } + + // Set position on sphere + x = Math.cos( t ) * rand; + y = Math.sin( t ) * rand; + + // Apply radius scale to this position + x *= radiusScale.x; + y *= radiusScale.y; + + // Translate to the base position. + x += base.x; + y += base.y; + z += base.z; + + // Set the values in the typed array. + attribute.typedArray.setVec3Components( index, x, y, z ); + }, + + randomDirectionVector3OnSphere: ( function() { + 'use strict'; + + var v = new THREE.Vector3(); + + /** + * Given an SPE.ShaderAttribute instance, create a direction vector from the given + * position, using `speed` as the magnitude. Values are saved to the attribute. + * + * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to. + * @param {Number} index The offset in the attribute's TypedArray to save the result from. + * @param {Number} posX The particle's x coordinate. + * @param {Number} posY The particle's y coordinate. + * @param {Number} posZ The particle's z coordinate. + * @param {Object} emitterPosition THREE.Vector3 instance describing the emitter's base position. + * @param {Number} speed The magnitude to apply to the vector. + * @param {Number} speedSpread The amount of randomness to apply to the magnitude. + */ + return function( attribute, index, posX, posY, posZ, emitterPosition, speed, speedSpread ) { + v.copy( emitterPosition ); + + v.x -= posX; + v.y -= posY; + v.z -= posZ; + + v.normalize().multiplyScalar( -this.randomFloat( speed, speedSpread ) ); + + attribute.typedArray.setVec3Components( index, v.x, v.y, v.z ); + }; + }() ), + + + randomDirectionVector3OnDisc: ( function() { + 'use strict'; + + var v = new THREE.Vector3(); + + /** + * Given an SPE.ShaderAttribute instance, create a direction vector from the given + * position, using `speed` as the magnitude. Values are saved to the attribute. + * + * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to. + * @param {Number} index The offset in the attribute's TypedArray to save the result from. + * @param {Number} posX The particle's x coordinate. + * @param {Number} posY The particle's y coordinate. + * @param {Number} posZ The particle's z coordinate. + * @param {Object} emitterPosition THREE.Vector3 instance describing the emitter's base position. + * @param {Number} speed The magnitude to apply to the vector. + * @param {Number} speedSpread The amount of randomness to apply to the magnitude. + */ + return function( attribute, index, posX, posY, posZ, emitterPosition, speed, speedSpread ) { + v.copy( emitterPosition ); + + v.x -= posX; + v.y -= posY; + v.z -= posZ; + + v.normalize().multiplyScalar( -this.randomFloat( speed, speedSpread ) ); + + attribute.typedArray.setVec3Components( index, v.x, v.y, 0 ); + }; + }() ), + + getPackedRotationAxis: ( function() { + 'use strict'; + + var v = new THREE.Vector3(), + vSpread = new THREE.Vector3(), + c = new THREE.Color(), + addOne = new THREE.Vector3( 1, 1, 1 ); + + /** + * Given a rotation axis, and a rotation axis spread vector, + * calculate a randomised rotation axis, and pack it into + * a hexadecimal value represented in decimal form. + * @param {Object} axis THREE.Vector3 instance describing the rotation axis. + * @param {Object} axisSpread THREE.Vector3 instance describing the amount of randomness to apply to the rotation axis. + * @return {Number} The packed rotation axis, with randomness. + */ + return function( axis, axisSpread ) { + v.copy( axis ).normalize(); + vSpread.copy( axisSpread ).normalize(); + + v.x += ( -axisSpread.x * 0.5 ) + ( Math.random() * axisSpread.x ); + v.y += ( -axisSpread.y * 0.5 ) + ( Math.random() * axisSpread.y ); + v.z += ( -axisSpread.z * 0.5 ) + ( Math.random() * axisSpread.z ); + + // v.x = Math.abs( v.x ); + // v.y = Math.abs( v.y ); + // v.z = Math.abs( v.z ); + + v.normalize().add( addOne ).multiplyScalar( 0.5 ); + + c.setRGB( v.x, v.y, v.z ); + + return c.getHex(); + }; + }() ) +}; + + +/** + * An SPE.Group instance. + * @typedef {Object} Group + * @see SPE.Group + */ + +/** + * A map of options to configure an SPE.Group instance. + * @typedef {Object} GroupOptions + * + * @property {Object} texture An object describing the texture used by the group. + * + * @property {Object} texture.value An instance of THREE.Texture. + * + * @property {Object=} texture.frames A THREE.Vector2 instance describing the number + * of frames on the x- and y-axis of the given texture. + * If not provided, the texture will NOT be treated as + * a sprite-sheet and as such will NOT be animated. + * + * @property {Number} [texture.frameCount=texture.frames.x * texture.frames.y] The total number of frames in the sprite-sheet. + * Allows for sprite-sheets that don't fill the entire + * texture. + * + * @property {Number} texture.loop The number of loops through the sprite-sheet that should + * be performed over the course of a single particle's lifetime. + * + * @property {Number} fixedTimeStep If no `dt` (or `deltaTime`) value is passed to this group's + * `tick()` function, this number will be used to move the particle + * simulation forward. Value in SECONDS. + * + * @property {Boolean} hasPerspective Whether the distance a particle is from the camera should affect + * the particle's size. + * + * @property {Boolean} colorize Whether the particles in this group should be rendered with color, or + * whether the only color of particles will come from the provided texture. + * + * @property {Number} blending One of Three.js's blending modes to apply to this group's `ShaderMaterial`. + * + * @property {Boolean} transparent Whether these particle's should be rendered with transparency. + * + * @property {Number} alphaTest Sets the alpha value to be used when running an alpha test on the `texture.value` property. Value between 0 and 1. + * + * @property {Boolean} depthWrite Whether rendering the group has any effect on the depth buffer. + * + * @property {Boolean} depthTest Whether to have depth test enabled when rendering this group. + * + * @property {Boolean} fog Whether this group's particles should be affected by their scene's fog. + * + * @property {Number} scale The scale factor to apply to this group's particle sizes. Useful for + * setting particle sizes to be relative to renderer size. + */ + + +/** + * The SPE.Group class. Creates a new group, containing a material, geometry, and mesh. + * + * @constructor + * @param {GroupOptions} options A map of options to configure the group instance. + */ +SPE.Group = function( options ) { + 'use strict'; + + var utils = SPE.utils, + types = utils.types; + + // Ensure we have a map of options to play with + options = utils.ensureTypedArg( options, types.OBJECT, {} ); + options.texture = utils.ensureTypedArg( options.texture, types.OBJECT, {} ); + + // Assign a UUID to this instance + this.uuid = THREE.MathUtils.generateUUID(); + + // If no `deltaTime` value is passed to the `SPE.Group.tick` function, + // the value of this property will be used to advance the simulation. + this.fixedTimeStep = utils.ensureTypedArg( options.fixedTimeStep, types.NUMBER, 0.016 ); + + // Set properties used in the uniforms map, starting with the + // texture stuff. + this.texture = utils.ensureInstanceOf( options.texture.value, THREE.Texture, null ); + this.textureFrames = utils.ensureInstanceOf( options.texture.frames, THREE.Vector2, new THREE.Vector2( 1, 1 ) ); + this.textureFrameCount = utils.ensureTypedArg( options.texture.frameCount, types.NUMBER, this.textureFrames.x * this.textureFrames.y ); + this.textureLoop = utils.ensureTypedArg( options.texture.loop, types.NUMBER, 1 ); + this.textureFrames.max( new THREE.Vector2( 1, 1 ) ); + + this.hasPerspective = utils.ensureTypedArg( options.hasPerspective, types.BOOLEAN, true ); + this.colorize = utils.ensureTypedArg( options.colorize, types.BOOLEAN, true ); + + this.maxParticleCount = utils.ensureTypedArg( options.maxParticleCount, types.NUMBER, null ); + + + // Set properties used to define the ShaderMaterial's appearance. + this.blending = utils.ensureTypedArg( options.blending, types.NUMBER, THREE.AdditiveBlending ); + this.transparent = utils.ensureTypedArg( options.transparent, types.BOOLEAN, true ); + this.alphaTest = parseFloat( utils.ensureTypedArg( options.alphaTest, types.NUMBER, 0.0 ) ); + this.depthWrite = utils.ensureTypedArg( options.depthWrite, types.BOOLEAN, false ); + this.depthTest = utils.ensureTypedArg( options.depthTest, types.BOOLEAN, true ); + this.fog = utils.ensureTypedArg( options.fog, types.BOOLEAN, true ); + this.scale = utils.ensureTypedArg( options.scale, types.NUMBER, 300 ); + + // Where emitter's go to curl up in a warm blanket and live + // out their days. + this.emitters = []; + this.emitterIDs = []; + + // Create properties for use by the emitter pooling functions. + this._pool = []; + this._poolCreationSettings = null; + this._createNewWhenPoolEmpty = 0; + + // Whether all attributes should be forced to updated + // their entire buffer contents on the next tick. + // + // Used when an emitter is removed. + this._attributesNeedRefresh = false; + this._attributesNeedDynamicReset = false; + + this.particleCount = 0; + + + // Map of uniforms to be applied to the ShaderMaterial instance. + this.uniforms = { + tex: { + type: 't', + value: this.texture + }, + textureAnimation: { + type: 'v4', + value: new THREE.Vector4( + this.textureFrames.x, + this.textureFrames.y, + this.textureFrameCount, + Math.max( Math.abs( this.textureLoop ), 1.0 ) + ) + }, + fogColor: { + type: 'c', + value: this.fog ? new THREE.Color() : null + }, + fogNear: { + type: 'f', + value: 10 + }, + fogFar: { + type: 'f', + value: 200 + }, + fogDensity: { + type: 'f', + value: 0.5 + }, + deltaTime: { + type: 'f', + value: 0 + }, + runTime: { + type: 'f', + value: 0 + }, + scale: { + type: 'f', + value: this.scale + } + }; + + // Add some defines into the mix... + this.defines = { + HAS_PERSPECTIVE: this.hasPerspective, + COLORIZE: this.colorize, + VALUE_OVER_LIFETIME_LENGTH: SPE.valueOverLifetimeLength, + + SHOULD_ROTATE_TEXTURE: false, + SHOULD_ROTATE_PARTICLES: false, + SHOULD_WIGGLE_PARTICLES: false, + + SHOULD_CALCULATE_SPRITE: this.textureFrames.x > 1 || this.textureFrames.y > 1 + }; + + // Map of all attributes to be applied to the particles. + // + // See SPE.ShaderAttribute for a bit more info on this bit. + this.attributes = { + position: new SPE.ShaderAttribute( 'v3', true ), + acceleration: new SPE.ShaderAttribute( 'v4', true ), // w component is drag + velocity: new SPE.ShaderAttribute( 'v3', true ), + rotation: new SPE.ShaderAttribute( 'v4', true ), + rotationCenter: new SPE.ShaderAttribute( 'v3', true ), + params: new SPE.ShaderAttribute( 'v4', true ), // Holds (alive, age, delay, wiggle) + size: new SPE.ShaderAttribute( 'v4', true ), + angle: new SPE.ShaderAttribute( 'v4', true ), + color: new SPE.ShaderAttribute( 'v4', true ), + opacity: new SPE.ShaderAttribute( 'v4', true ) + }; + + this.attributeKeys = Object.keys( this.attributes ); + this.attributeCount = this.attributeKeys.length; + + // Create the ShaderMaterial instance that'll help render the + // particles. + this.material = new THREE.ShaderMaterial( { + uniforms: this.uniforms, + vertexShader: SPE.shaders.vertex, + fragmentShader: SPE.shaders.fragment, + blending: this.blending, + transparent: this.transparent, + alphaTest: this.alphaTest, + depthWrite: this.depthWrite, + depthTest: this.depthTest, + defines: this.defines, + fog: this.fog + } ); + + // Create the BufferGeometry and Points instances, ensuring + // the geometry and material are given to the latter. + this.geometry = new THREE.BufferGeometry(); + this.mesh = new THREE.Points( this.geometry, this.material ); + + if ( this.maxParticleCount === null ) { + console.warn( 'SPE.Group: No maxParticleCount specified. Adding emitters after rendering will probably cause errors.' ); + } +}; + +SPE.Group.constructor = SPE.Group; + + +SPE.Group.prototype._updateDefines = function() { + 'use strict'; + + var emitters = this.emitters, + i = emitters.length - 1, + emitter, + defines = this.defines; + + for ( i; i >= 0; --i ) { + emitter = emitters[ i ]; + + // Only do angle calculation if there's no spritesheet defined. + // + // Saves calculations being done and then overwritten in the shaders. + if ( !defines.SHOULD_CALCULATE_SPRITE ) { + defines.SHOULD_ROTATE_TEXTURE = defines.SHOULD_ROTATE_TEXTURE || !!Math.max( + Math.max.apply( null, emitter.angle.value ), + Math.max.apply( null, emitter.angle.spread ) + ); + } + + defines.SHOULD_ROTATE_PARTICLES = defines.SHOULD_ROTATE_PARTICLES || !!Math.max( + emitter.rotation.angle, + emitter.rotation.angleSpread + ); + + defines.SHOULD_WIGGLE_PARTICLES = defines.SHOULD_WIGGLE_PARTICLES || !!Math.max( + emitter.wiggle.value, + emitter.wiggle.spread + ); + } + + this.material.needsUpdate = true; +}; + +SPE.Group.prototype._applyAttributesToGeometry = function() { + 'use strict'; + + var attributes = this.attributes, + geometry = this.geometry, + geometryAttributes = geometry.attributes, + attribute, + geometryAttribute; + + // Loop through all the shader attributes and assign (or re-assign) + // typed array buffers to each one. + for ( var attr in attributes ) { + if ( attributes.hasOwnProperty( attr ) ) { + attribute = attributes[ attr ]; + geometryAttribute = geometryAttributes[ attr ]; + + // Update the array if this attribute exists on the geometry. + // + // This needs to be done because the attribute's typed array might have + // been resized and reinstantiated, and might now be looking at a + // different ArrayBuffer, so reference needs updating. + if ( geometryAttribute ) { + geometryAttribute.array = attribute.typedArray.array; + } + + // // Add the attribute to the geometry if it doesn't already exist. + else { + geometry.setAttribute( 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.MathUtils.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; +}; - /** - * 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; - }; /***/ }) -/******/ ]); \ No newline at end of file + +/******/ }); +/************************************************************************/ +/******/ // The module cache +/******/ var __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ var cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/************************************************************************/ +var __webpack_exports__ = {}; +// This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk. +(() => { +/*!******************!*\ + !*** ./index.js ***! + \******************/ +/** + * Particles component for A-Frame. + * + * ShaderParticleEngine by Squarefeet (https://github.com/squarefeet). + */ + +var SPE = __webpack_require__(/*! ./lib/SPE.js */ "./lib/SPE.js"); + +if (typeof AFRAME === 'undefined') { + throw new Error('Component attempted to register before AFRAME was available.'); +} + +AFRAME.registerComponent('particle-system', { + + schema: { + preset: { + type: 'string', + default: '', + oneOf: ['default', 'dust', 'snow', 'rain'] + }, + maxAge: { + type: 'number', + default: 6 + }, + positionSpread: { + type: 'vec3', + default: { x: 0, y: 0, z: 0 } + }, + type: { + type: 'number', + default: SPE.distributions.BOX + }, + rotationAxis: { + type: 'string', + default: 'x' + }, + rotationAngle: { + type: 'number', + default: 0 + }, + rotationAngleSpread: { + type: 'number', + default: 0 + }, + accelerationValue: { + type: 'vec3', + default: { x: 0, y: -10, z: 0 } + }, + accelerationSpread: { + type: 'vec3', + default: { x: 10, y: 0, z: 10 } + }, + velocityValue: { + type: 'vec3', + default: { x: 0, y: 25, z: 0 } + }, + velocitySpread: { + type: 'vec3', + default: { x: 10, y: 7.5, z: 10 } + }, + dragValue: { + type: 'number', + default: 0 + }, + dragSpread: { + type: 'number', + default: 0 + }, + dragRandomise: { + type: 'boolean', + default: false + }, + color: { + type: 'array', + default: [ '#0000FF', '#FF0000' ] + }, + size: { + type: 'array', + default: [ '1' ] + }, + sizeSpread: { + type: 'array', + default: [ '0' ] + }, + direction: { + type: 'number', + default: 1 + }, + duration: { + type: 'number', + default: Infinity + }, + particleCount: { + type: 'number', + default: 1000 + }, + texture: { + type: 'asset', + default: 'https://cdn.rawgit.com/IdeaSpaceVR/aframe-particle-system-component/master/dist/images/star2.png' + }, + randomise: { + type: 'boolean', + default: false + }, + opacity: { + type: 'array', + default: [ '1' ] + }, + opacitySpread: { + type: 'array', + default: [ '0' ] + }, + maxParticleCount: { + type: 'number', + default: 250000 + }, + blending: { + type: 'number', + default: THREE.AdditiveBlending, + oneOf: [THREE.NoBlending,THREE.NormalBlending,THREE.AdditiveBlending,THREE.SubtractiveBlending,THREE.MultiplyBlending] + }, + enabled: { + type:'boolean', + default:true + } + }, + + + init: function() { + + this.presets = {}; + + /* preset settings can be overwritten */ + + this.presets['dust'] = { + maxAge: 20, + positionSpread: {x:100,y:100,z:100}, + rotationAngle: 3.14, + accelerationValue: {x: 0, y: 0, z: 0}, + accelerationSpread: {x: 0, y: 0, z: 0}, + velocityValue: {x: 1, y: 0.3, z: 1}, + velocitySpread: {x: 0.5, y: 1, z: 0.5}, + color: ['#FFFFFF'], + particleCount: 100, + texture: 'https://cdn.rawgit.com/IdeaSpaceVR/aframe-particle-system-component/master/dist/images/smokeparticle.png' + }; + + + this.presets['snow'] = { + maxAge: 20, + positionSpread: {x:100,y:100,z:100}, + rotationAngle: 3.14, + accelerationValue: {x: 0, y: 0, z: 0}, + accelerationSpread: {x: 0.2, y: 0, z: 0.2}, + velocityValue: {x: 0, y: 8, z: 0}, + velocitySpread: {x: 2, y: 0, z: 2}, + color: ['#FFFFFF'], + particleCount: 200, + texture: 'https://cdn.rawgit.com/IdeaSpaceVR/aframe-particle-system-component/master/dist/images/smokeparticle.png' + }; + + + this.presets['rain'] = { + maxAge: 1, + positionSpread: {x:100,y:100,z:100}, + rotationAngle: 3.14, + accelerationValue: {x: 0, y: 3, z: 0}, + accelerationSpread: {x: 2, y: 1, z: 2}, + velocityValue: {x: 0, y: 75, z: 0}, + velocitySpread: {x: 10, y: 50, z: 10}, + color: ['#FFFFFF'], + size: [0.4], + texture: 'https://cdn.rawgit.com/IdeaSpaceVR/aframe-particle-system-component/master/dist/images/raindrop.png' + }; + + + }, + + + update: function (oldData) { + + // Remove old particle group. + if (this.particleGroup) { + this.el.removeObject3D('particle-system'); + } + + // Set the selected preset, if any, or use an empty object to keep schema defaults + this.preset = this.presets[this.data.preset] || {}; + + // Get custom, preset, or default data for each property defined in the schema + for (var key in this.data) { + this.data[key] = this.applyPreset(key); + } + + this.initParticleSystem(this.data); + + if(this.data.enabled === true) { + this.startParticles() + } else { + this.stopParticles() + } + }, + + + applyPreset: function (key) { + // !this.attrValue[key] = the user did not set a custom value + // this.preset[key] = there exists a value for this key in the selected preset + if (!this.attrValue[key] && this.preset[key]) { + return this.preset[key]; + } else { + // Otherwise stick to the user or schema default value + return this.data[key]; + } + }, + + + tick: function(time, dt) { + + this.particleGroup.tick(dt / 1000); + }, + + + remove: function() { + + // Remove particle system. + if (!this.particleGroup) { return; } + this.el.removeObject3D('particle-system'); + }, + + startParticles: function() { + this.particleGroup.emitters.forEach(function(em) { em.enable() }); + }, + + stopParticles: function() { + this.particleGroup.emitters.forEach(function(em) { em.disable() }); + }, + + + initParticleSystem: function(settings) { + + var loader = new THREE.TextureLoader(); + var particle_texture = loader.load( + settings.texture, + function (texture) { + return texture; + }, + function (xhr) { + console.log((xhr.loaded / xhr.total * 100) + '% loaded'); + }, + function (xhr) { + console.log('An error occurred'); + } + ); + + this.particleGroup = new SPE.Group({ + texture: { + value: particle_texture + }, + maxParticleCount: settings.maxParticleCount, + blending: settings.blending + }); + + var emitter = new SPE.Emitter({ + maxAge: { + value: settings.maxAge + }, + type: { + value: settings.type + }, + position: { + spread: new THREE.Vector3(settings.positionSpread.x, settings.positionSpread.y, settings.positionSpread.z), + randomise: settings.randomise + //spreadClamp: new THREE.Vector3( 2, 2, 2 ), + //radius: 4 + }, + rotation: { + axis: (settings.rotationAxis=='x'?new THREE.Vector3(1, 0, 0):(settings.rotationAxis=='y'?new THREE.Vector3(0, 1, 0):(settings.rotationAxis=='z'?new THREE.Vector3(0, 0, 1):new THREE.Vector3(0, 1, 0)))), + angle: settings.rotationAngle, + angleSpread: settings.rotationAngleSpread, + static: true + }, + acceleration: { + value: new THREE.Vector3(settings.accelerationValue.x, settings.accelerationValue.y, settings.accelerationValue.z), + spread: new THREE.Vector3(settings.accelerationSpread.x, settings.accelerationSpread.y, settings.accelerationSpread.z) + }, + velocity: { + value: new THREE.Vector3(settings.velocityValue.x, settings.velocityValue.y, settings.velocityValue.z), + spread: new THREE.Vector3(settings.velocitySpread.x, settings.velocitySpread.y, settings.velocitySpread.z) + }, + drag: { + value: new THREE.Vector3(settings.dragValue.x, settings.dragValue.y, settings.dragValue.z), + spread: new THREE.Vector3(settings.dragSpread.x, settings.dragSpread.y, settings.dragSpread.z), + randomise: settings.dragRandomise + }, + color: { + value: settings.color.map(function(c) { return new THREE.Color(c); }) + }, + size: { value: settings.size.map(function (s) { return parseFloat(s); }), + spread: settings.sizeSpread.map(function (s) { return parseFloat(s); }) }, + + /*wiggle: { value: 4, spread: 2 }, //settings.wiggle,*/ + /*drag: { + value: settings.drag + },*/ + direction: { + value: settings.direction + }, + duration: settings.duration, + opacity: { value: settings.opacity.map(function (o) { return parseFloat(o); }), + spread: settings.opacitySpread.map(function (o) { return parseFloat(o); }) }, + particleCount: settings.particleCount + }); + + this.particleGroup.addEmitter(emitter); + this.particleGroup.mesh.frustumCulled = false; + this.el.setObject3D('particle-system', this.particleGroup.mesh); + } +}); + +})(); + +/******/ })() +; +//# sourceMappingURL=aframe-particle-system-component.js.map \ No newline at end of file diff --git a/dist/aframe-particle-system-component.js.map b/dist/aframe-particle-system-component.js.map new file mode 100644 index 0000000..048e9b0 --- /dev/null +++ b/dist/aframe-particle-system-component.js.map @@ -0,0 +1 @@ +{"version":3,"file":"aframe-particle-system-component.js","mappings":";;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa,QAAQ;AACrB,cAAc,QAAQ;AACtB,cAAc,QAAQ;AACtB,cAAc,QAAQ;AACtB;;AAEA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;;AAEA;AACA;AACA,kBAAkB;AAClB;AACA;;AAEA;AACA;AACA,kBAAkB;AAClB;AACA;;AAEA;AACA;AACA,kBAAkB;AAClB;AACA;AACA,KAAK;;;AAGL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;;AAEA;AACA,KAAK,IAA0C;AAC/C,IAAI,oCAAe,GAAG;AAAA;AAAA;AAAA;AAAA,kGAAE;AACxB;AACA,KAAK,EAEJ;;;AAGD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,UAAU;AACrB,WAAW,QAAQ;AACnB,WAAW,QAAQ;AACnB,WAAW,QAAQ;AACnB;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,QAAQ;AACnB;AACA;AACA;;AAEA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,YAAY,QAAQ;AACpB,YAAY,2BAA2B;AACvC;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA,YAAY,QAAQ;AACpB,YAAY,2BAA2B;AACvC;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;;AAGA;AACA;AACA,YAAY,QAAQ;AACpB,YAAY,QAAQ;AACpB,aAAa,QAAQ;AACrB;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA,qBAAqB,UAAU;AAC/B;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;;;AAGA;AACA;AACA;AACA;AACA;AACA,WAAW,QAAQ;AACnB,WAAW,YAAY,oCAAoC;AAC3D,YAAY,sBAAsB;AAClC;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;;AAEA;AACA;AACA;AACA,WAAW,QAAQ;AACnB,WAAW,SAAS;AACpB,YAAY,sBAAsB;AAClC;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA,WAAW,QAAQ;AACnB,WAAW,QAAQ;AACnB,WAAW,QAAQ;AACnB,YAAY,sBAAsB;AAClC;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,WAAW,QAAQ;AACnB,WAAW,SAAS;AACpB,YAAY,sBAAsB;AAClC;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA,WAAW,QAAQ;AACnB,WAAW,QAAQ;AACnB,WAAW,QAAQ;AACnB,WAAW,QAAQ;AACnB,YAAY,sBAAsB;AAClC;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,WAAW,QAAQ;AACnB,WAAW,SAAS;AACpB,YAAY,sBAAsB;AAClC;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA,WAAW,QAAQ;AACnB,WAAW,QAAQ;AACnB,WAAW,QAAQ;AACnB,WAAW,QAAQ;AACnB,WAAW,QAAQ;AACnB,YAAY,sBAAsB;AAClC;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,WAAW,QAAQ;AACnB,WAAW,SAAS;AACpB,YAAY,sBAAsB;AAClC;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA,WAAW,QAAQ;AACnB,WAAW,SAAS;AACpB,YAAY,sBAAsB;AAClC;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA,WAAW,QAAQ;AACnB,WAAW,OAAO;AAClB,YAAY,sBAAsB;AAClC;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA,WAAW,QAAQ;AACnB,WAAW,QAAQ;AACnB,YAAY,sBAAsB;AAClC;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,QAAQ;AACpB,YAAY,cAAc;AAC1B;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,QAAQ;AACpB,YAAY,kBAAkB;AAC9B;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,WAAW,QAAQ;AACnB,WAAW,UAAU;AACrB,WAAW,WAAW;AACtB;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;;AAEA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA,WAAW;AACX;AACA;;AAEA;AACA;AACA,WAAW;AACX;AACA;;AAEA;AACA;AACA,WAAW;AACX;AACA;;AAEA;AACA;AACA,WAAW;AACX;AACA;;AAEA;AACA;AACA,WAAW;AACX;AACA;;AAEA;AACA;AACA,WAAW;AACX;AACA;;AAEA;AACA;AACA,WAAW;AACX;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,WAAW,QAAQ;AACnB,WAAW,QAAQ;AACnB;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;;;AAIA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,YAAY,QAAQ;AACpB,YAAY,QAAQ;AACpB;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,QAAQ;AACpB;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,QAAQ;AACpB;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,YAAY,QAAQ;AACpB;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;;AAGA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,iCAAiC;AACjC,+BAA+B;AAC/B,+BAA+B;AAC/B,uCAAuC;AACvC,6BAA6B;AAC7B;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC;AACrC,iCAAiC;AACjC,iCAAiC;AACjC,uCAAuC;AACvC,+BAA+B;AAC/B,6BAA6B;AAC7B,8BAA8B;AAC9B,8BAA8B;AAC9B,gCAAgC;AAChC;;AAEA;AACA;AACA,6BAA6B;AAC7B;AACA,kCAAkC;AAClC;;AAEA;AACA,uCAAuC;AACvC;AACA;;;AAGA;AACA;AACA;AACA,0CAA0C;AAC1C,0CAA0C;AAC1C,UAAU;;AAEV,0CAA0C;AAC1C,4DAA4D;AAC5D,UAAU;;AAEV,4CAA4C;AAC5C,+CAA+C;AAC/C,UAAU;;AAEV,0CAA0C;AAC1C,sCAAsC;AACtC,UAAU;;AAEV,0CAA0C;AAC1C,sCAAsC;AACtC,UAAU;;AAEV;AACA;AACA,sCAAsC;AACtC,0BAA0B;AAC1B,UAAU;;AAEV,qCAAqC;AACrC,oCAAoC;AACpC,UAAU;AACV;;;AAGA;AACA;AACA;AACA;AACA,2CAA2C;AAC3C,iCAAiC;;AAEjC,8FAA8F;AAC9F,0EAA0E;AAC1E,oDAAoD;;AAEpD,2CAA2C;AAC3C,2CAA2C;AAC3C,2CAA2C;;AAE3C,qBAAqB;AACrB,UAAU;AACV;;AAEA;AACA,kDAAkD;AAClD,iCAAiC;;AAEjC,8FAA8F;AAC9F,0EAA0E;AAC1E,oDAAoD;;AAEpD,2CAA2C;AAC3C,2CAA2C;AAC3C,2CAA2C;;AAE3C,6BAA6B;AAC7B,6BAA6B;;AAE7B,qBAAqB;AACrB,UAAU;AACV;;AAEA;AACA,8EAA8E;AAC9E,qCAAqC;AACrC,uFAAuF;AACvF,gCAAgC;AAChC,0CAA0C;;AAE1C;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,2DAA2D;AAC3D;AACA,6BAA6B,oCAAoC,OAAO;AACxE,oCAAoC;AACpC,yGAAyG;AACzG,+FAA+F;AAC/F,cAAc;AACd;AACA,0BAA0B;AAC1B,UAAU;AACV;;AAEA;AACA,+HAA+H;AAC/H,sCAAsC;AACtC,8GAA8G;AAC9G,8GAA8G;AAC9G,8GAA8G;AAC9G,0BAA0B;AAC1B,UAAU;AACV;;AAEA;AACA,2BAA2B;AAC3B,4BAA4B;AAC5B,UAAU;;AAEV,yBAAyB;AACzB,4BAA4B;AAC5B,UAAU;;AAEV,4BAA4B;AAC5B,4BAA4B;AAC5B,UAAU;;AAEV,4BAA4B;AAC5B,4BAA4B;AAC5B,UAAU;AACV;;AAEA;AACA,2CAA2C;AAC3C,2DAA2D;AAC3D,UAAU;;AAEV,2CAA2C;AAC3C,kCAAkC;AAClC,UAAU;;AAEV,+CAA+C;AAC/C,0CAA0C;AAC1C,UAAU;AACV;;;AAGA;AACA;AACA;AACA;AACA,mEAAmE;AACnE,uCAAuC;AACvC,qCAAqC;AACrC,qCAAqC;AACrC,mCAAmC;AACnC;AACA;AACA;AACA;AACA,6IAA6I;AAC7I,aAAa;AACb;AACA,sEAAsE;AACtE,wCAAwC;AACxC,+BAA+B;AAC/B,gBAAgB;AAChB;AACA,4DAA4D;AAC5D,4CAA4C;AAC5C,+BAA+B;AAC/B,mCAAmC;;AAEnC,iCAAiC;AACjC,gEAAgE;AAChE,4FAA4F;AAC5F,iDAAiD;AACjD,iEAAiE;AACjE,gFAAgF;AAChF,aAAa;AACb;AACA;;;AAGA;AACA;AACA,uEAAuE;AACvE;AACA;AACA,gDAAgD;AAChD,sDAAsD;AACtD,yCAAyC;AACzC,yCAAyC;;AAEzC,uEAAuE;AACvE;AACA;;AAEA;AACA;AACA,gDAAgD;AAChD,gDAAgD;AAChD,mDAAmD;AACnD,gDAAgD;;AAEhD,gEAAgE;AAChE,qEAAqE;AACrE;;AAEA;AACA,yDAAyD;AACzD;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAGA,gBAAgB;;;AAGhB;AACA;AACA;AACA,kCAAkC;AAClC,sCAAsC;AACtC,wCAAwC;AACxC,mDAAmD;AACnD,mDAAmD;;AAEnD;AACA,6DAA6D;AAC7D,2DAA2D;AAC3D,2DAA2D;AAC3D;;AAEA;AACA;AACA;;AAEA;AACA,qCAAqC;AACrC,2CAA2C;AAC3C,gCAAgC;AAChC,mCAAmC;;AAEnC;AACA,kEAAkE;;AAElE;AACA,oBAAoB;AACpB,qBAAqB;AACrB,4BAA4B;AAC5B,oBAAoB;;;AAGpB;AACA;AACA,8BAA8B;AAC9B,8BAA8B;AAC9B,8BAA8B;AAC9B;;;AAGA;AACA;AACA,oDAAoD;AACpD;;AAEA;AACA,4DAA4D;;AAE5D;AACA,sFAAsF;;AAEtF;AACA;AACA,gEAAgE;AAChE;AACA,mCAAmC;AACnC;;AAEA;AACA,4DAA4D;;;AAG5D;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY;AACZ;AACA,6BAA6B;AAC7B;;AAEA,2EAA2E;;AAE3E;AACA,6BAA6B;;AAE7B;AACA;AACA,4EAA4E;AAC5E;;AAEA;AACA;AACA;AACA,8CAA8C;AAC9C,8CAA8C;AAC9C,gDAAgD;AAChD,kDAAkD;AAClD,8FAA8F;;AAE9F,6DAA6D;AAC7D,iEAAiE;;AAEjE,+CAA+C;AAC/C,yCAAyC;;AAEzC,0CAA0C;AAC1C,0CAA0C;AAC1C,uCAAuC;AACvC,oCAAoC;AACpC;;AAEA;AACA;AACA;;AAEA;AACA,2CAA2C;AAC3C,mDAAmD;;AAEnD;AACA;;AAEA,IAAI;AACJ;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;;AAEA;;AAEA,gBAAgB;AAChB,uCAAuC;AACvC;AACA;AACA,qDAAqD;AACrD;;AAEA;;AAEA;;AAEA,uDAAuD;AACvD,6EAA6E;;AAE7E;;AAEA,IAAI;AACJ;AACA;;;AAGA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;;AAEA;AACA;AACA,kBAAkB;AAClB;AACA;;AAEA;AACA;AACA,kBAAkB;AAClB;AACA;;AAEA;AACA;AACA,kBAAkB;AAClB;AACA;AACA,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA,gBAAgB,gCAAgC;AAChD,gBAAgB,QAAQ;AACxB,gBAAgB,gCAAgC;AAChD,gBAAgB,6CAA6C;AAC7D;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAgB,oCAAoC;AACpD,gBAAgB,QAAQ;AACxB,gBAAgB,gCAAgC;AAChD,gBAAgB,6CAA6C;AAC7D;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,0CAA0C,QAAQ;AAClD;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA,KAAK;;AAEL;AACA;AACA;AACA,gBAAgB,QAAQ;AACxB,gBAAgB,UAAU;AAC1B,gBAAgB,QAAQ;AACxB,gBAAgB,qBAAqB;AACrC;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAgB,cAAc;AAC9B,gBAAgB,UAAU;AAC1B,gBAAgB,QAAQ;AACxB,gBAAgB,qBAAqB;AACrC;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,0CAA0C,QAAQ;AAClD;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAgB,QAAQ;AACxB,gBAAgB,QAAQ;AACxB,gBAAgB,QAAQ;AACxB;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAgB,OAAO;AACvB,gBAAgB,QAAQ;AACxB,gBAAgB,iBAAiB;AACjC;AACA;AACA;;AAEA;AACA;AACA;;;AAGA,yBAAyB,mBAAmB;AAC5C;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA,KAAK;;AAEL;AACA;AACA,gBAAgB,QAAQ;AACxB,gBAAgB,QAAQ;AACxB,gBAAgB,QAAQ;AACxB,gBAAgB,cAAc;AAC9B;AACA;AACA;;AAEA;AACA,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA,gBAAgB,QAAQ;AACxB,gBAAgB,SAAS;AACzB,gBAAgB,kBAAkB;AAClC;AACA;AACA;;AAEA;AACA;;AAEA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,KAAK;;AAEL;AACA;AACA;AACA,gBAAgB,iBAAiB;AACjC,gBAAgB,iBAAiB;AACjC,gBAAgB,QAAQ;AACxB,gBAAgB,iCAAiC;AACjD;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;;AAEL;AACA;AACA,gBAAgB,QAAQ;AACxB,gBAAgB,QAAQ;AACxB,gBAAgB,QAAQ;AACxB,gBAAgB,cAAc;AAC9B;AACA;AACA;AACA;AACA,KAAK;;AAEL;AACA;AACA;AACA,gBAAgB,QAAQ;AACxB,gBAAgB,QAAQ;AACxB,gBAAgB,iBAAiB;AACjC;AACA;AACA;;AAEA;;AAEA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA,KAAK;;AAEL;AACA;AACA;AACA,gBAAgB,OAAO;AACvB,gBAAgB,eAAe;AAC/B;AACA;AACA;;AAEA,yBAAyB,sBAAsB;AAC/C;AACA;AA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CA,cAAc,QAAQ;AACtB;AACA,cAAc,QAAQ;AACtB;AACA,cAAc,SAAS;AACvB;AACA;AACA;AACA;AACA,cAAc,QAAQ;AACtB;AACA;AACA;AACA,cAAc,QAAQ;AACtB;AACA;AACA,cAAc,QAAQ;AACtB;AACA;AACA;AACA,cAAc,SAAS;AACvB;AACA;AACA,cAAc,SAAS;AACvB;AACA;AACA,cAAc,QAAQ;AACtB;AACA,cAAc,SAAS;AACvB;AACA,cAAc,QAAQ;AACtB;AACA,cAAc,SAAS;AACvB;AACA,cAAc,SAAS;AACvB;AACA,cAAc,SAAS;AACvB;AACA,cAAc,QAAQ;AACtB;AACA;;;AAGA;AACA;AACA;AACA;AACA,WAAW,cAAc;AACzB;AACA;AACA;;AAEA;AACA;;AAEA;AACA,8DAA8D;AAC9D,8EAA8E;;AAE9E;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;;;AAGA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;;AAEN;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;;;AAGA;AACA;;AAEA;AACA;AACA;AACA;;AAEA,aAAa,QAAQ;AACrB;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,WAAW,SAAS;AACpB;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;;AAGA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;;;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,yBAAyB,SAAS;AAClC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,WAAW,SAAS;AACpB;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA,yBAAyB,SAAS;AAClC;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;;AAGA;AACA;AACA;AACA;AACA;AACA,YAAY;AACZ;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;;AAEA;AACA;;;AAGA;AACA;AACA;AACA,YAAY,uBAAuB;AACnC,YAAY,OAAO;AACnB;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;;AAGA;AACA;AACA;AACA,YAAY;AACZ;AACA;AACA;AACA;AACA;;;AAGA;AACA;AACA;AACA,WAAW,QAAQ;AACnB,WAAW,sBAAsB;AACjC,WAAW,SAAS;AACpB,YAAY,OAAO;AACnB;AACA;AACA;;AAEA;;AAEA;AACA;AACA;;AAEA;AACA,qBAAqB,iBAAiB;AACtC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;;;AAIA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA,KAAK;;AAEL;AACA;;;AAGA;AACA;AACA;AACA;AACA,YAAY,QAAQ;AACpB,YAAY,QAAQ;AACpB,YAAY,OAAO;AACnB;AACA;AACA;;AAEA;AACA,yBAAyB,iBAAiB;AAC1C;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;;;AAIA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA,aAAa,QAAQ;AACrB;AACA;AACA;;;AAGA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,aAAa,QAAQ;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;;;AAGA;AACA;AACA;AACA,YAAY,QAAQ;AACpB;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,8BAA8B,iBAAiB;AAC/C;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA,iBAAiB,QAAQ;AACzB;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA,iBAAiB,QAAQ;AACzB;AACA;;AAEA;AACA;AACA;AACA;;;AAGA;AACA;AACA;AACA,YAAY,OAAO;AACnB;AACA;AACA;AACA;AACA;AACA;AACA;;;AAGA;AACA;AACA,aAAa,QAAQ;AACrB;AACA;;AAEA;AACA;AACA;AACA,aAAa,QAAQ;AACrB;AACA,cAAc,cAAc;AAC5B;AACA;AACA;AACA;AACA,cAAc,QAAQ;AACtB;AACA;AACA;AACA,cAAc,aAAa;AAC3B;AACA;AACA;AACA;AACA;AACA,cAAc,SAAS;AACvB,cAAc,SAAS;AACvB;AACA;AACA;AACA;AACA;AACA;AACA,cAAc,SAAS;AACvB;AACA;AACA,cAAc,QAAQ,UAAU;AAChC,cAAc,QAAQ;AACtB,cAAc,QAAQ;AACtB;AACA;AACA,cAAc,QAAQ,YAAY;AAClC,cAAc,QAAQ;AACtB,cAAc,QAAQ;AACtB;AACA;AACA;AACA,cAAc,QAAQ;AACtB;AACA;AACA;AACA;AACA,cAAc,QAAQ;AACtB,cAAc,QAAQ;AACtB,cAAc,cAAc;AAC5B,cAAc,SAAS;AACvB;AACA;AACA,cAAc,QAAQ,YAAY;AAClC,cAAc,QAAQ;AACtB,cAAc,QAAQ;AACtB;AACA;AACA,cAAc,cAAc;AAC5B,cAAc,SAAS;AACvB;AACA;AACA,cAAc,QAAQ,gBAAgB;AACtC,cAAc,QAAQ;AACtB,cAAc,QAAQ;AACtB;AACA;AACA,cAAc,cAAc;AAC5B,cAAc,SAAS;AACvB;AACA;AACA,cAAc,QAAQ,QAAQ;AAC9B,cAAc,QAAQ;AACtB,cAAc,QAAQ;AACtB,cAAc,SAAS;AACvB;AACA;AACA,cAAc,QAAQ,UAAU;AAChC;AACA;AACA;AACA;AACA,cAAc,QAAQ;AACtB,cAAc,QAAQ;AACtB;AACA;AACA,cAAc,QAAQ,YAAY;AAClC;AACA,cAAc,QAAQ;AACtB,cAAc,QAAQ;AACtB;AACA,cAAc,QAAQ;AACtB;AACA,cAAc,QAAQ;AACtB,cAAc,SAAS;AACvB,cAAc,QAAQ;AACtB,cAAc,SAAS;AACvB;AACA;AACA,cAAc,QAAQ,SAAS;AAC/B;AACA;AACA;AACA,cAAc,QAAQ;AACtB,cAAc,QAAQ;AACtB,cAAc,SAAS;AACvB;AACA;AACA,cAAc,QAAQ,WAAW;AACjC;AACA;AACA;AACA,cAAc,QAAQ;AACtB,cAAc,QAAQ;AACtB,cAAc,SAAS;AACvB;AACA;AACA,cAAc,QAAQ,QAAQ;AAC9B;AACA;AACA;AACA,cAAc,QAAQ;AACtB,cAAc,QAAQ;AACtB,cAAc,SAAS;AACvB;AACA;AACA,cAAc,QAAQ,SAAS;AAC/B;AACA;AACA;AACA;AACA;AACA,cAAc,QAAQ;AACtB,cAAc,QAAQ;AACtB,cAAc,SAAS;AACvB;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,WAAW,gBAAgB;AAC3B;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA,8DAA8D;AAC9D,gFAAgF;AAChF,gFAAgF;AAChF,wFAAwF;AACxF,4EAA4E;AAC5E,wEAAwE;AACxE,gFAAgF;AAChF,0EAA0E;AAC1E,8EAA8E;AAC9E,wEAAwE;AACxE,0EAA0E;AAC1E,4EAA4E;AAC5E,4EAA4E;;AAE5E;AACA;AACA;;AAEA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAGA;AACA;AACA;AACA;;;;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;;AAGA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;;AAGA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;;;AAIA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;;AAEA;;AAEA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA,iBAAiB;;AAEjB;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB,cAAc;AACd;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA,2CAA2C,QAAQ;AACnD;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;;AAGA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA,2CAA2C,QAAQ;AACnD;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA,aAAa,QAAQ;AACrB;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA,sDAAsD,YAAY;AAClE;;AAEA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA,mDAAmD,mBAAmB;AACtE;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,QAAQ;AACpB;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAGA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;;AAEA;AACA;AACA;;;AAGA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,YAAY,SAAS;AACrB,YAAY,eAAe;AAC3B;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA,sCAAsC,YAAY;AAClD;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA,YAAY,SAAS;AACrB;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,SAAS;AACrB;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,SAAS;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;;;;;;UC7gHA;UACA;;UAEA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;;UAEA;UACA;;UAEA;UACA;UACA;;;;;;;;;ACtBA;AACA;AACA;AACA;AACA;;AAEA,UAAU,mBAAO,CAAC,kCAAc;;AAEhC;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,uBAAuB;AACvB,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,uBAAuB;AACvB,SAAS;AACT;AACA;AACA,uBAAuB;AACvB,SAAS;AACT;AACA;AACA,uBAAuB;AACvB,SAAS;AACT;AACA;AACA,uBAAuB;AACvB,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,WAAW;AACX;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA,KAAK;;;AAGL;;AAEA;;AAEA;;AAEA;AACA;AACA,6BAA6B,kBAAkB;AAC/C;AACA,gCAAgC,iBAAiB;AACjD,iCAAiC,iBAAiB;AAClD,4BAA4B,mBAAmB;AAC/C,6BAA6B,qBAAqB;AAClD;AACA;AACA;AACA;;;AAGA;AACA;AACA,6BAA6B,kBAAkB;AAC/C;AACA,gCAAgC,iBAAiB;AACjD,iCAAiC,qBAAqB;AACtD,4BAA4B,iBAAiB;AAC7C,6BAA6B,iBAAiB;AAC9C;AACA;AACA;AACA;;;AAGA;AACA;AACA,6BAA6B,kBAAkB;AAC/C;AACA,gCAAgC,iBAAiB;AACjD,iCAAiC,iBAAiB;AAClD,4BAA4B,kBAAkB;AAC9C,6BAA6B,oBAAoB;AACjD;AACA;AACA;AACA;;;AAGA,KAAK;;;AAGL;;AAEA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA,UAAU;AACV;AACA;AACA,KAAK;;;AAGL;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;;;AAGL;;AAEA;AACA,KAAK;;;AAGL;;AAEA;AACA,mCAAmC;AACnC;AACA,KAAK;;AAEL;AACA,2DAA2D,aAAa;AACxE,KAAK;;AAEL;AACA,2DAA2D,cAAc;AACzE,KAAK;;;AAGL;;AAEA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA,SAAS;;AAET;AACA;AACA;AACA,aAAa;AACb;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA,aAAa;AACb;AACA,wDAAwD,4BAA4B;AACpF,aAAa;AACb,oBAAoB,wCAAwC,uBAAuB;AACnF,mEAAmE,uBAAuB,GAAG;;AAE7F,wBAAwB,qBAAqB;AAC7C;AACA;AACA,aAAa;AACb;AACA;AACA,aAAa;AACb;AACA,uBAAuB,2CAA2C,uBAAuB;AACzF,yEAAyE,uBAAuB,GAAG;AACnG;AACA,SAAS;;AAET;AACA;AACA;AACA;AACA,CAAC","sources":["webpack://aframe-particle-system-component/./lib/SPE.js","webpack://aframe-particle-system-component/webpack/bootstrap","webpack://aframe-particle-system-component/./index.js"],"sourcesContent":["/* shader-particle-engine 1.0.6\n *\n * (c) 2015 Luke Moody (http://www.github.com/squarefeet)\n * Originally based on Lee Stemkoski's original work (https://github.com/stemkoski/stemkoski.github.com/blob/master/Three.js/js/ParticleEngine.js).\n *\n * shader-particle-engine may be freely distributed under the MIT license (See LICENSE at root of this repository.)\n */\n/**\n * @typedef {Number} distribution\n * @property {Number} SPE.distributions.BOX Values will be distributed within a box.\n * @property {Number} SPE.distributions.SPHERE Values will be distributed within a sphere.\n * @property {Number} SPE.distributions.DISC Values will be distributed within a 2D disc.\n */\n\n/**\n * Namespace for Shader Particle Engine.\n *\n * All SPE-related code sits under this namespace.\n *\n * @type {Object}\n * @namespace\n */\nvar SPE = {\n\n /**\n * A map of supported distribution types used\n * by SPE.Emitter instances.\n *\n * These distribution types can be applied to\n * an emitter globally, which will affect the\n * `position`, `velocity`, and `acceleration`\n * value calculations for an emitter, or they\n * can be applied on a per-property basis.\n *\n * @enum {Number}\n */\n distributions: {\n /**\n * Values will be distributed within a box.\n * @type {Number}\n */\n BOX: 1,\n\n /**\n * Values will be distributed on a sphere.\n * @type {Number}\n */\n SPHERE: 2,\n\n /**\n * Values will be distributed on a 2d-disc shape.\n * @type {Number}\n */\n DISC: 3,\n\n /**\n * Values will be distributed along a line.\n * @type {Number}\n */\n LINE: 4\n },\n\n\n /**\n * Set this value to however many 'steps' you\n * want value-over-lifetime properties to have.\n *\n * It's adjustable to fix an interpolation problem:\n *\n * Assuming you specify an opacity value as [0, 1, 0]\n * and the `valueOverLifetimeLength` is 4, then the\n * opacity value array will be reinterpolated to\n * be [0, 0.66, 0.66, 0].\n * This isn't ideal, as particles would never reach\n * full opacity.\n *\n * NOTE:\n * This property affects the length of ALL\n * value-over-lifetime properties for ALL\n * emitters and ALL groups.\n *\n * Only values >= 3 && <= 4 are allowed.\n *\n * @type {Number}\n */\n valueOverLifetimeLength: 4\n};\n\n// Module loader support:\nif ( typeof define === 'function' && define.amd ) {\n define( 'spe', SPE );\n}\nelse if ( typeof exports !== 'undefined' && typeof module !== 'undefined' ) {\n module.exports = SPE;\n}\n\n\n/**\n * A helper class for TypedArrays.\n *\n * Allows for easy resizing, assignment of various component-based\n * types (Vector2s, Vector3s, Vector4s, Mat3s, Mat4s),\n * as well as Colors (where components are `r`, `g`, `b`),\n * Numbers, and setting from other TypedArrays.\n *\n * @author Luke Moody\n * @constructor\n * @param {Function} TypedArrayConstructor The constructor to use (Float32Array, Uint8Array, etc.)\n * @param {Number} size The size of the array to create\n * @param {Number} componentSize The number of components per-value (ie. 3 for a vec3, 9 for a Mat3, etc.)\n * @param {Number} indexOffset The index in the array from which to start assigning values. Default `0` if none provided\n */\nSPE.TypedArrayHelper = function( TypedArrayConstructor, size, componentSize, indexOffset ) {\n 'use strict';\n\n this.componentSize = componentSize || 1;\n this.size = ( size || 1 );\n this.TypedArrayConstructor = TypedArrayConstructor || Float32Array;\n this.array = new TypedArrayConstructor( size * this.componentSize );\n this.indexOffset = indexOffset || 0;\n};\n\nSPE.TypedArrayHelper.constructor = SPE.TypedArrayHelper;\n\n/**\n * Sets the size of the internal array.\n *\n * Delegates to `this.shrink` or `this.grow` depending on size\n * argument's relation to the current size of the internal array.\n *\n * Note that if the array is to be shrunk, data will be lost.\n *\n * @param {Number} size The new size of the array.\n */\nSPE.TypedArrayHelper.prototype.setSize = function( size, noComponentMultiply ) {\n 'use strict';\n\n var currentArraySize = this.array.length;\n\n if ( !noComponentMultiply ) {\n size = size * this.componentSize;\n }\n\n if ( size < currentArraySize ) {\n return this.shrink( size );\n }\n else if ( size > currentArraySize ) {\n return this.grow( size );\n }\n else {\n console.info( 'TypedArray is already of size:', size + '.', 'Will not resize.' );\n }\n};\n\n/**\n * Shrinks the internal array.\n *\n * @param {Number} size The new size of the typed array. Must be smaller than `this.array.length`.\n * @return {SPE.TypedArrayHelper} Instance of this class.\n */\nSPE.TypedArrayHelper.prototype.shrink = function( size ) {\n 'use strict';\n\n this.array = this.array.subarray( 0, size );\n this.size = size;\n return this;\n};\n\n/**\n * Grows the internal array.\n * @param {Number} size The new size of the typed array. Must be larger than `this.array.length`.\n * @return {SPE.TypedArrayHelper} Instance of this class.\n */\nSPE.TypedArrayHelper.prototype.grow = function( size ) {\n 'use strict';\n\n var existingArray = this.array,\n newArray = new this.TypedArrayConstructor( size );\n\n newArray.set( existingArray );\n this.array = newArray;\n this.size = size;\n\n return this;\n};\n\n\n/**\n * Perform a splice operation on this array's buffer.\n * @param {Number} start The start index of the splice. Will be multiplied by the number of components for this attribute.\n * @param {Number} end The end index of the splice. Will be multiplied by the number of components for this attribute.\n * @returns {Object} The SPE.TypedArrayHelper instance.\n */\nSPE.TypedArrayHelper.prototype.splice = function( start, end ) {\n 'use strict';\n start *= this.componentSize;\n end *= this.componentSize;\n\n var data = [],\n array = this.array,\n size = array.length;\n\n for ( var i = 0; i < size; ++i ) {\n if ( i < start || i >= end ) {\n data.push( array[ i ] );\n }\n // array[ i ] = 0;\n }\n\n this.setFromArray( 0, data );\n\n return this;\n};\n\n\n/**\n * Copies from the given TypedArray into this one, using the index argument\n * as the start position. Alias for `TypedArray.set`. Will automatically resize\n * if the given source array is of a larger size than the internal array.\n *\n * @param {Number} index The start position from which to copy into this array.\n * @param {TypedArray} array The array from which to copy; the source array.\n * @return {SPE.TypedArrayHelper} Instance of this class.\n */\nSPE.TypedArrayHelper.prototype.setFromArray = function( index, array ) {\n 'use strict';\n\n var sourceArraySize = array.length,\n newSize = index + sourceArraySize;\n\n if ( newSize > this.array.length ) {\n this.grow( newSize );\n }\n else if ( newSize < this.array.length ) {\n this.shrink( newSize );\n }\n\n this.array.set( array, this.indexOffset + index );\n\n return this;\n};\n\n/**\n * Set a Vector2 value at `index`.\n *\n * @param {Number} index The index at which to set the vec2 values from.\n * @param {Vector2} vec2 Any object that has `x` and `y` properties.\n * @return {SPE.TypedArrayHelper} Instance of this class.\n */\nSPE.TypedArrayHelper.prototype.setVec2 = function( index, vec2 ) {\n 'use strict';\n\n return this.setVec2Components( index, vec2.x, vec2.y );\n};\n\n/**\n * Set a Vector2 value using raw components.\n *\n * @param {Number} index The index at which to set the vec2 values from.\n * @param {Number} x The Vec2's `x` component.\n * @param {Number} y The Vec2's `y` component.\n * @return {SPE.TypedArrayHelper} Instance of this class.\n */\nSPE.TypedArrayHelper.prototype.setVec2Components = function( index, x, y ) {\n 'use strict';\n\n var array = this.array,\n i = this.indexOffset + ( index * this.componentSize );\n\n array[ i ] = x;\n array[ i + 1 ] = y;\n return this;\n};\n\n/**\n * Set a Vector3 value at `index`.\n *\n * @param {Number} index The index at which to set the vec3 values from.\n * @param {Vector3} vec2 Any object that has `x`, `y`, and `z` properties.\n * @return {SPE.TypedArrayHelper} Instance of this class.\n */\nSPE.TypedArrayHelper.prototype.setVec3 = function( index, vec3 ) {\n 'use strict';\n\n return this.setVec3Components( index, vec3.x, vec3.y, vec3.z );\n};\n\n/**\n * Set a Vector3 value using raw components.\n *\n * @param {Number} index The index at which to set the vec3 values from.\n * @param {Number} x The Vec3's `x` component.\n * @param {Number} y The Vec3's `y` component.\n * @param {Number} z The Vec3's `z` component.\n * @return {SPE.TypedArrayHelper} Instance of this class.\n */\nSPE.TypedArrayHelper.prototype.setVec3Components = function( index, x, y, z ) {\n 'use strict';\n\n var array = this.array,\n i = this.indexOffset + ( index * this.componentSize );\n\n array[ i ] = x;\n array[ i + 1 ] = y;\n array[ i + 2 ] = z;\n return this;\n};\n\n/**\n * Set a Vector4 value at `index`.\n *\n * @param {Number} index The index at which to set the vec4 values from.\n * @param {Vector4} vec2 Any object that has `x`, `y`, `z`, and `w` properties.\n * @return {SPE.TypedArrayHelper} Instance of this class.\n */\nSPE.TypedArrayHelper.prototype.setVec4 = function( index, vec4 ) {\n 'use strict';\n\n return this.setVec4Components( index, vec4.x, vec4.y, vec4.z, vec4.w );\n};\n\n/**\n * Set a Vector4 value using raw components.\n *\n * @param {Number} index The index at which to set the vec4 values from.\n * @param {Number} x The Vec4's `x` component.\n * @param {Number} y The Vec4's `y` component.\n * @param {Number} z The Vec4's `z` component.\n * @param {Number} w The Vec4's `w` component.\n * @return {SPE.TypedArrayHelper} Instance of this class.\n */\nSPE.TypedArrayHelper.prototype.setVec4Components = function( index, x, y, z, w ) {\n 'use strict';\n\n var array = this.array,\n i = this.indexOffset + ( index * this.componentSize );\n\n array[ i ] = x;\n array[ i + 1 ] = y;\n array[ i + 2 ] = z;\n array[ i + 3 ] = w;\n return this;\n};\n\n/**\n * Set a Matrix3 value at `index`.\n *\n * @param {Number} index The index at which to set the matrix values from.\n * @param {Matrix3} mat3 The 3x3 matrix to set from. Must have a TypedArray property named `elements` to copy from.\n * @return {SPE.TypedArrayHelper} Instance of this class.\n */\nSPE.TypedArrayHelper.prototype.setMat3 = function( index, mat3 ) {\n 'use strict';\n\n return this.setFromArray( this.indexOffset + ( index * this.componentSize ), mat3.elements );\n};\n\n/**\n * Set a Matrix4 value at `index`.\n *\n * @param {Number} index The index at which to set the matrix values from.\n * @param {Matrix4} mat3 The 4x4 matrix to set from. Must have a TypedArray property named `elements` to copy from.\n * @return {SPE.TypedArrayHelper} Instance of this class.\n */\nSPE.TypedArrayHelper.prototype.setMat4 = function( index, mat4 ) {\n 'use strict';\n\n return this.setFromArray( this.indexOffset + ( index * this.componentSize ), mat4.elements );\n};\n\n/**\n * Set a Color value at `index`.\n *\n * @param {Number} index The index at which to set the vec3 values from.\n * @param {Color} color Any object that has `r`, `g`, and `b` properties.\n * @return {SPE.TypedArrayHelper} Instance of this class.\n */\nSPE.TypedArrayHelper.prototype.setColor = function( index, color ) {\n 'use strict';\n\n return this.setVec3Components( index, color.r, color.g, color.b );\n};\n\n/**\n * Set a Number value at `index`.\n *\n * @param {Number} index The index at which to set the vec3 values from.\n * @param {Number} numericValue The number to assign to this index in the array.\n * @return {SPE.TypedArrayHelper} Instance of this class.\n */\nSPE.TypedArrayHelper.prototype.setNumber = function( index, numericValue ) {\n 'use strict';\n\n this.array[ this.indexOffset + ( index * this.componentSize ) ] = numericValue;\n return this;\n};\n\n/**\n * Returns the value of the array at the given index, taking into account\n * the `indexOffset` property of this class.\n *\n * Note that this function ignores the component size and will just return a\n * single value.\n *\n * @param {Number} index The index in the array to fetch.\n * @return {Number} The value at the given index.\n */\nSPE.TypedArrayHelper.prototype.getValueAtIndex = function( index ) {\n 'use strict';\n\n return this.array[ this.indexOffset + index ];\n};\n\n/**\n * Returns the component value of the array at the given index, taking into account\n * the `indexOffset` property of this class.\n *\n * If the componentSize is set to 3, then it will return a new TypedArray\n * of length 3.\n *\n * @param {Number} index The index in the array to fetch.\n * @return {TypedArray} The component value at the given index.\n */\nSPE.TypedArrayHelper.prototype.getComponentValueAtIndex = function( index ) {\n 'use strict';\n\n return this.array.subarray( this.indexOffset + ( index * this.componentSize ) );\n};\n\n/**\n * A helper to handle creating and updating a THREE.BufferAttribute instance.\n *\n * @author Luke Moody\n * @constructor\n * @param {String} type The buffer attribute type. See SPE.ShaderAttribute.typeSizeMap for valid values.\n * @param {Boolean=} dynamicBuffer Whether this buffer attribute should be marked as dynamic or not.\n * @param {Function=} arrayType A reference to a TypedArray constructor. Defaults to Float32Array if none provided.\n */\nSPE.ShaderAttribute = function( type, dynamicBuffer, arrayType ) {\n\t'use strict';\n\n\tvar typeMap = SPE.ShaderAttribute.typeSizeMap;\n\n\tthis.type = typeof type === 'string' && typeMap.hasOwnProperty( type ) ? type : 'f';\n\tthis.componentSize = typeMap[ this.type ];\n\tthis.arrayType = arrayType || Float32Array;\n\tthis.typedArray = null;\n\tthis.bufferAttribute = null;\n\tthis.dynamicBuffer = !!dynamicBuffer;\n\n\tthis.updateMin = 0;\n\tthis.updateMax = 0;\n};\n\nSPE.ShaderAttribute.constructor = SPE.ShaderAttribute;\n\n/**\n * A map of uniform types to their component size.\n * @enum {Number}\n */\nSPE.ShaderAttribute.typeSizeMap = {\n\t/**\n\t * Float\n\t * @type {Number}\n\t */\n\tf: 1,\n\n\t/**\n\t * Vec2\n\t * @type {Number}\n\t */\n\tv2: 2,\n\n\t/**\n\t * Vec3\n\t * @type {Number}\n\t */\n\tv3: 3,\n\n\t/**\n\t * Vec4\n\t * @type {Number}\n\t */\n\tv4: 4,\n\n\t/**\n\t * Color\n\t * @type {Number}\n\t */\n\tc: 3,\n\n\t/**\n\t * Mat3\n\t * @type {Number}\n\t */\n\tm3: 9,\n\n\t/**\n\t * Mat4\n\t * @type {Number}\n\t */\n\tm4: 16\n};\n\n/**\n * Calculate the minimum and maximum update range for this buffer attribute using\n * component size independant min and max values.\n *\n * @param {Number} min The start of the range to mark as needing an update.\n * @param {Number} max The end of the range to mark as needing an update.\n */\nSPE.ShaderAttribute.prototype.setUpdateRange = function( min, max ) {\n\t'use strict';\n\n\tthis.updateMin = Math.min( min * this.componentSize, this.updateMin * this.componentSize );\n\tthis.updateMax = Math.max( max * this.componentSize, this.updateMax * this.componentSize );\n};\n\n/**\n * Calculate the number of indices that this attribute should mark as needing\n * updating. Also marks the attribute as needing an update.\n */\nSPE.ShaderAttribute.prototype.flagUpdate = function() {\n\t'use strict';\n\n\tvar attr = this.bufferAttribute,\n\t\trange = attr.updateRange;\n\n\trange.offset = this.updateMin;\n\trange.count = Math.min( ( this.updateMax - this.updateMin ) + this.componentSize, this.typedArray.array.length );\n\t// console.log( range.offset, range.count, this.typedArray.array.length );\n\t// console.log( 'flagUpdate:', range.offset, range.count );\n\tattr.needsUpdate = true;\n};\n\n\n\n/**\n * Reset the index update counts for this attribute\n */\nSPE.ShaderAttribute.prototype.resetUpdateRange = function() {\n\t'use strict';\n\n\tthis.updateMin = 0;\n\tthis.updateMax = 0;\n};\n\nSPE.ShaderAttribute.prototype.resetDynamic = function() {\n\t'use strict';\n\tthis.bufferAttribute.usage = this.dynamicBuffer ?\n\t\tTHREE.DynamicDrawUsage :\n\t\tTHREE.StaticDrawUsage;\n};\n\n/**\n * Perform a splice operation on this attribute's buffer.\n * @param {Number} start The start index of the splice. Will be multiplied by the number of components for this attribute.\n * @param {Number} end The end index of the splice. Will be multiplied by the number of components for this attribute.\n */\nSPE.ShaderAttribute.prototype.splice = function( start, end ) {\n\t'use strict';\n\n\tthis.typedArray.splice( start, end );\n\n\t// Reset the reference to the attribute's typed array\n\t// since it has probably changed.\n\tthis.forceUpdateAll();\n};\n\nSPE.ShaderAttribute.prototype.forceUpdateAll = function() {\n\t'use strict';\n\n\tthis.bufferAttribute.array = this.typedArray.array;\n\tthis.bufferAttribute.updateRange.offset = 0;\n\tthis.bufferAttribute.updateRange.count = -1;\n\t// this.bufferAttribute.dynamic = false;\n\t// this.bufferAttribute.usage = this.dynamicBuffer ?\n\t// \tTHREE.DynamicDrawUsage :\n\t// \tTHREE.StaticDrawUsage;\n\n\tthis.bufferAttribute.usage = THREE.StaticDrawUsage;\n\tthis.bufferAttribute.needsUpdate = true;\n};\n\n/**\n * Make sure this attribute has a typed array associated with it.\n *\n * If it does, then it will ensure the typed array is of the correct size.\n *\n * If not, a new SPE.TypedArrayHelper instance will be created.\n *\n * @param {Number} size The size of the typed array to create or update to.\n */\nSPE.ShaderAttribute.prototype._ensureTypedArray = function( size ) {\n\t'use strict';\n\n\t// Condition that's most likely to be true at the top: no change.\n\tif ( this.typedArray !== null && this.typedArray.size === size * this.componentSize ) {\n\t\treturn;\n\t}\n\n\t// Resize the array if we need to, telling the TypedArrayHelper to\n\t// ignore it's component size when evaluating size.\n\telse if ( this.typedArray !== null && this.typedArray.size !== size ) {\n\t\tthis.typedArray.setSize( size );\n\t}\n\n\t// This condition should only occur once in an attribute's lifecycle.\n\telse if ( this.typedArray === null ) {\n\t\tthis.typedArray = new SPE.TypedArrayHelper( this.arrayType, size, this.componentSize );\n\t}\n};\n\n\n/**\n * Creates a THREE.BufferAttribute instance if one doesn't exist already.\n *\n * Ensures a typed array is present by calling _ensureTypedArray() first.\n *\n * If a buffer attribute exists already, then it will be marked as needing an update.\n *\n * @param {Number} size The size of the typed array to create if one doesn't exist, or resize existing array to.\n */\nSPE.ShaderAttribute.prototype._createBufferAttribute = function( size ) {\n\t'use strict';\n\n\t// Make sure the typedArray is present and correct.\n\tthis._ensureTypedArray( size );\n\n\t// Don't create it if it already exists, but do\n\t// flag that it needs updating on the next render\n\t// cycle.\n\tif ( this.bufferAttribute !== null ) {\n\t\tthis.bufferAttribute.array = this.typedArray.array;\n\n\t\t// Since THREE.js version 81, dynamic count calculation was removed\n\t\t// so I need to do it manually here.\n\t\t//\n\t\t// In the next minor release, I may well remove this check and force\n\t\t// dependency on THREE r81+.\n\t\tif ( parseFloat( THREE.REVISION ) >= 81 ) {\n\t\t\tthis.bufferAttribute.count = this.bufferAttribute.array.length / this.bufferAttribute.itemSize;\n\t\t}\n\n\t\tthis.bufferAttribute.needsUpdate = true;\n\t\treturn;\n\t}\n\n\tthis.bufferAttribute = new THREE.BufferAttribute( this.typedArray.array, this.componentSize );\n\t// this.bufferAttribute.dynamic = this.dynamicBuffer;\n\tthis.bufferAttribute.usage = this.dynamicBuffer ?\n\t\tTHREE.DynamicDrawUsage :\n\t\tTHREE.StaticDrawUsage;\n};\n\n/**\n * Returns the length of the typed array associated with this attribute.\n * @return {Number} The length of the typed array. Will be 0 if no typed array has been created yet.\n */\nSPE.ShaderAttribute.prototype.getLength = function() {\n\t'use strict';\n\n\tif ( this.typedArray === null ) {\n\t\treturn 0;\n\t}\n\n\treturn this.typedArray.array.length;\n};\n\n\nSPE.shaderChunks = {\n // Register color-packing define statements.\n defines: [\n '#define PACKED_COLOR_SIZE 256.0',\n '#define PACKED_COLOR_DIVISOR 255.0'\n ].join( '\\n' ),\n\n // All uniforms used by vertex / fragment shaders\n uniforms: [\n 'uniform float deltaTime;',\n 'uniform float runTime;',\n 'uniform sampler2D tex;',\n 'uniform vec4 textureAnimation;',\n 'uniform float scale;',\n ].join( '\\n' ),\n\n // All attributes used by the vertex shader.\n //\n // Note that some attributes are squashed into other ones:\n //\n // * Drag is acceleration.w\n attributes: [\n 'attribute vec4 acceleration;',\n 'attribute vec3 velocity;',\n 'attribute vec4 rotation;',\n 'attribute vec3 rotationCenter;',\n 'attribute vec4 params;',\n 'attribute vec4 size;',\n 'attribute vec4 angle;',\n 'attribute vec4 color;',\n 'attribute vec4 opacity;'\n ].join( '\\n' ),\n\n //\n varyings: [\n 'varying vec4 vColor;',\n '#ifdef SHOULD_ROTATE_TEXTURE',\n ' varying float vAngle;',\n '#endif',\n\n '#ifdef SHOULD_CALCULATE_SPRITE',\n ' varying vec4 vSpriteSheet;',\n '#endif'\n ].join( '\\n' ),\n\n\n // Branch-avoiding comparison fns\n // - http://theorangeduck.com/page/avoiding-shader-conditionals\n branchAvoidanceFunctions: [\n 'float when_gt(float x, float y) {',\n ' return max(sign(x - y), 0.0);',\n '}',\n\n 'float when_lt(float x, float y) {',\n ' return min( max(1.0 - sign(x - y), 0.0), 1.0 );',\n '}',\n\n 'float when_eq( float x, float y ) {',\n ' return 1.0 - abs( sign( x - y ) );',\n '}',\n\n 'float when_ge(float x, float y) {',\n ' return 1.0 - when_lt(x, y);',\n '}',\n\n 'float when_le(float x, float y) {',\n ' return 1.0 - when_gt(x, y);',\n '}',\n\n // Branch-avoiding logical operators\n // (to be used with above comparison fns)\n 'float and(float a, float b) {',\n ' return a * b;',\n '}',\n\n 'float or(float a, float b) {',\n ' return min(a + b, 1.0);',\n '}',\n ].join( '\\n' ),\n\n\n // From:\n // - http://stackoverflow.com/a/12553149\n // - https://stackoverflow.com/questions/22895237/hexadecimal-to-rgb-values-in-webgl-shader\n unpackColor: [\n 'vec3 unpackColor( in float hex ) {',\n ' vec3 c = vec3( 0.0 );',\n\n ' float r = mod( (hex / PACKED_COLOR_SIZE / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );',\n ' float g = mod( (hex / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );',\n ' float b = mod( hex, PACKED_COLOR_SIZE );',\n\n ' c.r = r / PACKED_COLOR_DIVISOR;',\n ' c.g = g / PACKED_COLOR_DIVISOR;',\n ' c.b = b / PACKED_COLOR_DIVISOR;',\n\n ' return c;',\n '}',\n ].join( '\\n' ),\n\n unpackRotationAxis: [\n 'vec3 unpackRotationAxis( in float hex ) {',\n ' vec3 c = vec3( 0.0 );',\n\n ' float r = mod( (hex / PACKED_COLOR_SIZE / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );',\n ' float g = mod( (hex / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );',\n ' float b = mod( hex, PACKED_COLOR_SIZE );',\n\n ' c.r = r / PACKED_COLOR_DIVISOR;',\n ' c.g = g / PACKED_COLOR_DIVISOR;',\n ' c.b = b / PACKED_COLOR_DIVISOR;',\n\n ' c *= vec3( 2.0 );',\n ' c -= vec3( 1.0 );',\n\n ' return c;',\n '}',\n ].join( '\\n' ),\n\n floatOverLifetime: [\n 'float getFloatOverLifetime( in float positionInTime, in vec4 attr ) {',\n ' highp float value = 0.0;',\n ' float deltaAge = positionInTime * float( VALUE_OVER_LIFETIME_LENGTH - 1 );',\n ' float fIndex = 0.0;',\n ' float shouldApplyValue = 0.0;',\n\n // This might look a little odd, but it's faster in the testing I've done than using branches.\n // Uses basic maths to avoid branching.\n //\n // Take a look at the branch-avoidance functions defined above,\n // and be sure to check out The Orange Duck site where I got this\n // from (link above).\n\n // Fix for static emitters (age is always zero).\n ' value += attr[ 0 ] * when_eq( deltaAge, 0.0 );',\n '',\n ' for( int i = 0; i < VALUE_OVER_LIFETIME_LENGTH - 1; ++i ) {',\n ' fIndex = float( i );',\n ' shouldApplyValue = and( when_gt( deltaAge, fIndex ), when_le( deltaAge, fIndex + 1.0 ) );',\n ' value += shouldApplyValue * mix( attr[ i ], attr[ i + 1 ], deltaAge - fIndex );',\n ' }',\n '',\n ' return value;',\n '}',\n ].join( '\\n' ),\n\n colorOverLifetime: [\n 'vec3 getColorOverLifetime( in float positionInTime, in vec3 color1, in vec3 color2, in vec3 color3, in vec3 color4 ) {',\n ' vec3 value = vec3( 0.0 );',\n ' value.x = getFloatOverLifetime( positionInTime, vec4( color1.x, color2.x, color3.x, color4.x ) );',\n ' value.y = getFloatOverLifetime( positionInTime, vec4( color1.y, color2.y, color3.y, color4.y ) );',\n ' value.z = getFloatOverLifetime( positionInTime, vec4( color1.z, color2.z, color3.z, color4.z ) );',\n ' return value;',\n '}',\n ].join( '\\n' ),\n\n paramFetchingFunctions: [\n 'float getAlive() {',\n ' return params.x;',\n '}',\n\n 'float getAge() {',\n ' return params.y;',\n '}',\n\n 'float getMaxAge() {',\n ' return params.z;',\n '}',\n\n 'float getWiggle() {',\n ' return params.w;',\n '}',\n ].join( '\\n' ),\n\n forceFetchingFunctions: [\n 'vec4 getPosition( in float age ) {',\n ' return modelViewMatrix * vec4( position, 1.0 );',\n '}',\n\n 'vec3 getVelocity( in float age ) {',\n ' return velocity * age;',\n '}',\n\n 'vec3 getAcceleration( in float age ) {',\n ' return acceleration.xyz * age;',\n '}',\n ].join( '\\n' ),\n\n\n rotationFunctions: [\n // Huge thanks to:\n // - http://www.neilmendoza.com/glsl-rotation-about-an-arbitrary-axis/\n '#ifdef SHOULD_ROTATE_PARTICLES',\n ' mat4 getRotationMatrix( in vec3 axis, in float angle) {',\n ' axis = normalize(axis);',\n ' float s = sin(angle);',\n ' float c = cos(angle);',\n ' float oc = 1.0 - c;',\n '',\n ' 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,',\n ' oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0,',\n ' oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0,',\n ' 0.0, 0.0, 0.0, 1.0);',\n ' }',\n '',\n ' vec3 getRotation( in vec3 pos, in float positionInTime ) {',\n ' if( rotation.y == 0.0 ) {',\n ' return pos;',\n ' }',\n '',\n ' vec3 axis = unpackRotationAxis( rotation.x );',\n ' vec3 center = rotationCenter;',\n ' vec3 translated;',\n ' mat4 rotationMatrix;',\n\n ' float angle = 0.0;',\n ' angle += when_eq( rotation.z, 0.0 ) * rotation.y;',\n ' angle += when_gt( rotation.z, 0.0 ) * mix( 0.0, rotation.y, positionInTime );',\n ' translated = rotationCenter - pos;',\n ' rotationMatrix = getRotationMatrix( axis, angle );',\n ' return center - vec3( rotationMatrix * vec4( translated, 0.0 ) );',\n ' }',\n '#endif'\n ].join( '\\n' ),\n\n\n // Fragment chunks\n rotateTexture: [\n ' vec2 vUv = vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y );',\n '',\n ' #ifdef SHOULD_ROTATE_TEXTURE',\n ' float x = gl_PointCoord.x - 0.5;',\n ' float y = 1.0 - gl_PointCoord.y - 0.5;',\n ' float c = cos( -vAngle );',\n ' float s = sin( -vAngle );',\n\n ' vUv = vec2( c * x + s * y + 0.5, c * y - s * x + 0.5 );',\n ' #endif',\n '',\n\n // Spritesheets overwrite angle calculations.\n ' #ifdef SHOULD_CALCULATE_SPRITE',\n ' float framesX = vSpriteSheet.x;',\n ' float framesY = vSpriteSheet.y;',\n ' float columnNorm = vSpriteSheet.z;',\n ' float rowNorm = vSpriteSheet.w;',\n\n ' vUv.x = gl_PointCoord.x * framesX + columnNorm;',\n ' vUv.y = 1.0 - (gl_PointCoord.y * framesY + rowNorm);',\n ' #endif',\n\n '',\n ' vec4 rotatedTexture = texture2D( tex, vUv );',\n ].join( '\\n' )\n};\n\nSPE.shaders = {\n\tvertex: [\n\t\tSPE.shaderChunks.defines,\n\t\tSPE.shaderChunks.uniforms,\n\t\tSPE.shaderChunks.attributes,\n\t\tSPE.shaderChunks.varyings,\n\n\t\tTHREE.ShaderChunk.common,\n\t\tTHREE.ShaderChunk.logdepthbuf_pars_vertex,\n\t\tTHREE.ShaderChunk.fog_pars_vertex,\n\n\t\tSPE.shaderChunks.branchAvoidanceFunctions,\n\t\tSPE.shaderChunks.unpackColor,\n\t\tSPE.shaderChunks.unpackRotationAxis,\n\t\tSPE.shaderChunks.floatOverLifetime,\n\t\tSPE.shaderChunks.colorOverLifetime,\n\t\tSPE.shaderChunks.paramFetchingFunctions,\n\t\tSPE.shaderChunks.forceFetchingFunctions,\n\t\tSPE.shaderChunks.rotationFunctions,\n\n\n\t\t'void main() {',\n\n\n\t\t//\n\t\t// Setup...\n\t\t//\n\t\t' highp float age = getAge();',\n\t\t' highp float alive = getAlive();',\n\t\t' highp float maxAge = getMaxAge();',\n\t\t' highp float positionInTime = (age / maxAge);',\n\t\t' highp float isAlive = when_gt( alive, 0.0 );',\n\n\t\t' #ifdef SHOULD_WIGGLE_PARTICLES',\n\t\t' float wiggleAmount = positionInTime * getWiggle();',\n\t\t' float wiggleSin = isAlive * sin( wiggleAmount );',\n\t\t' float wiggleCos = isAlive * cos( wiggleAmount );',\n\t\t' #endif',\n\n\t\t//\n\t\t// Forces\n\t\t//\n\n\t\t// Get forces & position\n\t\t' vec3 vel = getVelocity( age );',\n\t\t' vec3 accel = getAcceleration( age );',\n\t\t' vec3 force = vec3( 0.0 );',\n\t\t' vec3 pos = vec3( position );',\n\n\t\t// Calculate the required drag to apply to the forces.\n\t\t' float drag = 1.0 - (positionInTime * 0.5) * acceleration.w;',\n\n\t\t// Integrate forces...\n\t\t' force += vel;',\n\t\t' force *= drag;',\n\t\t' force += accel * age;',\n\t\t' pos += force;',\n\n\n\t\t// Wiggly wiggly wiggle!\n\t\t' #ifdef SHOULD_WIGGLE_PARTICLES',\n\t\t' pos.x += wiggleSin;',\n\t\t' pos.y += wiggleCos;',\n\t\t' pos.z += wiggleSin;',\n\t\t' #endif',\n\n\n\t\t// Rotate the emitter around it's central point\n\t\t' #ifdef SHOULD_ROTATE_PARTICLES',\n\t\t' pos = getRotation( pos, positionInTime );',\n\t\t' #endif',\n\n\t\t// Convert pos to a world-space value\n\t\t' vec4 mvPosition = modelViewMatrix * vec4( pos, 1.0 );',\n\n\t\t// Determine point size.\n\t\t' highp float pointSize = getFloatOverLifetime( positionInTime, size ) * isAlive;',\n\n\t\t// Determine perspective\n\t\t' #ifdef HAS_PERSPECTIVE',\n\t\t' float perspective = scale / length( mvPosition.xyz );',\n\t\t' #else',\n\t\t' float perspective = 1.0;',\n\t\t' #endif',\n\n\t\t// Apply perpective to pointSize value\n\t\t' float pointSizePerspective = pointSize * perspective;',\n\n\n\t\t//\n\t\t// Appearance\n\t\t//\n\n\t\t// Determine color and opacity for this particle\n\t\t' #ifdef COLORIZE',\n\t\t' vec3 c = isAlive * getColorOverLifetime(',\n\t\t' positionInTime,',\n\t\t' unpackColor( color.x ),',\n\t\t' unpackColor( color.y ),',\n\t\t' unpackColor( color.z ),',\n\t\t' unpackColor( color.w )',\n\t\t' );',\n\t\t' #else',\n\t\t' vec3 c = vec3(1.0);',\n\t\t' #endif',\n\n\t\t' float o = isAlive * getFloatOverLifetime( positionInTime, opacity );',\n\n\t\t// Assign color to vColor varying.\n\t\t' vColor = vec4( c, o );',\n\n\t\t// Determine angle\n\t\t' #ifdef SHOULD_ROTATE_TEXTURE',\n\t\t' vAngle = isAlive * getFloatOverLifetime( positionInTime, angle );',\n\t\t' #endif',\n\n\t\t// If this particle is using a sprite-sheet as a texture, we'll have to figure out\n\t\t// what frame of the texture the particle is using at it's current position in time.\n\t\t' #ifdef SHOULD_CALCULATE_SPRITE',\n\t\t' float framesX = textureAnimation.x;',\n\t\t' float framesY = textureAnimation.y;',\n\t\t' float loopCount = textureAnimation.w;',\n\t\t' float totalFrames = textureAnimation.z;',\n\t\t' float frameNumber = mod( (positionInTime * loopCount) * totalFrames, totalFrames );',\n\n\t\t' float column = floor(mod( frameNumber, framesX ));',\n\t\t' float row = floor( (frameNumber - column) / framesX );',\n\n\t\t' float columnNorm = column / framesX;',\n\t\t' float rowNorm = row / framesY;',\n\n\t\t' vSpriteSheet.x = 1.0 / framesX;',\n\t\t' vSpriteSheet.y = 1.0 / framesY;',\n\t\t' vSpriteSheet.z = columnNorm;',\n\t\t' vSpriteSheet.w = rowNorm;',\n\t\t' #endif',\n\n\t\t//\n\t\t// Write values\n\t\t//\n\n\t\t// Set PointSize according to size at current point in time.\n\t\t' gl_PointSize = pointSizePerspective;',\n\t\t' gl_Position = projectionMatrix * mvPosition;',\n\n\t\tTHREE.ShaderChunk.logdepthbuf_vertex,\n\t\tTHREE.ShaderChunk.fog_vertex,\n\n\t\t'}'\n\t].join( '\\n' ),\n\n\tfragment: [\n\t\tSPE.shaderChunks.uniforms,\n\n\t\tTHREE.ShaderChunk.common,\n\t\tTHREE.ShaderChunk.fog_pars_fragment,\n\t\tTHREE.ShaderChunk.logdepthbuf_pars_fragment,\n\n\t\tSPE.shaderChunks.varyings,\n\n\t\tSPE.shaderChunks.branchAvoidanceFunctions,\n\n\t\t'void main() {',\n\t\t' vec3 outgoingLight = vColor.xyz;',\n\t\t' ',\n\t\t' #ifdef ALPHATEST',\n\t\t' if ( vColor.w < float(ALPHATEST) ) discard;',\n\t\t' #endif',\n\n\t\tSPE.shaderChunks.rotateTexture,\n\n\t\tTHREE.ShaderChunk.logdepthbuf_fragment,\n\n\t\t' outgoingLight = vColor.xyz * rotatedTexture.xyz;',\n\t\t' gl_FragColor = vec4( outgoingLight.xyz, rotatedTexture.w * vColor.w );',\n\n\t\tTHREE.ShaderChunk.fog_fragment,\n\n\t\t'}'\n\t].join( '\\n' )\n};\n\n\n/**\n * A bunch of utility functions used throughout the library.\n * @namespace\n * @type {Object}\n */\nSPE.utils = {\n /**\n * A map of types used by `SPE.utils.ensureTypedArg` and\n * `SPE.utils.ensureArrayTypedArg` to compare types against.\n *\n * @enum {String}\n */\n types: {\n /**\n * Boolean type.\n * @type {String}\n */\n BOOLEAN: 'boolean',\n\n /**\n * String type.\n * @type {String}\n */\n STRING: 'string',\n\n /**\n * Number type.\n * @type {String}\n */\n NUMBER: 'number',\n\n /**\n * Object type.\n * @type {String}\n */\n OBJECT: 'object'\n },\n\n /**\n * Given a value, a type, and a default value to fallback to,\n * ensure the given argument adheres to the type requesting,\n * returning the default value if type check is false.\n *\n * @param {(boolean|string|number|object)} arg The value to perform a type-check on.\n * @param {String} type The type the `arg` argument should adhere to.\n * @param {(boolean|string|number|object)} defaultValue A default value to fallback on if the type check fails.\n * @return {(boolean|string|number|object)} The given value if type check passes, or the default value if it fails.\n */\n ensureTypedArg: function( arg, type, defaultValue ) {\n 'use strict';\n\n if ( typeof arg === type ) {\n return arg;\n }\n else {\n return defaultValue;\n }\n },\n\n /**\n * Given an array of values, a type, and a default value,\n * ensure the given array's contents ALL adhere to the provided type,\n * returning the default value if type check fails.\n *\n * If the given value to check isn't an Array, delegates to SPE.utils.ensureTypedArg.\n *\n * @param {Array|boolean|string|number|object} arg The array of values to check type of.\n * @param {String} type The type that should be adhered to.\n * @param {(boolean|string|number|object)} defaultValue A default fallback value.\n * @return {(boolean|string|number|object)} The given value if type check passes, or the default value if it fails.\n */\n ensureArrayTypedArg: function( arg, type, defaultValue ) {\n 'use strict';\n\n // If the argument being checked is an array, loop through\n // it and ensure all the values are of the correct type,\n // falling back to the defaultValue if any aren't.\n if ( Array.isArray( arg ) ) {\n for ( var i = arg.length - 1; i >= 0; --i ) {\n if ( typeof arg[ i ] !== type ) {\n return defaultValue;\n }\n }\n\n return arg;\n }\n\n // If the arg isn't an array then just fallback to\n // checking the type.\n return this.ensureTypedArg( arg, type, defaultValue );\n },\n\n /**\n * Ensures the given value is an instance of a constructor function.\n *\n * @param {Object} arg The value to check instance of.\n * @param {Function} instance The constructor of the instance to check against.\n * @param {Object} defaultValue A default fallback value if instance check fails\n * @return {Object} The given value if type check passes, or the default value if it fails.\n */\n ensureInstanceOf: function( arg, instance, defaultValue ) {\n 'use strict';\n\n if ( instance !== undefined && arg instanceof instance ) {\n return arg;\n }\n else {\n return defaultValue;\n }\n },\n\n /**\n * Given an array of values, ensure the instances of all items in the array\n * matches the given instance constructor falling back to a default value if\n * the check fails.\n *\n * If given value isn't an Array, delegates to `SPE.utils.ensureInstanceOf`.\n *\n * @param {Array|Object} arg The value to perform the instanceof check on.\n * @param {Function} instance The constructor of the instance to check against.\n * @param {Object} defaultValue A default fallback value if instance check fails\n * @return {Object} The given value if type check passes, or the default value if it fails.\n */\n ensureArrayInstanceOf: function( arg, instance, defaultValue ) {\n 'use strict';\n\n // If the argument being checked is an array, loop through\n // it and ensure all the values are of the correct type,\n // falling back to the defaultValue if any aren't.\n if ( Array.isArray( arg ) ) {\n for ( var i = arg.length - 1; i >= 0; --i ) {\n if ( instance !== undefined && arg[ i ] instanceof instance === false ) {\n return defaultValue;\n }\n }\n\n return arg;\n }\n\n // If the arg isn't an array then just fallback to\n // checking the type.\n return this.ensureInstanceOf( arg, instance, defaultValue );\n },\n\n /**\n * Ensures that any \"value-over-lifetime\" properties of an emitter are\n * of the correct length (as dictated by `SPE.valueOverLifetimeLength`).\n *\n * Delegates to `SPE.utils.interpolateArray` for array resizing.\n *\n * If properties aren't arrays, then property values are put into one.\n *\n * @param {Object} property The property of an SPE.Emitter instance to check compliance of.\n * @param {Number} minLength The minimum length of the array to create.\n * @param {Number} maxLength The maximum length of the array to create.\n */\n ensureValueOverLifetimeCompliance: function( property, minLength, maxLength ) {\n 'use strict';\n\n minLength = minLength || 3;\n maxLength = maxLength || 3;\n\n // First, ensure both properties are arrays.\n if ( Array.isArray( property._value ) === false ) {\n property._value = [ property._value ];\n }\n\n if ( Array.isArray( property._spread ) === false ) {\n property._spread = [ property._spread ];\n }\n\n var valueLength = this.clamp( property._value.length, minLength, maxLength ),\n spreadLength = this.clamp( property._spread.length, minLength, maxLength ),\n desiredLength = Math.max( valueLength, spreadLength );\n\n if ( property._value.length !== desiredLength ) {\n property._value = this.interpolateArray( property._value, desiredLength );\n }\n\n if ( property._spread.length !== desiredLength ) {\n property._spread = this.interpolateArray( property._spread, desiredLength );\n }\n },\n\n /**\n * Performs linear interpolation (lerp) on an array.\n *\n * For example, lerping [1, 10], with a `newLength` of 10 will produce [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].\n *\n * Delegates to `SPE.utils.lerpTypeAgnostic` to perform the actual\n * interpolation.\n *\n * @param {Array} srcArray The array to lerp.\n * @param {Number} newLength The length the array should be interpolated to.\n * @return {Array} The interpolated array.\n */\n interpolateArray: function( srcArray, newLength ) {\n 'use strict';\n\n var sourceLength = srcArray.length,\n newArray = [ typeof srcArray[ 0 ].clone === 'function' ? srcArray[ 0 ].clone() : srcArray[ 0 ] ],\n factor = ( sourceLength - 1 ) / ( newLength - 1 );\n\n\n for ( var i = 1; i < newLength - 1; ++i ) {\n var f = i * factor,\n before = Math.floor( f ),\n after = Math.ceil( f ),\n delta = f - before;\n\n newArray[ i ] = this.lerpTypeAgnostic( srcArray[ before ], srcArray[ after ], delta );\n }\n\n newArray.push(\n typeof srcArray[ sourceLength - 1 ].clone === 'function' ?\n srcArray[ sourceLength - 1 ].clone() :\n srcArray[ sourceLength - 1 ]\n );\n\n return newArray;\n },\n\n /**\n * Clamp a number to between the given min and max values.\n * @param {Number} value The number to clamp.\n * @param {Number} min The minimum value.\n * @param {Number} max The maximum value.\n * @return {Number} The clamped number.\n */\n clamp: function( value, min, max ) {\n 'use strict';\n\n return Math.max( min, Math.min( value, max ) );\n },\n\n /**\n * If the given value is less than the epsilon value, then return\n * a randomised epsilon value if specified, or just the epsilon value if not.\n * Works for negative numbers as well as positive.\n *\n * @param {Number} value The value to perform the operation on.\n * @param {Boolean} randomise Whether the value should be randomised.\n * @return {Number} The result of the operation.\n */\n zeroToEpsilon: function( value, randomise ) {\n 'use strict';\n\n var epsilon = 0.00001,\n result = value;\n\n result = randomise ? Math.random() * epsilon * 10 : epsilon;\n\n if ( value < 0 && value > -epsilon ) {\n result = -result;\n }\n\n // if ( value === 0 ) {\n // result = randomise ? Math.random() * epsilon * 10 : epsilon;\n // }\n // else if ( value > 0 && value < epsilon ) {\n // result = randomise ? Math.random() * epsilon * 10 : epsilon;\n // }\n // else if ( value < 0 && value > -epsilon ) {\n // result = -( randomise ? Math.random() * epsilon * 10 : epsilon );\n // }\n\n return result;\n },\n\n /**\n * Linearly interpolates two values of various types. The given values\n * must be of the same type for the interpolation to work.\n * @param {(number|Object)} start The start value of the lerp.\n * @param {(number|object)} end The end value of the lerp.\n * @param {Number} delta The delta posiiton of the lerp operation. Ideally between 0 and 1 (inclusive).\n * @return {(number|object|undefined)} The result of the operation. Result will be undefined if\n * the start and end arguments aren't a supported type, or\n * if their types do not match.\n */\n lerpTypeAgnostic: function( start, end, delta ) {\n 'use strict';\n\n var types = this.types,\n out;\n\n if ( typeof start === types.NUMBER && typeof end === types.NUMBER ) {\n return start + ( ( end - start ) * delta );\n }\n else if ( start instanceof THREE.Vector2 && end instanceof THREE.Vector2 ) {\n out = start.clone();\n out.x = this.lerp( start.x, end.x, delta );\n out.y = this.lerp( start.y, end.y, delta );\n return out;\n }\n else if ( start instanceof THREE.Vector3 && end instanceof THREE.Vector3 ) {\n out = start.clone();\n out.x = this.lerp( start.x, end.x, delta );\n out.y = this.lerp( start.y, end.y, delta );\n out.z = this.lerp( start.z, end.z, delta );\n return out;\n }\n else if ( start instanceof THREE.Vector4 && end instanceof THREE.Vector4 ) {\n out = start.clone();\n out.x = this.lerp( start.x, end.x, delta );\n out.y = this.lerp( start.y, end.y, delta );\n out.z = this.lerp( start.z, end.z, delta );\n out.w = this.lerp( start.w, end.w, delta );\n return out;\n }\n else if ( start instanceof THREE.Color && end instanceof THREE.Color ) {\n out = start.clone();\n out.r = this.lerp( start.r, end.r, delta );\n out.g = this.lerp( start.g, end.g, delta );\n out.b = this.lerp( start.b, end.b, delta );\n return out;\n }\n else {\n console.warn( 'Invalid argument types, or argument types do not match:', start, end );\n }\n },\n\n /**\n * Perform a linear interpolation operation on two numbers.\n * @param {Number} start The start value.\n * @param {Number} end The end value.\n * @param {Number} delta The position to interpolate to.\n * @return {Number} The result of the lerp operation.\n */\n lerp: function( start, end, delta ) {\n 'use strict';\n return start + ( ( end - start ) * delta );\n },\n\n /**\n * Rounds a number to a nearest multiple.\n *\n * @param {Number} n The number to round.\n * @param {Number} multiple The multiple to round to.\n * @return {Number} The result of the round operation.\n */\n roundToNearestMultiple: function( n, multiple ) {\n 'use strict';\n\n var remainder = 0;\n\n if ( multiple === 0 ) {\n return n;\n }\n\n remainder = Math.abs( n ) % multiple;\n\n if ( remainder === 0 ) {\n return n;\n }\n\n if ( n < 0 ) {\n return -( Math.abs( n ) - remainder );\n }\n\n return n + multiple - remainder;\n },\n\n /**\n * Check if all items in an array are equal. Uses strict equality.\n *\n * @param {Array} array The array of values to check equality of.\n * @return {Boolean} Whether the array's values are all equal or not.\n */\n arrayValuesAreEqual: function( array ) {\n 'use strict';\n\n for ( var i = 0; i < array.length - 1; ++i ) {\n if ( array[ i ] !== array[ i + 1 ] ) {\n return false;\n }\n }\n\n return true;\n },\n\n // colorsAreEqual: function() {\n // var colors = Array.prototype.slice.call( arguments ),\n // numColors = colors.length;\n\n // for ( var i = 0, color1, color2; i < numColors - 1; ++i ) {\n // color1 = colors[ i ];\n // color2 = colors[ i + 1 ];\n\n // if (\n // color1.r !== color2.r ||\n // color1.g !== color2.g ||\n // color1.b !== color2.b\n // ) {\n // return false\n // }\n // }\n\n // return true;\n // },\n\n\n /**\n * Given a start value and a spread value, create and return a random\n * number.\n * @param {Number} base The start value.\n * @param {Number} spread The size of the random variance to apply.\n * @return {Number} A randomised number.\n */\n randomFloat: function( base, spread ) {\n 'use strict';\n return base + spread * ( Math.random() - 0.5 );\n },\n\n\n\n /**\n * Given an SPE.ShaderAttribute instance, and various other settings,\n * assign values to the attribute's array in a `vec3` format.\n *\n * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to.\n * @param {Number} index The offset in the attribute's TypedArray to save the result from.\n * @param {Object} base THREE.Vector3 instance describing the start value.\n * @param {Object} spread THREE.Vector3 instance describing the random variance to apply to the start value.\n * @param {Object} spreadClamp THREE.Vector3 instance describing the multiples to clamp the randomness to.\n */\n randomVector3: function( attribute, index, base, spread, spreadClamp ) {\n 'use strict';\n\n var x = base.x + ( Math.random() * spread.x - ( spread.x * 0.5 ) ),\n y = base.y + ( Math.random() * spread.y - ( spread.y * 0.5 ) ),\n z = base.z + ( Math.random() * spread.z - ( spread.z * 0.5 ) );\n\n // var x = this.randomFloat( base.x, spread.x ),\n // y = this.randomFloat( base.y, spread.y ),\n // z = this.randomFloat( base.z, spread.z );\n\n if ( spreadClamp ) {\n x = -spreadClamp.x * 0.5 + this.roundToNearestMultiple( x, spreadClamp.x );\n y = -spreadClamp.y * 0.5 + this.roundToNearestMultiple( y, spreadClamp.y );\n z = -spreadClamp.z * 0.5 + this.roundToNearestMultiple( z, spreadClamp.z );\n }\n\n attribute.typedArray.setVec3Components( index, x, y, z );\n },\n\n /**\n * Given an SPE.Shader attribute instance, and various other settings,\n * assign Color values to the attribute.\n * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to.\n * @param {Number} index The offset in the attribute's TypedArray to save the result from.\n * @param {Object} base THREE.Color instance describing the start color.\n * @param {Object} spread THREE.Vector3 instance describing the random variance to apply to the start color.\n */\n randomColor: function( attribute, index, base, spread ) {\n 'use strict';\n\n var r = base.r + ( Math.random() * spread.x ),\n g = base.g + ( Math.random() * spread.y ),\n b = base.b + ( Math.random() * spread.z );\n\n r = this.clamp( r, 0, 1 );\n g = this.clamp( g, 0, 1 );\n b = this.clamp( b, 0, 1 );\n\n\n attribute.typedArray.setVec3Components( index, r, g, b );\n },\n\n\n randomColorAsHex: ( function() {\n 'use strict';\n\n var workingColor = new THREE.Color();\n\n /**\n * Assigns a random color value, encoded as a hex value in decimal\n * format, to a SPE.ShaderAttribute instance.\n * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to.\n * @param {Number} index The offset in the attribute's TypedArray to save the result from.\n * @param {Object} base THREE.Color instance describing the start color.\n * @param {Object} spread THREE.Vector3 instance describing the random variance to apply to the start color.\n */\n return function( attribute, index, base, spread ) {\n var numItems = base.length,\n colors = [];\n\n for ( var i = 0; i < numItems; ++i ) {\n var spreadVector = spread[ i ];\n\n workingColor.copy( base[ i ] );\n\n workingColor.r += ( Math.random() * spreadVector.x ) - ( spreadVector.x * 0.5 );\n workingColor.g += ( Math.random() * spreadVector.y ) - ( spreadVector.y * 0.5 );\n workingColor.b += ( Math.random() * spreadVector.z ) - ( spreadVector.z * 0.5 );\n\n workingColor.r = this.clamp( workingColor.r, 0, 1 );\n workingColor.g = this.clamp( workingColor.g, 0, 1 );\n workingColor.b = this.clamp( workingColor.b, 0, 1 );\n\n colors.push( workingColor.getHex() );\n }\n\n attribute.typedArray.setVec4Components( index, colors[ 0 ], colors[ 1 ], colors[ 2 ], colors[ 3 ] );\n };\n }() ),\n\n /**\n * Given an SPE.ShaderAttribute instance, and various other settings,\n * assign values to the attribute's array in a `vec3` format.\n *\n * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to.\n * @param {Number} index The offset in the attribute's TypedArray to save the result from.\n * @param {Object} start THREE.Vector3 instance describing the start line position.\n * @param {Object} end THREE.Vector3 instance describing the end line position.\n */\n randomVector3OnLine: function( attribute, index, start, end ) {\n 'use strict';\n var pos = start.clone();\n\n pos.lerp( end, Math.random() );\n\n attribute.typedArray.setVec3Components( index, pos.x, pos.y, pos.z );\n },\n\n /**\n * Given an SPE.Shader attribute instance, and various other settings,\n * assign Color values to the attribute.\n * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to.\n * @param {Number} index The offset in the attribute's TypedArray to save the result from.\n * @param {Object} base THREE.Color instance describing the start color.\n * @param {Object} spread THREE.Vector3 instance describing the random variance to apply to the start color.\n */\n\n /**\n * Assigns a random vector 3 value to an SPE.ShaderAttribute instance, projecting the\n * given values onto a sphere.\n *\n * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to.\n * @param {Number} index The offset in the attribute's TypedArray to save the result from.\n * @param {Object} base THREE.Vector3 instance describing the origin of the transform.\n * @param {Number} radius The radius of the sphere to project onto.\n * @param {Number} radiusSpread The amount of randomness to apply to the projection result\n * @param {Object} radiusScale THREE.Vector3 instance describing the scale of each axis of the sphere.\n * @param {Number} radiusSpreadClamp What numeric multiple the projected value should be clamped to.\n */\n randomVector3OnSphere: function(\n attribute, index, base, radius, radiusSpread, radiusScale, radiusSpreadClamp, distributionClamp\n ) {\n 'use strict';\n\n var depth = 2 * Math.random() - 1,\n t = 6.2832 * Math.random(),\n r = Math.sqrt( 1 - depth * depth ),\n rand = this.randomFloat( radius, radiusSpread ),\n x = 0,\n y = 0,\n z = 0;\n\n\n if ( radiusSpreadClamp ) {\n rand = Math.round( rand / radiusSpreadClamp ) * radiusSpreadClamp;\n }\n\n\n\n // Set position on sphere\n x = r * Math.cos( t ) * rand;\n y = r * Math.sin( t ) * rand;\n z = depth * rand;\n\n // Apply radius scale to this position\n x *= radiusScale.x;\n y *= radiusScale.y;\n z *= radiusScale.z;\n\n // Translate to the base position.\n x += base.x;\n y += base.y;\n z += base.z;\n\n // Set the values in the typed array.\n attribute.typedArray.setVec3Components( index, x, y, z );\n },\n\n seededRandom: function( seed ) {\n var x = Math.sin( seed ) * 10000;\n return x - ( x | 0 );\n },\n\n\n\n /**\n * Assigns a random vector 3 value to an SPE.ShaderAttribute instance, projecting the\n * given values onto a 2d-disc.\n *\n * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to.\n * @param {Number} index The offset in the attribute's TypedArray to save the result from.\n * @param {Object} base THREE.Vector3 instance describing the origin of the transform.\n * @param {Number} radius The radius of the sphere to project onto.\n * @param {Number} radiusSpread The amount of randomness to apply to the projection result\n * @param {Object} radiusScale THREE.Vector3 instance describing the scale of each axis of the disc. The z-component is ignored.\n * @param {Number} radiusSpreadClamp What numeric multiple the projected value should be clamped to.\n */\n randomVector3OnDisc: function( attribute, index, base, radius, radiusSpread, radiusScale, radiusSpreadClamp ) {\n 'use strict';\n\n var t = 6.2832 * Math.random(),\n rand = Math.abs( this.randomFloat( radius, radiusSpread ) ),\n x = 0,\n y = 0,\n z = 0;\n\n if ( radiusSpreadClamp ) {\n rand = Math.round( rand / radiusSpreadClamp ) * radiusSpreadClamp;\n }\n\n // Set position on sphere\n x = Math.cos( t ) * rand;\n y = Math.sin( t ) * rand;\n\n // Apply radius scale to this position\n x *= radiusScale.x;\n y *= radiusScale.y;\n\n // Translate to the base position.\n x += base.x;\n y += base.y;\n z += base.z;\n\n // Set the values in the typed array.\n attribute.typedArray.setVec3Components( index, x, y, z );\n },\n\n randomDirectionVector3OnSphere: ( function() {\n 'use strict';\n\n var v = new THREE.Vector3();\n\n /**\n * Given an SPE.ShaderAttribute instance, create a direction vector from the given\n * position, using `speed` as the magnitude. Values are saved to the attribute.\n *\n * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to.\n * @param {Number} index The offset in the attribute's TypedArray to save the result from.\n * @param {Number} posX The particle's x coordinate.\n * @param {Number} posY The particle's y coordinate.\n * @param {Number} posZ The particle's z coordinate.\n * @param {Object} emitterPosition THREE.Vector3 instance describing the emitter's base position.\n * @param {Number} speed The magnitude to apply to the vector.\n * @param {Number} speedSpread The amount of randomness to apply to the magnitude.\n */\n return function( attribute, index, posX, posY, posZ, emitterPosition, speed, speedSpread ) {\n v.copy( emitterPosition );\n\n v.x -= posX;\n v.y -= posY;\n v.z -= posZ;\n\n v.normalize().multiplyScalar( -this.randomFloat( speed, speedSpread ) );\n\n attribute.typedArray.setVec3Components( index, v.x, v.y, v.z );\n };\n }() ),\n\n\n randomDirectionVector3OnDisc: ( function() {\n 'use strict';\n\n var v = new THREE.Vector3();\n\n /**\n * Given an SPE.ShaderAttribute instance, create a direction vector from the given\n * position, using `speed` as the magnitude. Values are saved to the attribute.\n *\n * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to.\n * @param {Number} index The offset in the attribute's TypedArray to save the result from.\n * @param {Number} posX The particle's x coordinate.\n * @param {Number} posY The particle's y coordinate.\n * @param {Number} posZ The particle's z coordinate.\n * @param {Object} emitterPosition THREE.Vector3 instance describing the emitter's base position.\n * @param {Number} speed The magnitude to apply to the vector.\n * @param {Number} speedSpread The amount of randomness to apply to the magnitude.\n */\n return function( attribute, index, posX, posY, posZ, emitterPosition, speed, speedSpread ) {\n v.copy( emitterPosition );\n\n v.x -= posX;\n v.y -= posY;\n v.z -= posZ;\n\n v.normalize().multiplyScalar( -this.randomFloat( speed, speedSpread ) );\n\n attribute.typedArray.setVec3Components( index, v.x, v.y, 0 );\n };\n }() ),\n\n getPackedRotationAxis: ( function() {\n 'use strict';\n\n var v = new THREE.Vector3(),\n vSpread = new THREE.Vector3(),\n c = new THREE.Color(),\n addOne = new THREE.Vector3( 1, 1, 1 );\n\n /**\n * Given a rotation axis, and a rotation axis spread vector,\n * calculate a randomised rotation axis, and pack it into\n * a hexadecimal value represented in decimal form.\n * @param {Object} axis THREE.Vector3 instance describing the rotation axis.\n * @param {Object} axisSpread THREE.Vector3 instance describing the amount of randomness to apply to the rotation axis.\n * @return {Number} The packed rotation axis, with randomness.\n */\n return function( axis, axisSpread ) {\n v.copy( axis ).normalize();\n vSpread.copy( axisSpread ).normalize();\n\n v.x += ( -axisSpread.x * 0.5 ) + ( Math.random() * axisSpread.x );\n v.y += ( -axisSpread.y * 0.5 ) + ( Math.random() * axisSpread.y );\n v.z += ( -axisSpread.z * 0.5 ) + ( Math.random() * axisSpread.z );\n\n // v.x = Math.abs( v.x );\n // v.y = Math.abs( v.y );\n // v.z = Math.abs( v.z );\n\n v.normalize().add( addOne ).multiplyScalar( 0.5 );\n\n c.setRGB( v.x, v.y, v.z );\n\n return c.getHex();\n };\n }() )\n};\n\n\n/**\n * An SPE.Group instance.\n * @typedef {Object} Group\n * @see SPE.Group\n */\n\n/**\n * A map of options to configure an SPE.Group instance.\n * @typedef {Object} GroupOptions\n *\n * @property {Object} texture An object describing the texture used by the group.\n *\n * @property {Object} texture.value An instance of THREE.Texture.\n *\n * @property {Object=} texture.frames A THREE.Vector2 instance describing the number\n * of frames on the x- and y-axis of the given texture.\n * If not provided, the texture will NOT be treated as\n * a sprite-sheet and as such will NOT be animated.\n *\n * @property {Number} [texture.frameCount=texture.frames.x * texture.frames.y] The total number of frames in the sprite-sheet.\n * Allows for sprite-sheets that don't fill the entire\n * texture.\n *\n * @property {Number} texture.loop The number of loops through the sprite-sheet that should\n * be performed over the course of a single particle's lifetime.\n *\n * @property {Number} fixedTimeStep If no `dt` (or `deltaTime`) value is passed to this group's\n * `tick()` function, this number will be used to move the particle\n * simulation forward. Value in SECONDS.\n *\n * @property {Boolean} hasPerspective Whether the distance a particle is from the camera should affect\n * the particle's size.\n *\n * @property {Boolean} colorize Whether the particles in this group should be rendered with color, or\n * whether the only color of particles will come from the provided texture.\n *\n * @property {Number} blending One of Three.js's blending modes to apply to this group's `ShaderMaterial`.\n *\n * @property {Boolean} transparent Whether these particle's should be rendered with transparency.\n *\n * @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.\n *\n * @property {Boolean} depthWrite Whether rendering the group has any effect on the depth buffer.\n *\n * @property {Boolean} depthTest Whether to have depth test enabled when rendering this group.\n *\n * @property {Boolean} fog Whether this group's particles should be affected by their scene's fog.\n *\n * @property {Number} scale The scale factor to apply to this group's particle sizes. Useful for\n * setting particle sizes to be relative to renderer size.\n */\n\n\n/**\n * The SPE.Group class. Creates a new group, containing a material, geometry, and mesh.\n *\n * @constructor\n * @param {GroupOptions} options A map of options to configure the group instance.\n */\nSPE.Group = function( options ) {\n 'use strict';\n\n var utils = SPE.utils,\n types = utils.types;\n\n // Ensure we have a map of options to play with\n options = utils.ensureTypedArg( options, types.OBJECT, {} );\n options.texture = utils.ensureTypedArg( options.texture, types.OBJECT, {} );\n\n // Assign a UUID to this instance\n this.uuid = THREE.MathUtils.generateUUID();\n\n // If no `deltaTime` value is passed to the `SPE.Group.tick` function,\n // the value of this property will be used to advance the simulation.\n this.fixedTimeStep = utils.ensureTypedArg( options.fixedTimeStep, types.NUMBER, 0.016 );\n\n // Set properties used in the uniforms map, starting with the\n // texture stuff.\n this.texture = utils.ensureInstanceOf( options.texture.value, THREE.Texture, null );\n this.textureFrames = utils.ensureInstanceOf( options.texture.frames, THREE.Vector2, new THREE.Vector2( 1, 1 ) );\n this.textureFrameCount = utils.ensureTypedArg( options.texture.frameCount, types.NUMBER, this.textureFrames.x * this.textureFrames.y );\n this.textureLoop = utils.ensureTypedArg( options.texture.loop, types.NUMBER, 1 );\n this.textureFrames.max( new THREE.Vector2( 1, 1 ) );\n\n this.hasPerspective = utils.ensureTypedArg( options.hasPerspective, types.BOOLEAN, true );\n this.colorize = utils.ensureTypedArg( options.colorize, types.BOOLEAN, true );\n\n this.maxParticleCount = utils.ensureTypedArg( options.maxParticleCount, types.NUMBER, null );\n\n\n // Set properties used to define the ShaderMaterial's appearance.\n this.blending = utils.ensureTypedArg( options.blending, types.NUMBER, THREE.AdditiveBlending );\n this.transparent = utils.ensureTypedArg( options.transparent, types.BOOLEAN, true );\n this.alphaTest = parseFloat( utils.ensureTypedArg( options.alphaTest, types.NUMBER, 0.0 ) );\n this.depthWrite = utils.ensureTypedArg( options.depthWrite, types.BOOLEAN, false );\n this.depthTest = utils.ensureTypedArg( options.depthTest, types.BOOLEAN, true );\n this.fog = utils.ensureTypedArg( options.fog, types.BOOLEAN, true );\n this.scale = utils.ensureTypedArg( options.scale, types.NUMBER, 300 );\n\n // Where emitter's go to curl up in a warm blanket and live\n // out their days.\n this.emitters = [];\n this.emitterIDs = [];\n\n // Create properties for use by the emitter pooling functions.\n this._pool = [];\n this._poolCreationSettings = null;\n this._createNewWhenPoolEmpty = 0;\n\n // Whether all attributes should be forced to updated\n // their entire buffer contents on the next tick.\n //\n // Used when an emitter is removed.\n this._attributesNeedRefresh = false;\n this._attributesNeedDynamicReset = false;\n\n this.particleCount = 0;\n\n\n // Map of uniforms to be applied to the ShaderMaterial instance.\n this.uniforms = {\n tex: {\n type: 't',\n value: this.texture\n },\n textureAnimation: {\n type: 'v4',\n value: new THREE.Vector4(\n this.textureFrames.x,\n this.textureFrames.y,\n this.textureFrameCount,\n Math.max( Math.abs( this.textureLoop ), 1.0 )\n )\n },\n fogColor: {\n type: 'c',\n value: this.fog ? new THREE.Color() : null\n },\n fogNear: {\n type: 'f',\n value: 10\n },\n fogFar: {\n type: 'f',\n value: 200\n },\n fogDensity: {\n type: 'f',\n value: 0.5\n },\n deltaTime: {\n type: 'f',\n value: 0\n },\n runTime: {\n type: 'f',\n value: 0\n },\n scale: {\n type: 'f',\n value: this.scale\n }\n };\n\n // Add some defines into the mix...\n this.defines = {\n HAS_PERSPECTIVE: this.hasPerspective,\n COLORIZE: this.colorize,\n VALUE_OVER_LIFETIME_LENGTH: SPE.valueOverLifetimeLength,\n\n SHOULD_ROTATE_TEXTURE: false,\n SHOULD_ROTATE_PARTICLES: false,\n SHOULD_WIGGLE_PARTICLES: false,\n\n SHOULD_CALCULATE_SPRITE: this.textureFrames.x > 1 || this.textureFrames.y > 1\n };\n\n // Map of all attributes to be applied to the particles.\n //\n // See SPE.ShaderAttribute for a bit more info on this bit.\n this.attributes = {\n position: new SPE.ShaderAttribute( 'v3', true ),\n acceleration: new SPE.ShaderAttribute( 'v4', true ), // w component is drag\n velocity: new SPE.ShaderAttribute( 'v3', true ),\n rotation: new SPE.ShaderAttribute( 'v4', true ),\n rotationCenter: new SPE.ShaderAttribute( 'v3', true ),\n params: new SPE.ShaderAttribute( 'v4', true ), // Holds (alive, age, delay, wiggle)\n size: new SPE.ShaderAttribute( 'v4', true ),\n angle: new SPE.ShaderAttribute( 'v4', true ),\n color: new SPE.ShaderAttribute( 'v4', true ),\n opacity: new SPE.ShaderAttribute( 'v4', true )\n };\n\n this.attributeKeys = Object.keys( this.attributes );\n this.attributeCount = this.attributeKeys.length;\n\n // Create the ShaderMaterial instance that'll help render the\n // particles.\n this.material = new THREE.ShaderMaterial( {\n uniforms: this.uniforms,\n vertexShader: SPE.shaders.vertex,\n fragmentShader: SPE.shaders.fragment,\n blending: this.blending,\n transparent: this.transparent,\n alphaTest: this.alphaTest,\n depthWrite: this.depthWrite,\n depthTest: this.depthTest,\n defines: this.defines,\n fog: this.fog\n } );\n\n // Create the BufferGeometry and Points instances, ensuring\n // the geometry and material are given to the latter.\n this.geometry = new THREE.BufferGeometry();\n this.mesh = new THREE.Points( this.geometry, this.material );\n\n if ( this.maxParticleCount === null ) {\n console.warn( 'SPE.Group: No maxParticleCount specified. Adding emitters after rendering will probably cause errors.' );\n }\n};\n\nSPE.Group.constructor = SPE.Group;\n\n\nSPE.Group.prototype._updateDefines = function() {\n 'use strict';\n\n var emitters = this.emitters,\n i = emitters.length - 1,\n emitter,\n defines = this.defines;\n\n for ( i; i >= 0; --i ) {\n emitter = emitters[ i ];\n\n // Only do angle calculation if there's no spritesheet defined.\n //\n // Saves calculations being done and then overwritten in the shaders.\n if ( !defines.SHOULD_CALCULATE_SPRITE ) {\n defines.SHOULD_ROTATE_TEXTURE = defines.SHOULD_ROTATE_TEXTURE || !!Math.max(\n Math.max.apply( null, emitter.angle.value ),\n Math.max.apply( null, emitter.angle.spread )\n );\n }\n\n defines.SHOULD_ROTATE_PARTICLES = defines.SHOULD_ROTATE_PARTICLES || !!Math.max(\n emitter.rotation.angle,\n emitter.rotation.angleSpread\n );\n\n defines.SHOULD_WIGGLE_PARTICLES = defines.SHOULD_WIGGLE_PARTICLES || !!Math.max(\n emitter.wiggle.value,\n emitter.wiggle.spread\n );\n }\n\n this.material.needsUpdate = true;\n};\n\nSPE.Group.prototype._applyAttributesToGeometry = function() {\n 'use strict';\n\n var attributes = this.attributes,\n geometry = this.geometry,\n geometryAttributes = geometry.attributes,\n attribute,\n geometryAttribute;\n\n // Loop through all the shader attributes and assign (or re-assign)\n // typed array buffers to each one.\n for ( var attr in attributes ) {\n if ( attributes.hasOwnProperty( attr ) ) {\n attribute = attributes[ attr ];\n geometryAttribute = geometryAttributes[ attr ];\n\n // Update the array if this attribute exists on the geometry.\n //\n // This needs to be done because the attribute's typed array might have\n // been resized and reinstantiated, and might now be looking at a\n // different ArrayBuffer, so reference needs updating.\n if ( geometryAttribute ) {\n geometryAttribute.array = attribute.typedArray.array;\n }\n\n // // Add the attribute to the geometry if it doesn't already exist.\n else {\n geometry.setAttribute( attr, attribute.bufferAttribute );\n }\n\n // Mark the attribute as needing an update the next time a frame is rendered.\n attribute.bufferAttribute.needsUpdate = true;\n }\n }\n\n // Mark the draw range on the geometry. This will ensure\n // only the values in the attribute buffers that are\n // associated with a particle will be used in THREE's\n // render cycle.\n this.geometry.setDrawRange( 0, this.particleCount );\n};\n\n/**\n * Adds an SPE.Emitter instance to this group, creating particle values and\n * assigning them to this group's shader attributes.\n *\n * @param {Emitter} emitter The emitter to add to this group.\n */\nSPE.Group.prototype.addEmitter = function( emitter ) {\n 'use strict';\n\n // Ensure an actual emitter instance is passed here.\n //\n // Decided not to throw here, just in case a scene's\n // rendering would be paused. Logging an error instead\n // of stopping execution if exceptions aren't caught.\n if ( emitter instanceof SPE.Emitter === false ) {\n console.error( '`emitter` argument must be instance of SPE.Emitter. Was provided with:', emitter );\n return;\n }\n\n // If the emitter already exists as a member of this group, then\n // stop here, we don't want to add it again.\n else if ( this.emitterIDs.indexOf( emitter.uuid ) > -1 ) {\n console.error( 'Emitter already exists in this group. Will not add again.' );\n return;\n }\n\n // And finally, if the emitter is a member of another group,\n // don't add it to this group.\n else if ( emitter.group !== null ) {\n console.error( 'Emitter already belongs to another group. Will not add to requested group.' );\n return;\n }\n\n var attributes = this.attributes,\n start = this.particleCount,\n end = start + emitter.particleCount;\n\n // Update this group's particle count.\n this.particleCount = end;\n\n // Emit a warning if the emitter being added will exceed the buffer sizes specified.\n if ( this.maxParticleCount !== null && this.particleCount > this.maxParticleCount ) {\n console.warn( 'SPE.Group: maxParticleCount exceeded. Requesting', this.particleCount, 'particles, can support only', this.maxParticleCount );\n }\n\n\n // Set the `particlesPerSecond` value (PPS) on the emitter.\n // It's used to determine how many particles to release\n // on a per-frame basis.\n emitter._calculatePPSValue( emitter.maxAge._value + emitter.maxAge._spread );\n emitter._setBufferUpdateRanges( this.attributeKeys );\n\n // Store the offset value in the TypedArray attributes for this emitter.\n emitter._setAttributeOffset( start );\n\n // Save a reference to this group on the emitter so it knows\n // where it belongs.\n emitter.group = this;\n\n // Store reference to the attributes on the emitter for\n // easier access during the emitter's tick function.\n emitter.attributes = this.attributes;\n\n\n\n // Ensure the attributes and their BufferAttributes exist, and their\n // TypedArrays are of the correct size.\n for ( var attr in attributes ) {\n if ( attributes.hasOwnProperty( attr ) ) {\n // When creating a buffer, pass through the maxParticle count\n // if one is specified.\n attributes[ attr ]._createBufferAttribute(\n this.maxParticleCount !== null ?\n this.maxParticleCount :\n this.particleCount\n );\n }\n }\n\n // Loop through each particle this emitter wants to have, and create the attributes values,\n // storing them in the TypedArrays that each attribute holds.\n for ( var i = start; i < end; ++i ) {\n emitter._assignPositionValue( i );\n emitter._assignForceValue( i, 'velocity' );\n emitter._assignForceValue( i, 'acceleration' );\n emitter._assignAbsLifetimeValue( i, 'opacity' );\n emitter._assignAbsLifetimeValue( i, 'size' );\n emitter._assignAngleValue( i );\n emitter._assignRotationValue( i );\n emitter._assignParamsValue( i );\n emitter._assignColorValue( i );\n }\n\n // Update the geometry and make sure the attributes are referencing\n // the typed arrays properly.\n this._applyAttributesToGeometry();\n\n // Store this emitter in this group's emitter's store.\n this.emitters.push( emitter );\n this.emitterIDs.push( emitter.uuid );\n\n // Update certain flags to enable shader calculations only if they're necessary.\n this._updateDefines( emitter );\n\n // Update the material since defines might have changed\n this.material.needsUpdate = true;\n this.geometry.needsUpdate = true;\n this._attributesNeedRefresh = true;\n\n // Return the group to enable chaining.\n return this;\n};\n\n/**\n * Removes an SPE.Emitter instance from this group. When called,\n * all particle's belonging to the given emitter will be instantly\n * removed from the scene.\n *\n * @param {Emitter} emitter The emitter to add to this group.\n */\nSPE.Group.prototype.removeEmitter = function( emitter ) {\n 'use strict';\n\n var emitterIndex = this.emitterIDs.indexOf( emitter.uuid );\n\n // Ensure an actual emitter instance is passed here.\n //\n // Decided not to throw here, just in case a scene's\n // rendering would be paused. Logging an error instead\n // of stopping execution if exceptions aren't caught.\n if ( emitter instanceof SPE.Emitter === false ) {\n console.error( '`emitter` argument must be instance of SPE.Emitter. Was provided with:', emitter );\n return;\n }\n\n // Issue an error if the emitter isn't a member of this group.\n else if ( emitterIndex === -1 ) {\n console.error( 'Emitter does not exist in this group. Will not remove.' );\n return;\n }\n\n // Kill all particles by marking them as dead\n // and their age as 0.\n var start = emitter.attributeOffset,\n end = start + emitter.particleCount,\n params = this.attributes.params.typedArray;\n\n // Set alive and age to zero.\n for ( var i = start; i < end; ++i ) {\n params.array[ i * 4 ] = 0.0;\n params.array[ i * 4 + 1 ] = 0.0;\n }\n\n // Remove the emitter from this group's \"store\".\n this.emitters.splice( emitterIndex, 1 );\n this.emitterIDs.splice( emitterIndex, 1 );\n\n // Remove this emitter's attribute values from all shader attributes.\n // The `.splice()` call here also marks each attribute's buffer\n // as needing to update it's entire contents.\n for ( var attr in this.attributes ) {\n if ( this.attributes.hasOwnProperty( attr ) ) {\n this.attributes[ attr ].splice( start, end );\n }\n }\n\n // Ensure this group's particle count is correct.\n this.particleCount -= emitter.particleCount;\n\n // Call the emitter's remove method.\n emitter._onRemove();\n\n // Set a flag to indicate that the attribute buffers should\n // be updated in their entirety on the next frame.\n this._attributesNeedRefresh = true;\n};\n\n\n/**\n * Fetch a single emitter instance from the pool.\n * If there are no objects in the pool, a new emitter will be\n * created if specified.\n *\n * @return {Emitter|null}\n */\nSPE.Group.prototype.getFromPool = function() {\n 'use strict';\n\n var pool = this._pool,\n createNew = this._createNewWhenPoolEmpty;\n\n if ( pool.length ) {\n return pool.pop();\n }\n else if ( createNew ) {\n var emitter = new SPE.Emitter( this._poolCreationSettings );\n\n this.addEmitter( emitter );\n\n return emitter;\n }\n\n return null;\n};\n\n\n/**\n * Release an emitter into the pool.\n *\n * @param {ShaderParticleEmitter} emitter\n * @return {Group} This group instance.\n */\nSPE.Group.prototype.releaseIntoPool = function( emitter ) {\n 'use strict';\n\n if ( emitter instanceof SPE.Emitter === false ) {\n console.error( 'Argument is not instanceof SPE.Emitter:', emitter );\n return;\n }\n\n emitter.reset();\n this._pool.unshift( emitter );\n\n return this;\n};\n\n\n/**\n * Get the pool array\n *\n * @return {Array}\n */\nSPE.Group.prototype.getPool = function() {\n 'use strict';\n return this._pool;\n};\n\n\n/**\n * Add a pool of emitters to this particle group\n *\n * @param {Number} numEmitters The number of emitters to add to the pool.\n * @param {EmitterOptions|Array} emitterOptions An object, or array of objects, describing the options to pass to each emitter.\n * @param {Boolean} createNew Should a new emitter be created if the pool runs out?\n * @return {Group} This group instance.\n */\nSPE.Group.prototype.addPool = function( numEmitters, emitterOptions, createNew ) {\n 'use strict';\n\n var emitter;\n\n // Save relevant settings and flags.\n this._poolCreationSettings = emitterOptions;\n this._createNewWhenPoolEmpty = !!createNew;\n\n // Create the emitters, add them to this group and the pool.\n for ( var i = 0; i < numEmitters; ++i ) {\n if ( Array.isArray( emitterOptions ) ) {\n emitter = new SPE.Emitter( emitterOptions[ i ] );\n }\n else {\n emitter = new SPE.Emitter( emitterOptions );\n }\n this.addEmitter( emitter );\n this.releaseIntoPool( emitter );\n }\n\n return this;\n};\n\n\n\nSPE.Group.prototype._triggerSingleEmitter = function( pos ) {\n 'use strict';\n\n var emitter = this.getFromPool(),\n self = this;\n\n if ( emitter === null ) {\n console.log( 'SPE.Group pool ran out.' );\n return;\n }\n\n // TODO:\n // - Make sure buffers are update with thus new position.\n if ( pos instanceof THREE.Vector3 ) {\n emitter.position.value.copy( pos );\n\n // Trigger the setter for this property to force an\n // update to the emitter's position attribute.\n emitter.position.value = emitter.position.value;\n }\n\n emitter.enable();\n\n setTimeout( function() {\n emitter.disable();\n self.releaseIntoPool( emitter );\n }, ( Math.max( emitter.duration, ( emitter.maxAge.value + emitter.maxAge.spread ) ) ) * 1000 );\n\n return this;\n};\n\n\n/**\n * Set a given number of emitters as alive, with an optional position\n * vector3 to move them to.\n *\n * @param {Number} numEmitters The number of emitters to activate\n * @param {Object} [position=undefined] A THREE.Vector3 instance describing the position to activate the emitter(s) at.\n * @return {Group} This group instance.\n */\nSPE.Group.prototype.triggerPoolEmitter = function( numEmitters, position ) {\n 'use strict';\n\n if ( typeof numEmitters === 'number' && numEmitters > 1 ) {\n for ( var i = 0; i < numEmitters; ++i ) {\n this._triggerSingleEmitter( position );\n }\n }\n else {\n this._triggerSingleEmitter( position );\n }\n\n return this;\n};\n\n\n\nSPE.Group.prototype._updateUniforms = function( dt ) {\n 'use strict';\n\n this.uniforms.runTime.value += dt;\n this.uniforms.deltaTime.value = dt;\n};\n\nSPE.Group.prototype._resetBufferRanges = function() {\n 'use strict';\n\n var keys = this.attributeKeys,\n i = this.attributeCount - 1,\n attrs = this.attributes;\n\n for ( i; i >= 0; --i ) {\n attrs[ keys[ i ] ].resetUpdateRange();\n }\n};\n\n\nSPE.Group.prototype._updateBuffers = function( emitter ) {\n 'use strict';\n\n var keys = this.attributeKeys,\n i = this.attributeCount - 1,\n attrs = this.attributes,\n emitterRanges = emitter.bufferUpdateRanges,\n key,\n emitterAttr,\n attr;\n\n for ( i; i >= 0; --i ) {\n key = keys[ i ];\n emitterAttr = emitterRanges[ key ];\n attr = attrs[ key ];\n attr.setUpdateRange( emitterAttr.min, emitterAttr.max );\n attr.flagUpdate();\n }\n};\n\n\n/**\n * Simulate all the emitter's belonging to this group, updating\n * attribute values along the way.\n * @param {Number} [dt=Group's `fixedTimeStep` value] The number of seconds to simulate the group's emitters for (deltaTime)\n */\nSPE.Group.prototype.tick = function( dt ) {\n 'use strict';\n\n var emitters = this.emitters,\n numEmitters = emitters.length,\n deltaTime = dt || this.fixedTimeStep,\n keys = this.attributeKeys,\n i,\n attrs = this.attributes;\n\n // Update uniform values.\n this._updateUniforms( deltaTime );\n\n // Reset buffer update ranges on the shader attributes.\n this._resetBufferRanges();\n\n\n // If nothing needs updating, then stop here.\n if (\n numEmitters === 0 &&\n this._attributesNeedRefresh === false &&\n this._attributesNeedDynamicReset === false\n ) {\n return;\n }\n\n // Loop through each emitter in this group and\n // simulate it, then update the shader attribute\n // buffers.\n for ( var i = 0, emitter; i < numEmitters; ++i ) {\n emitter = emitters[ i ];\n emitter.tick( deltaTime );\n this._updateBuffers( emitter );\n }\n\n // If the shader attributes have been refreshed,\n // then the dynamic properties of each buffer\n // attribute will need to be reset back to\n // what they should be.\n if ( this._attributesNeedDynamicReset === true ) {\n i = this.attributeCount - 1;\n\n for ( i; i >= 0; --i ) {\n attrs[ keys[ i ] ].resetDynamic();\n }\n\n this._attributesNeedDynamicReset = false;\n }\n\n // If this group's shader attributes need a full refresh\n // then mark each attribute's buffer attribute as\n // needing so.\n if ( this._attributesNeedRefresh === true ) {\n i = this.attributeCount - 1;\n\n for ( i; i >= 0; --i ) {\n attrs[ keys[ i ] ].forceUpdateAll();\n }\n\n this._attributesNeedRefresh = false;\n this._attributesNeedDynamicReset = true;\n }\n};\n\n\n/**\n * Dipose the geometry and material for the group.\n *\n * @return {Group} Group instance.\n */\nSPE.Group.prototype.dispose = function() {\n 'use strict';\n this.geometry.dispose();\n this.material.dispose();\n return this;\n};\n\n\n/**\n * An SPE.Emitter instance.\n * @typedef {Object} Emitter\n * @see SPE.Emitter\n */\n\n/**\n * A map of options to configure an SPE.Emitter instance.\n *\n * @typedef {Object} EmitterOptions\n *\n * @property {distribution} [type=BOX] The default distribution this emitter should use to control\n * its particle's spawn position and force behaviour.\n * Must be an SPE.distributions.* value.\n *\n *\n * @property {Number} [particleCount=100] The total number of particles this emitter will hold. NOTE: this is not the number\n * of particles emitted in a second, or anything like that. The number of particles\n * emitted per-second is calculated by particleCount / maxAge (approximately!)\n *\n * @property {Number|null} [duration=null] The duration in seconds that this emitter should live for. If not specified, the emitter\n * will emit particles indefinitely.\n * NOTE: When an emitter is older than a specified duration, the emitter is NOT removed from\n * it's group, but rather is just marked as dead, allowing it to be reanimated at a later time\n * using `SPE.Emitter.prototype.enable()`.\n *\n * @property {Boolean} [isStatic=false] Whether this emitter should be not be simulated (true).\n * @property {Boolean} [activeMultiplier=1] A value between 0 and 1 describing what percentage of this emitter's particlesPerSecond should be\n * emitted, where 0 is 0%, and 1 is 100%.\n * For example, having an emitter with 100 particles, a maxAge of 2, yields a particlesPerSecond\n * value of 50. Setting `activeMultiplier` to 0.5, then, will only emit 25 particles per second (0.5 = 50%).\n * Values greater than 1 will emulate a burst of particles, causing the emitter to run out of particles\n * before it's next activation cycle.\n *\n * @property {Boolean} [direction=1] The direction of the emitter. If value is `1`, emitter will start at beginning of particle's lifecycle.\n * If value is `-1`, emitter will start at end of particle's lifecycle and work it's way backwards.\n *\n * @property {Object} [maxAge={}] An object describing the particle's maximum age in seconds.\n * @property {Number} [maxAge.value=2] A number between 0 and 1 describing the amount of maxAge to apply to all particles.\n * @property {Number} [maxAge.spread=0] A number describing the maxAge variance on a per-particle basis.\n *\n *\n * @property {Object} [position={}] An object describing this emitter's position.\n * @property {Object} [position.value=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's base position.\n * @property {Object} [position.spread=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's position variance on a per-particle basis.\n * Note that when using a SPHERE or DISC distribution, only the x-component\n * of this vector is used.\n * When using a LINE distribution, this value is the endpoint of the LINE.\n * @property {Object} [position.spreadClamp=new THREE.Vector3()] A THREE.Vector3 instance describing the numeric multiples the particle's should\n * be spread out over.\n * Note that when using a SPHERE or DISC distribution, only the x-component\n * of this vector is used.\n * When using a LINE distribution, this property is ignored.\n * @property {Number} [position.radius=10] This emitter's base radius.\n * @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.\n * @property {distribution} [position.distribution=value of the `type` option.] A specific distribution to use when radiusing particles. Overrides the `type` option.\n * @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.\n *\n *\n * @property {Object} [velocity={}] An object describing this particle velocity.\n * @property {Object} [velocity.value=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's base velocity.\n * @property {Object} [velocity.spread=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's velocity variance on a per-particle basis.\n * Note that when using a SPHERE or DISC distribution, only the x-component\n * of this vector is used.\n * @property {distribution} [velocity.distribution=value of the `type` option.] A specific distribution to use when calculating a particle's velocity. Overrides the `type` option.\n * @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.\n *\n *\n * @property {Object} [acceleration={}] An object describing this particle's acceleration.\n * @property {Object} [acceleration.value=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's base acceleration.\n * @property {Object} [acceleration.spread=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's acceleration variance on a per-particle basis.\n * Note that when using a SPHERE or DISC distribution, only the x-component\n * of this vector is used.\n * @property {distribution} [acceleration.distribution=value of the `type` option.] A specific distribution to use when calculating a particle's acceleration. Overrides the `type` option.\n * @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.\n *\n *\n * @property {Object} [drag={}] An object describing this particle drag. Drag is applied to both velocity and acceleration values.\n * @property {Number} [drag.value=0] A number between 0 and 1 describing the amount of drag to apply to all particles.\n * @property {Number} [drag.spread=0] A number describing the drag variance on a per-particle basis.\n * @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.\n *\n *\n * @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,\n * or shimmy, or waggle, or... Well you get the idea. The wiggle is calculated over-time, meaning that a particle will\n * start off with no wiggle, and end up wiggling about with the distance of the `value` specified by the time it dies.\n * It's quite handy to simulate fire embers, or similar effects where the particle's position should slightly change over\n * 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.\n * @property {Number} [wiggle.value=0] A number describing the amount of wiggle to apply to all particles. It's measured in distance.\n * @property {Number} [wiggle.spread=0] A number describing the wiggle variance on a per-particle basis.\n *\n *\n * @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`\n * over a particle's lifetime. Rotation values affect both a particle's position and the forces applied to it.\n * @property {Object} [rotation.axis=new THREE.Vector3(0, 1, 0)] A THREE.Vector3 instance describing this emitter's axis of rotation.\n * @property {Object} [rotation.axisSpread=new THREE.Vector3()] A THREE.Vector3 instance describing the amount of variance to apply to the axis of rotation on\n * a per-particle basis.\n * @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.\n * Otherwise, the particles will rotate from 0radians to this value over their lifetimes.\n * @property {Number} [rotation.angleSpread=0] The amount of variance in each particle's rotation angle.\n * @property {Boolean} [rotation.static=false] Whether the rotation should be static or not.\n * @property {Object} [rotation.center=The value of `position.value`] A THREE.Vector3 instance describing the center point of rotation.\n * @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.\n *\n *\n * @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\n * given to describe specific value changes over a particle's lifetime.\n * Depending on the value of SPE.valueOverLifetimeLength, if arrays of THREE.Color instances are given, then the array will be interpolated to\n * have a length matching the value of SPE.valueOverLifetimeLength.\n * @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.\n * @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.\n * @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.\n *\n *\n * @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\n * given to describe specific value changes over a particle's lifetime.\n * Depending on the value of SPE.valueOverLifetimeLength, if arrays of numbers are given, then the array will be interpolated to\n * have a length matching the value of SPE.valueOverLifetimeLength.\n * @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.\n * @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.\n * @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.\n *\n *\n * @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\n * given to describe specific value changes over a particle's lifetime.\n * Depending on the value of SPE.valueOverLifetimeLength, if arrays of numbers are given, then the array will be interpolated to\n * have a length matching the value of SPE.valueOverLifetimeLength.\n * @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.\n * @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.\n * @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.\n *\n *\n * @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.\n * NOTE: if a particle's texture is a sprite-sheet, this value IS IGNORED.\n * This property is a \"value-over-lifetime\" property, meaning an array of values and spreads can be\n * given to describe specific value changes over a particle's lifetime.\n * Depending on the value of SPE.valueOverLifetimeLength, if arrays of numbers are given, then the array will be interpolated to\n * have a length matching the value of SPE.valueOverLifetimeLength.\n * @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.\n * @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.\n * @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.\n *\n */\n\n/**\n * The SPE.Emitter class.\n *\n * @constructor\n *\n * @param {EmitterOptions} options A map of options to configure the emitter.\n */\nSPE.Emitter = function( options ) {\n 'use strict';\n\n var utils = SPE.utils,\n types = utils.types,\n lifetimeLength = SPE.valueOverLifetimeLength;\n\n // Ensure we have a map of options to play with,\n // and that each option is in the correct format.\n options = utils.ensureTypedArg( options, types.OBJECT, {} );\n options.position = utils.ensureTypedArg( options.position, types.OBJECT, {} );\n options.velocity = utils.ensureTypedArg( options.velocity, types.OBJECT, {} );\n options.acceleration = utils.ensureTypedArg( options.acceleration, types.OBJECT, {} );\n options.radius = utils.ensureTypedArg( options.radius, types.OBJECT, {} );\n options.drag = utils.ensureTypedArg( options.drag, types.OBJECT, {} );\n options.rotation = utils.ensureTypedArg( options.rotation, types.OBJECT, {} );\n options.color = utils.ensureTypedArg( options.color, types.OBJECT, {} );\n options.opacity = utils.ensureTypedArg( options.opacity, types.OBJECT, {} );\n options.size = utils.ensureTypedArg( options.size, types.OBJECT, {} );\n options.angle = utils.ensureTypedArg( options.angle, types.OBJECT, {} );\n options.wiggle = utils.ensureTypedArg( options.wiggle, types.OBJECT, {} );\n options.maxAge = utils.ensureTypedArg( options.maxAge, types.OBJECT, {} );\n\n if ( options.onParticleSpawn ) {\n console.warn( 'onParticleSpawn has been removed. Please set properties directly to alter values at runtime.' );\n }\n\n this.uuid = THREE.MathUtils.generateUUID();\n\n this.type = utils.ensureTypedArg( options.type, types.NUMBER, SPE.distributions.BOX );\n\n // Start assigning properties...kicking it off with props that DON'T support values over\n // lifetimes.\n //\n // Btw, values over lifetimes are just the new way of referring to *Start, *Middle, and *End.\n this.position = {\n _value: utils.ensureInstanceOf( options.position.value, THREE.Vector3, new THREE.Vector3() ),\n _spread: utils.ensureInstanceOf( options.position.spread, THREE.Vector3, new THREE.Vector3() ),\n _spreadClamp: utils.ensureInstanceOf( options.position.spreadClamp, THREE.Vector3, new THREE.Vector3() ),\n _distribution: utils.ensureTypedArg( options.position.distribution, types.NUMBER, this.type ),\n _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false ),\n _radius: utils.ensureTypedArg( options.position.radius, types.NUMBER, 10 ),\n _radiusScale: utils.ensureInstanceOf( options.position.radiusScale, THREE.Vector3, new THREE.Vector3( 1, 1, 1 ) ),\n _distributionClamp: utils.ensureTypedArg( options.position.distributionClamp, types.NUMBER, 0 ),\n };\n\n this.velocity = {\n _value: utils.ensureInstanceOf( options.velocity.value, THREE.Vector3, new THREE.Vector3() ),\n _spread: utils.ensureInstanceOf( options.velocity.spread, THREE.Vector3, new THREE.Vector3() ),\n _distribution: utils.ensureTypedArg( options.velocity.distribution, types.NUMBER, this.type ),\n _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )\n };\n\n this.acceleration = {\n _value: utils.ensureInstanceOf( options.acceleration.value, THREE.Vector3, new THREE.Vector3() ),\n _spread: utils.ensureInstanceOf( options.acceleration.spread, THREE.Vector3, new THREE.Vector3() ),\n _distribution: utils.ensureTypedArg( options.acceleration.distribution, types.NUMBER, this.type ),\n _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )\n };\n\n this.drag = {\n _value: utils.ensureTypedArg( options.drag.value, types.NUMBER, 0 ),\n _spread: utils.ensureTypedArg( options.drag.spread, types.NUMBER, 0 ),\n _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )\n };\n\n this.wiggle = {\n _value: utils.ensureTypedArg( options.wiggle.value, types.NUMBER, 0 ),\n _spread: utils.ensureTypedArg( options.wiggle.spread, types.NUMBER, 0 )\n };\n\n this.rotation = {\n _axis: utils.ensureInstanceOf( options.rotation.axis, THREE.Vector3, new THREE.Vector3( 0.0, 1.0, 0.0 ) ),\n _axisSpread: utils.ensureInstanceOf( options.rotation.axisSpread, THREE.Vector3, new THREE.Vector3() ),\n _angle: utils.ensureTypedArg( options.rotation.angle, types.NUMBER, 0 ),\n _angleSpread: utils.ensureTypedArg( options.rotation.angleSpread, types.NUMBER, 0 ),\n _static: utils.ensureTypedArg( options.rotation.static, types.BOOLEAN, false ),\n _center: utils.ensureInstanceOf( options.rotation.center, THREE.Vector3, this.position._value.clone() ),\n _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )\n };\n\n\n this.maxAge = {\n _value: utils.ensureTypedArg( options.maxAge.value, types.NUMBER, 2 ),\n _spread: utils.ensureTypedArg( options.maxAge.spread, types.NUMBER, 0 )\n };\n\n\n\n // The following properties can support either single values, or an array of values that change\n // the property over a particle's lifetime (value over lifetime).\n this.color = {\n _value: utils.ensureArrayInstanceOf( options.color.value, THREE.Color, new THREE.Color() ),\n _spread: utils.ensureArrayInstanceOf( options.color.spread, THREE.Vector3, new THREE.Vector3() ),\n _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )\n };\n\n this.opacity = {\n _value: utils.ensureArrayTypedArg( options.opacity.value, types.NUMBER, 1 ),\n _spread: utils.ensureArrayTypedArg( options.opacity.spread, types.NUMBER, 0 ),\n _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )\n };\n\n this.size = {\n _value: utils.ensureArrayTypedArg( options.size.value, types.NUMBER, 1 ),\n _spread: utils.ensureArrayTypedArg( options.size.spread, types.NUMBER, 0 ),\n _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )\n };\n\n this.angle = {\n _value: utils.ensureArrayTypedArg( options.angle.value, types.NUMBER, 0 ),\n _spread: utils.ensureArrayTypedArg( options.angle.spread, types.NUMBER, 0 ),\n _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )\n };\n\n\n // Assign renaining option values.\n this.particleCount = utils.ensureTypedArg( options.particleCount, types.NUMBER, 100 );\n this.duration = utils.ensureTypedArg( options.duration, types.NUMBER, null );\n this.isStatic = utils.ensureTypedArg( options.isStatic, types.BOOLEAN, false );\n this.activeMultiplier = utils.ensureTypedArg( options.activeMultiplier, types.NUMBER, 1 );\n this.direction = utils.ensureTypedArg( options.direction, types.NUMBER, 1 );\n\n // Whether this emitter is alive or not.\n this.alive = utils.ensureTypedArg( options.alive, types.BOOLEAN, true );\n\n\n // The following properties are set internally and are not\n // user-controllable.\n this.particlesPerSecond = 0;\n\n // The current particle index for which particles should\n // be marked as active on the next update cycle.\n this.activationIndex = 0;\n\n // The offset in the typed arrays this emitter's\n // particle's values will start at\n this.attributeOffset = 0;\n\n // The end of the range in the attribute buffers\n this.attributeEnd = 0;\n\n\n\n // Holds the time the emitter has been alive for.\n this.age = 0.0;\n\n // Holds the number of currently-alive particles\n this.activeParticleCount = 0.0;\n\n // Holds a reference to this emitter's group once\n // it's added to one.\n this.group = null;\n\n // Holds a reference to this emitter's group's attributes object\n // for easier access.\n this.attributes = null;\n\n // Holds a reference to the params attribute's typed array\n // for quicker access.\n this.paramsArray = null;\n\n // A set of flags to determine whether particular properties\n // should be re-randomised when a particle is reset.\n //\n // If a `randomise` property is given, this is preferred.\n // Otherwise, it looks at whether a spread value has been\n // given.\n //\n // It allows randomization to be turned off as desired. If\n // all randomization is turned off, then I'd expect a performance\n // boost as no attribute buffers (excluding the `params`)\n // would have to be re-passed to the GPU each frame (since nothing\n // except the `params` attribute would have changed).\n this.resetFlags = {\n // params: utils.ensureTypedArg( options.maxAge.randomise, types.BOOLEAN, !!options.maxAge.spread ) ||\n // utils.ensureTypedArg( options.wiggle.randomise, types.BOOLEAN, !!options.wiggle.spread ),\n position: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false ) ||\n utils.ensureTypedArg( options.radius.randomise, types.BOOLEAN, false ),\n velocity: utils.ensureTypedArg( options.velocity.randomise, types.BOOLEAN, false ),\n acceleration: utils.ensureTypedArg( options.acceleration.randomise, types.BOOLEAN, false ) ||\n utils.ensureTypedArg( options.drag.randomise, types.BOOLEAN, false ),\n rotation: utils.ensureTypedArg( options.rotation.randomise, types.BOOLEAN, false ),\n rotationCenter: utils.ensureTypedArg( options.rotation.randomise, types.BOOLEAN, false ),\n size: utils.ensureTypedArg( options.size.randomise, types.BOOLEAN, false ),\n color: utils.ensureTypedArg( options.color.randomise, types.BOOLEAN, false ),\n opacity: utils.ensureTypedArg( options.opacity.randomise, types.BOOLEAN, false ),\n angle: utils.ensureTypedArg( options.angle.randomise, types.BOOLEAN, false )\n };\n\n this.updateFlags = {};\n this.updateCounts = {};\n\n // A map to indicate which emitter parameters should update\n // which attribute.\n this.updateMap = {\n maxAge: 'params',\n position: 'position',\n velocity: 'velocity',\n acceleration: 'acceleration',\n drag: 'acceleration',\n wiggle: 'params',\n rotation: 'rotation',\n size: 'size',\n color: 'color',\n opacity: 'opacity',\n angle: 'angle'\n };\n\n for ( var i in this.updateMap ) {\n if ( this.updateMap.hasOwnProperty( i ) ) {\n this.updateCounts[ this.updateMap[ i ] ] = 0.0;\n this.updateFlags[ this.updateMap[ i ] ] = false;\n this._createGetterSetters( this[ i ], i );\n }\n }\n\n this.bufferUpdateRanges = {};\n this.attributeKeys = null;\n this.attributeCount = 0;\n\n\n // Ensure that the value-over-lifetime property objects above\n // have value and spread properties that are of the same length.\n //\n // Also, for now, make sure they have a length of 3 (min/max arguments here).\n utils.ensureValueOverLifetimeCompliance( this.color, lifetimeLength, lifetimeLength );\n utils.ensureValueOverLifetimeCompliance( this.opacity, lifetimeLength, lifetimeLength );\n utils.ensureValueOverLifetimeCompliance( this.size, lifetimeLength, lifetimeLength );\n utils.ensureValueOverLifetimeCompliance( this.angle, lifetimeLength, lifetimeLength );\n};\n\nSPE.Emitter.constructor = SPE.Emitter;\n\nSPE.Emitter.prototype._createGetterSetters = function( propObj, propName ) {\n 'use strict';\n\n var self = this;\n\n for ( var i in propObj ) {\n if ( propObj.hasOwnProperty( i ) ) {\n\n var name = i.replace( '_', '' );\n\n Object.defineProperty( propObj, name, {\n get: ( function( prop ) {\n return function() {\n return this[ prop ];\n };\n }( i ) ),\n\n set: ( function( prop ) {\n return function( value ) {\n var mapName = self.updateMap[ propName ],\n prevValue = this[ prop ],\n length = SPE.valueOverLifetimeLength;\n\n if ( prop === '_rotationCenter' ) {\n self.updateFlags.rotationCenter = true;\n self.updateCounts.rotationCenter = 0.0;\n }\n else if ( prop === '_randomise' ) {\n self.resetFlags[ mapName ] = value;\n }\n else {\n self.updateFlags[ mapName ] = true;\n self.updateCounts[ mapName ] = 0.0;\n }\n\n self.group._updateDefines();\n\n this[ prop ] = value;\n\n // If the previous value was an array, then make\n // sure the provided value is interpolated correctly.\n if ( Array.isArray( prevValue ) ) {\n SPE.utils.ensureValueOverLifetimeCompliance( self[ propName ], length, length );\n }\n };\n }( i ) )\n } );\n }\n }\n};\n\nSPE.Emitter.prototype._setBufferUpdateRanges = function( keys ) {\n 'use strict';\n\n this.attributeKeys = keys;\n this.attributeCount = keys.length;\n\n for ( var i = this.attributeCount - 1; i >= 0; --i ) {\n this.bufferUpdateRanges[ keys[ i ] ] = {\n min: Number.POSITIVE_INFINITY,\n max: Number.NEGATIVE_INFINITY\n };\n }\n};\n\nSPE.Emitter.prototype._calculatePPSValue = function( groupMaxAge ) {\n 'use strict';\n\n var particleCount = this.particleCount;\n\n\n // Calculate the `particlesPerSecond` value for this emitter. It's used\n // when determining which particles should die and which should live to\n // see another day. Or be born, for that matter. The \"God\" property.\n if ( this.duration ) {\n this.particlesPerSecond = particleCount / ( groupMaxAge < this.duration ? groupMaxAge : this.duration );\n }\n else {\n this.particlesPerSecond = particleCount / groupMaxAge;\n }\n};\n\nSPE.Emitter.prototype._setAttributeOffset = function( startIndex ) {\n this.attributeOffset = startIndex;\n this.activationIndex = startIndex;\n this.activationEnd = startIndex + this.particleCount;\n};\n\n\nSPE.Emitter.prototype._assignValue = function( prop, index ) {\n 'use strict';\n\n switch ( prop ) {\n case 'position':\n this._assignPositionValue( index );\n break;\n\n case 'velocity':\n case 'acceleration':\n this._assignForceValue( index, prop );\n break;\n\n case 'size':\n case 'opacity':\n this._assignAbsLifetimeValue( index, prop );\n break;\n\n case 'angle':\n this._assignAngleValue( index );\n break;\n\n case 'params':\n this._assignParamsValue( index );\n break;\n\n case 'rotation':\n this._assignRotationValue( index );\n break;\n\n case 'color':\n this._assignColorValue( index );\n break;\n }\n};\n\nSPE.Emitter.prototype._assignPositionValue = function( index ) {\n 'use strict';\n\n var distributions = SPE.distributions,\n utils = SPE.utils,\n prop = this.position,\n attr = this.attributes.position,\n value = prop._value,\n spread = prop._spread,\n distribution = prop._distribution;\n\n switch ( distribution ) {\n case distributions.BOX:\n utils.randomVector3( attr, index, value, spread, prop._spreadClamp );\n break;\n\n case distributions.SPHERE:\n utils.randomVector3OnSphere( attr, index, value, prop._radius, prop._spread.x, prop._radiusScale, prop._spreadClamp.x, prop._distributionClamp || this.particleCount );\n break;\n\n case distributions.DISC:\n utils.randomVector3OnDisc( attr, index, value, prop._radius, prop._spread.x, prop._radiusScale, prop._spreadClamp.x );\n break;\n\n case distributions.LINE:\n utils.randomVector3OnLine( attr, index, value, spread );\n break;\n }\n};\n\nSPE.Emitter.prototype._assignForceValue = function( index, attrName ) {\n 'use strict';\n\n var distributions = SPE.distributions,\n utils = SPE.utils,\n prop = this[ attrName ],\n value = prop._value,\n spread = prop._spread,\n distribution = prop._distribution,\n pos,\n positionX,\n positionY,\n positionZ,\n i;\n\n switch ( distribution ) {\n case distributions.BOX:\n utils.randomVector3( this.attributes[ attrName ], index, value, spread );\n break;\n\n case distributions.SPHERE:\n pos = this.attributes.position.typedArray.array;\n i = index * 3;\n\n // Ensure position values aren't zero, otherwise no force will be\n // applied.\n // positionX = utils.zeroToEpsilon( pos[ i ], true );\n // positionY = utils.zeroToEpsilon( pos[ i + 1 ], true );\n // positionZ = utils.zeroToEpsilon( pos[ i + 2 ], true );\n positionX = pos[ i ];\n positionY = pos[ i + 1 ];\n positionZ = pos[ i + 2 ];\n\n utils.randomDirectionVector3OnSphere(\n this.attributes[ attrName ], index,\n positionX, positionY, positionZ,\n this.position._value,\n prop._value.x,\n prop._spread.x\n );\n break;\n\n case distributions.DISC:\n pos = this.attributes.position.typedArray.array;\n i = index * 3;\n\n // Ensure position values aren't zero, otherwise no force will be\n // applied.\n // positionX = utils.zeroToEpsilon( pos[ i ], true );\n // positionY = utils.zeroToEpsilon( pos[ i + 1 ], true );\n // positionZ = utils.zeroToEpsilon( pos[ i + 2 ], true );\n positionX = pos[ i ];\n positionY = pos[ i + 1 ];\n positionZ = pos[ i + 2 ];\n\n utils.randomDirectionVector3OnDisc(\n this.attributes[ attrName ], index,\n positionX, positionY, positionZ,\n this.position._value,\n prop._value.x,\n prop._spread.x\n );\n break;\n\n case distributions.LINE:\n utils.randomVector3OnLine( this.attributes[ attrName ], index, value, spread );\n break;\n }\n\n if ( attrName === 'acceleration' ) {\n var drag = utils.clamp( utils.randomFloat( this.drag._value, this.drag._spread ), 0, 1 );\n this.attributes.acceleration.typedArray.array[ index * 4 + 3 ] = drag;\n }\n};\n\nSPE.Emitter.prototype._assignAbsLifetimeValue = function( index, propName ) {\n 'use strict';\n\n var array = this.attributes[ propName ].typedArray,\n prop = this[ propName ],\n utils = SPE.utils,\n value;\n\n if ( utils.arrayValuesAreEqual( prop._value ) && utils.arrayValuesAreEqual( prop._spread ) ) {\n value = Math.abs( utils.randomFloat( prop._value[ 0 ], prop._spread[ 0 ] ) );\n array.setVec4Components( index, value, value, value, value );\n }\n else {\n array.setVec4Components( index,\n Math.abs( utils.randomFloat( prop._value[ 0 ], prop._spread[ 0 ] ) ),\n Math.abs( utils.randomFloat( prop._value[ 1 ], prop._spread[ 1 ] ) ),\n Math.abs( utils.randomFloat( prop._value[ 2 ], prop._spread[ 2 ] ) ),\n Math.abs( utils.randomFloat( prop._value[ 3 ], prop._spread[ 3 ] ) )\n );\n }\n};\n\nSPE.Emitter.prototype._assignAngleValue = function( index ) {\n 'use strict';\n\n var array = this.attributes.angle.typedArray,\n prop = this.angle,\n utils = SPE.utils,\n value;\n\n if ( utils.arrayValuesAreEqual( prop._value ) && utils.arrayValuesAreEqual( prop._spread ) ) {\n value = utils.randomFloat( prop._value[ 0 ], prop._spread[ 0 ] );\n array.setVec4Components( index, value, value, value, value );\n }\n else {\n array.setVec4Components( index,\n utils.randomFloat( prop._value[ 0 ], prop._spread[ 0 ] ),\n utils.randomFloat( prop._value[ 1 ], prop._spread[ 1 ] ),\n utils.randomFloat( prop._value[ 2 ], prop._spread[ 2 ] ),\n utils.randomFloat( prop._value[ 3 ], prop._spread[ 3 ] )\n );\n }\n};\n\nSPE.Emitter.prototype._assignParamsValue = function( index ) {\n 'use strict';\n\n this.attributes.params.typedArray.setVec4Components( index,\n this.isStatic ? 1 : 0,\n 0.0,\n Math.abs( SPE.utils.randomFloat( this.maxAge._value, this.maxAge._spread ) ),\n SPE.utils.randomFloat( this.wiggle._value, this.wiggle._spread )\n );\n};\n\nSPE.Emitter.prototype._assignRotationValue = function( index ) {\n 'use strict';\n\n this.attributes.rotation.typedArray.setVec3Components( index,\n SPE.utils.getPackedRotationAxis( this.rotation._axis, this.rotation._axisSpread ),\n SPE.utils.randomFloat( this.rotation._angle, this.rotation._angleSpread ),\n this.rotation._static ? 0 : 1\n );\n\n this.attributes.rotationCenter.typedArray.setVec3( index, this.rotation._center );\n};\n\nSPE.Emitter.prototype._assignColorValue = function( index ) {\n 'use strict';\n SPE.utils.randomColorAsHex( this.attributes.color, index, this.color._value, this.color._spread );\n};\n\nSPE.Emitter.prototype._resetParticle = function( index ) {\n 'use strict';\n\n var resetFlags = this.resetFlags,\n updateFlags = this.updateFlags,\n updateCounts = this.updateCounts,\n keys = this.attributeKeys,\n key,\n updateFlag;\n\n for ( var i = this.attributeCount - 1; i >= 0; --i ) {\n key = keys[ i ];\n updateFlag = updateFlags[ key ];\n\n if ( resetFlags[ key ] === true || updateFlag === true ) {\n this._assignValue( key, index );\n this._updateAttributeUpdateRange( key, index );\n\n if ( updateFlag === true && updateCounts[ key ] === this.particleCount ) {\n updateFlags[ key ] = false;\n updateCounts[ key ] = 0.0;\n }\n else if ( updateFlag == true ) {\n ++updateCounts[ key ];\n }\n }\n }\n};\n\nSPE.Emitter.prototype._updateAttributeUpdateRange = function( attr, i ) {\n 'use strict';\n\n var ranges = this.bufferUpdateRanges[ attr ];\n\n ranges.min = Math.min( i, ranges.min );\n ranges.max = Math.max( i, ranges.max );\n};\n\nSPE.Emitter.prototype._resetBufferRanges = function() {\n 'use strict';\n\n var ranges = this.bufferUpdateRanges,\n keys = this.bufferUpdateKeys,\n i = this.bufferUpdateCount - 1,\n key;\n\n for ( i; i >= 0; --i ) {\n key = keys[ i ];\n ranges[ key ].min = Number.POSITIVE_INFINITY;\n ranges[ key ].max = Number.NEGATIVE_INFINITY;\n }\n};\n\nSPE.Emitter.prototype._onRemove = function() {\n 'use strict';\n // Reset any properties of the emitter that were set by\n // a group when it was added.\n this.particlesPerSecond = 0;\n this.attributeOffset = 0;\n this.activationIndex = 0;\n this.activeParticleCount = 0;\n this.group = null;\n this.attributes = null;\n this.paramsArray = null;\n this.age = 0.0;\n};\n\nSPE.Emitter.prototype._decrementParticleCount = function() {\n 'use strict';\n --this.activeParticleCount;\n\n // TODO:\n // - Trigger event if count === 0.\n};\n\nSPE.Emitter.prototype._incrementParticleCount = function() {\n 'use strict';\n ++this.activeParticleCount;\n\n // TODO:\n // - Trigger event if count === this.particleCount.\n};\n\nSPE.Emitter.prototype._checkParticleAges = function( start, end, params, dt ) {\n 'use strict';\n for ( var i = end - 1, index, maxAge, age, alive; i >= start; --i ) {\n index = i * 4;\n\n alive = params[ index ];\n\n if ( alive === 0.0 ) {\n continue;\n }\n\n // Increment age\n age = params[ index + 1 ];\n maxAge = params[ index + 2 ];\n\n if ( this.direction === 1 ) {\n age += dt;\n\n if ( age >= maxAge ) {\n age = 0.0;\n alive = 0.0;\n this._decrementParticleCount();\n }\n }\n else {\n age -= dt;\n\n if ( age <= 0.0 ) {\n age = maxAge;\n alive = 0.0;\n this._decrementParticleCount();\n }\n }\n\n params[ index ] = alive;\n params[ index + 1 ] = age;\n\n this._updateAttributeUpdateRange( 'params', i );\n }\n};\n\nSPE.Emitter.prototype._activateParticles = function( activationStart, activationEnd, params, dtPerParticle ) {\n 'use strict';\n var direction = this.direction;\n\n for ( var i = activationStart, index, dtValue; i < activationEnd; ++i ) {\n index = i * 4;\n\n // Don't re-activate particles that aren't dead yet.\n // if ( params[ index ] !== 0.0 && ( this.particleCount !== 1 || this.activeMultiplier !== 1 ) ) {\n // continue;\n // }\n\n if ( params[ index ] != 0.0 && this.particleCount !== 1 ) {\n continue;\n }\n\n // Increment the active particle count.\n this._incrementParticleCount();\n\n // Mark the particle as alive.\n params[ index ] = 1.0;\n\n // Reset the particle\n this._resetParticle( i );\n\n // Move each particle being activated to\n // it's actual position in time.\n //\n // This stops particles being 'clumped' together\n // when frame rates are on the lower side of 60fps\n // or not constant (a very real possibility!)\n dtValue = dtPerParticle * ( i - activationStart )\n params[ index + 1 ] = direction === -1 ? params[ index + 2 ] - dtValue : dtValue;\n\n this._updateAttributeUpdateRange( 'params', i );\n }\n};\n\n/**\n * Simulates one frame's worth of particles, updating particles\n * that are already alive, and marking ones that are currently dead\n * but should be alive as alive.\n *\n * If the emitter is marked as static, then this function will do nothing.\n *\n * @param {Number} dt The number of seconds to simulate (deltaTime)\n */\nSPE.Emitter.prototype.tick = function( dt ) {\n 'use strict';\n\n if ( this.isStatic ) {\n return;\n }\n\n if ( this.paramsArray === null ) {\n this.paramsArray = this.attributes.params.typedArray.array;\n }\n\n var start = this.attributeOffset,\n end = start + this.particleCount,\n params = this.paramsArray, // vec3( alive, age, maxAge, wiggle )\n ppsDt = this.particlesPerSecond * this.activeMultiplier * dt,\n activationIndex = this.activationIndex;\n\n // Reset the buffer update indices.\n this._resetBufferRanges();\n\n // Increment age for those particles that are alive,\n // and kill off any particles whose age is over the limit.\n this._checkParticleAges( start, end, params, dt );\n\n // If the emitter is dead, reset the age of the emitter to zero,\n // ready to go again if required\n if ( this.alive === false ) {\n this.age = 0.0;\n return;\n }\n\n // If the emitter has a specified lifetime and we've exceeded it,\n // mark the emitter as dead.\n if ( this.duration !== null && this.age > this.duration ) {\n this.alive = false;\n this.age = 0.0;\n return;\n }\n\n\n var activationStart = this.particleCount === 1 ? activationIndex : ( activationIndex | 0 ),\n activationEnd = Math.min( activationStart + ppsDt, this.activationEnd ),\n activationCount = activationEnd - this.activationIndex | 0,\n dtPerParticle = activationCount > 0 ? dt / activationCount : 0;\n\n this._activateParticles( activationStart, activationEnd, params, dtPerParticle );\n\n // Move the activation window forward, soldier.\n this.activationIndex += ppsDt;\n\n if ( this.activationIndex > end ) {\n this.activationIndex = start;\n }\n\n\n // Increment the age of the emitter.\n this.age += dt;\n};\n\n/**\n * Resets all the emitter's particles to their start positions\n * and marks the particles as dead if the `force` argument is\n * true.\n *\n * @param {Boolean} [force=undefined] If true, all particles will be marked as dead instantly.\n * @return {Emitter} This emitter instance.\n */\nSPE.Emitter.prototype.reset = function( force ) {\n 'use strict';\n\n this.age = 0.0;\n this.alive = false;\n\n if ( force === true ) {\n var start = this.attributeOffset,\n end = start + this.particleCount,\n array = this.paramsArray,\n attr = this.attributes.params.bufferAttribute;\n\n for ( var i = end - 1, index; i >= start; --i ) {\n index = i * 4;\n\n array[ index ] = 0.0;\n array[ index + 1 ] = 0.0;\n }\n\n attr.updateRange.offset = 0;\n attr.updateRange.count = -1;\n attr.needsUpdate = true;\n }\n\n return this;\n};\n\n/**\n * Enables the emitter. If not already enabled, the emitter\n * will start emitting particles.\n *\n * @return {Emitter} This emitter instance.\n */\nSPE.Emitter.prototype.enable = function() {\n 'use strict';\n this.alive = true;\n return this;\n};\n\n/**\n * Disables th emitter, but does not instantly remove it's\n * particles fromt the scene. When called, the emitter will be\n * 'switched off' and just stop emitting. Any particle's alive will\n * be allowed to finish their lifecycle.\n *\n * @return {Emitter} This emitter instance.\n */\nSPE.Emitter.prototype.disable = function() {\n 'use strict';\n\n this.alive = false;\n return this;\n};\n\n/**\n * Remove this emitter from it's parent group (if it has been added to one).\n * Delgates to SPE.group.prototype.removeEmitter().\n *\n * When called, all particle's belonging to this emitter will be instantly\n * removed from the scene.\n *\n * @return {Emitter} This emitter instance.\n *\n * @see SPE.Group.prototype.removeEmitter\n */\nSPE.Emitter.prototype.remove = function() {\n 'use strict';\n if ( this.group !== null ) {\n this.group.removeEmitter( this );\n }\n else {\n console.error( 'Emitter does not belong to a group, cannot remove.' );\n }\n\n return this;\n};\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","/**\n * Particles component for A-Frame.\n *\n * ShaderParticleEngine by Squarefeet (https://github.com/squarefeet).\n */\n\nvar SPE = require('./lib/SPE.js');\n\nif (typeof AFRAME === 'undefined') {\n throw new Error('Component attempted to register before AFRAME was available.');\n}\n\nAFRAME.registerComponent('particle-system', {\n\n schema: {\n preset: {\n type: 'string',\n default: '',\n oneOf: ['default', 'dust', 'snow', 'rain']\n },\n maxAge: {\n type: 'number',\n default: 6\n },\n positionSpread: {\n type: 'vec3',\n default: { x: 0, y: 0, z: 0 }\n },\n type: {\n type: 'number',\n default: SPE.distributions.BOX\n },\n rotationAxis: {\n type: 'string',\n default: 'x'\n },\n rotationAngle: {\n type: 'number',\n default: 0\n },\n rotationAngleSpread: {\n type: 'number',\n default: 0\n },\n accelerationValue: {\n type: 'vec3',\n default: { x: 0, y: -10, z: 0 }\n },\n accelerationSpread: {\n type: 'vec3',\n default: { x: 10, y: 0, z: 10 }\n },\n velocityValue: {\n type: 'vec3',\n default: { x: 0, y: 25, z: 0 }\n },\n velocitySpread: {\n type: 'vec3',\n default: { x: 10, y: 7.5, z: 10 }\n },\n dragValue: {\n type: 'number',\n default: 0\n },\n dragSpread: {\n type: 'number',\n default: 0\n },\n dragRandomise: {\n type: 'boolean',\n default: false\n },\n color: {\n type: 'array',\n default: [ '#0000FF', '#FF0000' ]\n },\n size: {\n type: 'array',\n default: [ '1' ]\n },\n sizeSpread: {\n type: 'array',\n default: [ '0' ]\n },\n direction: {\n type: 'number',\n default: 1\n },\n duration: {\n type: 'number',\n default: Infinity\n },\n particleCount: {\n type: 'number',\n default: 1000\n },\n texture: {\n type: 'asset',\n default: 'https://cdn.rawgit.com/IdeaSpaceVR/aframe-particle-system-component/master/dist/images/star2.png'\n },\n randomise: {\n type: 'boolean',\n default: false\n },\n opacity: {\n type: 'array',\n default: [ '1' ]\n },\n opacitySpread: {\n type: 'array',\n default: [ '0' ]\n },\n maxParticleCount: {\n type: 'number',\n default: 250000\n },\n blending: {\n type: 'number',\n default: THREE.AdditiveBlending,\n oneOf: [THREE.NoBlending,THREE.NormalBlending,THREE.AdditiveBlending,THREE.SubtractiveBlending,THREE.MultiplyBlending]\n },\n enabled: {\n type:'boolean',\n default:true\n }\n },\n\n\n init: function() {\n\n this.presets = {};\n\n /* preset settings can be overwritten */\n\n this.presets['dust'] = {\n maxAge: 20,\n positionSpread: {x:100,y:100,z:100},\n rotationAngle: 3.14,\n accelerationValue: {x: 0, y: 0, z: 0},\n accelerationSpread: {x: 0, y: 0, z: 0},\n velocityValue: {x: 1, y: 0.3, z: 1},\n velocitySpread: {x: 0.5, y: 1, z: 0.5},\n color: ['#FFFFFF'],\n particleCount: 100,\n texture: 'https://cdn.rawgit.com/IdeaSpaceVR/aframe-particle-system-component/master/dist/images/smokeparticle.png'\n };\n\n\n this.presets['snow'] = {\n maxAge: 20,\n positionSpread: {x:100,y:100,z:100},\n rotationAngle: 3.14,\n accelerationValue: {x: 0, y: 0, z: 0},\n accelerationSpread: {x: 0.2, y: 0, z: 0.2},\n velocityValue: {x: 0, y: 8, z: 0},\n velocitySpread: {x: 2, y: 0, z: 2},\n color: ['#FFFFFF'],\n particleCount: 200,\n texture: 'https://cdn.rawgit.com/IdeaSpaceVR/aframe-particle-system-component/master/dist/images/smokeparticle.png'\n };\n\n\n this.presets['rain'] = {\n maxAge: 1,\n positionSpread: {x:100,y:100,z:100},\n rotationAngle: 3.14,\n accelerationValue: {x: 0, y: 3, z: 0},\n accelerationSpread: {x: 2, y: 1, z: 2},\n velocityValue: {x: 0, y: 75, z: 0},\n velocitySpread: {x: 10, y: 50, z: 10},\n color: ['#FFFFFF'],\n size: [0.4],\n texture: 'https://cdn.rawgit.com/IdeaSpaceVR/aframe-particle-system-component/master/dist/images/raindrop.png'\n };\n\n\n },\n\n\n update: function (oldData) {\n\n // Remove old particle group.\n if (this.particleGroup) {\n this.el.removeObject3D('particle-system');\n }\n\n // Set the selected preset, if any, or use an empty object to keep schema defaults\n this.preset = this.presets[this.data.preset] || {};\n\n // Get custom, preset, or default data for each property defined in the schema\n for (var key in this.data) {\n this.data[key] = this.applyPreset(key);\n }\n\n this.initParticleSystem(this.data);\n\n if(this.data.enabled === true) {\n this.startParticles()\n } else {\n this.stopParticles()\n }\n },\n\n\n applyPreset: function (key) {\n // !this.attrValue[key] = the user did not set a custom value\n // this.preset[key] = there exists a value for this key in the selected preset\n if (!this.attrValue[key] && this.preset[key]) {\n return this.preset[key];\n } else {\n // Otherwise stick to the user or schema default value\n return this.data[key];\n }\n },\n\n\n tick: function(time, dt) {\n\n this.particleGroup.tick(dt / 1000);\n },\n\n\n remove: function() {\n\n // Remove particle system.\n if (!this.particleGroup) { return; }\n this.el.removeObject3D('particle-system');\n },\n\n startParticles: function() {\n this.particleGroup.emitters.forEach(function(em) { em.enable() });\n },\n\n stopParticles: function() {\n this.particleGroup.emitters.forEach(function(em) { em.disable() });\n },\n\n\n initParticleSystem: function(settings) {\n\n var loader = new THREE.TextureLoader();\n var particle_texture = loader.load(\n settings.texture,\n function (texture) {\n return texture;\n },\n function (xhr) {\n console.log((xhr.loaded / xhr.total * 100) + '% loaded');\n },\n function (xhr) {\n console.log('An error occurred');\n }\n );\n\n this.particleGroup = new SPE.Group({\n texture: {\n value: particle_texture\n },\n maxParticleCount: settings.maxParticleCount,\n blending: settings.blending\n });\n\n var emitter = new SPE.Emitter({\n maxAge: {\n value: settings.maxAge\n },\n type: {\n value: settings.type\n },\n position: {\n spread: new THREE.Vector3(settings.positionSpread.x, settings.positionSpread.y, settings.positionSpread.z),\n randomise: settings.randomise\n //spreadClamp: new THREE.Vector3( 2, 2, 2 ),\n //radius: 4\n },\n rotation: {\n axis: (settings.rotationAxis=='x'?new THREE.Vector3(1, 0, 0):(settings.rotationAxis=='y'?new THREE.Vector3(0, 1, 0):(settings.rotationAxis=='z'?new THREE.Vector3(0, 0, 1):new THREE.Vector3(0, 1, 0)))),\n angle: settings.rotationAngle,\n angleSpread: settings.rotationAngleSpread,\n static: true\n },\n acceleration: {\n value: new THREE.Vector3(settings.accelerationValue.x, settings.accelerationValue.y, settings.accelerationValue.z),\n spread: new THREE.Vector3(settings.accelerationSpread.x, settings.accelerationSpread.y, settings.accelerationSpread.z)\n },\n velocity: {\n value: new THREE.Vector3(settings.velocityValue.x, settings.velocityValue.y, settings.velocityValue.z),\n spread: new THREE.Vector3(settings.velocitySpread.x, settings.velocitySpread.y, settings.velocitySpread.z)\n },\n drag: {\n value: new THREE.Vector3(settings.dragValue.x, settings.dragValue.y, settings.dragValue.z),\n spread: new THREE.Vector3(settings.dragSpread.x, settings.dragSpread.y, settings.dragSpread.z),\n randomise: settings.dragRandomise\n },\n color: {\n value: settings.color.map(function(c) { return new THREE.Color(c); })\n },\n size: { value: settings.size.map(function (s) { return parseFloat(s); }),\n spread: settings.sizeSpread.map(function (s) { return parseFloat(s); }) },\n\n /*wiggle: { value: 4, spread: 2 }, //settings.wiggle,*/\n /*drag: {\n value: settings.drag\n },*/\n direction: {\n value: settings.direction\n },\n duration: settings.duration,\n opacity: { value: settings.opacity.map(function (o) { return parseFloat(o); }),\n spread: settings.opacitySpread.map(function (o) { return parseFloat(o); }) },\n particleCount: settings.particleCount\n });\n\n this.particleGroup.addEmitter(emitter);\n this.particleGroup.mesh.frustumCulled = false;\n this.el.setObject3D('particle-system', this.particleGroup.mesh);\n }\n});\n"],"names":[],"sourceRoot":""} \ No newline at end of file diff --git a/dist/aframe-particle-system-component.min.js b/dist/aframe-particle-system-component.min.js index 5052ba9..5da823a 100644 --- a/dist/aframe-particle-system-component.min.js +++ b/dist/aframe-particle-system-component.min.js @@ -1,2 +1,2 @@ -!function(e){function t(i){if(r[i])return r[i].exports;var a=r[i]={exports:{},id:i,loaded:!1};return e[i].call(a.exports,a,a.exports,t),a.loaded=!0,a.exports}var r={};return t.m=e,t.c=r,t.p="",t(0)}([function(e,t,r){var i=r(1);if("undefined"==typeof AFRAME)throw new Error("Component attempted to register before AFRAME was available.");AFRAME.registerComponent("particle-system",{schema:{preset:{type:"string",default:"",oneOf:["default","dust","snow","rain"]},maxAge:{type:"number",default:6},positionSpread:{type:"vec3",default:{x:0,y:0,z:0}},type:{type:"number",default:i.distributions.BOX},rotationAxis:{type:"string",default:"x"},rotationAngle:{type:"number",default:0},rotationAngleSpread:{type:"number",default:0},accelerationValue:{type:"vec3",default:{x:0,y:-10,z:0}},accelerationSpread:{type:"vec3",default:{x:10,y:0,z:10}},velocityValue:{type:"vec3",default:{x:0,y:25,z:0}},velocitySpread:{type:"vec3",default:{x:10,y:7.5,z:10}},dragValue:{type:"number",default:0},dragSpread:{type:"number",default:0},dragRandomise:{type:"boolean",default:!1},color:{type:"array",default:["#0000FF","#FF0000"]},size:{type:"array",default:["1"]},sizeSpread:{type:"array",default:["0"]},direction:{type:"number",default:1},duration:{type:"number",default:1/0},particleCount:{type:"number",default:1e3},texture:{type:"asset",default:"https://cdn.rawgit.com/IdeaSpaceVR/aframe-particle-system-component/master/dist/images/star2.png"},randomise:{type:"boolean",default:!1},opacity:{type:"array",default:["1"]},opacitySpread:{type:"array",default:["0"]},maxParticleCount:{type:"number",default:25e4},blending:{type:"number",default:THREE.AdditiveBlending,oneOf:[THREE.NoBlending,THREE.NormalBlending,THREE.AdditiveBlending,THREE.SubtractiveBlending,THREE.MultiplyBlending]},enabled:{type:"boolean",default:!0}},init:function(){this.presets={},this.presets.dust={maxAge:20,positionSpread:{x:100,y:100,z:100},rotationAngle:3.14,accelerationValue:{x:0,y:0,z:0},accelerationSpread:{x:0,y:0,z:0},velocityValue:{x:1,y:.3,z:1},velocitySpread:{x:.5,y:1,z:.5},color:["#FFFFFF"],particleCount:100,texture:"https://cdn.rawgit.com/IdeaSpaceVR/aframe-particle-system-component/master/dist/images/smokeparticle.png"},this.presets.snow={maxAge:20,positionSpread:{x:100,y:100,z:100},rotationAngle:3.14,accelerationValue:{x:0,y:0,z:0},accelerationSpread:{x:.2,y:0,z:.2},velocityValue:{x:0,y:8,z:0},velocitySpread:{x:2,y:0,z:2},color:["#FFFFFF"],particleCount:200,texture:"https://cdn.rawgit.com/IdeaSpaceVR/aframe-particle-system-component/master/dist/images/smokeparticle.png"},this.presets.rain={maxAge:1,positionSpread:{x:100,y:100,z:100},rotationAngle:3.14,accelerationValue:{x:0,y:3,z:0},accelerationSpread:{x:2,y:1,z:2},velocityValue:{x:0,y:75,z:0},velocitySpread:{x:10,y:50,z:10},color:["#FFFFFF"],size:.4,texture:"https://cdn.rawgit.com/IdeaSpaceVR/aframe-particle-system-component/master/dist/images/raindrop.png"}},update:function(e){this.particleGroup&&this.el.removeObject3D("particle-system"),this.preset=this.presets[this.data.preset]||{};for(var t in this.data)this.data[t]=this.applyPreset(t);this.initParticleSystem(this.data),this.data.enabled===!0?this.startParticles():this.stopParticles()},applyPreset:function(e){return!this.attrValue[e]&&this.preset[e]?this.preset[e]:this.data[e]},tick:function(e,t){this.particleGroup.tick(t/1e3)},remove:function(){this.particleGroup&&this.el.removeObject3D("particle-system")},startParticles:function(){this.particleGroup.emitters.forEach(function(e){e.enable()})},stopParticles:function(){this.particleGroup.emitters.forEach(function(e){e.disable()})},initParticleSystem:function(e){var t=new THREE.TextureLoader,r=t.load(e.texture,function(e){return e},function(e){console.log(e.loaded/e.total*100+"% loaded")},function(e){console.log("An error occurred")});this.particleGroup=new i.Group({texture:{value:r},maxParticleCount:e.maxParticleCount,blending:e.blending});var a=new i.Emitter({maxAge:{value:e.maxAge},type:{value:e.type},position:{spread:new THREE.Vector3(e.positionSpread.x,e.positionSpread.y,e.positionSpread.z),randomise:e.randomise},rotation:{axis:"x"==e.rotationAxis?new THREE.Vector3(1,0,0):"y"==e.rotationAxis?new THREE.Vector3(0,1,0):"z"==e.rotationAxis?new THREE.Vector3(0,0,1):new THREE.Vector3(0,1,0),angle:e.rotationAngle,angleSpread:e.rotationAngleSpread,static:!0},acceleration:{value:new THREE.Vector3(e.accelerationValue.x,e.accelerationValue.y,e.accelerationValue.z),spread:new THREE.Vector3(e.accelerationSpread.x,e.accelerationSpread.y,e.accelerationSpread.z)},velocity:{value:new THREE.Vector3(e.velocityValue.x,e.velocityValue.y,e.velocityValue.z),spread:new THREE.Vector3(e.velocitySpread.x,e.velocitySpread.y,e.velocitySpread.z)},drag:{value:new THREE.Vector3(e.dragValue.x,e.dragValue.y,e.dragValue.z),spread:new THREE.Vector3(e.dragSpread.x,e.dragSpread.y,e.dragSpread.z),randomise:e.dragRandomise},color:{value:e.color.map(function(e){return new THREE.Color(e)})},size:{value:e.size.map(function(e){return parseFloat(e)}),spread:e.sizeSpread.map(function(e){return parseFloat(e)})},direction:{value:e.direction},duration:e.duration,opacity:{value:e.opacity.map(function(e){return parseFloat(e)}),spread:e.opacitySpread.map(function(e){return parseFloat(e)})},particleCount:e.particleCount});this.particleGroup.addEmitter(a),this.particleGroup.mesh.frustumCulled=!1,this.el.setObject3D("particle-system",this.particleGroup.mesh)}})},function(e,t,r){var i,a,o={distributions:{BOX:1,SPHERE:2,DISC:3,LINE:4},valueOverLifetimeLength:4};i=o,a="function"==typeof i?i.call(t,r,t,e):i,!(void 0!==a&&(e.exports=a)),o.TypedArrayHelper=function(e,t,r,i){"use strict";this.componentSize=r||1,this.size=t||1,this.TypedArrayConstructor=e||Float32Array,this.array=new e(t*this.componentSize),this.indexOffset=i||0},o.TypedArrayHelper.constructor=o.TypedArrayHelper,o.TypedArrayHelper.prototype.setSize=function(e,t){"use strict";var r=this.array.length;return t||(e*=this.componentSize),er?this.grow(e):void console.info("TypedArray is already of size:",e+".","Will not resize.")},o.TypedArrayHelper.prototype.shrink=function(e){"use strict";return this.array=this.array.subarray(0,e),this.size=e,this},o.TypedArrayHelper.prototype.grow=function(e){"use strict";var t=this.array,r=new this.TypedArrayConstructor(e);return r.set(t),this.array=r,this.size=e,this},o.TypedArrayHelper.prototype.splice=function(e,t){"use strict";e*=this.componentSize,t*=this.componentSize;for(var r=[],i=this.array,a=i.length,o=0;o=t)&&r.push(i[o]);return this.setFromArray(0,r),this},o.TypedArrayHelper.prototype.setFromArray=function(e,t){"use strict";var r=t.length,i=e+r;return i>this.array.length?this.grow(i):i=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),void(this.bufferAttribute.usage=this.dynamicBuffer?THREE.DynamicDrawUsage:THREE.StaticDrawUsage))},o.ShaderAttribute.prototype.getLength=function(){"use strict";return null===this.typedArray?0:this.typedArray.array.length},o.shaderChunks={defines:["#define PACKED_COLOR_SIZE 256.0","#define PACKED_COLOR_DIVISOR 255.0"].join("\n"),uniforms:["uniform float deltaTime;","uniform float runTime;","uniform sampler2D tex;","uniform vec4 textureAnimation;","uniform float scale;"].join("\n"),attributes:["attribute vec4 acceleration;","attribute vec3 velocity;","attribute vec4 rotation;","attribute vec3 rotationCenter;","attribute vec4 params;","attribute vec4 size;","attribute vec4 angle;","attribute vec4 color;","attribute vec4 opacity;"].join("\n"),varyings:["varying vec4 vColor;","#ifdef SHOULD_ROTATE_TEXTURE"," varying float vAngle;","#endif","#ifdef SHOULD_CALCULATE_SPRITE"," varying vec4 vSpriteSheet;","#endif"].join("\n"),branchAvoidanceFunctions:["float when_gt(float x, float y) {"," return max(sign(x - y), 0.0);","}","float when_lt(float x, float y) {"," return min( max(1.0 - sign(x - y), 0.0), 1.0 );","}","float when_eq( float x, float y ) {"," return 1.0 - abs( sign( x - y ) );","}","float when_ge(float x, float y) {"," return 1.0 - when_lt(x, y);","}","float when_le(float x, float y) {"," return 1.0 - when_gt(x, y);","}","float and(float a, float b) {"," return a * b;","}","float or(float a, float b) {"," return min(a + b, 1.0);","}"].join("\n"),unpackColor:["vec3 unpackColor( in float hex ) {"," vec3 c = vec3( 0.0 );"," float r = mod( (hex / PACKED_COLOR_SIZE / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );"," float g = mod( (hex / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );"," float b = mod( hex, PACKED_COLOR_SIZE );"," c.r = r / PACKED_COLOR_DIVISOR;"," c.g = g / PACKED_COLOR_DIVISOR;"," c.b = b / PACKED_COLOR_DIVISOR;"," return c;","}"].join("\n"),unpackRotationAxis:["vec3 unpackRotationAxis( in float hex ) {"," vec3 c = vec3( 0.0 );"," float r = mod( (hex / PACKED_COLOR_SIZE / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );"," float g = mod( (hex / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );"," float b = mod( hex, PACKED_COLOR_SIZE );"," c.r = r / PACKED_COLOR_DIVISOR;"," c.g = g / PACKED_COLOR_DIVISOR;"," c.b = b / PACKED_COLOR_DIVISOR;"," c *= vec3( 2.0 );"," c -= vec3( 1.0 );"," return c;","}"].join("\n"),floatOverLifetime:["float getFloatOverLifetime( in float positionInTime, in vec4 attr ) {"," highp float value = 0.0;"," float deltaAge = positionInTime * float( VALUE_OVER_LIFETIME_LENGTH - 1 );"," float fIndex = 0.0;"," float shouldApplyValue = 0.0;"," value += attr[ 0 ] * when_eq( deltaAge, 0.0 );",""," for( int i = 0; i < VALUE_OVER_LIFETIME_LENGTH - 1; ++i ) {"," fIndex = float( i );"," shouldApplyValue = and( when_gt( deltaAge, fIndex ), when_le( deltaAge, fIndex + 1.0 ) );"," value += shouldApplyValue * mix( attr[ i ], attr[ i + 1 ], deltaAge - fIndex );"," }",""," return value;","}"].join("\n"),colorOverLifetime:["vec3 getColorOverLifetime( in float positionInTime, in vec3 color1, in vec3 color2, in vec3 color3, in vec3 color4 ) {"," vec3 value = vec3( 0.0 );"," value.x = getFloatOverLifetime( positionInTime, vec4( color1.x, color2.x, color3.x, color4.x ) );"," value.y = getFloatOverLifetime( positionInTime, vec4( color1.y, color2.y, color3.y, color4.y ) );"," value.z = getFloatOverLifetime( positionInTime, vec4( color1.z, color2.z, color3.z, color4.z ) );"," return value;","}"].join("\n"),paramFetchingFunctions:["float getAlive() {"," return params.x;","}","float getAge() {"," return params.y;","}","float getMaxAge() {"," return params.z;","}","float getWiggle() {"," return params.w;","}"].join("\n"),forceFetchingFunctions:["vec4 getPosition( in float age ) {"," return modelViewMatrix * vec4( position, 1.0 );","}","vec3 getVelocity( in float age ) {"," return velocity * age;","}","vec3 getAcceleration( in float age ) {"," return acceleration.xyz * age;","}"].join("\n"),rotationFunctions:["#ifdef SHOULD_ROTATE_PARTICLES"," mat4 getRotationMatrix( in vec3 axis, in float angle) {"," axis = normalize(axis);"," float s = sin(angle);"," float c = cos(angle);"," float oc = 1.0 - c;",""," return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0,"," oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0,"," oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0,"," 0.0, 0.0, 0.0, 1.0);"," }",""," vec3 getRotation( in vec3 pos, in float positionInTime ) {"," if( rotation.y == 0.0 ) {"," return pos;"," }",""," vec3 axis = unpackRotationAxis( rotation.x );"," vec3 center = rotationCenter;"," vec3 translated;"," mat4 rotationMatrix;"," float angle = 0.0;"," angle += when_eq( rotation.z, 0.0 ) * rotation.y;"," angle += when_gt( rotation.z, 0.0 ) * mix( 0.0, rotation.y, positionInTime );"," translated = rotationCenter - pos;"," rotationMatrix = getRotationMatrix( axis, angle );"," return center - vec3( rotationMatrix * vec4( translated, 0.0 ) );"," }","#endif"].join("\n"),rotateTexture:[" vec2 vUv = vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y );",""," #ifdef SHOULD_ROTATE_TEXTURE"," float x = gl_PointCoord.x - 0.5;"," float y = 1.0 - gl_PointCoord.y - 0.5;"," float c = cos( -vAngle );"," float s = sin( -vAngle );"," vUv = vec2( c * x + s * y + 0.5, c * y - s * x + 0.5 );"," #endif",""," #ifdef SHOULD_CALCULATE_SPRITE"," float framesX = vSpriteSheet.x;"," float framesY = vSpriteSheet.y;"," float columnNorm = vSpriteSheet.z;"," float rowNorm = vSpriteSheet.w;"," vUv.x = gl_PointCoord.x * framesX + columnNorm;"," vUv.y = 1.0 - (gl_PointCoord.y * framesY + rowNorm);"," #endif",""," vec4 rotatedTexture = texture2D( tex, vUv );"].join("\n")},o.shaders={vertex:[o.shaderChunks.defines,o.shaderChunks.uniforms,o.shaderChunks.attributes,o.shaderChunks.varyings,THREE.ShaderChunk.common,THREE.ShaderChunk.logdepthbuf_pars_vertex,THREE.ShaderChunk.fog_pars_vertex,o.shaderChunks.branchAvoidanceFunctions,o.shaderChunks.unpackColor,o.shaderChunks.unpackRotationAxis,o.shaderChunks.floatOverLifetime,o.shaderChunks.colorOverLifetime,o.shaderChunks.paramFetchingFunctions,o.shaderChunks.forceFetchingFunctions,o.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:[o.shaderChunks.uniforms,THREE.ShaderChunk.common,THREE.ShaderChunk.fog_pars_fragment,THREE.ShaderChunk.logdepthbuf_pars_fragment,o.shaderChunks.varyings,o.shaderChunks.branchAvoidanceFunctions,"void main() {"," vec3 outgoingLight = vColor.xyz;"," "," #ifdef ALPHATEST"," if ( vColor.w < float(ALPHATEST) ) discard;"," #endif",o.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")},o.utils={types:{BOOLEAN:"boolean",STRING:"string",NUMBER:"number",OBJECT:"object"},ensureTypedArg:function(e,t,r){"use strict";return typeof e===t?e:r},ensureArrayTypedArg:function(e,t,r){"use strict";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:function(e,t,r){"use strict";return void 0!==t&&e instanceof t?e:r},ensureArrayInstanceOf:function(e,t,r){"use strict";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:function(e,t,r){"use strict";t=t||3,r=r||3,Array.isArray(e._value)===!1&&(e._value=[e._value]),Array.isArray(e._spread)===!1&&(e._spread=[e._spread]);var i=this.clamp(e._value.length,t,r),a=this.clamp(e._spread.length,t,r),o=Math.max(i,a);e._value.length!==o&&(e._value=this.interpolateArray(e._value,o)),e._spread.length!==o&&(e._spread=this.interpolateArray(e._spread,o))},interpolateArray:function(e,t){"use strict";for(var r=e.length,i=["function"==typeof e[0].clone?e[0].clone():e[0]],a=(r-1)/(t-1),o=1;o-r&&(i=-i),i},lerpTypeAgnostic:function(e,t,r){"use strict";var i,a=this.types;return typeof e===a.NUMBER&&typeof t===a.NUMBER?e+(t-e)*r:e instanceof THREE.Vector2&&t instanceof THREE.Vector2?(i=e.clone(),i.x=this.lerp(e.x,t.x,r),i.y=this.lerp(e.y,t.y,r),i):e instanceof THREE.Vector3&&t instanceof THREE.Vector3?(i=e.clone(),i.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 THREE.Vector4&&t instanceof THREE.Vector4?(i=e.clone(),i.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 THREE.Color&&t instanceof THREE.Color?(i=e.clone(),i.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:function(e,t,r){"use strict";return e+(t-e)*r},roundToNearestMultiple:function(e,t){"use strict";var r=0;return 0===t?e:(r=Math.abs(e)%t,0===r?e:e<0?-(Math.abs(e)-r):e+t-r)},arrayValuesAreEqual:function(e){"use strict";for(var t=0;t1||this.textureFrames.y>1},this.attributes={position:new o.ShaderAttribute("v3",!0),acceleration:new o.ShaderAttribute("v4",!0),velocity:new o.ShaderAttribute("v3",!0),rotation:new o.ShaderAttribute("v4",!0),rotationCenter:new o.ShaderAttribute("v3",!0),params:new o.ShaderAttribute("v4",!0),size:new o.ShaderAttribute("v4",!0),angle:new o.ShaderAttribute("v4",!0),color:new o.ShaderAttribute("v4",!0),opacity:new o.ShaderAttribute("v4",!0)},this.attributeKeys=Object.keys(this.attributes),this.attributeCount=this.attributeKeys.length,this.material=new THREE.ShaderMaterial({uniforms:this.uniforms,vertexShader:o.shaders.vertex,fragmentShader:o.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.")},o.Group.constructor=o.Group,o.Group.prototype._updateDefines=function(){"use strict";var e,t=this.emitters,r=t.length-1,i=this.defines;for(r;r>=0;--r)e=t[r],i.SHOULD_CALCULATE_SPRITE||(i.SHOULD_ROTATE_TEXTURE=i.SHOULD_ROTATE_TEXTURE||!!Math.max(Math.max.apply(null,e.angle.value),Math.max.apply(null,e.angle.spread))),i.SHOULD_ROTATE_PARTICLES=i.SHOULD_ROTATE_PARTICLES||!!Math.max(e.rotation.angle,e.rotation.angleSpread),i.SHOULD_WIGGLE_PARTICLES=i.SHOULD_WIGGLE_PARTICLES||!!Math.max(e.wiggle.value,e.wiggle.spread);this.material.needsUpdate=!0},o.Group.prototype._applyAttributesToGeometry=function(){"use strict";var e,t,r=this.attributes,i=this.geometry,a=i.attributes;for(var o in r)r.hasOwnProperty(o)&&(e=r[o],t=a[o],t?t.array=e.typedArray.array:i.setAttribute(o,e.bufferAttribute),e.bufferAttribute.needsUpdate=!0);this.geometry.setDrawRange(0,this.particleCount)},o.Group.prototype.addEmitter=function(e){"use strict";if(e instanceof o.Emitter==!1)return void console.error("`emitter` argument must be instance of SPE.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.");var 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(var a in t)t.hasOwnProperty(a)&&t[a]._createBufferAttribute(null!==this.maxParticleCount?this.maxParticleCount:this.particleCount);for(var s=r;s1)for(var r=0;r=0;--t)r[e[t]].resetUpdateRange()},o.Group.prototype._updateBuffers=function(e){"use strict";var t,r,i,a=this.attributeKeys,o=this.attributeCount-1,s=this.attributes,n=e.bufferUpdateRanges;for(o;o>=0;--o)t=a[o],r=n[t],i=s[t],i.setUpdateRange(r.min,r.max),i.flagUpdate()},o.Group.prototype.tick=function(e){"use strict";var t,r=this.emitters,i=r.length,a=e||this.fixedTimeStep,o=this.attributeKeys,s=this.attributes;if(this._updateUniforms(a),this._resetBufferRanges(),0!==i||this._attributesNeedRefresh!==!1||this._attributesNeedDynamicReset!==!1){for(var n,t=0;t=0;--t)s[o[t]].resetDynamic();this._attributesNeedDynamicReset=!1}if(this._attributesNeedRefresh===!0){for(t=this.attributeCount-1;t>=0;--t)s[o[t]].forceUpdateAll();this._attributesNeedRefresh=!1,this._attributesNeedDynamicReset=!0}}},o.Group.prototype.dispose=function(){"use strict";return this.geometry.dispose(),this.material.dispose(),this},o.Emitter=function(e){"use strict";var t=o.utils,r=t.types,i=o.valueOverLifetimeLength;e=t.ensureTypedArg(e,r.OBJECT,{}),e.position=t.ensureTypedArg(e.position,r.OBJECT,{}),e.velocity=t.ensureTypedArg(e.velocity,r.OBJECT,{}),e.acceleration=t.ensureTypedArg(e.acceleration,r.OBJECT,{}),e.radius=t.ensureTypedArg(e.radius,r.OBJECT,{}),e.drag=t.ensureTypedArg(e.drag,r.OBJECT,{}),e.rotation=t.ensureTypedArg(e.rotation,r.OBJECT,{}),e.color=t.ensureTypedArg(e.color,r.OBJECT,{}),e.opacity=t.ensureTypedArg(e.opacity,r.OBJECT,{}),e.size=t.ensureTypedArg(e.size,r.OBJECT,{}),e.angle=t.ensureTypedArg(e.angle,r.OBJECT,{}),e.wiggle=t.ensureTypedArg(e.wiggle,r.OBJECT,{}),e.maxAge=t.ensureTypedArg(e.maxAge,r.OBJECT,{}),e.onParticleSpawn&&console.warn("onParticleSpawn has been removed. Please set properties directly to alter values at runtime."),this.uuid=THREE.Math.generateUUID(),this.type=t.ensureTypedArg(e.type,r.NUMBER,o.distributions.BOX),this.position={_value:t.ensureInstanceOf(e.position.value,THREE.Vector3,new THREE.Vector3),_spread:t.ensureInstanceOf(e.position.spread,THREE.Vector3,new THREE.Vector3),_spreadClamp:t.ensureInstanceOf(e.position.spreadClamp,THREE.Vector3,new THREE.Vector3),_distribution:t.ensureTypedArg(e.position.distribution,r.NUMBER,this.type),_randomise:t.ensureTypedArg(e.position.randomise,r.BOOLEAN,!1),_radius:t.ensureTypedArg(e.position.radius,r.NUMBER,10),_radiusScale:t.ensureInstanceOf(e.position.radiusScale,THREE.Vector3,new THREE.Vector3(1,1,1)),_distributionClamp:t.ensureTypedArg(e.position.distributionClamp,r.NUMBER,0)},this.velocity={_value:t.ensureInstanceOf(e.velocity.value,THREE.Vector3,new THREE.Vector3),_spread:t.ensureInstanceOf(e.velocity.spread,THREE.Vector3,new THREE.Vector3),_distribution:t.ensureTypedArg(e.velocity.distribution,r.NUMBER,this.type),_randomise:t.ensureTypedArg(e.position.randomise,r.BOOLEAN,!1)},this.acceleration={_value:t.ensureInstanceOf(e.acceleration.value,THREE.Vector3,new THREE.Vector3),_spread:t.ensureInstanceOf(e.acceleration.spread,THREE.Vector3,new THREE.Vector3),_distribution:t.ensureTypedArg(e.acceleration.distribution,r.NUMBER,this.type),_randomise:t.ensureTypedArg(e.position.randomise,r.BOOLEAN,!1)},this.drag={_value:t.ensureTypedArg(e.drag.value,r.NUMBER,0),_spread:t.ensureTypedArg(e.drag.spread,r.NUMBER,0),_randomise:t.ensureTypedArg(e.position.randomise,r.BOOLEAN,!1)},this.wiggle={_value:t.ensureTypedArg(e.wiggle.value,r.NUMBER,0),_spread:t.ensureTypedArg(e.wiggle.spread,r.NUMBER,0)},this.rotation={_axis:t.ensureInstanceOf(e.rotation.axis,THREE.Vector3,new THREE.Vector3(0,1,0)),_axisSpread:t.ensureInstanceOf(e.rotation.axisSpread,THREE.Vector3,new THREE.Vector3),_angle:t.ensureTypedArg(e.rotation.angle,r.NUMBER,0),_angleSpread:t.ensureTypedArg(e.rotation.angleSpread,r.NUMBER,0),_static:t.ensureTypedArg(e.rotation.static,r.BOOLEAN,!1),_center:t.ensureInstanceOf(e.rotation.center,THREE.Vector3,this.position._value.clone()),_randomise:t.ensureTypedArg(e.position.randomise,r.BOOLEAN,!1)},this.maxAge={_value:t.ensureTypedArg(e.maxAge.value,r.NUMBER,2),_spread:t.ensureTypedArg(e.maxAge.spread,r.NUMBER,0)},this.color={_value:t.ensureArrayInstanceOf(e.color.value,THREE.Color,new THREE.Color),_spread:t.ensureArrayInstanceOf(e.color.spread,THREE.Vector3,new THREE.Vector3),_randomise:t.ensureTypedArg(e.position.randomise,r.BOOLEAN,!1)},this.opacity={_value:t.ensureArrayTypedArg(e.opacity.value,r.NUMBER,1),_spread:t.ensureArrayTypedArg(e.opacity.spread,r.NUMBER,0),_randomise:t.ensureTypedArg(e.position.randomise,r.BOOLEAN,!1)},this.size={_value:t.ensureArrayTypedArg(e.size.value,r.NUMBER,1),_spread:t.ensureArrayTypedArg(e.size.spread,r.NUMBER,0),_randomise:t.ensureTypedArg(e.position.randomise,r.BOOLEAN,!1)},this.angle={_value:t.ensureArrayTypedArg(e.angle.value,r.NUMBER,0),_spread:t.ensureArrayTypedArg(e.angle.spread,r.NUMBER,0),_randomise:t.ensureTypedArg(e.position.randomise,r.BOOLEAN,!1)},this.particleCount=t.ensureTypedArg(e.particleCount,r.NUMBER,100),this.duration=t.ensureTypedArg(e.duration,r.NUMBER,null),this.isStatic=t.ensureTypedArg(e.isStatic,r.BOOLEAN,!1),this.activeMultiplier=t.ensureTypedArg(e.activeMultiplier,r.NUMBER,1),this.direction=t.ensureTypedArg(e.direction,r.NUMBER,1),this.alive=t.ensureTypedArg(e.alive,r.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:t.ensureTypedArg(e.position.randomise,r.BOOLEAN,!1)||t.ensureTypedArg(e.radius.randomise,r.BOOLEAN,!1),velocity:t.ensureTypedArg(e.velocity.randomise,r.BOOLEAN,!1),acceleration:t.ensureTypedArg(e.acceleration.randomise,r.BOOLEAN,!1)||t.ensureTypedArg(e.drag.randomise,r.BOOLEAN,!1),rotation:t.ensureTypedArg(e.rotation.randomise,r.BOOLEAN,!1),rotationCenter:t.ensureTypedArg(e.rotation.randomise,r.BOOLEAN,!1),size:t.ensureTypedArg(e.size.randomise,r.BOOLEAN,!1),color:t.ensureTypedArg(e.color.randomise,r.BOOLEAN,!1),opacity:t.ensureTypedArg(e.opacity.randomise,r.BOOLEAN,!1),angle:t.ensureTypedArg(e.angle.randomise,r.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 a in this.updateMap)this.updateMap.hasOwnProperty(a)&&(this.updateCounts[this.updateMap[a]]=0,this.updateFlags[this.updateMap[a]]=!1,this._createGetterSetters(this[a],a));this.bufferUpdateRanges={},this.attributeKeys=null,this.attributeCount=0,t.ensureValueOverLifetimeCompliance(this.color,i,i),t.ensureValueOverLifetimeCompliance(this.opacity,i,i),t.ensureValueOverLifetimeCompliance(this.size,i,i),t.ensureValueOverLifetimeCompliance(this.angle,i,i)},o.Emitter.constructor=o.Emitter,o.Emitter.prototype._createGetterSetters=function(e,t){"use strict";var r=this;for(var i in e)if(e.hasOwnProperty(i)){var a=i.replace("_","");Object.defineProperty(e,a,{get:function(e){return function(){return this[e]}}(i),set:function(e){return function(i){var a=r.updateMap[t],s=this[e],n=o.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)&&o.utils.ensureValueOverLifetimeCompliance(r[t],n,n)}}(i)})}},o.Emitter.prototype._setBufferUpdateRanges=function(e){"use strict";this.attributeKeys=e,this.attributeCount=e.length;for(var t=this.attributeCount-1;t>=0;--t)this.bufferUpdateRanges[e[t]]={min:Number.POSITIVE_INFINITY,max:Number.NEGATIVE_INFINITY}},o.Emitter.prototype._calculatePPSValue=function(e){"use strict";var t=this.particleCount;this.duration?this.particlesPerSecond=t/(e=0;--n)t=s[n],r=a[t],i[t]!==!0&&r!==!0||(this._assignValue(t,e),this._updateAttributeUpdateRange(t,e),r===!0&&o[t]===this.particleCount?(a[t]=!1,o[t]=0):1==r&&++o[t])},o.Emitter.prototype._updateAttributeUpdateRange=function(e,t){"use strict";var r=this.bufferUpdateRanges[e];r.min=Math.min(t,r.min),r.max=Math.max(t,r.max)},o.Emitter.prototype._resetBufferRanges=function(){"use strict";var e,t=this.bufferUpdateRanges,r=this.bufferUpdateKeys,i=this.bufferUpdateCount-1;for(i;i>=0;--i)e=r[i],t[e].min=Number.POSITIVE_INFINITY,t[e].max=Number.NEGATIVE_INFINITY},o.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},o.Emitter.prototype._decrementParticleCount=function(){"use strict";--this.activeParticleCount},o.Emitter.prototype._incrementParticleCount=function(){"use strict";++this.activeParticleCount},o.Emitter.prototype._checkParticleAges=function(e,t,r,i){"use strict";for(var a,o,s,n,u=t-1;u>=e;--u)a=4*u,n=r[a],0!==n&&(s=r[a+1],o=r[a+2],1===this.direction?(s+=i,s>=o&&(s=0,n=0,this._decrementParticleCount())):(s-=i,s<=0&&(s=o,n=0,this._decrementParticleCount())),r[a]=n,r[a+1]=s,this._updateAttributeUpdateRange("params",u))},o.Emitter.prototype._activateParticles=function(e,t,r,i){"use strict";for(var a,o,s=this.direction,n=e;nthis.duration)return this.alive=!1,void(this.age=0);var s=1===this.particleCount?o:0|o,n=Math.min(s+a,this.activationEnd),u=n-this.activationIndex|0,l=u>0?e/u:0;this._activateParticles(s,n,i,l),this.activationIndex+=a,this.activationIndex>r&&(this.activationIndex=t),this.age+=e}},o.Emitter.prototype.reset=function(e){"use strict";if(this.age=0,this.alive=!1,e===!0){for(var t,r=this.attributeOffset,i=r+this.particleCount,a=this.paramsArray,o=this.attributes.params.bufferAttribute,s=i-1;s>=r;--s)t=4*s,a[t]=0,a[t+1]=0;o.updateRange.offset=0,o.updateRange.count=-1,o.needsUpdate=!0}return this},o.Emitter.prototype.enable=function(){"use strict";return this.alive=!0,this},o.Emitter.prototype.disable=function(){"use strict";return this.alive=!1,this},o.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 +(()=>{var e={544:(e,t,r)=>{var i,a,s={distributions:{BOX:1,SPHERE:2,DISC:3,LINE:4},valueOverLifetimeLength:4};void 0===(a="function"==typeof(i=s)?i.call(t,r,t,e):i)||(e.exports=a),s.TypedArrayHelper=function(e,t,r,i){"use strict";this.componentSize=r||1,this.size=t||1,this.TypedArrayConstructor=e||Float32Array,this.array=new e(t*this.componentSize),this.indexOffset=i||0},s.TypedArrayHelper.constructor=s.TypedArrayHelper,s.TypedArrayHelper.prototype.setSize=function(e,t){"use strict";var r=this.array.length;return t||(e*=this.componentSize),er?this.grow(e):void console.info("TypedArray is already of size:",e+".","Will not resize.")},s.TypedArrayHelper.prototype.shrink=function(e){"use strict";return this.array=this.array.subarray(0,e),this.size=e,this},s.TypedArrayHelper.prototype.grow=function(e){"use strict";var t=this.array,r=new this.TypedArrayConstructor(e);return r.set(t),this.array=r,this.size=e,this},s.TypedArrayHelper.prototype.splice=function(e,t){"use strict";e*=this.componentSize,t*=this.componentSize;for(var r=[],i=this.array,a=i.length,s=0;s=t)&&r.push(i[s]);return this.setFromArray(0,r),this},s.TypedArrayHelper.prototype.setFromArray=function(e,t){"use strict";var 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 THREE.BufferAttribute(this.typedArray.array,this.componentSize),this.bufferAttribute.usage=this.dynamicBuffer?THREE.DynamicDrawUsage:THREE.StaticDrawUsage},s.ShaderAttribute.prototype.getLength=function(){"use strict";return null===this.typedArray?0:this.typedArray.array.length},s.shaderChunks={defines:["#define PACKED_COLOR_SIZE 256.0","#define PACKED_COLOR_DIVISOR 255.0"].join("\n"),uniforms:["uniform float deltaTime;","uniform float runTime;","uniform sampler2D tex;","uniform vec4 textureAnimation;","uniform float scale;"].join("\n"),attributes:["attribute vec4 acceleration;","attribute vec3 velocity;","attribute vec4 rotation;","attribute vec3 rotationCenter;","attribute vec4 params;","attribute vec4 size;","attribute vec4 angle;","attribute vec4 color;","attribute vec4 opacity;"].join("\n"),varyings:["varying vec4 vColor;","#ifdef SHOULD_ROTATE_TEXTURE"," varying float vAngle;","#endif","#ifdef SHOULD_CALCULATE_SPRITE"," varying vec4 vSpriteSheet;","#endif"].join("\n"),branchAvoidanceFunctions:["float when_gt(float x, float y) {"," return max(sign(x - y), 0.0);","}","float when_lt(float x, float y) {"," return min( max(1.0 - sign(x - y), 0.0), 1.0 );","}","float when_eq( float x, float y ) {"," return 1.0 - abs( sign( x - y ) );","}","float when_ge(float x, float y) {"," return 1.0 - when_lt(x, y);","}","float when_le(float x, float y) {"," return 1.0 - when_gt(x, y);","}","float and(float a, float b) {"," return a * b;","}","float or(float a, float b) {"," return min(a + b, 1.0);","}"].join("\n"),unpackColor:["vec3 unpackColor( in float hex ) {"," vec3 c = vec3( 0.0 );"," float r = mod( (hex / PACKED_COLOR_SIZE / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );"," float g = mod( (hex / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );"," float b = mod( hex, PACKED_COLOR_SIZE );"," c.r = r / PACKED_COLOR_DIVISOR;"," c.g = g / PACKED_COLOR_DIVISOR;"," c.b = b / PACKED_COLOR_DIVISOR;"," return c;","}"].join("\n"),unpackRotationAxis:["vec3 unpackRotationAxis( in float hex ) {"," vec3 c = vec3( 0.0 );"," float r = mod( (hex / PACKED_COLOR_SIZE / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );"," float g = mod( (hex / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );"," float b = mod( hex, PACKED_COLOR_SIZE );"," c.r = r / PACKED_COLOR_DIVISOR;"," c.g = g / PACKED_COLOR_DIVISOR;"," c.b = b / PACKED_COLOR_DIVISOR;"," c *= vec3( 2.0 );"," c -= vec3( 1.0 );"," return c;","}"].join("\n"),floatOverLifetime:["float getFloatOverLifetime( in float positionInTime, in vec4 attr ) {"," highp float value = 0.0;"," float deltaAge = positionInTime * float( VALUE_OVER_LIFETIME_LENGTH - 1 );"," float fIndex = 0.0;"," float shouldApplyValue = 0.0;"," value += attr[ 0 ] * when_eq( deltaAge, 0.0 );",""," for( int i = 0; i < VALUE_OVER_LIFETIME_LENGTH - 1; ++i ) {"," fIndex = float( i );"," shouldApplyValue = and( when_gt( deltaAge, fIndex ), when_le( deltaAge, fIndex + 1.0 ) );"," value += shouldApplyValue * mix( attr[ i ], attr[ i + 1 ], deltaAge - fIndex );"," }",""," return value;","}"].join("\n"),colorOverLifetime:["vec3 getColorOverLifetime( in float positionInTime, in vec3 color1, in vec3 color2, in vec3 color3, in vec3 color4 ) {"," vec3 value = vec3( 0.0 );"," value.x = getFloatOverLifetime( positionInTime, vec4( color1.x, color2.x, color3.x, color4.x ) );"," value.y = getFloatOverLifetime( positionInTime, vec4( color1.y, color2.y, color3.y, color4.y ) );"," value.z = getFloatOverLifetime( positionInTime, vec4( color1.z, color2.z, color3.z, color4.z ) );"," return value;","}"].join("\n"),paramFetchingFunctions:["float getAlive() {"," return params.x;","}","float getAge() {"," return params.y;","}","float getMaxAge() {"," return params.z;","}","float getWiggle() {"," return params.w;","}"].join("\n"),forceFetchingFunctions:["vec4 getPosition( in float age ) {"," return modelViewMatrix * vec4( position, 1.0 );","}","vec3 getVelocity( in float age ) {"," return velocity * age;","}","vec3 getAcceleration( in float age ) {"," return acceleration.xyz * age;","}"].join("\n"),rotationFunctions:["#ifdef SHOULD_ROTATE_PARTICLES"," mat4 getRotationMatrix( in vec3 axis, in float angle) {"," axis = normalize(axis);"," float s = sin(angle);"," float c = cos(angle);"," float oc = 1.0 - c;",""," return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0,"," oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0,"," oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0,"," 0.0, 0.0, 0.0, 1.0);"," }",""," vec3 getRotation( in vec3 pos, in float positionInTime ) {"," if( rotation.y == 0.0 ) {"," return pos;"," }",""," vec3 axis = unpackRotationAxis( rotation.x );"," vec3 center = rotationCenter;"," vec3 translated;"," mat4 rotationMatrix;"," float angle = 0.0;"," angle += when_eq( rotation.z, 0.0 ) * rotation.y;"," angle += when_gt( rotation.z, 0.0 ) * mix( 0.0, rotation.y, positionInTime );"," translated = rotationCenter - pos;"," rotationMatrix = getRotationMatrix( axis, angle );"," return center - vec3( rotationMatrix * vec4( translated, 0.0 ) );"," }","#endif"].join("\n"),rotateTexture:[" vec2 vUv = vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y );",""," #ifdef SHOULD_ROTATE_TEXTURE"," float x = gl_PointCoord.x - 0.5;"," float y = 1.0 - gl_PointCoord.y - 0.5;"," float c = cos( -vAngle );"," float s = sin( -vAngle );"," vUv = vec2( c * x + s * y + 0.5, c * y - s * x + 0.5 );"," #endif",""," #ifdef SHOULD_CALCULATE_SPRITE"," float framesX = vSpriteSheet.x;"," float framesY = vSpriteSheet.y;"," float columnNorm = vSpriteSheet.z;"," float rowNorm = vSpriteSheet.w;"," vUv.x = gl_PointCoord.x * framesX + columnNorm;"," vUv.y = 1.0 - (gl_PointCoord.y * framesY + rowNorm);"," #endif",""," vec4 rotatedTexture = texture2D( tex, vUv );"].join("\n")},s.shaders={vertex:[s.shaderChunks.defines,s.shaderChunks.uniforms,s.shaderChunks.attributes,s.shaderChunks.varyings,THREE.ShaderChunk.common,THREE.ShaderChunk.logdepthbuf_pars_vertex,THREE.ShaderChunk.fog_pars_vertex,s.shaderChunks.branchAvoidanceFunctions,s.shaderChunks.unpackColor,s.shaderChunks.unpackRotationAxis,s.shaderChunks.floatOverLifetime,s.shaderChunks.colorOverLifetime,s.shaderChunks.paramFetchingFunctions,s.shaderChunks.forceFetchingFunctions,s.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:[s.shaderChunks.uniforms,THREE.ShaderChunk.common,THREE.ShaderChunk.fog_pars_fragment,THREE.ShaderChunk.logdepthbuf_pars_fragment,s.shaderChunks.varyings,s.shaderChunks.branchAvoidanceFunctions,"void main() {"," vec3 outgoingLight = vColor.xyz;"," "," #ifdef ALPHATEST"," if ( vColor.w < float(ALPHATEST) ) discard;"," #endif",s.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")},s.utils={types:{BOOLEAN:"boolean",STRING:"string",NUMBER:"number",OBJECT:"object"},ensureTypedArg:function(e,t,r){"use strict";return typeof e===t?e:r},ensureArrayTypedArg:function(e,t,r){"use strict";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:function(e,t,r){"use strict";return void 0!==t&&e instanceof t?e:r},ensureArrayInstanceOf:function(e,t,r){"use strict";if(Array.isArray(e)){for(var i=e.length-1;i>=0;--i)if(void 0!==t&&e[i]instanceof t==0)return r;return e}return this.ensureInstanceOf(e,t,r)},ensureValueOverLifetimeCompliance:function(e,t,r){"use strict";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:function(e,t){"use strict";for(var r=e.length,i=["function"==typeof e[0].clone?e[0].clone():e[0]],a=(r-1)/(t-1),s=1;s-1e-5&&(i=-i),i},lerpTypeAgnostic:function(e,t,r){"use strict";var i,a=this.types;return typeof e===a.NUMBER&&typeof t===a.NUMBER?e+(t-e)*r:e instanceof THREE.Vector2&&t instanceof THREE.Vector2?((i=e.clone()).x=this.lerp(e.x,t.x,r),i.y=this.lerp(e.y,t.y,r),i):e instanceof THREE.Vector3&&t instanceof THREE.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 THREE.Vector4&&t instanceof THREE.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 THREE.Color&&t instanceof THREE.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:function(e,t,r){"use strict";return e+(t-e)*r},roundToNearestMultiple:function(e,t){"use strict";var r;return 0===t||0==(r=Math.abs(e)%t)?e:e<0?-(Math.abs(e)-r):e+t-r},arrayValuesAreEqual:function(e){"use strict";for(var t=0;t1||this.textureFrames.y>1},this.attributes={position:new s.ShaderAttribute("v3",!0),acceleration:new s.ShaderAttribute("v4",!0),velocity:new s.ShaderAttribute("v3",!0),rotation:new s.ShaderAttribute("v4",!0),rotationCenter:new s.ShaderAttribute("v3",!0),params:new s.ShaderAttribute("v4",!0),size:new s.ShaderAttribute("v4",!0),angle:new s.ShaderAttribute("v4",!0),color:new s.ShaderAttribute("v4",!0),opacity:new s.ShaderAttribute("v4",!0)},this.attributeKeys=Object.keys(this.attributes),this.attributeCount=this.attributeKeys.length,this.material=new THREE.ShaderMaterial({uniforms:this.uniforms,vertexShader:s.shaders.vertex,fragmentShader:s.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.")},s.Group.constructor=s.Group,s.Group.prototype._updateDefines=function(){"use strict";for(var e,t=this.emitters,r=t.length-1,i=this.defines;r>=0;--r)e=t[r],i.SHOULD_CALCULATE_SPRITE||(i.SHOULD_ROTATE_TEXTURE=i.SHOULD_ROTATE_TEXTURE||!!Math.max(Math.max.apply(null,e.angle.value),Math.max.apply(null,e.angle.spread))),i.SHOULD_ROTATE_PARTICLES=i.SHOULD_ROTATE_PARTICLES||!!Math.max(e.rotation.angle,e.rotation.angleSpread),i.SHOULD_WIGGLE_PARTICLES=i.SHOULD_WIGGLE_PARTICLES||!!Math.max(e.wiggle.value,e.wiggle.spread);this.material.needsUpdate=!0},s.Group.prototype._applyAttributesToGeometry=function(){"use strict";var e,t,r=this.attributes,i=this.geometry,a=i.attributes;for(var s in r)r.hasOwnProperty(s)&&(e=r[s],(t=a[s])?t.array=e.typedArray.array:i.setAttribute(s,e.bufferAttribute),e.bufferAttribute.needsUpdate=!0);this.geometry.setDrawRange(0,this.particleCount)},s.Group.prototype.addEmitter=function(e){"use strict";if(e instanceof s.Emitter!=0)if(this.emitterIDs.indexOf(e.uuid)>-1)console.error("Emitter already exists in this group. Will not add again.");else{if(null===e.group){var t=this.attributes,r=this.particleCount,i=r+e.particleCount;for(var a in 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,t)t.hasOwnProperty(a)&&t[a]._createBufferAttribute(null!==this.maxParticleCount?this.maxParticleCount:this.particleCount);for(var o=r;o1)for(var r=0;r=0;--t)r[e[t]].resetUpdateRange()},s.Group.prototype._updateBuffers=function(e){"use strict";for(var t,r,i,a=this.attributeKeys,s=this.attributeCount-1,o=this.attributes,n=e.bufferUpdateRanges;s>=0;--s)r=n[t=a[s]],(i=o[t]).setUpdateRange(r.min,r.max),i.flagUpdate()},s.Group.prototype.tick=function(e){"use strict";var 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(var o,n=0;n=0;--n)s[a[n]].resetDynamic();this._attributesNeedDynamicReset=!1}if(!0===this._attributesNeedRefresh){for(n=this.attributeCount-1;n>=0;--n)s[a[n]].forceUpdateAll();this._attributesNeedRefresh=!1,this._attributesNeedDynamicReset=!0}}},s.Group.prototype.dispose=function(){"use strict";return this.geometry.dispose(),this.material.dispose(),this},s.Emitter=function(e){"use strict";var t=s.utils,r=t.types,i=s.valueOverLifetimeLength;for(var a in(e=t.ensureTypedArg(e,r.OBJECT,{})).position=t.ensureTypedArg(e.position,r.OBJECT,{}),e.velocity=t.ensureTypedArg(e.velocity,r.OBJECT,{}),e.acceleration=t.ensureTypedArg(e.acceleration,r.OBJECT,{}),e.radius=t.ensureTypedArg(e.radius,r.OBJECT,{}),e.drag=t.ensureTypedArg(e.drag,r.OBJECT,{}),e.rotation=t.ensureTypedArg(e.rotation,r.OBJECT,{}),e.color=t.ensureTypedArg(e.color,r.OBJECT,{}),e.opacity=t.ensureTypedArg(e.opacity,r.OBJECT,{}),e.size=t.ensureTypedArg(e.size,r.OBJECT,{}),e.angle=t.ensureTypedArg(e.angle,r.OBJECT,{}),e.wiggle=t.ensureTypedArg(e.wiggle,r.OBJECT,{}),e.maxAge=t.ensureTypedArg(e.maxAge,r.OBJECT,{}),e.onParticleSpawn&&console.warn("onParticleSpawn has been removed. Please set properties directly to alter values at runtime."),this.uuid=THREE.MathUtils.generateUUID(),this.type=t.ensureTypedArg(e.type,r.NUMBER,s.distributions.BOX),this.position={_value:t.ensureInstanceOf(e.position.value,THREE.Vector3,new THREE.Vector3),_spread:t.ensureInstanceOf(e.position.spread,THREE.Vector3,new THREE.Vector3),_spreadClamp:t.ensureInstanceOf(e.position.spreadClamp,THREE.Vector3,new THREE.Vector3),_distribution:t.ensureTypedArg(e.position.distribution,r.NUMBER,this.type),_randomise:t.ensureTypedArg(e.position.randomise,r.BOOLEAN,!1),_radius:t.ensureTypedArg(e.position.radius,r.NUMBER,10),_radiusScale:t.ensureInstanceOf(e.position.radiusScale,THREE.Vector3,new THREE.Vector3(1,1,1)),_distributionClamp:t.ensureTypedArg(e.position.distributionClamp,r.NUMBER,0)},this.velocity={_value:t.ensureInstanceOf(e.velocity.value,THREE.Vector3,new THREE.Vector3),_spread:t.ensureInstanceOf(e.velocity.spread,THREE.Vector3,new THREE.Vector3),_distribution:t.ensureTypedArg(e.velocity.distribution,r.NUMBER,this.type),_randomise:t.ensureTypedArg(e.position.randomise,r.BOOLEAN,!1)},this.acceleration={_value:t.ensureInstanceOf(e.acceleration.value,THREE.Vector3,new THREE.Vector3),_spread:t.ensureInstanceOf(e.acceleration.spread,THREE.Vector3,new THREE.Vector3),_distribution:t.ensureTypedArg(e.acceleration.distribution,r.NUMBER,this.type),_randomise:t.ensureTypedArg(e.position.randomise,r.BOOLEAN,!1)},this.drag={_value:t.ensureTypedArg(e.drag.value,r.NUMBER,0),_spread:t.ensureTypedArg(e.drag.spread,r.NUMBER,0),_randomise:t.ensureTypedArg(e.position.randomise,r.BOOLEAN,!1)},this.wiggle={_value:t.ensureTypedArg(e.wiggle.value,r.NUMBER,0),_spread:t.ensureTypedArg(e.wiggle.spread,r.NUMBER,0)},this.rotation={_axis:t.ensureInstanceOf(e.rotation.axis,THREE.Vector3,new THREE.Vector3(0,1,0)),_axisSpread:t.ensureInstanceOf(e.rotation.axisSpread,THREE.Vector3,new THREE.Vector3),_angle:t.ensureTypedArg(e.rotation.angle,r.NUMBER,0),_angleSpread:t.ensureTypedArg(e.rotation.angleSpread,r.NUMBER,0),_static:t.ensureTypedArg(e.rotation.static,r.BOOLEAN,!1),_center:t.ensureInstanceOf(e.rotation.center,THREE.Vector3,this.position._value.clone()),_randomise:t.ensureTypedArg(e.position.randomise,r.BOOLEAN,!1)},this.maxAge={_value:t.ensureTypedArg(e.maxAge.value,r.NUMBER,2),_spread:t.ensureTypedArg(e.maxAge.spread,r.NUMBER,0)},this.color={_value:t.ensureArrayInstanceOf(e.color.value,THREE.Color,new THREE.Color),_spread:t.ensureArrayInstanceOf(e.color.spread,THREE.Vector3,new THREE.Vector3),_randomise:t.ensureTypedArg(e.position.randomise,r.BOOLEAN,!1)},this.opacity={_value:t.ensureArrayTypedArg(e.opacity.value,r.NUMBER,1),_spread:t.ensureArrayTypedArg(e.opacity.spread,r.NUMBER,0),_randomise:t.ensureTypedArg(e.position.randomise,r.BOOLEAN,!1)},this.size={_value:t.ensureArrayTypedArg(e.size.value,r.NUMBER,1),_spread:t.ensureArrayTypedArg(e.size.spread,r.NUMBER,0),_randomise:t.ensureTypedArg(e.position.randomise,r.BOOLEAN,!1)},this.angle={_value:t.ensureArrayTypedArg(e.angle.value,r.NUMBER,0),_spread:t.ensureArrayTypedArg(e.angle.spread,r.NUMBER,0),_randomise:t.ensureTypedArg(e.position.randomise,r.BOOLEAN,!1)},this.particleCount=t.ensureTypedArg(e.particleCount,r.NUMBER,100),this.duration=t.ensureTypedArg(e.duration,r.NUMBER,null),this.isStatic=t.ensureTypedArg(e.isStatic,r.BOOLEAN,!1),this.activeMultiplier=t.ensureTypedArg(e.activeMultiplier,r.NUMBER,1),this.direction=t.ensureTypedArg(e.direction,r.NUMBER,1),this.alive=t.ensureTypedArg(e.alive,r.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:t.ensureTypedArg(e.position.randomise,r.BOOLEAN,!1)||t.ensureTypedArg(e.radius.randomise,r.BOOLEAN,!1),velocity:t.ensureTypedArg(e.velocity.randomise,r.BOOLEAN,!1),acceleration:t.ensureTypedArg(e.acceleration.randomise,r.BOOLEAN,!1)||t.ensureTypedArg(e.drag.randomise,r.BOOLEAN,!1),rotation:t.ensureTypedArg(e.rotation.randomise,r.BOOLEAN,!1),rotationCenter:t.ensureTypedArg(e.rotation.randomise,r.BOOLEAN,!1),size:t.ensureTypedArg(e.size.randomise,r.BOOLEAN,!1),color:t.ensureTypedArg(e.color.randomise,r.BOOLEAN,!1),opacity:t.ensureTypedArg(e.opacity.randomise,r.BOOLEAN,!1),angle:t.ensureTypedArg(e.angle.randomise,r.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"},this.updateMap)this.updateMap.hasOwnProperty(a)&&(this.updateCounts[this.updateMap[a]]=0,this.updateFlags[this.updateMap[a]]=!1,this._createGetterSetters(this[a],a));this.bufferUpdateRanges={},this.attributeKeys=null,this.attributeCount=0,t.ensureValueOverLifetimeCompliance(this.color,i,i),t.ensureValueOverLifetimeCompliance(this.opacity,i,i),t.ensureValueOverLifetimeCompliance(this.size,i,i),t.ensureValueOverLifetimeCompliance(this.angle,i,i)},s.Emitter.constructor=s.Emitter,s.Emitter.prototype._createGetterSetters=function(e,t){"use strict";var r=this;for(var i in e)if(e.hasOwnProperty(i)){var a=i.replace("_","");Object.defineProperty(e,a,{get:function(e){return function(){return this[e]}}(i),set:function(e){return function(i){var a=r.updateMap[t],o=this[e],n=s.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(o)&&s.utils.ensureValueOverLifetimeCompliance(r[t],n,n)}}(i)})}},s.Emitter.prototype._setBufferUpdateRanges=function(e){"use strict";this.attributeKeys=e,this.attributeCount=e.length;for(var t=this.attributeCount-1;t>=0;--t)this.bufferUpdateRanges[e[t]]={min:Number.POSITIVE_INFINITY,max:Number.NEGATIVE_INFINITY}},s.Emitter.prototype._calculatePPSValue=function(e){"use strict";var t=this.particleCount;this.duration?this.particlesPerSecond=t/(e=0;--n)r=a[t=o[n]],!0!==i[t]&&!0!==r||(this._assignValue(t,e),this._updateAttributeUpdateRange(t,e),!0===r&&s[t]===this.particleCount?(a[t]=!1,s[t]=0):1==r&&++s[t])},s.Emitter.prototype._updateAttributeUpdateRange=function(e,t){"use strict";var r=this.bufferUpdateRanges[e];r.min=Math.min(t,r.min),r.max=Math.max(t,r.max)},s.Emitter.prototype._resetBufferRanges=function(){"use strict";for(var e,t=this.bufferUpdateRanges,r=this.bufferUpdateKeys,i=this.bufferUpdateCount-1;i>=0;--i)t[e=r[i]].min=Number.POSITIVE_INFINITY,t[e].max=Number.NEGATIVE_INFINITY},s.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},s.Emitter.prototype._decrementParticleCount=function(){"use strict";--this.activeParticleCount},s.Emitter.prototype._incrementParticleCount=function(){"use strict";++this.activeParticleCount},s.Emitter.prototype._checkParticleAges=function(e,t,r,i){"use strict";for(var a,s,o,n,u=t-1;u>=e;--u)0!==(n=r[a=4*u])&&(o=r[a+1],s=r[a+2],1===this.direction?(o+=i)>=s&&(o=0,n=0,this._decrementParticleCount()):(o-=i)<=0&&(o=s,n=0,this._decrementParticleCount()),r[a]=n,r[a+1]=o,this._updateAttributeUpdateRange("params",u))},s.Emitter.prototype._activateParticles=function(e,t,r,i){"use strict";for(var a,s,o=this.direction,n=e;nthis.duration)return this.alive=!1,void(this.age=0);var o=1===this.particleCount?s:0|s,n=Math.min(o+a,this.activationEnd),u=n-this.activationIndex|0,l=u>0?e/u:0;this._activateParticles(o,n,i,l),this.activationIndex+=a,this.activationIndex>r&&(this.activationIndex=t),this.age+=e}else this.age=0}},s.Emitter.prototype.reset=function(e){"use strict";if(this.age=0,this.alive=!1,!0===e){for(var t,r=this.attributeOffset,i=r+this.particleCount,a=this.paramsArray,s=this.attributes.params.bufferAttribute,o=i-1;o>=r;--o)a[t=4*o]=0,a[t+1]=0;s.updateRange.offset=0,s.updateRange.count=-1,s.needsUpdate=!0}return this},s.Emitter.prototype.enable=function(){"use strict";return this.alive=!0,this},s.Emitter.prototype.disable=function(){"use strict";return this.alive=!1,this},s.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}}},t={};function r(i){var a=t[i];if(void 0!==a)return a.exports;var s=t[i]={exports:{}};return e[i](s,s.exports,r),s.exports}(()=>{var e=r(544);if("undefined"==typeof AFRAME)throw new Error("Component attempted to register before AFRAME was available.");AFRAME.registerComponent("particle-system",{schema:{preset:{type:"string",default:"",oneOf:["default","dust","snow","rain"]},maxAge:{type:"number",default:6},positionSpread:{type:"vec3",default:{x:0,y:0,z:0}},type:{type:"number",default:e.distributions.BOX},rotationAxis:{type:"string",default:"x"},rotationAngle:{type:"number",default:0},rotationAngleSpread:{type:"number",default:0},accelerationValue:{type:"vec3",default:{x:0,y:-10,z:0}},accelerationSpread:{type:"vec3",default:{x:10,y:0,z:10}},velocityValue:{type:"vec3",default:{x:0,y:25,z:0}},velocitySpread:{type:"vec3",default:{x:10,y:7.5,z:10}},dragValue:{type:"number",default:0},dragSpread:{type:"number",default:0},dragRandomise:{type:"boolean",default:!1},color:{type:"array",default:["#0000FF","#FF0000"]},size:{type:"array",default:["1"]},sizeSpread:{type:"array",default:["0"]},direction:{type:"number",default:1},duration:{type:"number",default:1/0},particleCount:{type:"number",default:1e3},texture:{type:"asset",default:"https://cdn.rawgit.com/IdeaSpaceVR/aframe-particle-system-component/master/dist/images/star2.png"},randomise:{type:"boolean",default:!1},opacity:{type:"array",default:["1"]},opacitySpread:{type:"array",default:["0"]},maxParticleCount:{type:"number",default:25e4},blending:{type:"number",default:THREE.AdditiveBlending,oneOf:[THREE.NoBlending,THREE.NormalBlending,THREE.AdditiveBlending,THREE.SubtractiveBlending,THREE.MultiplyBlending]},enabled:{type:"boolean",default:!0}},init:function(){this.presets={},this.presets.dust={maxAge:20,positionSpread:{x:100,y:100,z:100},rotationAngle:3.14,accelerationValue:{x:0,y:0,z:0},accelerationSpread:{x:0,y:0,z:0},velocityValue:{x:1,y:.3,z:1},velocitySpread:{x:.5,y:1,z:.5},color:["#FFFFFF"],particleCount:100,texture:"https://cdn.rawgit.com/IdeaSpaceVR/aframe-particle-system-component/master/dist/images/smokeparticle.png"},this.presets.snow={maxAge:20,positionSpread:{x:100,y:100,z:100},rotationAngle:3.14,accelerationValue:{x:0,y:0,z:0},accelerationSpread:{x:.2,y:0,z:.2},velocityValue:{x:0,y:8,z:0},velocitySpread:{x:2,y:0,z:2},color:["#FFFFFF"],particleCount:200,texture:"https://cdn.rawgit.com/IdeaSpaceVR/aframe-particle-system-component/master/dist/images/smokeparticle.png"},this.presets.rain={maxAge:1,positionSpread:{x:100,y:100,z:100},rotationAngle:3.14,accelerationValue:{x:0,y:3,z:0},accelerationSpread:{x:2,y:1,z:2},velocityValue:{x:0,y:75,z:0},velocitySpread:{x:10,y:50,z:10},color:["#FFFFFF"],size:[.4],texture:"https://cdn.rawgit.com/IdeaSpaceVR/aframe-particle-system-component/master/dist/images/raindrop.png"}},update:function(e){for(var t in this.particleGroup&&this.el.removeObject3D("particle-system"),this.preset=this.presets[this.data.preset]||{},this.data)this.data[t]=this.applyPreset(t);this.initParticleSystem(this.data),!0===this.data.enabled?this.startParticles():this.stopParticles()},applyPreset:function(e){return!this.attrValue[e]&&this.preset[e]?this.preset[e]:this.data[e]},tick:function(e,t){this.particleGroup.tick(t/1e3)},remove:function(){this.particleGroup&&this.el.removeObject3D("particle-system")},startParticles:function(){this.particleGroup.emitters.forEach((function(e){e.enable()}))},stopParticles:function(){this.particleGroup.emitters.forEach((function(e){e.disable()}))},initParticleSystem:function(t){var r=(new THREE.TextureLoader).load(t.texture,(function(e){return e}),(function(e){console.log(e.loaded/e.total*100+"% loaded")}),(function(e){console.log("An error occurred")}));this.particleGroup=new e.Group({texture:{value:r},maxParticleCount:t.maxParticleCount,blending:t.blending});var i=new e.Emitter({maxAge:{value:t.maxAge},type:{value:t.type},position:{spread:new THREE.Vector3(t.positionSpread.x,t.positionSpread.y,t.positionSpread.z),randomise:t.randomise},rotation:{axis:"x"==t.rotationAxis?new THREE.Vector3(1,0,0):"y"==t.rotationAxis?new THREE.Vector3(0,1,0):"z"==t.rotationAxis?new THREE.Vector3(0,0,1):new THREE.Vector3(0,1,0),angle:t.rotationAngle,angleSpread:t.rotationAngleSpread,static:!0},acceleration:{value:new THREE.Vector3(t.accelerationValue.x,t.accelerationValue.y,t.accelerationValue.z),spread:new THREE.Vector3(t.accelerationSpread.x,t.accelerationSpread.y,t.accelerationSpread.z)},velocity:{value:new THREE.Vector3(t.velocityValue.x,t.velocityValue.y,t.velocityValue.z),spread:new THREE.Vector3(t.velocitySpread.x,t.velocitySpread.y,t.velocitySpread.z)},drag:{value:new THREE.Vector3(t.dragValue.x,t.dragValue.y,t.dragValue.z),spread:new THREE.Vector3(t.dragSpread.x,t.dragSpread.y,t.dragSpread.z),randomise:t.dragRandomise},color:{value:t.color.map((function(e){return new THREE.Color(e)}))},size:{value:t.size.map((function(e){return parseFloat(e)})),spread:t.sizeSpread.map((function(e){return parseFloat(e)}))},direction:{value:t.direction},duration:t.duration,opacity:{value:t.opacity.map((function(e){return parseFloat(e)})),spread:t.opacitySpread.map((function(e){return parseFloat(e)}))},particleCount:t.particleCount});this.particleGroup.addEmitter(i),this.particleGroup.mesh.frustumCulled=!1,this.el.setObject3D("particle-system",this.particleGroup.mesh)}})})()})(); +//# sourceMappingURL=aframe-particle-system-component.min.js.map \ No newline at end of file diff --git a/dist/aframe-particle-system-component.min.js.map b/dist/aframe-particle-system-component.min.js.map new file mode 100644 index 0000000..860b89c --- /dev/null +++ b/dist/aframe-particle-system-component.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"aframe-particle-system-component.min.js","mappings":"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,KACFR,KAAM,MAERS,uBAAwB,CACpB,qBACA,sBACA,IAEA,mBACA,sBACA,IAEA,sBACA,sBACA,IAEA,sBACA,sBACA,KACFT,KAAM,MAERU,uBAAwB,CACpB,qCACA,qDACA,IAEA,qCACA,4BACA,IAEA,yCACA,oCACA,KACFV,KAAM,MAGRW,kBAAmB,CAGf,iCACA,6DACA,iCACA,+BACA,+BACA,6BACA,GACA,sIACA,sIACA,sIACA,uIACA,OACA,GACA,gEACA,kCACA,yBACA,UACA,GACA,sDACA,sCACA,yBACA,6BAEA,2BACA,0DACA,sFACA,2CACA,2DACA,0EACA,OACA,UACFX,KAAM,MAIRY,cAAe,CACX,iEACA,GACA,mCACA,0CACA,gDACA,mCACA,mCAEA,iEACA,aACA,GAGA,qCACA,0CACA,0CACA,6CACA,0CAEA,0DACA,+DACA,aAEA,GACA,oDACFZ,KAAM,OAGZ9G,EAAI2H,QAAU,CACbC,OAAQ,CACP5H,EAAI4G,aAAaC,QACjB7G,EAAI4G,aAAaG,SACjB/G,EAAI4G,aAAaI,WACjBhH,EAAI4G,aAAaK,SAEjBhB,MAAM4B,YAAYC,OAClB7B,MAAM4B,YAAYE,wBAClB9B,MAAM4B,YAAYG,gBAElBhI,EAAI4G,aAAaM,yBACjBlH,EAAI4G,aAAaO,YACjBnH,EAAI4G,aAAaQ,mBACjBpH,EAAI4G,aAAaS,kBACjBrH,EAAI4G,aAAaU,kBACjBtH,EAAI4G,aAAaW,uBACjBvH,EAAI4G,aAAaY,uBACjBxH,EAAI4G,aAAaa,kBAGjB,gBAMA,kCACA,sCACA,wCACA,mDACA,mDAEA,qCACA,6DACA,2DACA,2DACA,aAOA,qCACA,2CACA,gCACA,mCAGA,kEAGA,oBACA,qBACA,4BACA,oBAIA,qCACA,8BACA,8BACA,8BACA,aAIA,qCACA,oDACA,aAGA,4DAGA,sFAGA,6BACA,gEACA,YACA,mCACA,aAGA,4DAQA,sBACA,kDACA,6BACA,qCACA,qCACA,qCACA,oCACA,YACA,YACA,6BACA,aAEA,2EAGA,6BAGA,mCACA,4EACA,aAIA,qCACA,8CACA,8CACA,gDACA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","sources":["webpack://aframe-particle-system-component/./lib/SPE.js","webpack://aframe-particle-system-component/webpack/bootstrap","webpack://aframe-particle-system-component/./index.js"],"sourcesContent":["/* shader-particle-engine 1.0.6\n *\n * (c) 2015 Luke Moody (http://www.github.com/squarefeet)\n * Originally based on Lee Stemkoski's original work (https://github.com/stemkoski/stemkoski.github.com/blob/master/Three.js/js/ParticleEngine.js).\n *\n * shader-particle-engine may be freely distributed under the MIT license (See LICENSE at root of this repository.)\n */\n/**\n * @typedef {Number} distribution\n * @property {Number} SPE.distributions.BOX Values will be distributed within a box.\n * @property {Number} SPE.distributions.SPHERE Values will be distributed within a sphere.\n * @property {Number} SPE.distributions.DISC Values will be distributed within a 2D disc.\n */\n\n/**\n * Namespace for Shader Particle Engine.\n *\n * All SPE-related code sits under this namespace.\n *\n * @type {Object}\n * @namespace\n */\nvar SPE = {\n\n /**\n * A map of supported distribution types used\n * by SPE.Emitter instances.\n *\n * These distribution types can be applied to\n * an emitter globally, which will affect the\n * `position`, `velocity`, and `acceleration`\n * value calculations for an emitter, or they\n * can be applied on a per-property basis.\n *\n * @enum {Number}\n */\n distributions: {\n /**\n * Values will be distributed within a box.\n * @type {Number}\n */\n BOX: 1,\n\n /**\n * Values will be distributed on a sphere.\n * @type {Number}\n */\n SPHERE: 2,\n\n /**\n * Values will be distributed on a 2d-disc shape.\n * @type {Number}\n */\n DISC: 3,\n\n /**\n * Values will be distributed along a line.\n * @type {Number}\n */\n LINE: 4\n },\n\n\n /**\n * Set this value to however many 'steps' you\n * want value-over-lifetime properties to have.\n *\n * It's adjustable to fix an interpolation problem:\n *\n * Assuming you specify an opacity value as [0, 1, 0]\n * and the `valueOverLifetimeLength` is 4, then the\n * opacity value array will be reinterpolated to\n * be [0, 0.66, 0.66, 0].\n * This isn't ideal, as particles would never reach\n * full opacity.\n *\n * NOTE:\n * This property affects the length of ALL\n * value-over-lifetime properties for ALL\n * emitters and ALL groups.\n *\n * Only values >= 3 && <= 4 are allowed.\n *\n * @type {Number}\n */\n valueOverLifetimeLength: 4\n};\n\n// Module loader support:\nif ( typeof define === 'function' && define.amd ) {\n define( 'spe', SPE );\n}\nelse if ( typeof exports !== 'undefined' && typeof module !== 'undefined' ) {\n module.exports = SPE;\n}\n\n\n/**\n * A helper class for TypedArrays.\n *\n * Allows for easy resizing, assignment of various component-based\n * types (Vector2s, Vector3s, Vector4s, Mat3s, Mat4s),\n * as well as Colors (where components are `r`, `g`, `b`),\n * Numbers, and setting from other TypedArrays.\n *\n * @author Luke Moody\n * @constructor\n * @param {Function} TypedArrayConstructor The constructor to use (Float32Array, Uint8Array, etc.)\n * @param {Number} size The size of the array to create\n * @param {Number} componentSize The number of components per-value (ie. 3 for a vec3, 9 for a Mat3, etc.)\n * @param {Number} indexOffset The index in the array from which to start assigning values. Default `0` if none provided\n */\nSPE.TypedArrayHelper = function( TypedArrayConstructor, size, componentSize, indexOffset ) {\n 'use strict';\n\n this.componentSize = componentSize || 1;\n this.size = ( size || 1 );\n this.TypedArrayConstructor = TypedArrayConstructor || Float32Array;\n this.array = new TypedArrayConstructor( size * this.componentSize );\n this.indexOffset = indexOffset || 0;\n};\n\nSPE.TypedArrayHelper.constructor = SPE.TypedArrayHelper;\n\n/**\n * Sets the size of the internal array.\n *\n * Delegates to `this.shrink` or `this.grow` depending on size\n * argument's relation to the current size of the internal array.\n *\n * Note that if the array is to be shrunk, data will be lost.\n *\n * @param {Number} size The new size of the array.\n */\nSPE.TypedArrayHelper.prototype.setSize = function( size, noComponentMultiply ) {\n 'use strict';\n\n var currentArraySize = this.array.length;\n\n if ( !noComponentMultiply ) {\n size = size * this.componentSize;\n }\n\n if ( size < currentArraySize ) {\n return this.shrink( size );\n }\n else if ( size > currentArraySize ) {\n return this.grow( size );\n }\n else {\n console.info( 'TypedArray is already of size:', size + '.', 'Will not resize.' );\n }\n};\n\n/**\n * Shrinks the internal array.\n *\n * @param {Number} size The new size of the typed array. Must be smaller than `this.array.length`.\n * @return {SPE.TypedArrayHelper} Instance of this class.\n */\nSPE.TypedArrayHelper.prototype.shrink = function( size ) {\n 'use strict';\n\n this.array = this.array.subarray( 0, size );\n this.size = size;\n return this;\n};\n\n/**\n * Grows the internal array.\n * @param {Number} size The new size of the typed array. Must be larger than `this.array.length`.\n * @return {SPE.TypedArrayHelper} Instance of this class.\n */\nSPE.TypedArrayHelper.prototype.grow = function( size ) {\n 'use strict';\n\n var existingArray = this.array,\n newArray = new this.TypedArrayConstructor( size );\n\n newArray.set( existingArray );\n this.array = newArray;\n this.size = size;\n\n return this;\n};\n\n\n/**\n * Perform a splice operation on this array's buffer.\n * @param {Number} start The start index of the splice. Will be multiplied by the number of components for this attribute.\n * @param {Number} end The end index of the splice. Will be multiplied by the number of components for this attribute.\n * @returns {Object} The SPE.TypedArrayHelper instance.\n */\nSPE.TypedArrayHelper.prototype.splice = function( start, end ) {\n 'use strict';\n start *= this.componentSize;\n end *= this.componentSize;\n\n var data = [],\n array = this.array,\n size = array.length;\n\n for ( var i = 0; i < size; ++i ) {\n if ( i < start || i >= end ) {\n data.push( array[ i ] );\n }\n // array[ i ] = 0;\n }\n\n this.setFromArray( 0, data );\n\n return this;\n};\n\n\n/**\n * Copies from the given TypedArray into this one, using the index argument\n * as the start position. Alias for `TypedArray.set`. Will automatically resize\n * if the given source array is of a larger size than the internal array.\n *\n * @param {Number} index The start position from which to copy into this array.\n * @param {TypedArray} array The array from which to copy; the source array.\n * @return {SPE.TypedArrayHelper} Instance of this class.\n */\nSPE.TypedArrayHelper.prototype.setFromArray = function( index, array ) {\n 'use strict';\n\n var sourceArraySize = array.length,\n newSize = index + sourceArraySize;\n\n if ( newSize > this.array.length ) {\n this.grow( newSize );\n }\n else if ( newSize < this.array.length ) {\n this.shrink( newSize );\n }\n\n this.array.set( array, this.indexOffset + index );\n\n return this;\n};\n\n/**\n * Set a Vector2 value at `index`.\n *\n * @param {Number} index The index at which to set the vec2 values from.\n * @param {Vector2} vec2 Any object that has `x` and `y` properties.\n * @return {SPE.TypedArrayHelper} Instance of this class.\n */\nSPE.TypedArrayHelper.prototype.setVec2 = function( index, vec2 ) {\n 'use strict';\n\n return this.setVec2Components( index, vec2.x, vec2.y );\n};\n\n/**\n * Set a Vector2 value using raw components.\n *\n * @param {Number} index The index at which to set the vec2 values from.\n * @param {Number} x The Vec2's `x` component.\n * @param {Number} y The Vec2's `y` component.\n * @return {SPE.TypedArrayHelper} Instance of this class.\n */\nSPE.TypedArrayHelper.prototype.setVec2Components = function( index, x, y ) {\n 'use strict';\n\n var array = this.array,\n i = this.indexOffset + ( index * this.componentSize );\n\n array[ i ] = x;\n array[ i + 1 ] = y;\n return this;\n};\n\n/**\n * Set a Vector3 value at `index`.\n *\n * @param {Number} index The index at which to set the vec3 values from.\n * @param {Vector3} vec2 Any object that has `x`, `y`, and `z` properties.\n * @return {SPE.TypedArrayHelper} Instance of this class.\n */\nSPE.TypedArrayHelper.prototype.setVec3 = function( index, vec3 ) {\n 'use strict';\n\n return this.setVec3Components( index, vec3.x, vec3.y, vec3.z );\n};\n\n/**\n * Set a Vector3 value using raw components.\n *\n * @param {Number} index The index at which to set the vec3 values from.\n * @param {Number} x The Vec3's `x` component.\n * @param {Number} y The Vec3's `y` component.\n * @param {Number} z The Vec3's `z` component.\n * @return {SPE.TypedArrayHelper} Instance of this class.\n */\nSPE.TypedArrayHelper.prototype.setVec3Components = function( index, x, y, z ) {\n 'use strict';\n\n var array = this.array,\n i = this.indexOffset + ( index * this.componentSize );\n\n array[ i ] = x;\n array[ i + 1 ] = y;\n array[ i + 2 ] = z;\n return this;\n};\n\n/**\n * Set a Vector4 value at `index`.\n *\n * @param {Number} index The index at which to set the vec4 values from.\n * @param {Vector4} vec2 Any object that has `x`, `y`, `z`, and `w` properties.\n * @return {SPE.TypedArrayHelper} Instance of this class.\n */\nSPE.TypedArrayHelper.prototype.setVec4 = function( index, vec4 ) {\n 'use strict';\n\n return this.setVec4Components( index, vec4.x, vec4.y, vec4.z, vec4.w );\n};\n\n/**\n * Set a Vector4 value using raw components.\n *\n * @param {Number} index The index at which to set the vec4 values from.\n * @param {Number} x The Vec4's `x` component.\n * @param {Number} y The Vec4's `y` component.\n * @param {Number} z The Vec4's `z` component.\n * @param {Number} w The Vec4's `w` component.\n * @return {SPE.TypedArrayHelper} Instance of this class.\n */\nSPE.TypedArrayHelper.prototype.setVec4Components = function( index, x, y, z, w ) {\n 'use strict';\n\n var array = this.array,\n i = this.indexOffset + ( index * this.componentSize );\n\n array[ i ] = x;\n array[ i + 1 ] = y;\n array[ i + 2 ] = z;\n array[ i + 3 ] = w;\n return this;\n};\n\n/**\n * Set a Matrix3 value at `index`.\n *\n * @param {Number} index The index at which to set the matrix values from.\n * @param {Matrix3} mat3 The 3x3 matrix to set from. Must have a TypedArray property named `elements` to copy from.\n * @return {SPE.TypedArrayHelper} Instance of this class.\n */\nSPE.TypedArrayHelper.prototype.setMat3 = function( index, mat3 ) {\n 'use strict';\n\n return this.setFromArray( this.indexOffset + ( index * this.componentSize ), mat3.elements );\n};\n\n/**\n * Set a Matrix4 value at `index`.\n *\n * @param {Number} index The index at which to set the matrix values from.\n * @param {Matrix4} mat3 The 4x4 matrix to set from. Must have a TypedArray property named `elements` to copy from.\n * @return {SPE.TypedArrayHelper} Instance of this class.\n */\nSPE.TypedArrayHelper.prototype.setMat4 = function( index, mat4 ) {\n 'use strict';\n\n return this.setFromArray( this.indexOffset + ( index * this.componentSize ), mat4.elements );\n};\n\n/**\n * Set a Color value at `index`.\n *\n * @param {Number} index The index at which to set the vec3 values from.\n * @param {Color} color Any object that has `r`, `g`, and `b` properties.\n * @return {SPE.TypedArrayHelper} Instance of this class.\n */\nSPE.TypedArrayHelper.prototype.setColor = function( index, color ) {\n 'use strict';\n\n return this.setVec3Components( index, color.r, color.g, color.b );\n};\n\n/**\n * Set a Number value at `index`.\n *\n * @param {Number} index The index at which to set the vec3 values from.\n * @param {Number} numericValue The number to assign to this index in the array.\n * @return {SPE.TypedArrayHelper} Instance of this class.\n */\nSPE.TypedArrayHelper.prototype.setNumber = function( index, numericValue ) {\n 'use strict';\n\n this.array[ this.indexOffset + ( index * this.componentSize ) ] = numericValue;\n return this;\n};\n\n/**\n * Returns the value of the array at the given index, taking into account\n * the `indexOffset` property of this class.\n *\n * Note that this function ignores the component size and will just return a\n * single value.\n *\n * @param {Number} index The index in the array to fetch.\n * @return {Number} The value at the given index.\n */\nSPE.TypedArrayHelper.prototype.getValueAtIndex = function( index ) {\n 'use strict';\n\n return this.array[ this.indexOffset + index ];\n};\n\n/**\n * Returns the component value of the array at the given index, taking into account\n * the `indexOffset` property of this class.\n *\n * If the componentSize is set to 3, then it will return a new TypedArray\n * of length 3.\n *\n * @param {Number} index The index in the array to fetch.\n * @return {TypedArray} The component value at the given index.\n */\nSPE.TypedArrayHelper.prototype.getComponentValueAtIndex = function( index ) {\n 'use strict';\n\n return this.array.subarray( this.indexOffset + ( index * this.componentSize ) );\n};\n\n/**\n * A helper to handle creating and updating a THREE.BufferAttribute instance.\n *\n * @author Luke Moody\n * @constructor\n * @param {String} type The buffer attribute type. See SPE.ShaderAttribute.typeSizeMap for valid values.\n * @param {Boolean=} dynamicBuffer Whether this buffer attribute should be marked as dynamic or not.\n * @param {Function=} arrayType A reference to a TypedArray constructor. Defaults to Float32Array if none provided.\n */\nSPE.ShaderAttribute = function( type, dynamicBuffer, arrayType ) {\n\t'use strict';\n\n\tvar typeMap = SPE.ShaderAttribute.typeSizeMap;\n\n\tthis.type = typeof type === 'string' && typeMap.hasOwnProperty( type ) ? type : 'f';\n\tthis.componentSize = typeMap[ this.type ];\n\tthis.arrayType = arrayType || Float32Array;\n\tthis.typedArray = null;\n\tthis.bufferAttribute = null;\n\tthis.dynamicBuffer = !!dynamicBuffer;\n\n\tthis.updateMin = 0;\n\tthis.updateMax = 0;\n};\n\nSPE.ShaderAttribute.constructor = SPE.ShaderAttribute;\n\n/**\n * A map of uniform types to their component size.\n * @enum {Number}\n */\nSPE.ShaderAttribute.typeSizeMap = {\n\t/**\n\t * Float\n\t * @type {Number}\n\t */\n\tf: 1,\n\n\t/**\n\t * Vec2\n\t * @type {Number}\n\t */\n\tv2: 2,\n\n\t/**\n\t * Vec3\n\t * @type {Number}\n\t */\n\tv3: 3,\n\n\t/**\n\t * Vec4\n\t * @type {Number}\n\t */\n\tv4: 4,\n\n\t/**\n\t * Color\n\t * @type {Number}\n\t */\n\tc: 3,\n\n\t/**\n\t * Mat3\n\t * @type {Number}\n\t */\n\tm3: 9,\n\n\t/**\n\t * Mat4\n\t * @type {Number}\n\t */\n\tm4: 16\n};\n\n/**\n * Calculate the minimum and maximum update range for this buffer attribute using\n * component size independant min and max values.\n *\n * @param {Number} min The start of the range to mark as needing an update.\n * @param {Number} max The end of the range to mark as needing an update.\n */\nSPE.ShaderAttribute.prototype.setUpdateRange = function( min, max ) {\n\t'use strict';\n\n\tthis.updateMin = Math.min( min * this.componentSize, this.updateMin * this.componentSize );\n\tthis.updateMax = Math.max( max * this.componentSize, this.updateMax * this.componentSize );\n};\n\n/**\n * Calculate the number of indices that this attribute should mark as needing\n * updating. Also marks the attribute as needing an update.\n */\nSPE.ShaderAttribute.prototype.flagUpdate = function() {\n\t'use strict';\n\n\tvar attr = this.bufferAttribute,\n\t\trange = attr.updateRange;\n\n\trange.offset = this.updateMin;\n\trange.count = Math.min( ( this.updateMax - this.updateMin ) + this.componentSize, this.typedArray.array.length );\n\t// console.log( range.offset, range.count, this.typedArray.array.length );\n\t// console.log( 'flagUpdate:', range.offset, range.count );\n\tattr.needsUpdate = true;\n};\n\n\n\n/**\n * Reset the index update counts for this attribute\n */\nSPE.ShaderAttribute.prototype.resetUpdateRange = function() {\n\t'use strict';\n\n\tthis.updateMin = 0;\n\tthis.updateMax = 0;\n};\n\nSPE.ShaderAttribute.prototype.resetDynamic = function() {\n\t'use strict';\n\tthis.bufferAttribute.usage = this.dynamicBuffer ?\n\t\tTHREE.DynamicDrawUsage :\n\t\tTHREE.StaticDrawUsage;\n};\n\n/**\n * Perform a splice operation on this attribute's buffer.\n * @param {Number} start The start index of the splice. Will be multiplied by the number of components for this attribute.\n * @param {Number} end The end index of the splice. Will be multiplied by the number of components for this attribute.\n */\nSPE.ShaderAttribute.prototype.splice = function( start, end ) {\n\t'use strict';\n\n\tthis.typedArray.splice( start, end );\n\n\t// Reset the reference to the attribute's typed array\n\t// since it has probably changed.\n\tthis.forceUpdateAll();\n};\n\nSPE.ShaderAttribute.prototype.forceUpdateAll = function() {\n\t'use strict';\n\n\tthis.bufferAttribute.array = this.typedArray.array;\n\tthis.bufferAttribute.updateRange.offset = 0;\n\tthis.bufferAttribute.updateRange.count = -1;\n\t// this.bufferAttribute.dynamic = false;\n\t// this.bufferAttribute.usage = this.dynamicBuffer ?\n\t// \tTHREE.DynamicDrawUsage :\n\t// \tTHREE.StaticDrawUsage;\n\n\tthis.bufferAttribute.usage = THREE.StaticDrawUsage;\n\tthis.bufferAttribute.needsUpdate = true;\n};\n\n/**\n * Make sure this attribute has a typed array associated with it.\n *\n * If it does, then it will ensure the typed array is of the correct size.\n *\n * If not, a new SPE.TypedArrayHelper instance will be created.\n *\n * @param {Number} size The size of the typed array to create or update to.\n */\nSPE.ShaderAttribute.prototype._ensureTypedArray = function( size ) {\n\t'use strict';\n\n\t// Condition that's most likely to be true at the top: no change.\n\tif ( this.typedArray !== null && this.typedArray.size === size * this.componentSize ) {\n\t\treturn;\n\t}\n\n\t// Resize the array if we need to, telling the TypedArrayHelper to\n\t// ignore it's component size when evaluating size.\n\telse if ( this.typedArray !== null && this.typedArray.size !== size ) {\n\t\tthis.typedArray.setSize( size );\n\t}\n\n\t// This condition should only occur once in an attribute's lifecycle.\n\telse if ( this.typedArray === null ) {\n\t\tthis.typedArray = new SPE.TypedArrayHelper( this.arrayType, size, this.componentSize );\n\t}\n};\n\n\n/**\n * Creates a THREE.BufferAttribute instance if one doesn't exist already.\n *\n * Ensures a typed array is present by calling _ensureTypedArray() first.\n *\n * If a buffer attribute exists already, then it will be marked as needing an update.\n *\n * @param {Number} size The size of the typed array to create if one doesn't exist, or resize existing array to.\n */\nSPE.ShaderAttribute.prototype._createBufferAttribute = function( size ) {\n\t'use strict';\n\n\t// Make sure the typedArray is present and correct.\n\tthis._ensureTypedArray( size );\n\n\t// Don't create it if it already exists, but do\n\t// flag that it needs updating on the next render\n\t// cycle.\n\tif ( this.bufferAttribute !== null ) {\n\t\tthis.bufferAttribute.array = this.typedArray.array;\n\n\t\t// Since THREE.js version 81, dynamic count calculation was removed\n\t\t// so I need to do it manually here.\n\t\t//\n\t\t// In the next minor release, I may well remove this check and force\n\t\t// dependency on THREE r81+.\n\t\tif ( parseFloat( THREE.REVISION ) >= 81 ) {\n\t\t\tthis.bufferAttribute.count = this.bufferAttribute.array.length / this.bufferAttribute.itemSize;\n\t\t}\n\n\t\tthis.bufferAttribute.needsUpdate = true;\n\t\treturn;\n\t}\n\n\tthis.bufferAttribute = new THREE.BufferAttribute( this.typedArray.array, this.componentSize );\n\t// this.bufferAttribute.dynamic = this.dynamicBuffer;\n\tthis.bufferAttribute.usage = this.dynamicBuffer ?\n\t\tTHREE.DynamicDrawUsage :\n\t\tTHREE.StaticDrawUsage;\n};\n\n/**\n * Returns the length of the typed array associated with this attribute.\n * @return {Number} The length of the typed array. Will be 0 if no typed array has been created yet.\n */\nSPE.ShaderAttribute.prototype.getLength = function() {\n\t'use strict';\n\n\tif ( this.typedArray === null ) {\n\t\treturn 0;\n\t}\n\n\treturn this.typedArray.array.length;\n};\n\n\nSPE.shaderChunks = {\n // Register color-packing define statements.\n defines: [\n '#define PACKED_COLOR_SIZE 256.0',\n '#define PACKED_COLOR_DIVISOR 255.0'\n ].join( '\\n' ),\n\n // All uniforms used by vertex / fragment shaders\n uniforms: [\n 'uniform float deltaTime;',\n 'uniform float runTime;',\n 'uniform sampler2D tex;',\n 'uniform vec4 textureAnimation;',\n 'uniform float scale;',\n ].join( '\\n' ),\n\n // All attributes used by the vertex shader.\n //\n // Note that some attributes are squashed into other ones:\n //\n // * Drag is acceleration.w\n attributes: [\n 'attribute vec4 acceleration;',\n 'attribute vec3 velocity;',\n 'attribute vec4 rotation;',\n 'attribute vec3 rotationCenter;',\n 'attribute vec4 params;',\n 'attribute vec4 size;',\n 'attribute vec4 angle;',\n 'attribute vec4 color;',\n 'attribute vec4 opacity;'\n ].join( '\\n' ),\n\n //\n varyings: [\n 'varying vec4 vColor;',\n '#ifdef SHOULD_ROTATE_TEXTURE',\n ' varying float vAngle;',\n '#endif',\n\n '#ifdef SHOULD_CALCULATE_SPRITE',\n ' varying vec4 vSpriteSheet;',\n '#endif'\n ].join( '\\n' ),\n\n\n // Branch-avoiding comparison fns\n // - http://theorangeduck.com/page/avoiding-shader-conditionals\n branchAvoidanceFunctions: [\n 'float when_gt(float x, float y) {',\n ' return max(sign(x - y), 0.0);',\n '}',\n\n 'float when_lt(float x, float y) {',\n ' return min( max(1.0 - sign(x - y), 0.0), 1.0 );',\n '}',\n\n 'float when_eq( float x, float y ) {',\n ' return 1.0 - abs( sign( x - y ) );',\n '}',\n\n 'float when_ge(float x, float y) {',\n ' return 1.0 - when_lt(x, y);',\n '}',\n\n 'float when_le(float x, float y) {',\n ' return 1.0 - when_gt(x, y);',\n '}',\n\n // Branch-avoiding logical operators\n // (to be used with above comparison fns)\n 'float and(float a, float b) {',\n ' return a * b;',\n '}',\n\n 'float or(float a, float b) {',\n ' return min(a + b, 1.0);',\n '}',\n ].join( '\\n' ),\n\n\n // From:\n // - http://stackoverflow.com/a/12553149\n // - https://stackoverflow.com/questions/22895237/hexadecimal-to-rgb-values-in-webgl-shader\n unpackColor: [\n 'vec3 unpackColor( in float hex ) {',\n ' vec3 c = vec3( 0.0 );',\n\n ' float r = mod( (hex / PACKED_COLOR_SIZE / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );',\n ' float g = mod( (hex / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );',\n ' float b = mod( hex, PACKED_COLOR_SIZE );',\n\n ' c.r = r / PACKED_COLOR_DIVISOR;',\n ' c.g = g / PACKED_COLOR_DIVISOR;',\n ' c.b = b / PACKED_COLOR_DIVISOR;',\n\n ' return c;',\n '}',\n ].join( '\\n' ),\n\n unpackRotationAxis: [\n 'vec3 unpackRotationAxis( in float hex ) {',\n ' vec3 c = vec3( 0.0 );',\n\n ' float r = mod( (hex / PACKED_COLOR_SIZE / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );',\n ' float g = mod( (hex / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );',\n ' float b = mod( hex, PACKED_COLOR_SIZE );',\n\n ' c.r = r / PACKED_COLOR_DIVISOR;',\n ' c.g = g / PACKED_COLOR_DIVISOR;',\n ' c.b = b / PACKED_COLOR_DIVISOR;',\n\n ' c *= vec3( 2.0 );',\n ' c -= vec3( 1.0 );',\n\n ' return c;',\n '}',\n ].join( '\\n' ),\n\n floatOverLifetime: [\n 'float getFloatOverLifetime( in float positionInTime, in vec4 attr ) {',\n ' highp float value = 0.0;',\n ' float deltaAge = positionInTime * float( VALUE_OVER_LIFETIME_LENGTH - 1 );',\n ' float fIndex = 0.0;',\n ' float shouldApplyValue = 0.0;',\n\n // This might look a little odd, but it's faster in the testing I've done than using branches.\n // Uses basic maths to avoid branching.\n //\n // Take a look at the branch-avoidance functions defined above,\n // and be sure to check out The Orange Duck site where I got this\n // from (link above).\n\n // Fix for static emitters (age is always zero).\n ' value += attr[ 0 ] * when_eq( deltaAge, 0.0 );',\n '',\n ' for( int i = 0; i < VALUE_OVER_LIFETIME_LENGTH - 1; ++i ) {',\n ' fIndex = float( i );',\n ' shouldApplyValue = and( when_gt( deltaAge, fIndex ), when_le( deltaAge, fIndex + 1.0 ) );',\n ' value += shouldApplyValue * mix( attr[ i ], attr[ i + 1 ], deltaAge - fIndex );',\n ' }',\n '',\n ' return value;',\n '}',\n ].join( '\\n' ),\n\n colorOverLifetime: [\n 'vec3 getColorOverLifetime( in float positionInTime, in vec3 color1, in vec3 color2, in vec3 color3, in vec3 color4 ) {',\n ' vec3 value = vec3( 0.0 );',\n ' value.x = getFloatOverLifetime( positionInTime, vec4( color1.x, color2.x, color3.x, color4.x ) );',\n ' value.y = getFloatOverLifetime( positionInTime, vec4( color1.y, color2.y, color3.y, color4.y ) );',\n ' value.z = getFloatOverLifetime( positionInTime, vec4( color1.z, color2.z, color3.z, color4.z ) );',\n ' return value;',\n '}',\n ].join( '\\n' ),\n\n paramFetchingFunctions: [\n 'float getAlive() {',\n ' return params.x;',\n '}',\n\n 'float getAge() {',\n ' return params.y;',\n '}',\n\n 'float getMaxAge() {',\n ' return params.z;',\n '}',\n\n 'float getWiggle() {',\n ' return params.w;',\n '}',\n ].join( '\\n' ),\n\n forceFetchingFunctions: [\n 'vec4 getPosition( in float age ) {',\n ' return modelViewMatrix * vec4( position, 1.0 );',\n '}',\n\n 'vec3 getVelocity( in float age ) {',\n ' return velocity * age;',\n '}',\n\n 'vec3 getAcceleration( in float age ) {',\n ' return acceleration.xyz * age;',\n '}',\n ].join( '\\n' ),\n\n\n rotationFunctions: [\n // Huge thanks to:\n // - http://www.neilmendoza.com/glsl-rotation-about-an-arbitrary-axis/\n '#ifdef SHOULD_ROTATE_PARTICLES',\n ' mat4 getRotationMatrix( in vec3 axis, in float angle) {',\n ' axis = normalize(axis);',\n ' float s = sin(angle);',\n ' float c = cos(angle);',\n ' float oc = 1.0 - c;',\n '',\n ' 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,',\n ' oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0,',\n ' oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0,',\n ' 0.0, 0.0, 0.0, 1.0);',\n ' }',\n '',\n ' vec3 getRotation( in vec3 pos, in float positionInTime ) {',\n ' if( rotation.y == 0.0 ) {',\n ' return pos;',\n ' }',\n '',\n ' vec3 axis = unpackRotationAxis( rotation.x );',\n ' vec3 center = rotationCenter;',\n ' vec3 translated;',\n ' mat4 rotationMatrix;',\n\n ' float angle = 0.0;',\n ' angle += when_eq( rotation.z, 0.0 ) * rotation.y;',\n ' angle += when_gt( rotation.z, 0.0 ) * mix( 0.0, rotation.y, positionInTime );',\n ' translated = rotationCenter - pos;',\n ' rotationMatrix = getRotationMatrix( axis, angle );',\n ' return center - vec3( rotationMatrix * vec4( translated, 0.0 ) );',\n ' }',\n '#endif'\n ].join( '\\n' ),\n\n\n // Fragment chunks\n rotateTexture: [\n ' vec2 vUv = vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y );',\n '',\n ' #ifdef SHOULD_ROTATE_TEXTURE',\n ' float x = gl_PointCoord.x - 0.5;',\n ' float y = 1.0 - gl_PointCoord.y - 0.5;',\n ' float c = cos( -vAngle );',\n ' float s = sin( -vAngle );',\n\n ' vUv = vec2( c * x + s * y + 0.5, c * y - s * x + 0.5 );',\n ' #endif',\n '',\n\n // Spritesheets overwrite angle calculations.\n ' #ifdef SHOULD_CALCULATE_SPRITE',\n ' float framesX = vSpriteSheet.x;',\n ' float framesY = vSpriteSheet.y;',\n ' float columnNorm = vSpriteSheet.z;',\n ' float rowNorm = vSpriteSheet.w;',\n\n ' vUv.x = gl_PointCoord.x * framesX + columnNorm;',\n ' vUv.y = 1.0 - (gl_PointCoord.y * framesY + rowNorm);',\n ' #endif',\n\n '',\n ' vec4 rotatedTexture = texture2D( tex, vUv );',\n ].join( '\\n' )\n};\n\nSPE.shaders = {\n\tvertex: [\n\t\tSPE.shaderChunks.defines,\n\t\tSPE.shaderChunks.uniforms,\n\t\tSPE.shaderChunks.attributes,\n\t\tSPE.shaderChunks.varyings,\n\n\t\tTHREE.ShaderChunk.common,\n\t\tTHREE.ShaderChunk.logdepthbuf_pars_vertex,\n\t\tTHREE.ShaderChunk.fog_pars_vertex,\n\n\t\tSPE.shaderChunks.branchAvoidanceFunctions,\n\t\tSPE.shaderChunks.unpackColor,\n\t\tSPE.shaderChunks.unpackRotationAxis,\n\t\tSPE.shaderChunks.floatOverLifetime,\n\t\tSPE.shaderChunks.colorOverLifetime,\n\t\tSPE.shaderChunks.paramFetchingFunctions,\n\t\tSPE.shaderChunks.forceFetchingFunctions,\n\t\tSPE.shaderChunks.rotationFunctions,\n\n\n\t\t'void main() {',\n\n\n\t\t//\n\t\t// Setup...\n\t\t//\n\t\t' highp float age = getAge();',\n\t\t' highp float alive = getAlive();',\n\t\t' highp float maxAge = getMaxAge();',\n\t\t' highp float positionInTime = (age / maxAge);',\n\t\t' highp float isAlive = when_gt( alive, 0.0 );',\n\n\t\t' #ifdef SHOULD_WIGGLE_PARTICLES',\n\t\t' float wiggleAmount = positionInTime * getWiggle();',\n\t\t' float wiggleSin = isAlive * sin( wiggleAmount );',\n\t\t' float wiggleCos = isAlive * cos( wiggleAmount );',\n\t\t' #endif',\n\n\t\t//\n\t\t// Forces\n\t\t//\n\n\t\t// Get forces & position\n\t\t' vec3 vel = getVelocity( age );',\n\t\t' vec3 accel = getAcceleration( age );',\n\t\t' vec3 force = vec3( 0.0 );',\n\t\t' vec3 pos = vec3( position );',\n\n\t\t// Calculate the required drag to apply to the forces.\n\t\t' float drag = 1.0 - (positionInTime * 0.5) * acceleration.w;',\n\n\t\t// Integrate forces...\n\t\t' force += vel;',\n\t\t' force *= drag;',\n\t\t' force += accel * age;',\n\t\t' pos += force;',\n\n\n\t\t// Wiggly wiggly wiggle!\n\t\t' #ifdef SHOULD_WIGGLE_PARTICLES',\n\t\t' pos.x += wiggleSin;',\n\t\t' pos.y += wiggleCos;',\n\t\t' pos.z += wiggleSin;',\n\t\t' #endif',\n\n\n\t\t// Rotate the emitter around it's central point\n\t\t' #ifdef SHOULD_ROTATE_PARTICLES',\n\t\t' pos = getRotation( pos, positionInTime );',\n\t\t' #endif',\n\n\t\t// Convert pos to a world-space value\n\t\t' vec4 mvPosition = modelViewMatrix * vec4( pos, 1.0 );',\n\n\t\t// Determine point size.\n\t\t' highp float pointSize = getFloatOverLifetime( positionInTime, size ) * isAlive;',\n\n\t\t// Determine perspective\n\t\t' #ifdef HAS_PERSPECTIVE',\n\t\t' float perspective = scale / length( mvPosition.xyz );',\n\t\t' #else',\n\t\t' float perspective = 1.0;',\n\t\t' #endif',\n\n\t\t// Apply perpective to pointSize value\n\t\t' float pointSizePerspective = pointSize * perspective;',\n\n\n\t\t//\n\t\t// Appearance\n\t\t//\n\n\t\t// Determine color and opacity for this particle\n\t\t' #ifdef COLORIZE',\n\t\t' vec3 c = isAlive * getColorOverLifetime(',\n\t\t' positionInTime,',\n\t\t' unpackColor( color.x ),',\n\t\t' unpackColor( color.y ),',\n\t\t' unpackColor( color.z ),',\n\t\t' unpackColor( color.w )',\n\t\t' );',\n\t\t' #else',\n\t\t' vec3 c = vec3(1.0);',\n\t\t' #endif',\n\n\t\t' float o = isAlive * getFloatOverLifetime( positionInTime, opacity );',\n\n\t\t// Assign color to vColor varying.\n\t\t' vColor = vec4( c, o );',\n\n\t\t// Determine angle\n\t\t' #ifdef SHOULD_ROTATE_TEXTURE',\n\t\t' vAngle = isAlive * getFloatOverLifetime( positionInTime, angle );',\n\t\t' #endif',\n\n\t\t// If this particle is using a sprite-sheet as a texture, we'll have to figure out\n\t\t// what frame of the texture the particle is using at it's current position in time.\n\t\t' #ifdef SHOULD_CALCULATE_SPRITE',\n\t\t' float framesX = textureAnimation.x;',\n\t\t' float framesY = textureAnimation.y;',\n\t\t' float loopCount = textureAnimation.w;',\n\t\t' float totalFrames = textureAnimation.z;',\n\t\t' float frameNumber = mod( (positionInTime * loopCount) * totalFrames, totalFrames );',\n\n\t\t' float column = floor(mod( frameNumber, framesX ));',\n\t\t' float row = floor( (frameNumber - column) / framesX );',\n\n\t\t' float columnNorm = column / framesX;',\n\t\t' float rowNorm = row / framesY;',\n\n\t\t' vSpriteSheet.x = 1.0 / framesX;',\n\t\t' vSpriteSheet.y = 1.0 / framesY;',\n\t\t' vSpriteSheet.z = columnNorm;',\n\t\t' vSpriteSheet.w = rowNorm;',\n\t\t' #endif',\n\n\t\t//\n\t\t// Write values\n\t\t//\n\n\t\t// Set PointSize according to size at current point in time.\n\t\t' gl_PointSize = pointSizePerspective;',\n\t\t' gl_Position = projectionMatrix * mvPosition;',\n\n\t\tTHREE.ShaderChunk.logdepthbuf_vertex,\n\t\tTHREE.ShaderChunk.fog_vertex,\n\n\t\t'}'\n\t].join( '\\n' ),\n\n\tfragment: [\n\t\tSPE.shaderChunks.uniforms,\n\n\t\tTHREE.ShaderChunk.common,\n\t\tTHREE.ShaderChunk.fog_pars_fragment,\n\t\tTHREE.ShaderChunk.logdepthbuf_pars_fragment,\n\n\t\tSPE.shaderChunks.varyings,\n\n\t\tSPE.shaderChunks.branchAvoidanceFunctions,\n\n\t\t'void main() {',\n\t\t' vec3 outgoingLight = vColor.xyz;',\n\t\t' ',\n\t\t' #ifdef ALPHATEST',\n\t\t' if ( vColor.w < float(ALPHATEST) ) discard;',\n\t\t' #endif',\n\n\t\tSPE.shaderChunks.rotateTexture,\n\n\t\tTHREE.ShaderChunk.logdepthbuf_fragment,\n\n\t\t' outgoingLight = vColor.xyz * rotatedTexture.xyz;',\n\t\t' gl_FragColor = vec4( outgoingLight.xyz, rotatedTexture.w * vColor.w );',\n\n\t\tTHREE.ShaderChunk.fog_fragment,\n\n\t\t'}'\n\t].join( '\\n' )\n};\n\n\n/**\n * A bunch of utility functions used throughout the library.\n * @namespace\n * @type {Object}\n */\nSPE.utils = {\n /**\n * A map of types used by `SPE.utils.ensureTypedArg` and\n * `SPE.utils.ensureArrayTypedArg` to compare types against.\n *\n * @enum {String}\n */\n types: {\n /**\n * Boolean type.\n * @type {String}\n */\n BOOLEAN: 'boolean',\n\n /**\n * String type.\n * @type {String}\n */\n STRING: 'string',\n\n /**\n * Number type.\n * @type {String}\n */\n NUMBER: 'number',\n\n /**\n * Object type.\n * @type {String}\n */\n OBJECT: 'object'\n },\n\n /**\n * Given a value, a type, and a default value to fallback to,\n * ensure the given argument adheres to the type requesting,\n * returning the default value if type check is false.\n *\n * @param {(boolean|string|number|object)} arg The value to perform a type-check on.\n * @param {String} type The type the `arg` argument should adhere to.\n * @param {(boolean|string|number|object)} defaultValue A default value to fallback on if the type check fails.\n * @return {(boolean|string|number|object)} The given value if type check passes, or the default value if it fails.\n */\n ensureTypedArg: function( arg, type, defaultValue ) {\n 'use strict';\n\n if ( typeof arg === type ) {\n return arg;\n }\n else {\n return defaultValue;\n }\n },\n\n /**\n * Given an array of values, a type, and a default value,\n * ensure the given array's contents ALL adhere to the provided type,\n * returning the default value if type check fails.\n *\n * If the given value to check isn't an Array, delegates to SPE.utils.ensureTypedArg.\n *\n * @param {Array|boolean|string|number|object} arg The array of values to check type of.\n * @param {String} type The type that should be adhered to.\n * @param {(boolean|string|number|object)} defaultValue A default fallback value.\n * @return {(boolean|string|number|object)} The given value if type check passes, or the default value if it fails.\n */\n ensureArrayTypedArg: function( arg, type, defaultValue ) {\n 'use strict';\n\n // If the argument being checked is an array, loop through\n // it and ensure all the values are of the correct type,\n // falling back to the defaultValue if any aren't.\n if ( Array.isArray( arg ) ) {\n for ( var i = arg.length - 1; i >= 0; --i ) {\n if ( typeof arg[ i ] !== type ) {\n return defaultValue;\n }\n }\n\n return arg;\n }\n\n // If the arg isn't an array then just fallback to\n // checking the type.\n return this.ensureTypedArg( arg, type, defaultValue );\n },\n\n /**\n * Ensures the given value is an instance of a constructor function.\n *\n * @param {Object} arg The value to check instance of.\n * @param {Function} instance The constructor of the instance to check against.\n * @param {Object} defaultValue A default fallback value if instance check fails\n * @return {Object} The given value if type check passes, or the default value if it fails.\n */\n ensureInstanceOf: function( arg, instance, defaultValue ) {\n 'use strict';\n\n if ( instance !== undefined && arg instanceof instance ) {\n return arg;\n }\n else {\n return defaultValue;\n }\n },\n\n /**\n * Given an array of values, ensure the instances of all items in the array\n * matches the given instance constructor falling back to a default value if\n * the check fails.\n *\n * If given value isn't an Array, delegates to `SPE.utils.ensureInstanceOf`.\n *\n * @param {Array|Object} arg The value to perform the instanceof check on.\n * @param {Function} instance The constructor of the instance to check against.\n * @param {Object} defaultValue A default fallback value if instance check fails\n * @return {Object} The given value if type check passes, or the default value if it fails.\n */\n ensureArrayInstanceOf: function( arg, instance, defaultValue ) {\n 'use strict';\n\n // If the argument being checked is an array, loop through\n // it and ensure all the values are of the correct type,\n // falling back to the defaultValue if any aren't.\n if ( Array.isArray( arg ) ) {\n for ( var i = arg.length - 1; i >= 0; --i ) {\n if ( instance !== undefined && arg[ i ] instanceof instance === false ) {\n return defaultValue;\n }\n }\n\n return arg;\n }\n\n // If the arg isn't an array then just fallback to\n // checking the type.\n return this.ensureInstanceOf( arg, instance, defaultValue );\n },\n\n /**\n * Ensures that any \"value-over-lifetime\" properties of an emitter are\n * of the correct length (as dictated by `SPE.valueOverLifetimeLength`).\n *\n * Delegates to `SPE.utils.interpolateArray` for array resizing.\n *\n * If properties aren't arrays, then property values are put into one.\n *\n * @param {Object} property The property of an SPE.Emitter instance to check compliance of.\n * @param {Number} minLength The minimum length of the array to create.\n * @param {Number} maxLength The maximum length of the array to create.\n */\n ensureValueOverLifetimeCompliance: function( property, minLength, maxLength ) {\n 'use strict';\n\n minLength = minLength || 3;\n maxLength = maxLength || 3;\n\n // First, ensure both properties are arrays.\n if ( Array.isArray( property._value ) === false ) {\n property._value = [ property._value ];\n }\n\n if ( Array.isArray( property._spread ) === false ) {\n property._spread = [ property._spread ];\n }\n\n var valueLength = this.clamp( property._value.length, minLength, maxLength ),\n spreadLength = this.clamp( property._spread.length, minLength, maxLength ),\n desiredLength = Math.max( valueLength, spreadLength );\n\n if ( property._value.length !== desiredLength ) {\n property._value = this.interpolateArray( property._value, desiredLength );\n }\n\n if ( property._spread.length !== desiredLength ) {\n property._spread = this.interpolateArray( property._spread, desiredLength );\n }\n },\n\n /**\n * Performs linear interpolation (lerp) on an array.\n *\n * For example, lerping [1, 10], with a `newLength` of 10 will produce [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].\n *\n * Delegates to `SPE.utils.lerpTypeAgnostic` to perform the actual\n * interpolation.\n *\n * @param {Array} srcArray The array to lerp.\n * @param {Number} newLength The length the array should be interpolated to.\n * @return {Array} The interpolated array.\n */\n interpolateArray: function( srcArray, newLength ) {\n 'use strict';\n\n var sourceLength = srcArray.length,\n newArray = [ typeof srcArray[ 0 ].clone === 'function' ? srcArray[ 0 ].clone() : srcArray[ 0 ] ],\n factor = ( sourceLength - 1 ) / ( newLength - 1 );\n\n\n for ( var i = 1; i < newLength - 1; ++i ) {\n var f = i * factor,\n before = Math.floor( f ),\n after = Math.ceil( f ),\n delta = f - before;\n\n newArray[ i ] = this.lerpTypeAgnostic( srcArray[ before ], srcArray[ after ], delta );\n }\n\n newArray.push(\n typeof srcArray[ sourceLength - 1 ].clone === 'function' ?\n srcArray[ sourceLength - 1 ].clone() :\n srcArray[ sourceLength - 1 ]\n );\n\n return newArray;\n },\n\n /**\n * Clamp a number to between the given min and max values.\n * @param {Number} value The number to clamp.\n * @param {Number} min The minimum value.\n * @param {Number} max The maximum value.\n * @return {Number} The clamped number.\n */\n clamp: function( value, min, max ) {\n 'use strict';\n\n return Math.max( min, Math.min( value, max ) );\n },\n\n /**\n * If the given value is less than the epsilon value, then return\n * a randomised epsilon value if specified, or just the epsilon value if not.\n * Works for negative numbers as well as positive.\n *\n * @param {Number} value The value to perform the operation on.\n * @param {Boolean} randomise Whether the value should be randomised.\n * @return {Number} The result of the operation.\n */\n zeroToEpsilon: function( value, randomise ) {\n 'use strict';\n\n var epsilon = 0.00001,\n result = value;\n\n result = randomise ? Math.random() * epsilon * 10 : epsilon;\n\n if ( value < 0 && value > -epsilon ) {\n result = -result;\n }\n\n // if ( value === 0 ) {\n // result = randomise ? Math.random() * epsilon * 10 : epsilon;\n // }\n // else if ( value > 0 && value < epsilon ) {\n // result = randomise ? Math.random() * epsilon * 10 : epsilon;\n // }\n // else if ( value < 0 && value > -epsilon ) {\n // result = -( randomise ? Math.random() * epsilon * 10 : epsilon );\n // }\n\n return result;\n },\n\n /**\n * Linearly interpolates two values of various types. The given values\n * must be of the same type for the interpolation to work.\n * @param {(number|Object)} start The start value of the lerp.\n * @param {(number|object)} end The end value of the lerp.\n * @param {Number} delta The delta posiiton of the lerp operation. Ideally between 0 and 1 (inclusive).\n * @return {(number|object|undefined)} The result of the operation. Result will be undefined if\n * the start and end arguments aren't a supported type, or\n * if their types do not match.\n */\n lerpTypeAgnostic: function( start, end, delta ) {\n 'use strict';\n\n var types = this.types,\n out;\n\n if ( typeof start === types.NUMBER && typeof end === types.NUMBER ) {\n return start + ( ( end - start ) * delta );\n }\n else if ( start instanceof THREE.Vector2 && end instanceof THREE.Vector2 ) {\n out = start.clone();\n out.x = this.lerp( start.x, end.x, delta );\n out.y = this.lerp( start.y, end.y, delta );\n return out;\n }\n else if ( start instanceof THREE.Vector3 && end instanceof THREE.Vector3 ) {\n out = start.clone();\n out.x = this.lerp( start.x, end.x, delta );\n out.y = this.lerp( start.y, end.y, delta );\n out.z = this.lerp( start.z, end.z, delta );\n return out;\n }\n else if ( start instanceof THREE.Vector4 && end instanceof THREE.Vector4 ) {\n out = start.clone();\n out.x = this.lerp( start.x, end.x, delta );\n out.y = this.lerp( start.y, end.y, delta );\n out.z = this.lerp( start.z, end.z, delta );\n out.w = this.lerp( start.w, end.w, delta );\n return out;\n }\n else if ( start instanceof THREE.Color && end instanceof THREE.Color ) {\n out = start.clone();\n out.r = this.lerp( start.r, end.r, delta );\n out.g = this.lerp( start.g, end.g, delta );\n out.b = this.lerp( start.b, end.b, delta );\n return out;\n }\n else {\n console.warn( 'Invalid argument types, or argument types do not match:', start, end );\n }\n },\n\n /**\n * Perform a linear interpolation operation on two numbers.\n * @param {Number} start The start value.\n * @param {Number} end The end value.\n * @param {Number} delta The position to interpolate to.\n * @return {Number} The result of the lerp operation.\n */\n lerp: function( start, end, delta ) {\n 'use strict';\n return start + ( ( end - start ) * delta );\n },\n\n /**\n * Rounds a number to a nearest multiple.\n *\n * @param {Number} n The number to round.\n * @param {Number} multiple The multiple to round to.\n * @return {Number} The result of the round operation.\n */\n roundToNearestMultiple: function( n, multiple ) {\n 'use strict';\n\n var remainder = 0;\n\n if ( multiple === 0 ) {\n return n;\n }\n\n remainder = Math.abs( n ) % multiple;\n\n if ( remainder === 0 ) {\n return n;\n }\n\n if ( n < 0 ) {\n return -( Math.abs( n ) - remainder );\n }\n\n return n + multiple - remainder;\n },\n\n /**\n * Check if all items in an array are equal. Uses strict equality.\n *\n * @param {Array} array The array of values to check equality of.\n * @return {Boolean} Whether the array's values are all equal or not.\n */\n arrayValuesAreEqual: function( array ) {\n 'use strict';\n\n for ( var i = 0; i < array.length - 1; ++i ) {\n if ( array[ i ] !== array[ i + 1 ] ) {\n return false;\n }\n }\n\n return true;\n },\n\n // colorsAreEqual: function() {\n // var colors = Array.prototype.slice.call( arguments ),\n // numColors = colors.length;\n\n // for ( var i = 0, color1, color2; i < numColors - 1; ++i ) {\n // color1 = colors[ i ];\n // color2 = colors[ i + 1 ];\n\n // if (\n // color1.r !== color2.r ||\n // color1.g !== color2.g ||\n // color1.b !== color2.b\n // ) {\n // return false\n // }\n // }\n\n // return true;\n // },\n\n\n /**\n * Given a start value and a spread value, create and return a random\n * number.\n * @param {Number} base The start value.\n * @param {Number} spread The size of the random variance to apply.\n * @return {Number} A randomised number.\n */\n randomFloat: function( base, spread ) {\n 'use strict';\n return base + spread * ( Math.random() - 0.5 );\n },\n\n\n\n /**\n * Given an SPE.ShaderAttribute instance, and various other settings,\n * assign values to the attribute's array in a `vec3` format.\n *\n * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to.\n * @param {Number} index The offset in the attribute's TypedArray to save the result from.\n * @param {Object} base THREE.Vector3 instance describing the start value.\n * @param {Object} spread THREE.Vector3 instance describing the random variance to apply to the start value.\n * @param {Object} spreadClamp THREE.Vector3 instance describing the multiples to clamp the randomness to.\n */\n randomVector3: function( attribute, index, base, spread, spreadClamp ) {\n 'use strict';\n\n var x = base.x + ( Math.random() * spread.x - ( spread.x * 0.5 ) ),\n y = base.y + ( Math.random() * spread.y - ( spread.y * 0.5 ) ),\n z = base.z + ( Math.random() * spread.z - ( spread.z * 0.5 ) );\n\n // var x = this.randomFloat( base.x, spread.x ),\n // y = this.randomFloat( base.y, spread.y ),\n // z = this.randomFloat( base.z, spread.z );\n\n if ( spreadClamp ) {\n x = -spreadClamp.x * 0.5 + this.roundToNearestMultiple( x, spreadClamp.x );\n y = -spreadClamp.y * 0.5 + this.roundToNearestMultiple( y, spreadClamp.y );\n z = -spreadClamp.z * 0.5 + this.roundToNearestMultiple( z, spreadClamp.z );\n }\n\n attribute.typedArray.setVec3Components( index, x, y, z );\n },\n\n /**\n * Given an SPE.Shader attribute instance, and various other settings,\n * assign Color values to the attribute.\n * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to.\n * @param {Number} index The offset in the attribute's TypedArray to save the result from.\n * @param {Object} base THREE.Color instance describing the start color.\n * @param {Object} spread THREE.Vector3 instance describing the random variance to apply to the start color.\n */\n randomColor: function( attribute, index, base, spread ) {\n 'use strict';\n\n var r = base.r + ( Math.random() * spread.x ),\n g = base.g + ( Math.random() * spread.y ),\n b = base.b + ( Math.random() * spread.z );\n\n r = this.clamp( r, 0, 1 );\n g = this.clamp( g, 0, 1 );\n b = this.clamp( b, 0, 1 );\n\n\n attribute.typedArray.setVec3Components( index, r, g, b );\n },\n\n\n randomColorAsHex: ( function() {\n 'use strict';\n\n var workingColor = new THREE.Color();\n\n /**\n * Assigns a random color value, encoded as a hex value in decimal\n * format, to a SPE.ShaderAttribute instance.\n * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to.\n * @param {Number} index The offset in the attribute's TypedArray to save the result from.\n * @param {Object} base THREE.Color instance describing the start color.\n * @param {Object} spread THREE.Vector3 instance describing the random variance to apply to the start color.\n */\n return function( attribute, index, base, spread ) {\n var numItems = base.length,\n colors = [];\n\n for ( var i = 0; i < numItems; ++i ) {\n var spreadVector = spread[ i ];\n\n workingColor.copy( base[ i ] );\n\n workingColor.r += ( Math.random() * spreadVector.x ) - ( spreadVector.x * 0.5 );\n workingColor.g += ( Math.random() * spreadVector.y ) - ( spreadVector.y * 0.5 );\n workingColor.b += ( Math.random() * spreadVector.z ) - ( spreadVector.z * 0.5 );\n\n workingColor.r = this.clamp( workingColor.r, 0, 1 );\n workingColor.g = this.clamp( workingColor.g, 0, 1 );\n workingColor.b = this.clamp( workingColor.b, 0, 1 );\n\n colors.push( workingColor.getHex() );\n }\n\n attribute.typedArray.setVec4Components( index, colors[ 0 ], colors[ 1 ], colors[ 2 ], colors[ 3 ] );\n };\n }() ),\n\n /**\n * Given an SPE.ShaderAttribute instance, and various other settings,\n * assign values to the attribute's array in a `vec3` format.\n *\n * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to.\n * @param {Number} index The offset in the attribute's TypedArray to save the result from.\n * @param {Object} start THREE.Vector3 instance describing the start line position.\n * @param {Object} end THREE.Vector3 instance describing the end line position.\n */\n randomVector3OnLine: function( attribute, index, start, end ) {\n 'use strict';\n var pos = start.clone();\n\n pos.lerp( end, Math.random() );\n\n attribute.typedArray.setVec3Components( index, pos.x, pos.y, pos.z );\n },\n\n /**\n * Given an SPE.Shader attribute instance, and various other settings,\n * assign Color values to the attribute.\n * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to.\n * @param {Number} index The offset in the attribute's TypedArray to save the result from.\n * @param {Object} base THREE.Color instance describing the start color.\n * @param {Object} spread THREE.Vector3 instance describing the random variance to apply to the start color.\n */\n\n /**\n * Assigns a random vector 3 value to an SPE.ShaderAttribute instance, projecting the\n * given values onto a sphere.\n *\n * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to.\n * @param {Number} index The offset in the attribute's TypedArray to save the result from.\n * @param {Object} base THREE.Vector3 instance describing the origin of the transform.\n * @param {Number} radius The radius of the sphere to project onto.\n * @param {Number} radiusSpread The amount of randomness to apply to the projection result\n * @param {Object} radiusScale THREE.Vector3 instance describing the scale of each axis of the sphere.\n * @param {Number} radiusSpreadClamp What numeric multiple the projected value should be clamped to.\n */\n randomVector3OnSphere: function(\n attribute, index, base, radius, radiusSpread, radiusScale, radiusSpreadClamp, distributionClamp\n ) {\n 'use strict';\n\n var depth = 2 * Math.random() - 1,\n t = 6.2832 * Math.random(),\n r = Math.sqrt( 1 - depth * depth ),\n rand = this.randomFloat( radius, radiusSpread ),\n x = 0,\n y = 0,\n z = 0;\n\n\n if ( radiusSpreadClamp ) {\n rand = Math.round( rand / radiusSpreadClamp ) * radiusSpreadClamp;\n }\n\n\n\n // Set position on sphere\n x = r * Math.cos( t ) * rand;\n y = r * Math.sin( t ) * rand;\n z = depth * rand;\n\n // Apply radius scale to this position\n x *= radiusScale.x;\n y *= radiusScale.y;\n z *= radiusScale.z;\n\n // Translate to the base position.\n x += base.x;\n y += base.y;\n z += base.z;\n\n // Set the values in the typed array.\n attribute.typedArray.setVec3Components( index, x, y, z );\n },\n\n seededRandom: function( seed ) {\n var x = Math.sin( seed ) * 10000;\n return x - ( x | 0 );\n },\n\n\n\n /**\n * Assigns a random vector 3 value to an SPE.ShaderAttribute instance, projecting the\n * given values onto a 2d-disc.\n *\n * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to.\n * @param {Number} index The offset in the attribute's TypedArray to save the result from.\n * @param {Object} base THREE.Vector3 instance describing the origin of the transform.\n * @param {Number} radius The radius of the sphere to project onto.\n * @param {Number} radiusSpread The amount of randomness to apply to the projection result\n * @param {Object} radiusScale THREE.Vector3 instance describing the scale of each axis of the disc. The z-component is ignored.\n * @param {Number} radiusSpreadClamp What numeric multiple the projected value should be clamped to.\n */\n randomVector3OnDisc: function( attribute, index, base, radius, radiusSpread, radiusScale, radiusSpreadClamp ) {\n 'use strict';\n\n var t = 6.2832 * Math.random(),\n rand = Math.abs( this.randomFloat( radius, radiusSpread ) ),\n x = 0,\n y = 0,\n z = 0;\n\n if ( radiusSpreadClamp ) {\n rand = Math.round( rand / radiusSpreadClamp ) * radiusSpreadClamp;\n }\n\n // Set position on sphere\n x = Math.cos( t ) * rand;\n y = Math.sin( t ) * rand;\n\n // Apply radius scale to this position\n x *= radiusScale.x;\n y *= radiusScale.y;\n\n // Translate to the base position.\n x += base.x;\n y += base.y;\n z += base.z;\n\n // Set the values in the typed array.\n attribute.typedArray.setVec3Components( index, x, y, z );\n },\n\n randomDirectionVector3OnSphere: ( function() {\n 'use strict';\n\n var v = new THREE.Vector3();\n\n /**\n * Given an SPE.ShaderAttribute instance, create a direction vector from the given\n * position, using `speed` as the magnitude. Values are saved to the attribute.\n *\n * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to.\n * @param {Number} index The offset in the attribute's TypedArray to save the result from.\n * @param {Number} posX The particle's x coordinate.\n * @param {Number} posY The particle's y coordinate.\n * @param {Number} posZ The particle's z coordinate.\n * @param {Object} emitterPosition THREE.Vector3 instance describing the emitter's base position.\n * @param {Number} speed The magnitude to apply to the vector.\n * @param {Number} speedSpread The amount of randomness to apply to the magnitude.\n */\n return function( attribute, index, posX, posY, posZ, emitterPosition, speed, speedSpread ) {\n v.copy( emitterPosition );\n\n v.x -= posX;\n v.y -= posY;\n v.z -= posZ;\n\n v.normalize().multiplyScalar( -this.randomFloat( speed, speedSpread ) );\n\n attribute.typedArray.setVec3Components( index, v.x, v.y, v.z );\n };\n }() ),\n\n\n randomDirectionVector3OnDisc: ( function() {\n 'use strict';\n\n var v = new THREE.Vector3();\n\n /**\n * Given an SPE.ShaderAttribute instance, create a direction vector from the given\n * position, using `speed` as the magnitude. Values are saved to the attribute.\n *\n * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to.\n * @param {Number} index The offset in the attribute's TypedArray to save the result from.\n * @param {Number} posX The particle's x coordinate.\n * @param {Number} posY The particle's y coordinate.\n * @param {Number} posZ The particle's z coordinate.\n * @param {Object} emitterPosition THREE.Vector3 instance describing the emitter's base position.\n * @param {Number} speed The magnitude to apply to the vector.\n * @param {Number} speedSpread The amount of randomness to apply to the magnitude.\n */\n return function( attribute, index, posX, posY, posZ, emitterPosition, speed, speedSpread ) {\n v.copy( emitterPosition );\n\n v.x -= posX;\n v.y -= posY;\n v.z -= posZ;\n\n v.normalize().multiplyScalar( -this.randomFloat( speed, speedSpread ) );\n\n attribute.typedArray.setVec3Components( index, v.x, v.y, 0 );\n };\n }() ),\n\n getPackedRotationAxis: ( function() {\n 'use strict';\n\n var v = new THREE.Vector3(),\n vSpread = new THREE.Vector3(),\n c = new THREE.Color(),\n addOne = new THREE.Vector3( 1, 1, 1 );\n\n /**\n * Given a rotation axis, and a rotation axis spread vector,\n * calculate a randomised rotation axis, and pack it into\n * a hexadecimal value represented in decimal form.\n * @param {Object} axis THREE.Vector3 instance describing the rotation axis.\n * @param {Object} axisSpread THREE.Vector3 instance describing the amount of randomness to apply to the rotation axis.\n * @return {Number} The packed rotation axis, with randomness.\n */\n return function( axis, axisSpread ) {\n v.copy( axis ).normalize();\n vSpread.copy( axisSpread ).normalize();\n\n v.x += ( -axisSpread.x * 0.5 ) + ( Math.random() * axisSpread.x );\n v.y += ( -axisSpread.y * 0.5 ) + ( Math.random() * axisSpread.y );\n v.z += ( -axisSpread.z * 0.5 ) + ( Math.random() * axisSpread.z );\n\n // v.x = Math.abs( v.x );\n // v.y = Math.abs( v.y );\n // v.z = Math.abs( v.z );\n\n v.normalize().add( addOne ).multiplyScalar( 0.5 );\n\n c.setRGB( v.x, v.y, v.z );\n\n return c.getHex();\n };\n }() )\n};\n\n\n/**\n * An SPE.Group instance.\n * @typedef {Object} Group\n * @see SPE.Group\n */\n\n/**\n * A map of options to configure an SPE.Group instance.\n * @typedef {Object} GroupOptions\n *\n * @property {Object} texture An object describing the texture used by the group.\n *\n * @property {Object} texture.value An instance of THREE.Texture.\n *\n * @property {Object=} texture.frames A THREE.Vector2 instance describing the number\n * of frames on the x- and y-axis of the given texture.\n * If not provided, the texture will NOT be treated as\n * a sprite-sheet and as such will NOT be animated.\n *\n * @property {Number} [texture.frameCount=texture.frames.x * texture.frames.y] The total number of frames in the sprite-sheet.\n * Allows for sprite-sheets that don't fill the entire\n * texture.\n *\n * @property {Number} texture.loop The number of loops through the sprite-sheet that should\n * be performed over the course of a single particle's lifetime.\n *\n * @property {Number} fixedTimeStep If no `dt` (or `deltaTime`) value is passed to this group's\n * `tick()` function, this number will be used to move the particle\n * simulation forward. Value in SECONDS.\n *\n * @property {Boolean} hasPerspective Whether the distance a particle is from the camera should affect\n * the particle's size.\n *\n * @property {Boolean} colorize Whether the particles in this group should be rendered with color, or\n * whether the only color of particles will come from the provided texture.\n *\n * @property {Number} blending One of Three.js's blending modes to apply to this group's `ShaderMaterial`.\n *\n * @property {Boolean} transparent Whether these particle's should be rendered with transparency.\n *\n * @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.\n *\n * @property {Boolean} depthWrite Whether rendering the group has any effect on the depth buffer.\n *\n * @property {Boolean} depthTest Whether to have depth test enabled when rendering this group.\n *\n * @property {Boolean} fog Whether this group's particles should be affected by their scene's fog.\n *\n * @property {Number} scale The scale factor to apply to this group's particle sizes. Useful for\n * setting particle sizes to be relative to renderer size.\n */\n\n\n/**\n * The SPE.Group class. Creates a new group, containing a material, geometry, and mesh.\n *\n * @constructor\n * @param {GroupOptions} options A map of options to configure the group instance.\n */\nSPE.Group = function( options ) {\n 'use strict';\n\n var utils = SPE.utils,\n types = utils.types;\n\n // Ensure we have a map of options to play with\n options = utils.ensureTypedArg( options, types.OBJECT, {} );\n options.texture = utils.ensureTypedArg( options.texture, types.OBJECT, {} );\n\n // Assign a UUID to this instance\n this.uuid = THREE.MathUtils.generateUUID();\n\n // If no `deltaTime` value is passed to the `SPE.Group.tick` function,\n // the value of this property will be used to advance the simulation.\n this.fixedTimeStep = utils.ensureTypedArg( options.fixedTimeStep, types.NUMBER, 0.016 );\n\n // Set properties used in the uniforms map, starting with the\n // texture stuff.\n this.texture = utils.ensureInstanceOf( options.texture.value, THREE.Texture, null );\n this.textureFrames = utils.ensureInstanceOf( options.texture.frames, THREE.Vector2, new THREE.Vector2( 1, 1 ) );\n this.textureFrameCount = utils.ensureTypedArg( options.texture.frameCount, types.NUMBER, this.textureFrames.x * this.textureFrames.y );\n this.textureLoop = utils.ensureTypedArg( options.texture.loop, types.NUMBER, 1 );\n this.textureFrames.max( new THREE.Vector2( 1, 1 ) );\n\n this.hasPerspective = utils.ensureTypedArg( options.hasPerspective, types.BOOLEAN, true );\n this.colorize = utils.ensureTypedArg( options.colorize, types.BOOLEAN, true );\n\n this.maxParticleCount = utils.ensureTypedArg( options.maxParticleCount, types.NUMBER, null );\n\n\n // Set properties used to define the ShaderMaterial's appearance.\n this.blending = utils.ensureTypedArg( options.blending, types.NUMBER, THREE.AdditiveBlending );\n this.transparent = utils.ensureTypedArg( options.transparent, types.BOOLEAN, true );\n this.alphaTest = parseFloat( utils.ensureTypedArg( options.alphaTest, types.NUMBER, 0.0 ) );\n this.depthWrite = utils.ensureTypedArg( options.depthWrite, types.BOOLEAN, false );\n this.depthTest = utils.ensureTypedArg( options.depthTest, types.BOOLEAN, true );\n this.fog = utils.ensureTypedArg( options.fog, types.BOOLEAN, true );\n this.scale = utils.ensureTypedArg( options.scale, types.NUMBER, 300 );\n\n // Where emitter's go to curl up in a warm blanket and live\n // out their days.\n this.emitters = [];\n this.emitterIDs = [];\n\n // Create properties for use by the emitter pooling functions.\n this._pool = [];\n this._poolCreationSettings = null;\n this._createNewWhenPoolEmpty = 0;\n\n // Whether all attributes should be forced to updated\n // their entire buffer contents on the next tick.\n //\n // Used when an emitter is removed.\n this._attributesNeedRefresh = false;\n this._attributesNeedDynamicReset = false;\n\n this.particleCount = 0;\n\n\n // Map of uniforms to be applied to the ShaderMaterial instance.\n this.uniforms = {\n tex: {\n type: 't',\n value: this.texture\n },\n textureAnimation: {\n type: 'v4',\n value: new THREE.Vector4(\n this.textureFrames.x,\n this.textureFrames.y,\n this.textureFrameCount,\n Math.max( Math.abs( this.textureLoop ), 1.0 )\n )\n },\n fogColor: {\n type: 'c',\n value: this.fog ? new THREE.Color() : null\n },\n fogNear: {\n type: 'f',\n value: 10\n },\n fogFar: {\n type: 'f',\n value: 200\n },\n fogDensity: {\n type: 'f',\n value: 0.5\n },\n deltaTime: {\n type: 'f',\n value: 0\n },\n runTime: {\n type: 'f',\n value: 0\n },\n scale: {\n type: 'f',\n value: this.scale\n }\n };\n\n // Add some defines into the mix...\n this.defines = {\n HAS_PERSPECTIVE: this.hasPerspective,\n COLORIZE: this.colorize,\n VALUE_OVER_LIFETIME_LENGTH: SPE.valueOverLifetimeLength,\n\n SHOULD_ROTATE_TEXTURE: false,\n SHOULD_ROTATE_PARTICLES: false,\n SHOULD_WIGGLE_PARTICLES: false,\n\n SHOULD_CALCULATE_SPRITE: this.textureFrames.x > 1 || this.textureFrames.y > 1\n };\n\n // Map of all attributes to be applied to the particles.\n //\n // See SPE.ShaderAttribute for a bit more info on this bit.\n this.attributes = {\n position: new SPE.ShaderAttribute( 'v3', true ),\n acceleration: new SPE.ShaderAttribute( 'v4', true ), // w component is drag\n velocity: new SPE.ShaderAttribute( 'v3', true ),\n rotation: new SPE.ShaderAttribute( 'v4', true ),\n rotationCenter: new SPE.ShaderAttribute( 'v3', true ),\n params: new SPE.ShaderAttribute( 'v4', true ), // Holds (alive, age, delay, wiggle)\n size: new SPE.ShaderAttribute( 'v4', true ),\n angle: new SPE.ShaderAttribute( 'v4', true ),\n color: new SPE.ShaderAttribute( 'v4', true ),\n opacity: new SPE.ShaderAttribute( 'v4', true )\n };\n\n this.attributeKeys = Object.keys( this.attributes );\n this.attributeCount = this.attributeKeys.length;\n\n // Create the ShaderMaterial instance that'll help render the\n // particles.\n this.material = new THREE.ShaderMaterial( {\n uniforms: this.uniforms,\n vertexShader: SPE.shaders.vertex,\n fragmentShader: SPE.shaders.fragment,\n blending: this.blending,\n transparent: this.transparent,\n alphaTest: this.alphaTest,\n depthWrite: this.depthWrite,\n depthTest: this.depthTest,\n defines: this.defines,\n fog: this.fog\n } );\n\n // Create the BufferGeometry and Points instances, ensuring\n // the geometry and material are given to the latter.\n this.geometry = new THREE.BufferGeometry();\n this.mesh = new THREE.Points( this.geometry, this.material );\n\n if ( this.maxParticleCount === null ) {\n console.warn( 'SPE.Group: No maxParticleCount specified. Adding emitters after rendering will probably cause errors.' );\n }\n};\n\nSPE.Group.constructor = SPE.Group;\n\n\nSPE.Group.prototype._updateDefines = function() {\n 'use strict';\n\n var emitters = this.emitters,\n i = emitters.length - 1,\n emitter,\n defines = this.defines;\n\n for ( i; i >= 0; --i ) {\n emitter = emitters[ i ];\n\n // Only do angle calculation if there's no spritesheet defined.\n //\n // Saves calculations being done and then overwritten in the shaders.\n if ( !defines.SHOULD_CALCULATE_SPRITE ) {\n defines.SHOULD_ROTATE_TEXTURE = defines.SHOULD_ROTATE_TEXTURE || !!Math.max(\n Math.max.apply( null, emitter.angle.value ),\n Math.max.apply( null, emitter.angle.spread )\n );\n }\n\n defines.SHOULD_ROTATE_PARTICLES = defines.SHOULD_ROTATE_PARTICLES || !!Math.max(\n emitter.rotation.angle,\n emitter.rotation.angleSpread\n );\n\n defines.SHOULD_WIGGLE_PARTICLES = defines.SHOULD_WIGGLE_PARTICLES || !!Math.max(\n emitter.wiggle.value,\n emitter.wiggle.spread\n );\n }\n\n this.material.needsUpdate = true;\n};\n\nSPE.Group.prototype._applyAttributesToGeometry = function() {\n 'use strict';\n\n var attributes = this.attributes,\n geometry = this.geometry,\n geometryAttributes = geometry.attributes,\n attribute,\n geometryAttribute;\n\n // Loop through all the shader attributes and assign (or re-assign)\n // typed array buffers to each one.\n for ( var attr in attributes ) {\n if ( attributes.hasOwnProperty( attr ) ) {\n attribute = attributes[ attr ];\n geometryAttribute = geometryAttributes[ attr ];\n\n // Update the array if this attribute exists on the geometry.\n //\n // This needs to be done because the attribute's typed array might have\n // been resized and reinstantiated, and might now be looking at a\n // different ArrayBuffer, so reference needs updating.\n if ( geometryAttribute ) {\n geometryAttribute.array = attribute.typedArray.array;\n }\n\n // // Add the attribute to the geometry if it doesn't already exist.\n else {\n geometry.setAttribute( attr, attribute.bufferAttribute );\n }\n\n // Mark the attribute as needing an update the next time a frame is rendered.\n attribute.bufferAttribute.needsUpdate = true;\n }\n }\n\n // Mark the draw range on the geometry. This will ensure\n // only the values in the attribute buffers that are\n // associated with a particle will be used in THREE's\n // render cycle.\n this.geometry.setDrawRange( 0, this.particleCount );\n};\n\n/**\n * Adds an SPE.Emitter instance to this group, creating particle values and\n * assigning them to this group's shader attributes.\n *\n * @param {Emitter} emitter The emitter to add to this group.\n */\nSPE.Group.prototype.addEmitter = function( emitter ) {\n 'use strict';\n\n // Ensure an actual emitter instance is passed here.\n //\n // Decided not to throw here, just in case a scene's\n // rendering would be paused. Logging an error instead\n // of stopping execution if exceptions aren't caught.\n if ( emitter instanceof SPE.Emitter === false ) {\n console.error( '`emitter` argument must be instance of SPE.Emitter. Was provided with:', emitter );\n return;\n }\n\n // If the emitter already exists as a member of this group, then\n // stop here, we don't want to add it again.\n else if ( this.emitterIDs.indexOf( emitter.uuid ) > -1 ) {\n console.error( 'Emitter already exists in this group. Will not add again.' );\n return;\n }\n\n // And finally, if the emitter is a member of another group,\n // don't add it to this group.\n else if ( emitter.group !== null ) {\n console.error( 'Emitter already belongs to another group. Will not add to requested group.' );\n return;\n }\n\n var attributes = this.attributes,\n start = this.particleCount,\n end = start + emitter.particleCount;\n\n // Update this group's particle count.\n this.particleCount = end;\n\n // Emit a warning if the emitter being added will exceed the buffer sizes specified.\n if ( this.maxParticleCount !== null && this.particleCount > this.maxParticleCount ) {\n console.warn( 'SPE.Group: maxParticleCount exceeded. Requesting', this.particleCount, 'particles, can support only', this.maxParticleCount );\n }\n\n\n // Set the `particlesPerSecond` value (PPS) on the emitter.\n // It's used to determine how many particles to release\n // on a per-frame basis.\n emitter._calculatePPSValue( emitter.maxAge._value + emitter.maxAge._spread );\n emitter._setBufferUpdateRanges( this.attributeKeys );\n\n // Store the offset value in the TypedArray attributes for this emitter.\n emitter._setAttributeOffset( start );\n\n // Save a reference to this group on the emitter so it knows\n // where it belongs.\n emitter.group = this;\n\n // Store reference to the attributes on the emitter for\n // easier access during the emitter's tick function.\n emitter.attributes = this.attributes;\n\n\n\n // Ensure the attributes and their BufferAttributes exist, and their\n // TypedArrays are of the correct size.\n for ( var attr in attributes ) {\n if ( attributes.hasOwnProperty( attr ) ) {\n // When creating a buffer, pass through the maxParticle count\n // if one is specified.\n attributes[ attr ]._createBufferAttribute(\n this.maxParticleCount !== null ?\n this.maxParticleCount :\n this.particleCount\n );\n }\n }\n\n // Loop through each particle this emitter wants to have, and create the attributes values,\n // storing them in the TypedArrays that each attribute holds.\n for ( var i = start; i < end; ++i ) {\n emitter._assignPositionValue( i );\n emitter._assignForceValue( i, 'velocity' );\n emitter._assignForceValue( i, 'acceleration' );\n emitter._assignAbsLifetimeValue( i, 'opacity' );\n emitter._assignAbsLifetimeValue( i, 'size' );\n emitter._assignAngleValue( i );\n emitter._assignRotationValue( i );\n emitter._assignParamsValue( i );\n emitter._assignColorValue( i );\n }\n\n // Update the geometry and make sure the attributes are referencing\n // the typed arrays properly.\n this._applyAttributesToGeometry();\n\n // Store this emitter in this group's emitter's store.\n this.emitters.push( emitter );\n this.emitterIDs.push( emitter.uuid );\n\n // Update certain flags to enable shader calculations only if they're necessary.\n this._updateDefines( emitter );\n\n // Update the material since defines might have changed\n this.material.needsUpdate = true;\n this.geometry.needsUpdate = true;\n this._attributesNeedRefresh = true;\n\n // Return the group to enable chaining.\n return this;\n};\n\n/**\n * Removes an SPE.Emitter instance from this group. When called,\n * all particle's belonging to the given emitter will be instantly\n * removed from the scene.\n *\n * @param {Emitter} emitter The emitter to add to this group.\n */\nSPE.Group.prototype.removeEmitter = function( emitter ) {\n 'use strict';\n\n var emitterIndex = this.emitterIDs.indexOf( emitter.uuid );\n\n // Ensure an actual emitter instance is passed here.\n //\n // Decided not to throw here, just in case a scene's\n // rendering would be paused. Logging an error instead\n // of stopping execution if exceptions aren't caught.\n if ( emitter instanceof SPE.Emitter === false ) {\n console.error( '`emitter` argument must be instance of SPE.Emitter. Was provided with:', emitter );\n return;\n }\n\n // Issue an error if the emitter isn't a member of this group.\n else if ( emitterIndex === -1 ) {\n console.error( 'Emitter does not exist in this group. Will not remove.' );\n return;\n }\n\n // Kill all particles by marking them as dead\n // and their age as 0.\n var start = emitter.attributeOffset,\n end = start + emitter.particleCount,\n params = this.attributes.params.typedArray;\n\n // Set alive and age to zero.\n for ( var i = start; i < end; ++i ) {\n params.array[ i * 4 ] = 0.0;\n params.array[ i * 4 + 1 ] = 0.0;\n }\n\n // Remove the emitter from this group's \"store\".\n this.emitters.splice( emitterIndex, 1 );\n this.emitterIDs.splice( emitterIndex, 1 );\n\n // Remove this emitter's attribute values from all shader attributes.\n // The `.splice()` call here also marks each attribute's buffer\n // as needing to update it's entire contents.\n for ( var attr in this.attributes ) {\n if ( this.attributes.hasOwnProperty( attr ) ) {\n this.attributes[ attr ].splice( start, end );\n }\n }\n\n // Ensure this group's particle count is correct.\n this.particleCount -= emitter.particleCount;\n\n // Call the emitter's remove method.\n emitter._onRemove();\n\n // Set a flag to indicate that the attribute buffers should\n // be updated in their entirety on the next frame.\n this._attributesNeedRefresh = true;\n};\n\n\n/**\n * Fetch a single emitter instance from the pool.\n * If there are no objects in the pool, a new emitter will be\n * created if specified.\n *\n * @return {Emitter|null}\n */\nSPE.Group.prototype.getFromPool = function() {\n 'use strict';\n\n var pool = this._pool,\n createNew = this._createNewWhenPoolEmpty;\n\n if ( pool.length ) {\n return pool.pop();\n }\n else if ( createNew ) {\n var emitter = new SPE.Emitter( this._poolCreationSettings );\n\n this.addEmitter( emitter );\n\n return emitter;\n }\n\n return null;\n};\n\n\n/**\n * Release an emitter into the pool.\n *\n * @param {ShaderParticleEmitter} emitter\n * @return {Group} This group instance.\n */\nSPE.Group.prototype.releaseIntoPool = function( emitter ) {\n 'use strict';\n\n if ( emitter instanceof SPE.Emitter === false ) {\n console.error( 'Argument is not instanceof SPE.Emitter:', emitter );\n return;\n }\n\n emitter.reset();\n this._pool.unshift( emitter );\n\n return this;\n};\n\n\n/**\n * Get the pool array\n *\n * @return {Array}\n */\nSPE.Group.prototype.getPool = function() {\n 'use strict';\n return this._pool;\n};\n\n\n/**\n * Add a pool of emitters to this particle group\n *\n * @param {Number} numEmitters The number of emitters to add to the pool.\n * @param {EmitterOptions|Array} emitterOptions An object, or array of objects, describing the options to pass to each emitter.\n * @param {Boolean} createNew Should a new emitter be created if the pool runs out?\n * @return {Group} This group instance.\n */\nSPE.Group.prototype.addPool = function( numEmitters, emitterOptions, createNew ) {\n 'use strict';\n\n var emitter;\n\n // Save relevant settings and flags.\n this._poolCreationSettings = emitterOptions;\n this._createNewWhenPoolEmpty = !!createNew;\n\n // Create the emitters, add them to this group and the pool.\n for ( var i = 0; i < numEmitters; ++i ) {\n if ( Array.isArray( emitterOptions ) ) {\n emitter = new SPE.Emitter( emitterOptions[ i ] );\n }\n else {\n emitter = new SPE.Emitter( emitterOptions );\n }\n this.addEmitter( emitter );\n this.releaseIntoPool( emitter );\n }\n\n return this;\n};\n\n\n\nSPE.Group.prototype._triggerSingleEmitter = function( pos ) {\n 'use strict';\n\n var emitter = this.getFromPool(),\n self = this;\n\n if ( emitter === null ) {\n console.log( 'SPE.Group pool ran out.' );\n return;\n }\n\n // TODO:\n // - Make sure buffers are update with thus new position.\n if ( pos instanceof THREE.Vector3 ) {\n emitter.position.value.copy( pos );\n\n // Trigger the setter for this property to force an\n // update to the emitter's position attribute.\n emitter.position.value = emitter.position.value;\n }\n\n emitter.enable();\n\n setTimeout( function() {\n emitter.disable();\n self.releaseIntoPool( emitter );\n }, ( Math.max( emitter.duration, ( emitter.maxAge.value + emitter.maxAge.spread ) ) ) * 1000 );\n\n return this;\n};\n\n\n/**\n * Set a given number of emitters as alive, with an optional position\n * vector3 to move them to.\n *\n * @param {Number} numEmitters The number of emitters to activate\n * @param {Object} [position=undefined] A THREE.Vector3 instance describing the position to activate the emitter(s) at.\n * @return {Group} This group instance.\n */\nSPE.Group.prototype.triggerPoolEmitter = function( numEmitters, position ) {\n 'use strict';\n\n if ( typeof numEmitters === 'number' && numEmitters > 1 ) {\n for ( var i = 0; i < numEmitters; ++i ) {\n this._triggerSingleEmitter( position );\n }\n }\n else {\n this._triggerSingleEmitter( position );\n }\n\n return this;\n};\n\n\n\nSPE.Group.prototype._updateUniforms = function( dt ) {\n 'use strict';\n\n this.uniforms.runTime.value += dt;\n this.uniforms.deltaTime.value = dt;\n};\n\nSPE.Group.prototype._resetBufferRanges = function() {\n 'use strict';\n\n var keys = this.attributeKeys,\n i = this.attributeCount - 1,\n attrs = this.attributes;\n\n for ( i; i >= 0; --i ) {\n attrs[ keys[ i ] ].resetUpdateRange();\n }\n};\n\n\nSPE.Group.prototype._updateBuffers = function( emitter ) {\n 'use strict';\n\n var keys = this.attributeKeys,\n i = this.attributeCount - 1,\n attrs = this.attributes,\n emitterRanges = emitter.bufferUpdateRanges,\n key,\n emitterAttr,\n attr;\n\n for ( i; i >= 0; --i ) {\n key = keys[ i ];\n emitterAttr = emitterRanges[ key ];\n attr = attrs[ key ];\n attr.setUpdateRange( emitterAttr.min, emitterAttr.max );\n attr.flagUpdate();\n }\n};\n\n\n/**\n * Simulate all the emitter's belonging to this group, updating\n * attribute values along the way.\n * @param {Number} [dt=Group's `fixedTimeStep` value] The number of seconds to simulate the group's emitters for (deltaTime)\n */\nSPE.Group.prototype.tick = function( dt ) {\n 'use strict';\n\n var emitters = this.emitters,\n numEmitters = emitters.length,\n deltaTime = dt || this.fixedTimeStep,\n keys = this.attributeKeys,\n i,\n attrs = this.attributes;\n\n // Update uniform values.\n this._updateUniforms( deltaTime );\n\n // Reset buffer update ranges on the shader attributes.\n this._resetBufferRanges();\n\n\n // If nothing needs updating, then stop here.\n if (\n numEmitters === 0 &&\n this._attributesNeedRefresh === false &&\n this._attributesNeedDynamicReset === false\n ) {\n return;\n }\n\n // Loop through each emitter in this group and\n // simulate it, then update the shader attribute\n // buffers.\n for ( var i = 0, emitter; i < numEmitters; ++i ) {\n emitter = emitters[ i ];\n emitter.tick( deltaTime );\n this._updateBuffers( emitter );\n }\n\n // If the shader attributes have been refreshed,\n // then the dynamic properties of each buffer\n // attribute will need to be reset back to\n // what they should be.\n if ( this._attributesNeedDynamicReset === true ) {\n i = this.attributeCount - 1;\n\n for ( i; i >= 0; --i ) {\n attrs[ keys[ i ] ].resetDynamic();\n }\n\n this._attributesNeedDynamicReset = false;\n }\n\n // If this group's shader attributes need a full refresh\n // then mark each attribute's buffer attribute as\n // needing so.\n if ( this._attributesNeedRefresh === true ) {\n i = this.attributeCount - 1;\n\n for ( i; i >= 0; --i ) {\n attrs[ keys[ i ] ].forceUpdateAll();\n }\n\n this._attributesNeedRefresh = false;\n this._attributesNeedDynamicReset = true;\n }\n};\n\n\n/**\n * Dipose the geometry and material for the group.\n *\n * @return {Group} Group instance.\n */\nSPE.Group.prototype.dispose = function() {\n 'use strict';\n this.geometry.dispose();\n this.material.dispose();\n return this;\n};\n\n\n/**\n * An SPE.Emitter instance.\n * @typedef {Object} Emitter\n * @see SPE.Emitter\n */\n\n/**\n * A map of options to configure an SPE.Emitter instance.\n *\n * @typedef {Object} EmitterOptions\n *\n * @property {distribution} [type=BOX] The default distribution this emitter should use to control\n * its particle's spawn position and force behaviour.\n * Must be an SPE.distributions.* value.\n *\n *\n * @property {Number} [particleCount=100] The total number of particles this emitter will hold. NOTE: this is not the number\n * of particles emitted in a second, or anything like that. The number of particles\n * emitted per-second is calculated by particleCount / maxAge (approximately!)\n *\n * @property {Number|null} [duration=null] The duration in seconds that this emitter should live for. If not specified, the emitter\n * will emit particles indefinitely.\n * NOTE: When an emitter is older than a specified duration, the emitter is NOT removed from\n * it's group, but rather is just marked as dead, allowing it to be reanimated at a later time\n * using `SPE.Emitter.prototype.enable()`.\n *\n * @property {Boolean} [isStatic=false] Whether this emitter should be not be simulated (true).\n * @property {Boolean} [activeMultiplier=1] A value between 0 and 1 describing what percentage of this emitter's particlesPerSecond should be\n * emitted, where 0 is 0%, and 1 is 100%.\n * For example, having an emitter with 100 particles, a maxAge of 2, yields a particlesPerSecond\n * value of 50. Setting `activeMultiplier` to 0.5, then, will only emit 25 particles per second (0.5 = 50%).\n * Values greater than 1 will emulate a burst of particles, causing the emitter to run out of particles\n * before it's next activation cycle.\n *\n * @property {Boolean} [direction=1] The direction of the emitter. If value is `1`, emitter will start at beginning of particle's lifecycle.\n * If value is `-1`, emitter will start at end of particle's lifecycle and work it's way backwards.\n *\n * @property {Object} [maxAge={}] An object describing the particle's maximum age in seconds.\n * @property {Number} [maxAge.value=2] A number between 0 and 1 describing the amount of maxAge to apply to all particles.\n * @property {Number} [maxAge.spread=0] A number describing the maxAge variance on a per-particle basis.\n *\n *\n * @property {Object} [position={}] An object describing this emitter's position.\n * @property {Object} [position.value=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's base position.\n * @property {Object} [position.spread=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's position variance on a per-particle basis.\n * Note that when using a SPHERE or DISC distribution, only the x-component\n * of this vector is used.\n * When using a LINE distribution, this value is the endpoint of the LINE.\n * @property {Object} [position.spreadClamp=new THREE.Vector3()] A THREE.Vector3 instance describing the numeric multiples the particle's should\n * be spread out over.\n * Note that when using a SPHERE or DISC distribution, only the x-component\n * of this vector is used.\n * When using a LINE distribution, this property is ignored.\n * @property {Number} [position.radius=10] This emitter's base radius.\n * @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.\n * @property {distribution} [position.distribution=value of the `type` option.] A specific distribution to use when radiusing particles. Overrides the `type` option.\n * @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.\n *\n *\n * @property {Object} [velocity={}] An object describing this particle velocity.\n * @property {Object} [velocity.value=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's base velocity.\n * @property {Object} [velocity.spread=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's velocity variance on a per-particle basis.\n * Note that when using a SPHERE or DISC distribution, only the x-component\n * of this vector is used.\n * @property {distribution} [velocity.distribution=value of the `type` option.] A specific distribution to use when calculating a particle's velocity. Overrides the `type` option.\n * @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.\n *\n *\n * @property {Object} [acceleration={}] An object describing this particle's acceleration.\n * @property {Object} [acceleration.value=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's base acceleration.\n * @property {Object} [acceleration.spread=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's acceleration variance on a per-particle basis.\n * Note that when using a SPHERE or DISC distribution, only the x-component\n * of this vector is used.\n * @property {distribution} [acceleration.distribution=value of the `type` option.] A specific distribution to use when calculating a particle's acceleration. Overrides the `type` option.\n * @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.\n *\n *\n * @property {Object} [drag={}] An object describing this particle drag. Drag is applied to both velocity and acceleration values.\n * @property {Number} [drag.value=0] A number between 0 and 1 describing the amount of drag to apply to all particles.\n * @property {Number} [drag.spread=0] A number describing the drag variance on a per-particle basis.\n * @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.\n *\n *\n * @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,\n * or shimmy, or waggle, or... Well you get the idea. The wiggle is calculated over-time, meaning that a particle will\n * start off with no wiggle, and end up wiggling about with the distance of the `value` specified by the time it dies.\n * It's quite handy to simulate fire embers, or similar effects where the particle's position should slightly change over\n * 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.\n * @property {Number} [wiggle.value=0] A number describing the amount of wiggle to apply to all particles. It's measured in distance.\n * @property {Number} [wiggle.spread=0] A number describing the wiggle variance on a per-particle basis.\n *\n *\n * @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`\n * over a particle's lifetime. Rotation values affect both a particle's position and the forces applied to it.\n * @property {Object} [rotation.axis=new THREE.Vector3(0, 1, 0)] A THREE.Vector3 instance describing this emitter's axis of rotation.\n * @property {Object} [rotation.axisSpread=new THREE.Vector3()] A THREE.Vector3 instance describing the amount of variance to apply to the axis of rotation on\n * a per-particle basis.\n * @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.\n * Otherwise, the particles will rotate from 0radians to this value over their lifetimes.\n * @property {Number} [rotation.angleSpread=0] The amount of variance in each particle's rotation angle.\n * @property {Boolean} [rotation.static=false] Whether the rotation should be static or not.\n * @property {Object} [rotation.center=The value of `position.value`] A THREE.Vector3 instance describing the center point of rotation.\n * @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.\n *\n *\n * @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\n * given to describe specific value changes over a particle's lifetime.\n * Depending on the value of SPE.valueOverLifetimeLength, if arrays of THREE.Color instances are given, then the array will be interpolated to\n * have a length matching the value of SPE.valueOverLifetimeLength.\n * @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.\n * @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.\n * @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.\n *\n *\n * @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\n * given to describe specific value changes over a particle's lifetime.\n * Depending on the value of SPE.valueOverLifetimeLength, if arrays of numbers are given, then the array will be interpolated to\n * have a length matching the value of SPE.valueOverLifetimeLength.\n * @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.\n * @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.\n * @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.\n *\n *\n * @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\n * given to describe specific value changes over a particle's lifetime.\n * Depending on the value of SPE.valueOverLifetimeLength, if arrays of numbers are given, then the array will be interpolated to\n * have a length matching the value of SPE.valueOverLifetimeLength.\n * @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.\n * @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.\n * @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.\n *\n *\n * @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.\n * NOTE: if a particle's texture is a sprite-sheet, this value IS IGNORED.\n * This property is a \"value-over-lifetime\" property, meaning an array of values and spreads can be\n * given to describe specific value changes over a particle's lifetime.\n * Depending on the value of SPE.valueOverLifetimeLength, if arrays of numbers are given, then the array will be interpolated to\n * have a length matching the value of SPE.valueOverLifetimeLength.\n * @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.\n * @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.\n * @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.\n *\n */\n\n/**\n * The SPE.Emitter class.\n *\n * @constructor\n *\n * @param {EmitterOptions} options A map of options to configure the emitter.\n */\nSPE.Emitter = function( options ) {\n 'use strict';\n\n var utils = SPE.utils,\n types = utils.types,\n lifetimeLength = SPE.valueOverLifetimeLength;\n\n // Ensure we have a map of options to play with,\n // and that each option is in the correct format.\n options = utils.ensureTypedArg( options, types.OBJECT, {} );\n options.position = utils.ensureTypedArg( options.position, types.OBJECT, {} );\n options.velocity = utils.ensureTypedArg( options.velocity, types.OBJECT, {} );\n options.acceleration = utils.ensureTypedArg( options.acceleration, types.OBJECT, {} );\n options.radius = utils.ensureTypedArg( options.radius, types.OBJECT, {} );\n options.drag = utils.ensureTypedArg( options.drag, types.OBJECT, {} );\n options.rotation = utils.ensureTypedArg( options.rotation, types.OBJECT, {} );\n options.color = utils.ensureTypedArg( options.color, types.OBJECT, {} );\n options.opacity = utils.ensureTypedArg( options.opacity, types.OBJECT, {} );\n options.size = utils.ensureTypedArg( options.size, types.OBJECT, {} );\n options.angle = utils.ensureTypedArg( options.angle, types.OBJECT, {} );\n options.wiggle = utils.ensureTypedArg( options.wiggle, types.OBJECT, {} );\n options.maxAge = utils.ensureTypedArg( options.maxAge, types.OBJECT, {} );\n\n if ( options.onParticleSpawn ) {\n console.warn( 'onParticleSpawn has been removed. Please set properties directly to alter values at runtime.' );\n }\n\n this.uuid = THREE.MathUtils.generateUUID();\n\n this.type = utils.ensureTypedArg( options.type, types.NUMBER, SPE.distributions.BOX );\n\n // Start assigning properties...kicking it off with props that DON'T support values over\n // lifetimes.\n //\n // Btw, values over lifetimes are just the new way of referring to *Start, *Middle, and *End.\n this.position = {\n _value: utils.ensureInstanceOf( options.position.value, THREE.Vector3, new THREE.Vector3() ),\n _spread: utils.ensureInstanceOf( options.position.spread, THREE.Vector3, new THREE.Vector3() ),\n _spreadClamp: utils.ensureInstanceOf( options.position.spreadClamp, THREE.Vector3, new THREE.Vector3() ),\n _distribution: utils.ensureTypedArg( options.position.distribution, types.NUMBER, this.type ),\n _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false ),\n _radius: utils.ensureTypedArg( options.position.radius, types.NUMBER, 10 ),\n _radiusScale: utils.ensureInstanceOf( options.position.radiusScale, THREE.Vector3, new THREE.Vector3( 1, 1, 1 ) ),\n _distributionClamp: utils.ensureTypedArg( options.position.distributionClamp, types.NUMBER, 0 ),\n };\n\n this.velocity = {\n _value: utils.ensureInstanceOf( options.velocity.value, THREE.Vector3, new THREE.Vector3() ),\n _spread: utils.ensureInstanceOf( options.velocity.spread, THREE.Vector3, new THREE.Vector3() ),\n _distribution: utils.ensureTypedArg( options.velocity.distribution, types.NUMBER, this.type ),\n _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )\n };\n\n this.acceleration = {\n _value: utils.ensureInstanceOf( options.acceleration.value, THREE.Vector3, new THREE.Vector3() ),\n _spread: utils.ensureInstanceOf( options.acceleration.spread, THREE.Vector3, new THREE.Vector3() ),\n _distribution: utils.ensureTypedArg( options.acceleration.distribution, types.NUMBER, this.type ),\n _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )\n };\n\n this.drag = {\n _value: utils.ensureTypedArg( options.drag.value, types.NUMBER, 0 ),\n _spread: utils.ensureTypedArg( options.drag.spread, types.NUMBER, 0 ),\n _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )\n };\n\n this.wiggle = {\n _value: utils.ensureTypedArg( options.wiggle.value, types.NUMBER, 0 ),\n _spread: utils.ensureTypedArg( options.wiggle.spread, types.NUMBER, 0 )\n };\n\n this.rotation = {\n _axis: utils.ensureInstanceOf( options.rotation.axis, THREE.Vector3, new THREE.Vector3( 0.0, 1.0, 0.0 ) ),\n _axisSpread: utils.ensureInstanceOf( options.rotation.axisSpread, THREE.Vector3, new THREE.Vector3() ),\n _angle: utils.ensureTypedArg( options.rotation.angle, types.NUMBER, 0 ),\n _angleSpread: utils.ensureTypedArg( options.rotation.angleSpread, types.NUMBER, 0 ),\n _static: utils.ensureTypedArg( options.rotation.static, types.BOOLEAN, false ),\n _center: utils.ensureInstanceOf( options.rotation.center, THREE.Vector3, this.position._value.clone() ),\n _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )\n };\n\n\n this.maxAge = {\n _value: utils.ensureTypedArg( options.maxAge.value, types.NUMBER, 2 ),\n _spread: utils.ensureTypedArg( options.maxAge.spread, types.NUMBER, 0 )\n };\n\n\n\n // The following properties can support either single values, or an array of values that change\n // the property over a particle's lifetime (value over lifetime).\n this.color = {\n _value: utils.ensureArrayInstanceOf( options.color.value, THREE.Color, new THREE.Color() ),\n _spread: utils.ensureArrayInstanceOf( options.color.spread, THREE.Vector3, new THREE.Vector3() ),\n _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )\n };\n\n this.opacity = {\n _value: utils.ensureArrayTypedArg( options.opacity.value, types.NUMBER, 1 ),\n _spread: utils.ensureArrayTypedArg( options.opacity.spread, types.NUMBER, 0 ),\n _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )\n };\n\n this.size = {\n _value: utils.ensureArrayTypedArg( options.size.value, types.NUMBER, 1 ),\n _spread: utils.ensureArrayTypedArg( options.size.spread, types.NUMBER, 0 ),\n _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )\n };\n\n this.angle = {\n _value: utils.ensureArrayTypedArg( options.angle.value, types.NUMBER, 0 ),\n _spread: utils.ensureArrayTypedArg( options.angle.spread, types.NUMBER, 0 ),\n _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )\n };\n\n\n // Assign renaining option values.\n this.particleCount = utils.ensureTypedArg( options.particleCount, types.NUMBER, 100 );\n this.duration = utils.ensureTypedArg( options.duration, types.NUMBER, null );\n this.isStatic = utils.ensureTypedArg( options.isStatic, types.BOOLEAN, false );\n this.activeMultiplier = utils.ensureTypedArg( options.activeMultiplier, types.NUMBER, 1 );\n this.direction = utils.ensureTypedArg( options.direction, types.NUMBER, 1 );\n\n // Whether this emitter is alive or not.\n this.alive = utils.ensureTypedArg( options.alive, types.BOOLEAN, true );\n\n\n // The following properties are set internally and are not\n // user-controllable.\n this.particlesPerSecond = 0;\n\n // The current particle index for which particles should\n // be marked as active on the next update cycle.\n this.activationIndex = 0;\n\n // The offset in the typed arrays this emitter's\n // particle's values will start at\n this.attributeOffset = 0;\n\n // The end of the range in the attribute buffers\n this.attributeEnd = 0;\n\n\n\n // Holds the time the emitter has been alive for.\n this.age = 0.0;\n\n // Holds the number of currently-alive particles\n this.activeParticleCount = 0.0;\n\n // Holds a reference to this emitter's group once\n // it's added to one.\n this.group = null;\n\n // Holds a reference to this emitter's group's attributes object\n // for easier access.\n this.attributes = null;\n\n // Holds a reference to the params attribute's typed array\n // for quicker access.\n this.paramsArray = null;\n\n // A set of flags to determine whether particular properties\n // should be re-randomised when a particle is reset.\n //\n // If a `randomise` property is given, this is preferred.\n // Otherwise, it looks at whether a spread value has been\n // given.\n //\n // It allows randomization to be turned off as desired. If\n // all randomization is turned off, then I'd expect a performance\n // boost as no attribute buffers (excluding the `params`)\n // would have to be re-passed to the GPU each frame (since nothing\n // except the `params` attribute would have changed).\n this.resetFlags = {\n // params: utils.ensureTypedArg( options.maxAge.randomise, types.BOOLEAN, !!options.maxAge.spread ) ||\n // utils.ensureTypedArg( options.wiggle.randomise, types.BOOLEAN, !!options.wiggle.spread ),\n position: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false ) ||\n utils.ensureTypedArg( options.radius.randomise, types.BOOLEAN, false ),\n velocity: utils.ensureTypedArg( options.velocity.randomise, types.BOOLEAN, false ),\n acceleration: utils.ensureTypedArg( options.acceleration.randomise, types.BOOLEAN, false ) ||\n utils.ensureTypedArg( options.drag.randomise, types.BOOLEAN, false ),\n rotation: utils.ensureTypedArg( options.rotation.randomise, types.BOOLEAN, false ),\n rotationCenter: utils.ensureTypedArg( options.rotation.randomise, types.BOOLEAN, false ),\n size: utils.ensureTypedArg( options.size.randomise, types.BOOLEAN, false ),\n color: utils.ensureTypedArg( options.color.randomise, types.BOOLEAN, false ),\n opacity: utils.ensureTypedArg( options.opacity.randomise, types.BOOLEAN, false ),\n angle: utils.ensureTypedArg( options.angle.randomise, types.BOOLEAN, false )\n };\n\n this.updateFlags = {};\n this.updateCounts = {};\n\n // A map to indicate which emitter parameters should update\n // which attribute.\n this.updateMap = {\n maxAge: 'params',\n position: 'position',\n velocity: 'velocity',\n acceleration: 'acceleration',\n drag: 'acceleration',\n wiggle: 'params',\n rotation: 'rotation',\n size: 'size',\n color: 'color',\n opacity: 'opacity',\n angle: 'angle'\n };\n\n for ( var i in this.updateMap ) {\n if ( this.updateMap.hasOwnProperty( i ) ) {\n this.updateCounts[ this.updateMap[ i ] ] = 0.0;\n this.updateFlags[ this.updateMap[ i ] ] = false;\n this._createGetterSetters( this[ i ], i );\n }\n }\n\n this.bufferUpdateRanges = {};\n this.attributeKeys = null;\n this.attributeCount = 0;\n\n\n // Ensure that the value-over-lifetime property objects above\n // have value and spread properties that are of the same length.\n //\n // Also, for now, make sure they have a length of 3 (min/max arguments here).\n utils.ensureValueOverLifetimeCompliance( this.color, lifetimeLength, lifetimeLength );\n utils.ensureValueOverLifetimeCompliance( this.opacity, lifetimeLength, lifetimeLength );\n utils.ensureValueOverLifetimeCompliance( this.size, lifetimeLength, lifetimeLength );\n utils.ensureValueOverLifetimeCompliance( this.angle, lifetimeLength, lifetimeLength );\n};\n\nSPE.Emitter.constructor = SPE.Emitter;\n\nSPE.Emitter.prototype._createGetterSetters = function( propObj, propName ) {\n 'use strict';\n\n var self = this;\n\n for ( var i in propObj ) {\n if ( propObj.hasOwnProperty( i ) ) {\n\n var name = i.replace( '_', '' );\n\n Object.defineProperty( propObj, name, {\n get: ( function( prop ) {\n return function() {\n return this[ prop ];\n };\n }( i ) ),\n\n set: ( function( prop ) {\n return function( value ) {\n var mapName = self.updateMap[ propName ],\n prevValue = this[ prop ],\n length = SPE.valueOverLifetimeLength;\n\n if ( prop === '_rotationCenter' ) {\n self.updateFlags.rotationCenter = true;\n self.updateCounts.rotationCenter = 0.0;\n }\n else if ( prop === '_randomise' ) {\n self.resetFlags[ mapName ] = value;\n }\n else {\n self.updateFlags[ mapName ] = true;\n self.updateCounts[ mapName ] = 0.0;\n }\n\n self.group._updateDefines();\n\n this[ prop ] = value;\n\n // If the previous value was an array, then make\n // sure the provided value is interpolated correctly.\n if ( Array.isArray( prevValue ) ) {\n SPE.utils.ensureValueOverLifetimeCompliance( self[ propName ], length, length );\n }\n };\n }( i ) )\n } );\n }\n }\n};\n\nSPE.Emitter.prototype._setBufferUpdateRanges = function( keys ) {\n 'use strict';\n\n this.attributeKeys = keys;\n this.attributeCount = keys.length;\n\n for ( var i = this.attributeCount - 1; i >= 0; --i ) {\n this.bufferUpdateRanges[ keys[ i ] ] = {\n min: Number.POSITIVE_INFINITY,\n max: Number.NEGATIVE_INFINITY\n };\n }\n};\n\nSPE.Emitter.prototype._calculatePPSValue = function( groupMaxAge ) {\n 'use strict';\n\n var particleCount = this.particleCount;\n\n\n // Calculate the `particlesPerSecond` value for this emitter. It's used\n // when determining which particles should die and which should live to\n // see another day. Or be born, for that matter. The \"God\" property.\n if ( this.duration ) {\n this.particlesPerSecond = particleCount / ( groupMaxAge < this.duration ? groupMaxAge : this.duration );\n }\n else {\n this.particlesPerSecond = particleCount / groupMaxAge;\n }\n};\n\nSPE.Emitter.prototype._setAttributeOffset = function( startIndex ) {\n this.attributeOffset = startIndex;\n this.activationIndex = startIndex;\n this.activationEnd = startIndex + this.particleCount;\n};\n\n\nSPE.Emitter.prototype._assignValue = function( prop, index ) {\n 'use strict';\n\n switch ( prop ) {\n case 'position':\n this._assignPositionValue( index );\n break;\n\n case 'velocity':\n case 'acceleration':\n this._assignForceValue( index, prop );\n break;\n\n case 'size':\n case 'opacity':\n this._assignAbsLifetimeValue( index, prop );\n break;\n\n case 'angle':\n this._assignAngleValue( index );\n break;\n\n case 'params':\n this._assignParamsValue( index );\n break;\n\n case 'rotation':\n this._assignRotationValue( index );\n break;\n\n case 'color':\n this._assignColorValue( index );\n break;\n }\n};\n\nSPE.Emitter.prototype._assignPositionValue = function( index ) {\n 'use strict';\n\n var distributions = SPE.distributions,\n utils = SPE.utils,\n prop = this.position,\n attr = this.attributes.position,\n value = prop._value,\n spread = prop._spread,\n distribution = prop._distribution;\n\n switch ( distribution ) {\n case distributions.BOX:\n utils.randomVector3( attr, index, value, spread, prop._spreadClamp );\n break;\n\n case distributions.SPHERE:\n utils.randomVector3OnSphere( attr, index, value, prop._radius, prop._spread.x, prop._radiusScale, prop._spreadClamp.x, prop._distributionClamp || this.particleCount );\n break;\n\n case distributions.DISC:\n utils.randomVector3OnDisc( attr, index, value, prop._radius, prop._spread.x, prop._radiusScale, prop._spreadClamp.x );\n break;\n\n case distributions.LINE:\n utils.randomVector3OnLine( attr, index, value, spread );\n break;\n }\n};\n\nSPE.Emitter.prototype._assignForceValue = function( index, attrName ) {\n 'use strict';\n\n var distributions = SPE.distributions,\n utils = SPE.utils,\n prop = this[ attrName ],\n value = prop._value,\n spread = prop._spread,\n distribution = prop._distribution,\n pos,\n positionX,\n positionY,\n positionZ,\n i;\n\n switch ( distribution ) {\n case distributions.BOX:\n utils.randomVector3( this.attributes[ attrName ], index, value, spread );\n break;\n\n case distributions.SPHERE:\n pos = this.attributes.position.typedArray.array;\n i = index * 3;\n\n // Ensure position values aren't zero, otherwise no force will be\n // applied.\n // positionX = utils.zeroToEpsilon( pos[ i ], true );\n // positionY = utils.zeroToEpsilon( pos[ i + 1 ], true );\n // positionZ = utils.zeroToEpsilon( pos[ i + 2 ], true );\n positionX = pos[ i ];\n positionY = pos[ i + 1 ];\n positionZ = pos[ i + 2 ];\n\n utils.randomDirectionVector3OnSphere(\n this.attributes[ attrName ], index,\n positionX, positionY, positionZ,\n this.position._value,\n prop._value.x,\n prop._spread.x\n );\n break;\n\n case distributions.DISC:\n pos = this.attributes.position.typedArray.array;\n i = index * 3;\n\n // Ensure position values aren't zero, otherwise no force will be\n // applied.\n // positionX = utils.zeroToEpsilon( pos[ i ], true );\n // positionY = utils.zeroToEpsilon( pos[ i + 1 ], true );\n // positionZ = utils.zeroToEpsilon( pos[ i + 2 ], true );\n positionX = pos[ i ];\n positionY = pos[ i + 1 ];\n positionZ = pos[ i + 2 ];\n\n utils.randomDirectionVector3OnDisc(\n this.attributes[ attrName ], index,\n positionX, positionY, positionZ,\n this.position._value,\n prop._value.x,\n prop._spread.x\n );\n break;\n\n case distributions.LINE:\n utils.randomVector3OnLine( this.attributes[ attrName ], index, value, spread );\n break;\n }\n\n if ( attrName === 'acceleration' ) {\n var drag = utils.clamp( utils.randomFloat( this.drag._value, this.drag._spread ), 0, 1 );\n this.attributes.acceleration.typedArray.array[ index * 4 + 3 ] = drag;\n }\n};\n\nSPE.Emitter.prototype._assignAbsLifetimeValue = function( index, propName ) {\n 'use strict';\n\n var array = this.attributes[ propName ].typedArray,\n prop = this[ propName ],\n utils = SPE.utils,\n value;\n\n if ( utils.arrayValuesAreEqual( prop._value ) && utils.arrayValuesAreEqual( prop._spread ) ) {\n value = Math.abs( utils.randomFloat( prop._value[ 0 ], prop._spread[ 0 ] ) );\n array.setVec4Components( index, value, value, value, value );\n }\n else {\n array.setVec4Components( index,\n Math.abs( utils.randomFloat( prop._value[ 0 ], prop._spread[ 0 ] ) ),\n Math.abs( utils.randomFloat( prop._value[ 1 ], prop._spread[ 1 ] ) ),\n Math.abs( utils.randomFloat( prop._value[ 2 ], prop._spread[ 2 ] ) ),\n Math.abs( utils.randomFloat( prop._value[ 3 ], prop._spread[ 3 ] ) )\n );\n }\n};\n\nSPE.Emitter.prototype._assignAngleValue = function( index ) {\n 'use strict';\n\n var array = this.attributes.angle.typedArray,\n prop = this.angle,\n utils = SPE.utils,\n value;\n\n if ( utils.arrayValuesAreEqual( prop._value ) && utils.arrayValuesAreEqual( prop._spread ) ) {\n value = utils.randomFloat( prop._value[ 0 ], prop._spread[ 0 ] );\n array.setVec4Components( index, value, value, value, value );\n }\n else {\n array.setVec4Components( index,\n utils.randomFloat( prop._value[ 0 ], prop._spread[ 0 ] ),\n utils.randomFloat( prop._value[ 1 ], prop._spread[ 1 ] ),\n utils.randomFloat( prop._value[ 2 ], prop._spread[ 2 ] ),\n utils.randomFloat( prop._value[ 3 ], prop._spread[ 3 ] )\n );\n }\n};\n\nSPE.Emitter.prototype._assignParamsValue = function( index ) {\n 'use strict';\n\n this.attributes.params.typedArray.setVec4Components( index,\n this.isStatic ? 1 : 0,\n 0.0,\n Math.abs( SPE.utils.randomFloat( this.maxAge._value, this.maxAge._spread ) ),\n SPE.utils.randomFloat( this.wiggle._value, this.wiggle._spread )\n );\n};\n\nSPE.Emitter.prototype._assignRotationValue = function( index ) {\n 'use strict';\n\n this.attributes.rotation.typedArray.setVec3Components( index,\n SPE.utils.getPackedRotationAxis( this.rotation._axis, this.rotation._axisSpread ),\n SPE.utils.randomFloat( this.rotation._angle, this.rotation._angleSpread ),\n this.rotation._static ? 0 : 1\n );\n\n this.attributes.rotationCenter.typedArray.setVec3( index, this.rotation._center );\n};\n\nSPE.Emitter.prototype._assignColorValue = function( index ) {\n 'use strict';\n SPE.utils.randomColorAsHex( this.attributes.color, index, this.color._value, this.color._spread );\n};\n\nSPE.Emitter.prototype._resetParticle = function( index ) {\n 'use strict';\n\n var resetFlags = this.resetFlags,\n updateFlags = this.updateFlags,\n updateCounts = this.updateCounts,\n keys = this.attributeKeys,\n key,\n updateFlag;\n\n for ( var i = this.attributeCount - 1; i >= 0; --i ) {\n key = keys[ i ];\n updateFlag = updateFlags[ key ];\n\n if ( resetFlags[ key ] === true || updateFlag === true ) {\n this._assignValue( key, index );\n this._updateAttributeUpdateRange( key, index );\n\n if ( updateFlag === true && updateCounts[ key ] === this.particleCount ) {\n updateFlags[ key ] = false;\n updateCounts[ key ] = 0.0;\n }\n else if ( updateFlag == true ) {\n ++updateCounts[ key ];\n }\n }\n }\n};\n\nSPE.Emitter.prototype._updateAttributeUpdateRange = function( attr, i ) {\n 'use strict';\n\n var ranges = this.bufferUpdateRanges[ attr ];\n\n ranges.min = Math.min( i, ranges.min );\n ranges.max = Math.max( i, ranges.max );\n};\n\nSPE.Emitter.prototype._resetBufferRanges = function() {\n 'use strict';\n\n var ranges = this.bufferUpdateRanges,\n keys = this.bufferUpdateKeys,\n i = this.bufferUpdateCount - 1,\n key;\n\n for ( i; i >= 0; --i ) {\n key = keys[ i ];\n ranges[ key ].min = Number.POSITIVE_INFINITY;\n ranges[ key ].max = Number.NEGATIVE_INFINITY;\n }\n};\n\nSPE.Emitter.prototype._onRemove = function() {\n 'use strict';\n // Reset any properties of the emitter that were set by\n // a group when it was added.\n this.particlesPerSecond = 0;\n this.attributeOffset = 0;\n this.activationIndex = 0;\n this.activeParticleCount = 0;\n this.group = null;\n this.attributes = null;\n this.paramsArray = null;\n this.age = 0.0;\n};\n\nSPE.Emitter.prototype._decrementParticleCount = function() {\n 'use strict';\n --this.activeParticleCount;\n\n // TODO:\n // - Trigger event if count === 0.\n};\n\nSPE.Emitter.prototype._incrementParticleCount = function() {\n 'use strict';\n ++this.activeParticleCount;\n\n // TODO:\n // - Trigger event if count === this.particleCount.\n};\n\nSPE.Emitter.prototype._checkParticleAges = function( start, end, params, dt ) {\n 'use strict';\n for ( var i = end - 1, index, maxAge, age, alive; i >= start; --i ) {\n index = i * 4;\n\n alive = params[ index ];\n\n if ( alive === 0.0 ) {\n continue;\n }\n\n // Increment age\n age = params[ index + 1 ];\n maxAge = params[ index + 2 ];\n\n if ( this.direction === 1 ) {\n age += dt;\n\n if ( age >= maxAge ) {\n age = 0.0;\n alive = 0.0;\n this._decrementParticleCount();\n }\n }\n else {\n age -= dt;\n\n if ( age <= 0.0 ) {\n age = maxAge;\n alive = 0.0;\n this._decrementParticleCount();\n }\n }\n\n params[ index ] = alive;\n params[ index + 1 ] = age;\n\n this._updateAttributeUpdateRange( 'params', i );\n }\n};\n\nSPE.Emitter.prototype._activateParticles = function( activationStart, activationEnd, params, dtPerParticle ) {\n 'use strict';\n var direction = this.direction;\n\n for ( var i = activationStart, index, dtValue; i < activationEnd; ++i ) {\n index = i * 4;\n\n // Don't re-activate particles that aren't dead yet.\n // if ( params[ index ] !== 0.0 && ( this.particleCount !== 1 || this.activeMultiplier !== 1 ) ) {\n // continue;\n // }\n\n if ( params[ index ] != 0.0 && this.particleCount !== 1 ) {\n continue;\n }\n\n // Increment the active particle count.\n this._incrementParticleCount();\n\n // Mark the particle as alive.\n params[ index ] = 1.0;\n\n // Reset the particle\n this._resetParticle( i );\n\n // Move each particle being activated to\n // it's actual position in time.\n //\n // This stops particles being 'clumped' together\n // when frame rates are on the lower side of 60fps\n // or not constant (a very real possibility!)\n dtValue = dtPerParticle * ( i - activationStart )\n params[ index + 1 ] = direction === -1 ? params[ index + 2 ] - dtValue : dtValue;\n\n this._updateAttributeUpdateRange( 'params', i );\n }\n};\n\n/**\n * Simulates one frame's worth of particles, updating particles\n * that are already alive, and marking ones that are currently dead\n * but should be alive as alive.\n *\n * If the emitter is marked as static, then this function will do nothing.\n *\n * @param {Number} dt The number of seconds to simulate (deltaTime)\n */\nSPE.Emitter.prototype.tick = function( dt ) {\n 'use strict';\n\n if ( this.isStatic ) {\n return;\n }\n\n if ( this.paramsArray === null ) {\n this.paramsArray = this.attributes.params.typedArray.array;\n }\n\n var start = this.attributeOffset,\n end = start + this.particleCount,\n params = this.paramsArray, // vec3( alive, age, maxAge, wiggle )\n ppsDt = this.particlesPerSecond * this.activeMultiplier * dt,\n activationIndex = this.activationIndex;\n\n // Reset the buffer update indices.\n this._resetBufferRanges();\n\n // Increment age for those particles that are alive,\n // and kill off any particles whose age is over the limit.\n this._checkParticleAges( start, end, params, dt );\n\n // If the emitter is dead, reset the age of the emitter to zero,\n // ready to go again if required\n if ( this.alive === false ) {\n this.age = 0.0;\n return;\n }\n\n // If the emitter has a specified lifetime and we've exceeded it,\n // mark the emitter as dead.\n if ( this.duration !== null && this.age > this.duration ) {\n this.alive = false;\n this.age = 0.0;\n return;\n }\n\n\n var activationStart = this.particleCount === 1 ? activationIndex : ( activationIndex | 0 ),\n activationEnd = Math.min( activationStart + ppsDt, this.activationEnd ),\n activationCount = activationEnd - this.activationIndex | 0,\n dtPerParticle = activationCount > 0 ? dt / activationCount : 0;\n\n this._activateParticles( activationStart, activationEnd, params, dtPerParticle );\n\n // Move the activation window forward, soldier.\n this.activationIndex += ppsDt;\n\n if ( this.activationIndex > end ) {\n this.activationIndex = start;\n }\n\n\n // Increment the age of the emitter.\n this.age += dt;\n};\n\n/**\n * Resets all the emitter's particles to their start positions\n * and marks the particles as dead if the `force` argument is\n * true.\n *\n * @param {Boolean} [force=undefined] If true, all particles will be marked as dead instantly.\n * @return {Emitter} This emitter instance.\n */\nSPE.Emitter.prototype.reset = function( force ) {\n 'use strict';\n\n this.age = 0.0;\n this.alive = false;\n\n if ( force === true ) {\n var start = this.attributeOffset,\n end = start + this.particleCount,\n array = this.paramsArray,\n attr = this.attributes.params.bufferAttribute;\n\n for ( var i = end - 1, index; i >= start; --i ) {\n index = i * 4;\n\n array[ index ] = 0.0;\n array[ index + 1 ] = 0.0;\n }\n\n attr.updateRange.offset = 0;\n attr.updateRange.count = -1;\n attr.needsUpdate = true;\n }\n\n return this;\n};\n\n/**\n * Enables the emitter. If not already enabled, the emitter\n * will start emitting particles.\n *\n * @return {Emitter} This emitter instance.\n */\nSPE.Emitter.prototype.enable = function() {\n 'use strict';\n this.alive = true;\n return this;\n};\n\n/**\n * Disables th emitter, but does not instantly remove it's\n * particles fromt the scene. When called, the emitter will be\n * 'switched off' and just stop emitting. Any particle's alive will\n * be allowed to finish their lifecycle.\n *\n * @return {Emitter} This emitter instance.\n */\nSPE.Emitter.prototype.disable = function() {\n 'use strict';\n\n this.alive = false;\n return this;\n};\n\n/**\n * Remove this emitter from it's parent group (if it has been added to one).\n * Delgates to SPE.group.prototype.removeEmitter().\n *\n * When called, all particle's belonging to this emitter will be instantly\n * removed from the scene.\n *\n * @return {Emitter} This emitter instance.\n *\n * @see SPE.Group.prototype.removeEmitter\n */\nSPE.Emitter.prototype.remove = function() {\n 'use strict';\n if ( this.group !== null ) {\n this.group.removeEmitter( this );\n }\n else {\n console.error( 'Emitter does not belong to a group, cannot remove.' );\n }\n\n return this;\n};\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","/**\n * Particles component for A-Frame.\n *\n * ShaderParticleEngine by Squarefeet (https://github.com/squarefeet).\n */\n\nvar SPE = require('./lib/SPE.js');\n\nif (typeof AFRAME === 'undefined') {\n throw new Error('Component attempted to register before AFRAME was available.');\n}\n\nAFRAME.registerComponent('particle-system', {\n\n schema: {\n preset: {\n type: 'string',\n default: '',\n oneOf: ['default', 'dust', 'snow', 'rain']\n },\n maxAge: {\n type: 'number',\n default: 6\n },\n positionSpread: {\n type: 'vec3',\n default: { x: 0, y: 0, z: 0 }\n },\n type: {\n type: 'number',\n default: SPE.distributions.BOX\n },\n rotationAxis: {\n type: 'string',\n default: 'x'\n },\n rotationAngle: {\n type: 'number',\n default: 0\n },\n rotationAngleSpread: {\n type: 'number',\n default: 0\n },\n accelerationValue: {\n type: 'vec3',\n default: { x: 0, y: -10, z: 0 }\n },\n accelerationSpread: {\n type: 'vec3',\n default: { x: 10, y: 0, z: 10 }\n },\n velocityValue: {\n type: 'vec3',\n default: { x: 0, y: 25, z: 0 }\n },\n velocitySpread: {\n type: 'vec3',\n default: { x: 10, y: 7.5, z: 10 }\n },\n dragValue: {\n type: 'number',\n default: 0\n },\n dragSpread: {\n type: 'number',\n default: 0\n },\n dragRandomise: {\n type: 'boolean',\n default: false\n },\n color: {\n type: 'array',\n default: [ '#0000FF', '#FF0000' ]\n },\n size: {\n type: 'array',\n default: [ '1' ]\n },\n sizeSpread: {\n type: 'array',\n default: [ '0' ]\n },\n direction: {\n type: 'number',\n default: 1\n },\n duration: {\n type: 'number',\n default: Infinity\n },\n particleCount: {\n type: 'number',\n default: 1000\n },\n texture: {\n type: 'asset',\n default: 'https://cdn.rawgit.com/IdeaSpaceVR/aframe-particle-system-component/master/dist/images/star2.png'\n },\n randomise: {\n type: 'boolean',\n default: false\n },\n opacity: {\n type: 'array',\n default: [ '1' ]\n },\n opacitySpread: {\n type: 'array',\n default: [ '0' ]\n },\n maxParticleCount: {\n type: 'number',\n default: 250000\n },\n blending: {\n type: 'number',\n default: THREE.AdditiveBlending,\n oneOf: [THREE.NoBlending,THREE.NormalBlending,THREE.AdditiveBlending,THREE.SubtractiveBlending,THREE.MultiplyBlending]\n },\n enabled: {\n type:'boolean',\n default:true\n }\n },\n\n\n init: function() {\n\n this.presets = {};\n\n /* preset settings can be overwritten */\n\n this.presets['dust'] = {\n maxAge: 20,\n positionSpread: {x:100,y:100,z:100},\n rotationAngle: 3.14,\n accelerationValue: {x: 0, y: 0, z: 0},\n accelerationSpread: {x: 0, y: 0, z: 0},\n velocityValue: {x: 1, y: 0.3, z: 1},\n velocitySpread: {x: 0.5, y: 1, z: 0.5},\n color: ['#FFFFFF'],\n particleCount: 100,\n texture: 'https://cdn.rawgit.com/IdeaSpaceVR/aframe-particle-system-component/master/dist/images/smokeparticle.png'\n };\n\n\n this.presets['snow'] = {\n maxAge: 20,\n positionSpread: {x:100,y:100,z:100},\n rotationAngle: 3.14,\n accelerationValue: {x: 0, y: 0, z: 0},\n accelerationSpread: {x: 0.2, y: 0, z: 0.2},\n velocityValue: {x: 0, y: 8, z: 0},\n velocitySpread: {x: 2, y: 0, z: 2},\n color: ['#FFFFFF'],\n particleCount: 200,\n texture: 'https://cdn.rawgit.com/IdeaSpaceVR/aframe-particle-system-component/master/dist/images/smokeparticle.png'\n };\n\n\n this.presets['rain'] = {\n maxAge: 1,\n positionSpread: {x:100,y:100,z:100},\n rotationAngle: 3.14,\n accelerationValue: {x: 0, y: 3, z: 0},\n accelerationSpread: {x: 2, y: 1, z: 2},\n velocityValue: {x: 0, y: 75, z: 0},\n velocitySpread: {x: 10, y: 50, z: 10},\n color: ['#FFFFFF'],\n size: [0.4],\n texture: 'https://cdn.rawgit.com/IdeaSpaceVR/aframe-particle-system-component/master/dist/images/raindrop.png'\n };\n\n\n },\n\n\n update: function (oldData) {\n\n // Remove old particle group.\n if (this.particleGroup) {\n this.el.removeObject3D('particle-system');\n }\n\n // Set the selected preset, if any, or use an empty object to keep schema defaults\n this.preset = this.presets[this.data.preset] || {};\n\n // Get custom, preset, or default data for each property defined in the schema\n for (var key in this.data) {\n this.data[key] = this.applyPreset(key);\n }\n\n this.initParticleSystem(this.data);\n\n if(this.data.enabled === true) {\n this.startParticles()\n } else {\n this.stopParticles()\n }\n },\n\n\n applyPreset: function (key) {\n // !this.attrValue[key] = the user did not set a custom value\n // this.preset[key] = there exists a value for this key in the selected preset\n if (!this.attrValue[key] && this.preset[key]) {\n return this.preset[key];\n } else {\n // Otherwise stick to the user or schema default value\n return this.data[key];\n }\n },\n\n\n tick: function(time, dt) {\n\n this.particleGroup.tick(dt / 1000);\n },\n\n\n remove: function() {\n\n // Remove particle system.\n if (!this.particleGroup) { return; }\n this.el.removeObject3D('particle-system');\n },\n\n startParticles: function() {\n this.particleGroup.emitters.forEach(function(em) { em.enable() });\n },\n\n stopParticles: function() {\n this.particleGroup.emitters.forEach(function(em) { em.disable() });\n },\n\n\n initParticleSystem: function(settings) {\n\n var loader = new THREE.TextureLoader();\n var particle_texture = loader.load(\n settings.texture,\n function (texture) {\n return texture;\n },\n function (xhr) {\n console.log((xhr.loaded / xhr.total * 100) + '% loaded');\n },\n function (xhr) {\n console.log('An error occurred');\n }\n );\n\n this.particleGroup = new SPE.Group({\n texture: {\n value: particle_texture\n },\n maxParticleCount: settings.maxParticleCount,\n blending: settings.blending\n });\n\n var emitter = new SPE.Emitter({\n maxAge: {\n value: settings.maxAge\n },\n type: {\n value: settings.type\n },\n position: {\n spread: new THREE.Vector3(settings.positionSpread.x, settings.positionSpread.y, settings.positionSpread.z),\n randomise: settings.randomise\n //spreadClamp: new THREE.Vector3( 2, 2, 2 ),\n //radius: 4\n },\n rotation: {\n axis: (settings.rotationAxis=='x'?new THREE.Vector3(1, 0, 0):(settings.rotationAxis=='y'?new THREE.Vector3(0, 1, 0):(settings.rotationAxis=='z'?new THREE.Vector3(0, 0, 1):new THREE.Vector3(0, 1, 0)))),\n angle: settings.rotationAngle,\n angleSpread: settings.rotationAngleSpread,\n static: true\n },\n acceleration: {\n value: new THREE.Vector3(settings.accelerationValue.x, settings.accelerationValue.y, settings.accelerationValue.z),\n spread: new THREE.Vector3(settings.accelerationSpread.x, settings.accelerationSpread.y, settings.accelerationSpread.z)\n },\n velocity: {\n value: new THREE.Vector3(settings.velocityValue.x, settings.velocityValue.y, settings.velocityValue.z),\n spread: new THREE.Vector3(settings.velocitySpread.x, settings.velocitySpread.y, settings.velocitySpread.z)\n },\n drag: {\n value: new THREE.Vector3(settings.dragValue.x, settings.dragValue.y, settings.dragValue.z),\n spread: new THREE.Vector3(settings.dragSpread.x, settings.dragSpread.y, settings.dragSpread.z),\n randomise: settings.dragRandomise\n },\n color: {\n value: settings.color.map(function(c) { return new THREE.Color(c); })\n },\n size: { value: settings.size.map(function (s) { return parseFloat(s); }),\n spread: settings.sizeSpread.map(function (s) { return parseFloat(s); }) },\n\n /*wiggle: { value: 4, spread: 2 }, //settings.wiggle,*/\n /*drag: {\n value: settings.drag\n },*/\n direction: {\n value: settings.direction\n },\n duration: settings.duration,\n opacity: { value: settings.opacity.map(function (o) { return parseFloat(o); }),\n spread: settings.opacitySpread.map(function (o) { return parseFloat(o); }) },\n particleCount: settings.particleCount\n });\n\n this.particleGroup.addEmitter(emitter);\n this.particleGroup.mesh.frustumCulled = false;\n this.el.setObject3D('particle-system', this.particleGroup.mesh);\n }\n});\n"],"names":["SPE","distributions","BOX","SPHERE","DISC","LINE","valueOverLifetimeLength","TypedArrayHelper","TypedArrayConstructor","size","componentSize","indexOffset","this","Float32Array","array","constructor","prototype","setSize","noComponentMultiply","currentArraySize","length","shrink","grow","console","info","subarray","existingArray","newArray","set","splice","start","end","data","i","push","setFromArray","index","newSize","setVec2","vec2","setVec2Components","x","y","setVec3","vec3","setVec3Components","z","setVec4","vec4","setVec4Components","w","setMat3","mat3","elements","setMat4","mat4","setColor","color","r","g","b","setNumber","numericValue","getValueAtIndex","getComponentValueAtIndex","ShaderAttribute","type","dynamicBuffer","arrayType","typeMap","typeSizeMap","hasOwnProperty","typedArray","bufferAttribute","updateMin","updateMax","f","v2","v3","v4","c","m3","m4","setUpdateRange","min","max","Math","flagUpdate","attr","range","updateRange","offset","count","needsUpdate","resetUpdateRange","resetDynamic","usage","THREE","DynamicDrawUsage","StaticDrawUsage","forceUpdateAll","_ensureTypedArray","_createBufferAttribute","parseFloat","REVISION","itemSize","BufferAttribute","getLength","shaderChunks","defines","join","uniforms","attributes","varyings","branchAvoidanceFunctions","unpackColor","unpackRotationAxis","floatOverLifetime","colorOverLifetime","paramFetchingFunctions","forceFetchingFunctions","rotationFunctions","rotateTexture","shaders","vertex","ShaderChunk","common","logdepthbuf_pars_vertex","fog_pars_vertex","logdepthbuf_vertex","fog_vertex","fragment","fog_pars_fragment","logdepthbuf_pars_fragment","logdepthbuf_fragment","fog_fragment","utils","types","BOOLEAN","STRING","NUMBER","OBJECT","ensureTypedArg","arg","defaultValue","ensureArrayTypedArg","Array","isArray","ensureInstanceOf","instance","undefined","ensureArrayInstanceOf","ensureValueOverLifetimeCompliance","property","minLength","maxLength","_value","_spread","valueLength","clamp","spreadLength","desiredLength","interpolateArray","srcArray","newLength","sourceLength","clone","factor","before","floor","after","ceil","delta","lerpTypeAgnostic","value","zeroToEpsilon","randomise","epsilon","result","random","out","Vector2","lerp","Vector3","Vector4","Color","warn","roundToNearestMultiple","n","multiple","remainder","abs","arrayValuesAreEqual","randomFloat","base","spread","randomVector3","attribute","spreadClamp","randomColor","randomColorAsHex","workingColor","numItems","colors","spreadVector","copy","getHex","randomVector3OnLine","pos","randomVector3OnSphere","radius","radiusSpread","radiusScale","radiusSpreadClamp","distributionClamp","depth","t","sqrt","rand","round","cos","sin","seededRandom","seed","randomVector3OnDisc","randomDirectionVector3OnSphere","v","posX","posY","posZ","emitterPosition","speed","speedSpread","normalize","multiplyScalar","randomDirectionVector3OnDisc","getPackedRotationAxis","vSpread","addOne","axis","axisSpread","add","setRGB","Group","options","texture","uuid","MathUtils","generateUUID","fixedTimeStep","Texture","textureFrames","frames","textureFrameCount","frameCount","textureLoop","loop","hasPerspective","colorize","maxParticleCount","blending","AdditiveBlending","transparent","alphaTest","depthWrite","depthTest","fog","scale","emitters","emitterIDs","_pool","_poolCreationSettings","_createNewWhenPoolEmpty","_attributesNeedRefresh","_attributesNeedDynamicReset","particleCount","tex","textureAnimation","fogColor","fogNear","fogFar","fogDensity","deltaTime","runTime","HAS_PERSPECTIVE","COLORIZE","VALUE_OVER_LIFETIME_LENGTH","SHOULD_ROTATE_TEXTURE","SHOULD_ROTATE_PARTICLES","SHOULD_WIGGLE_PARTICLES","SHOULD_CALCULATE_SPRITE","position","acceleration","velocity","rotation","rotationCenter","params","angle","opacity","attributeKeys","Object","keys","attributeCount","material","ShaderMaterial","vertexShader","fragmentShader","geometry","BufferGeometry","mesh","Points","_updateDefines","emitter","apply","angleSpread","wiggle","_applyAttributesToGeometry","geometryAttribute","geometryAttributes","setAttribute","setDrawRange","addEmitter","Emitter","indexOf","error","group","_calculatePPSValue","maxAge","_setBufferUpdateRanges","_setAttributeOffset","_assignPositionValue","_assignForceValue","_assignAbsLifetimeValue","_assignAngleValue","_assignRotationValue","_assignParamsValue","_assignColorValue","removeEmitter","emitterIndex","attributeOffset","_onRemove","getFromPool","pool","createNew","pop","releaseIntoPool","reset","unshift","getPool","addPool","numEmitters","emitterOptions","_triggerSingleEmitter","self","enable","setTimeout","disable","duration","log","triggerPoolEmitter","_updateUniforms","dt","_resetBufferRanges","attrs","_updateBuffers","key","emitterAttr","emitterRanges","bufferUpdateRanges","tick","dispose","lifetimeLength","drag","onParticleSpawn","_spreadClamp","_distribution","distribution","_randomise","_radius","_radiusScale","_distributionClamp","_axis","_axisSpread","_angle","_angleSpread","_static","static","_center","center","isStatic","activeMultiplier","direction","alive","particlesPerSecond","activationIndex","attributeEnd","age","activeParticleCount","paramsArray","resetFlags","updateFlags","updateCounts","updateMap","_createGetterSetters","propObj","propName","name","replace","defineProperty","get","prop","mapName","prevValue","Number","POSITIVE_INFINITY","NEGATIVE_INFINITY","groupMaxAge","startIndex","activationEnd","_assignValue","attrName","positionX","positionY","positionZ","_resetParticle","updateFlag","_updateAttributeUpdateRange","ranges","bufferUpdateKeys","bufferUpdateCount","_decrementParticleCount","_incrementParticleCount","_checkParticleAges","_activateParticles","activationStart","dtPerParticle","dtValue","ppsDt","activationCount","force","remove","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","exports","module","__webpack_modules__","AFRAME","Error","registerComponent","schema","preset","default","oneOf","positionSpread","rotationAxis","rotationAngle","rotationAngleSpread","accelerationValue","accelerationSpread","velocityValue","velocitySpread","dragValue","dragSpread","dragRandomise","sizeSpread","Infinity","opacitySpread","NoBlending","NormalBlending","SubtractiveBlending","MultiplyBlending","enabled","init","presets","update","oldData","particleGroup","el","removeObject3D","applyPreset","initParticleSystem","startParticles","stopParticles","attrValue","time","forEach","em","settings","particle_texture","TextureLoader","load","xhr","loaded","total","map","s","o","frustumCulled","setObject3D"],"sourceRoot":""} \ No newline at end of file diff --git a/examples/colors/index.html b/examples/colors/index.html index 06a4dc1..d0ee6ff 100644 --- a/examples/colors/index.html +++ b/examples/colors/index.html @@ -4,7 +4,7 @@ A-Frame Particle System Component Example - + diff --git a/examples/dust/index.html b/examples/dust/index.html index 529d832..bf8ca1c 100644 --- a/examples/dust/index.html +++ b/examples/dust/index.html @@ -4,7 +4,7 @@ A-Frame Particle System Component Example - + diff --git a/examples/rain/index.html b/examples/rain/index.html index 071938e..2712a36 100644 --- a/examples/rain/index.html +++ b/examples/rain/index.html @@ -4,7 +4,7 @@ A-Frame Particle System Component Example - + diff --git a/examples/snow/index.html b/examples/snow/index.html index 8724f65..ade622d 100644 --- a/examples/snow/index.html +++ b/examples/snow/index.html @@ -4,7 +4,7 @@ A-Frame Particle System Component Example - + diff --git a/examples/stars/index.html b/examples/stars/index.html index efb1c8e..81bfc4b 100644 --- a/examples/stars/index.html +++ b/examples/stars/index.html @@ -4,7 +4,7 @@ A-Frame Particle System Component Example - + diff --git a/index.html b/index.html index 7e1c53d..9420c3f 100644 --- a/index.html +++ b/index.html @@ -1,3 +1,4 @@ + A-Frame Particle System Component diff --git a/index.js b/index.js index 10c2e5e..acf9991 100644 --- a/index.js +++ b/index.js @@ -169,7 +169,7 @@ AFRAME.registerComponent('particle-system', { velocityValue: {x: 0, y: 75, z: 0}, velocitySpread: {x: 10, y: 50, z: 10}, color: ['#FFFFFF'], - size: 0.4, + size: [0.4], texture: 'https://cdn.rawgit.com/IdeaSpaceVR/aframe-particle-system-component/master/dist/images/raindrop.png' }; @@ -267,7 +267,7 @@ AFRAME.registerComponent('particle-system', { type: { value: settings.type }, - position: { + position: { spread: new THREE.Vector3(settings.positionSpread.x, settings.positionSpread.y, settings.positionSpread.z), randomise: settings.randomise //spreadClamp: new THREE.Vector3( 2, 2, 2 ), @@ -293,11 +293,11 @@ AFRAME.registerComponent('particle-system', { randomise: settings.dragRandomise }, color: { - value: settings.color.map(function(c) { return new THREE.Color(c); }) + value: settings.color.map(function(c) { return new THREE.Color(c); }) }, size: { value: settings.size.map(function (s) { return parseFloat(s); }), spread: settings.sizeSpread.map(function (s) { return parseFloat(s); }) }, - + /*wiggle: { value: 4, spread: 2 }, //settings.wiggle,*/ /*drag: { value: settings.drag @@ -307,7 +307,7 @@ AFRAME.registerComponent('particle-system', { }, duration: settings.duration, opacity: { value: settings.opacity.map(function (o) { return parseFloat(o); }), - spread: settings.opacitySpread.map(function (o) { return parseFloat(o); }) }, + spread: settings.opacitySpread.map(function (o) { return parseFloat(o); }) }, particleCount: settings.particleCount }); diff --git a/lib/SPE.js b/lib/SPE.js index 1edded9..6cca83a 100644 --- a/lib/SPE.js +++ b/lib/SPE.js @@ -1,5 +1,5 @@ /* 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). * @@ -1911,7 +1911,7 @@ SPE.Group = function( options ) { options.texture = utils.ensureTypedArg( options.texture, types.OBJECT, {} ); // Assign a UUID to this instance - this.uuid = THREE.Math.generateUUID(); + this.uuid = THREE.MathUtils.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. @@ -2773,7 +2773,7 @@ SPE.Emitter = function( options ) { console.warn( 'onParticleSpawn has been removed. Please set properties directly to alter values at runtime.' ); } - this.uuid = THREE.Math.generateUUID(); + this.uuid = THREE.MathUtils.generateUUID(); this.type = utils.ensureTypedArg( options.type, types.NUMBER, SPE.distributions.BOX ); @@ -3595,4 +3595,4 @@ SPE.Emitter.prototype.remove = function() { } return this; -}; \ No newline at end of file +}; diff --git a/lib/SPE.min.js b/lib/SPE.min.js index 6cfdcc8..49f5516 100644 --- a/lib/SPE.min.js +++ b/lib/SPE.min.js @@ -8,8 +8,8 @@ var SPE={distributions:{BOX:1,SPHERE:2,DISC:3,LINE:4},valueOverLifetimeLength:4};"function"==typeof define&&define.amd?define("spe",SPE):"undefined"!=typeof exports&&"undefined"!=typeof module&&(module.exports=SPE),SPE.TypedArrayHelper=function(a,b,c,d){"use strict";this.componentSize=c||1,this.size=b||1,this.TypedArrayConstructor=a||Float32Array,this.array=new a(b*this.componentSize),this.indexOffset=d||0},SPE.TypedArrayHelper.constructor=SPE.TypedArrayHelper,SPE.TypedArrayHelper.prototype.setSize=function(a,b){"use strict";var c=this.array.length;return b||(a*=this.componentSize),ac?this.grow(a):void console.info("TypedArray is already of size:",a+".","Will not resize.")},SPE.TypedArrayHelper.prototype.shrink=function(a){"use strict";return this.array=this.array.subarray(0,a),this.size=a,this},SPE.TypedArrayHelper.prototype.grow=function(a){"use strict";var b=this.array,c=new this.TypedArrayConstructor(a);return c.set(b),this.array=c,this.size=a,this}, SPE.TypedArrayHelper.prototype.splice=function(a,b){"use strict";a*=this.componentSize,b*=this.componentSize;for(var c=[],d=this.array,e=d.length,f=0;f=b)&&c.push(d[f]);return this.setFromArray(0,c),this},SPE.TypedArrayHelper.prototype.setFromArray=function(a,b){"use strict";var c=b.length,d=a+c;return d>this.array.length?this.grow(d):d=81&&(this.bufferAttribute.count=this.bufferAttribute.array.length/this.bufferAttribute.itemSize),void(this.bufferAttribute.needsUpdate=!0);this.bufferAttribute=new THREE.BufferAttribute(this.typedArray.array,this.componentSize),this.bufferAttribute.usage=this.dynamicBuffer?THREE.DynamicDrawUsage:THREE.StaticDrawUsage},SPE.ShaderAttribute.prototype.getLength=function(){"use strict";return null===this.typedArray?0:this.typedArray.array.length},SPE.shaderChunks={ defines:["#define PACKED_COLOR_SIZE 256.0","#define PACKED_COLOR_DIVISOR 255.0"].join("\n"),uniforms:["uniform float deltaTime;","uniform float runTime;","uniform sampler2D tex;","uniform vec4 textureAnimation;","uniform float scale;"].join("\n"),attributes:["attribute vec4 acceleration;","attribute vec3 velocity;","attribute vec4 rotation;","attribute vec3 rotationCenter;","attribute vec4 params;","attribute vec4 size;","attribute vec4 angle;","attribute vec4 color;","attribute vec4 opacity;"].join("\n"),varyings:["varying vec4 vColor;","#ifdef SHOULD_ROTATE_TEXTURE"," varying float vAngle;","#endif","#ifdef SHOULD_CALCULATE_SPRITE"," varying vec4 vSpriteSheet;","#endif"].join("\n"), branchAvoidanceFunctions:["float when_gt(float x, float y) {"," return max(sign(x - y), 0.0);","}","float when_lt(float x, float y) {"," return min( max(1.0 - sign(x - y), 0.0), 1.0 );","}","float when_eq( float x, float y ) {"," return 1.0 - abs( sign( x - y ) );","}","float when_ge(float x, float y) {"," return 1.0 - when_lt(x, y);","}","float when_le(float x, float y) {"," return 1.0 - when_gt(x, y);","}","float and(float a, float b) {"," return a * b;","}","float or(float a, float b) {"," return min(a + b, 1.0);","}"].join("\n"),unpackColor:["vec3 unpackColor( in float hex ) {"," vec3 c = vec3( 0.0 );"," float r = mod( (hex / PACKED_COLOR_SIZE / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );"," float g = mod( (hex / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );"," float b = mod( hex, PACKED_COLOR_SIZE );"," c.r = r / PACKED_COLOR_DIVISOR;"," c.g = g / PACKED_COLOR_DIVISOR;"," c.b = b / PACKED_COLOR_DIVISOR;"," return c;","}"].join("\n"), @@ -21,21 +21,21 @@ rotationFunctions:["#ifdef SHOULD_ROTATE_PARTICLES"," mat4 getRotationMatrix( rotateTexture:[" vec2 vUv = vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y );",""," #ifdef SHOULD_ROTATE_TEXTURE"," float x = gl_PointCoord.x - 0.5;"," float y = 1.0 - gl_PointCoord.y - 0.5;"," float c = cos( -vAngle );"," float s = sin( -vAngle );"," vUv = vec2( c * x + s * y + 0.5, c * y - s * x + 0.5 );"," #endif",""," #ifdef SHOULD_CALCULATE_SPRITE"," float framesX = vSpriteSheet.x;"," float framesY = vSpriteSheet.y;"," float columnNorm = vSpriteSheet.z;"," float rowNorm = vSpriteSheet.w;"," vUv.x = gl_PointCoord.x * framesX + columnNorm;"," vUv.y = 1.0 - (gl_PointCoord.y * framesY + rowNorm);"," #endif",""," vec4 rotatedTexture = texture2D( tex, vUv );"].join("\n")},SPE.shaders={ vertex:[SPE.shaderChunks.defines,SPE.shaderChunks.uniforms,SPE.shaderChunks.attributes,SPE.shaderChunks.varyings,THREE.ShaderChunk.common,THREE.ShaderChunk.logdepthbuf_pars_vertex,THREE.ShaderChunk.fog_pars_vertex,SPE.shaderChunks.branchAvoidanceFunctions,SPE.shaderChunks.unpackColor,SPE.shaderChunks.unpackRotationAxis,SPE.shaderChunks.floatOverLifetime,SPE.shaderChunks.colorOverLifetime,SPE.shaderChunks.paramFetchingFunctions,SPE.shaderChunks.forceFetchingFunctions,SPE.shaderChunks.rotationFunctions,"void main() {"," highp float age = getAge();"," highp float alive = getAlive();"," highp float maxAge = getMaxAge();"," highp float positionInTime = (age / maxAge);"," highp float isAlive = when_gt( alive, 0.0 );"," #ifdef SHOULD_WIGGLE_PARTICLES"," float wiggleAmount = positionInTime * getWiggle();"," float wiggleSin = isAlive * sin( wiggleAmount );"," float wiggleCos = isAlive * cos( wiggleAmount );"," #endif"," vec3 vel = getVelocity( age );"," vec3 accel = getAcceleration( age );"," vec3 force = vec3( 0.0 );"," vec3 pos = vec3( position );"," float drag = 1.0 - (positionInTime * 0.5) * acceleration.w;"," force += vel;"," force *= drag;"," force += accel * age;"," pos += force;"," #ifdef SHOULD_WIGGLE_PARTICLES"," pos.x += wiggleSin;"," pos.y += wiggleCos;"," pos.z += wiggleSin;"," #endif"," #ifdef SHOULD_ROTATE_PARTICLES"," pos = getRotation( pos, positionInTime );"," #endif"," vec4 mvPosition = modelViewMatrix * vec4( pos, 1.0 );"," highp float pointSize = getFloatOverLifetime( positionInTime, size ) * isAlive;"," #ifdef HAS_PERSPECTIVE"," float perspective = scale / length( mvPosition.xyz );"," #else"," float perspective = 1.0;"," #endif"," float pointSizePerspective = pointSize * perspective;"," #ifdef COLORIZE"," vec3 c = isAlive * getColorOverLifetime("," positionInTime,"," unpackColor( color.x ),"," unpackColor( color.y ),"," unpackColor( color.z ),"," unpackColor( color.w )"," );"," #else"," vec3 c = vec3(1.0);"," #endif"," float o = isAlive * getFloatOverLifetime( positionInTime, opacity );"," vColor = vec4( c, o );"," #ifdef SHOULD_ROTATE_TEXTURE"," vAngle = isAlive * getFloatOverLifetime( positionInTime, angle );"," #endif"," #ifdef SHOULD_CALCULATE_SPRITE"," float framesX = textureAnimation.x;"," float framesY = textureAnimation.y;"," float loopCount = textureAnimation.w;"," float totalFrames = textureAnimation.z;"," float frameNumber = mod( (positionInTime * loopCount) * totalFrames, totalFrames );"," float column = floor(mod( frameNumber, framesX ));"," float row = floor( (frameNumber - column) / framesX );"," float columnNorm = column / framesX;"," float rowNorm = row / framesY;"," vSpriteSheet.x = 1.0 / framesX;"," vSpriteSheet.y = 1.0 / framesY;"," vSpriteSheet.z = columnNorm;"," vSpriteSheet.w = rowNorm;"," #endif"," gl_PointSize = pointSizePerspective;"," gl_Position = projectionMatrix * mvPosition;",THREE.ShaderChunk.logdepthbuf_vertex,THREE.ShaderChunk.fog_vertex,"}"].join("\n"), fragment:[SPE.shaderChunks.uniforms,THREE.ShaderChunk.common,THREE.ShaderChunk.fog_pars_fragment,THREE.ShaderChunk.logdepthbuf_pars_fragment,SPE.shaderChunks.varyings,SPE.shaderChunks.branchAvoidanceFunctions,"void main() {"," vec3 outgoingLight = vColor.xyz;"," "," #ifdef ALPHATEST"," if ( vColor.w < float(ALPHATEST) ) discard;"," #endif",SPE.shaderChunks.rotateTexture,THREE.ShaderChunk.logdepthbuf_fragment," outgoingLight = vColor.xyz * rotatedTexture.xyz;"," gl_FragColor = vec4( outgoingLight.xyz, rotatedTexture.w * vColor.w );",THREE.ShaderChunk.fog_fragment,"}"].join("\n")},SPE.utils={types:{BOOLEAN:"boolean",STRING:"string",NUMBER:"number",OBJECT:"object"},ensureTypedArg:function(a,b,c){"use strict";return typeof a===b?a:c},ensureArrayTypedArg:function(a,b,c){"use strict";if(Array.isArray(a)){for(var d=a.length-1;d>=0;--d)if(typeof a[d]!==b)return c;return a}return this.ensureTypedArg(a,b,c)},ensureInstanceOf:function(a,b,c){"use strict" -;return void 0!==b&&a instanceof b?a:c},ensureArrayInstanceOf:function(a,b,c){"use strict";if(Array.isArray(a)){for(var d=a.length-1;d>=0;--d)if(void 0!==b&&a[d]instanceof b==!1)return c;return a}return this.ensureInstanceOf(a,b,c)},ensureValueOverLifetimeCompliance:function(a,b,c){"use strict";b=b||3,c=c||3,!1===Array.isArray(a._value)&&(a._value=[a._value]),!1===Array.isArray(a._spread)&&(a._spread=[a._spread]);var d=this.clamp(a._value.length,b,c),e=this.clamp(a._spread.length,b,c),f=Math.max(d,e);a._value.length!==f&&(a._value=this.interpolateArray(a._value,f)),a._spread.length!==f&&(a._spread=this.interpolateArray(a._spread,f))},interpolateArray:function(a,b){"use strict";for(var c=a.length,d=["function"==typeof a[0].clone?a[0].clone():a[0]],e=(c-1)/(b-1),f=1;f-c&&(d=-d),d},lerpTypeAgnostic:function(a,b,c){"use strict";var d,e=this.types;return typeof a===e.NUMBER&&typeof b===e.NUMBER?a+(b-a)*c:a instanceof THREE.Vector2&&b instanceof THREE.Vector2?(d=a.clone(),d.x=this.lerp(a.x,b.x,c),d.y=this.lerp(a.y,b.y,c),d):a instanceof THREE.Vector3&&b instanceof THREE.Vector3?(d=a.clone(),d.x=this.lerp(a.x,b.x,c),d.y=this.lerp(a.y,b.y,c),d.z=this.lerp(a.z,b.z,c),d):a instanceof THREE.Vector4&&b instanceof THREE.Vector4?(d=a.clone(),d.x=this.lerp(a.x,b.x,c),d.y=this.lerp(a.y,b.y,c),d.z=this.lerp(a.z,b.z,c),d.w=this.lerp(a.w,b.w,c),d):a instanceof THREE.Color&&b instanceof THREE.Color?(d=a.clone(),d.r=this.lerp(a.r,b.r,c),d.g=this.lerp(a.g,b.g,c),d.b=this.lerp(a.b,b.b,c),d):void console.warn("Invalid argument types, or argument types do not match:",a,b)},lerp:function(a,b,c){"use strict";return a+(b-a)*c}, -roundToNearestMultiple:function(a,b){"use strict";var c=0;return 0===b?a:(c=Math.abs(a)%b,0===c?a:a<0?-(Math.abs(a)-c):a+b-c)},arrayValuesAreEqual:function(a){"use strict";for(var b=0;b=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=MathUtils.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=MathUtils.abs(a)%b,0===c?a:a<0?-(MathUtils.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], +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||!!MathUtils.max(MathUtils.max.apply(null,a.angle.value),MathUtils.max.apply(null,a.angle.spread))),d.SHOULD_ROTATE_PARTICLES=d.SHOULD_ROTATE_PARTICLES||!!MathUtils.max(a.rotation.angle,a.rotation.angleSpread),d.SHOULD_WIGGLE_PARTICLES=d.SHOULD_WIGGLE_PARTICLES||!!MathUtils.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;c1)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(), +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.MathUtils.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), @@ -44,9 +44,9 @@ rotation:b.ensureTypedArg(a.rotation.randomise,c.BOOLEAN,!1),rotationCenter:b.en 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, +i.randomVector3OnLine(this.attributes[b],a,k,l)}if("acceleration"===b){var n=i.clamp(i.randomFloat(this.drag._value,this.drag._spread),0,1);this.attributes.acceleration.typedArray.array[4*a+3]=n}},SPE.Emitter.prototype._assignAbsLifetimeValue=function(a,b){"use strict";var c,d=this.attributes[b].typedArray,e=this[b],f=SPE.utils;f.arrayValuesAreEqual(e._value)&&f.arrayValuesAreEqual(e._spread)?(c=MathUtils.abs(f.randomFloat(e._value[0],e._spread[0])),d.setVec4Components(a,c,c,c,c)):d.setVec4Components(a,MathUtils.abs(f.randomFloat(e._value[0],e._spread[0])),MathUtils.abs(f.randomFloat(e._value[1],e._spread[1])),MathUtils.abs(f.randomFloat(e._value[2],e._spread[2])),MathUtils.abs(f.randomFloat(e._value[3],e._spread[3])))},SPE.Emitter.prototype._assignAngleValue=function(a){"use strict";var b,c=this.attributes.angle.typedArray,d=this.angle,e=SPE.utils;e.arrayValuesAreEqual(d._value)&&e.arrayValuesAreEqual(d._spread)?(b=e.randomFloat(d._value[0],d._spread[0]), +c.setVec4Components(a,b,b,b,b)):c.setVec4Components(a,e.randomFloat(d._value[0],d._spread[0]),e.randomFloat(d._value[1],d._spread[1]),e.randomFloat(d._value[2],d._spread[2]),e.randomFloat(d._value[3],d._spread[3]))},SPE.Emitter.prototype._assignParamsValue=function(a){"use strict";this.attributes.params.typedArray.setVec4Components(a,this.isStatic?1:0,0,MathUtils.abs(SPE.utils.randomFloat(this.maxAge._value,this.maxAge._spread)),SPE.utils.randomFloat(this.wiggle._value,this.wiggle._spread))},SPE.Emitter.prototype._assignRotationValue=function(a){"use strict";this.attributes.rotation.typedArray.setVec3Components(a,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(a,this.rotation._center)},SPE.Emitter.prototype._assignColorValue=function(a){"use strict" +;SPE.utils.randomColorAsHex(this.attributes.color,a,this.color._value,this.color._spread)},SPE.Emitter.prototype._resetParticle=function(a){"use strict";for(var b,c,d=this.resetFlags,e=this.updateFlags,f=this.updateCounts,g=this.attributeKeys,h=this.attributeCount-1;h>=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=MathUtils.min(b,c.min),c.max=MathUtils.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 +null===this.paramsArray&&(this.paramsArray=this.attributes.params.typedArray.array);var b=this.attributeOffset,c=b+this.particleCount,d=this.paramsArray,e=this.particlesPerSecond*this.activeMultiplier*a,f=this.activationIndex;if(this._resetBufferRanges(),this._checkParticleAges(b,c,d,a),!1===this.alive)return void(this.age=0);if(null!==this.duration&&this.age>this.duration)return this.alive=!1,void(this.age=0);var g=1===this.particleCount?f:0|f,h=MathUtils.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}; diff --git a/package.json b/package.json index 18c29c4..c28016d 100644 --- a/package.json +++ b/package.json @@ -7,19 +7,22 @@ "example": "examples" }, "devDependencies": { - "browserify": "^13.0.0", - "browserify-css": "^0.9.1", - "budo": "^8.2.2", - "randomcolor": "^0.4.4", - "semistandard": "^8.0.0", - "shelljs": "^0.7.0", - "shx": "^0.1.1", - "snazzy": "^4.0.0", - "webpack": "^1.13.0" + "@webpack-cli/generators": "^3.0.1", + "browserify": "^17.0.0", + "browserify-css": "^0.15.0", + "budo": "^11.8.4", + "randomcolor": "^0.6.2", + "semistandard": "^16.0.1", + "shelljs": "^0.8.5", + "shx": "^0.3.4", + "snazzy": "^9.0.0", + "webpack": "^5.75.0", + "webpack-cli": "^5.0.1" }, "scripts": { - "dev": "budo index.js:../../dist/aframe-particle-system-component.min.js --port 8000 --live --open", - "dist": "webpack index.js dist/aframe-particle-system-component.js && webpack -p index.js dist/aframe-particle-system-component.min.js", + "watch": "webpack --watch --mode development --output-filename aframe-particle-system-component.js", + "dev": "budo index.js:../../dist/aframe-particle-system-component.min.js --port 8000 --live --open --ssl --cors", + "dist": "webpack --mode development --output-filename aframe-particle-system-component.js && webpack", "lint": "semistandard -v | snazzy", "prepublish": "npm run dist", "preghpages": "npm run build && shx rm -rf gh-pages && shx mkdir gh-pages && shx cp -r examples/* gh-pages", diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..bfe5756 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,19 @@ +const path = require('path'); + +module.exports = { + entry: './index.js', + output: { + filename: 'aframe-particle-system-component.min.js', + path: path.resolve(__dirname, 'dist') + }, + mode: 'production', + devtool: 'source-map', + module: { + rules: [ + { + test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i, + type: 'asset', + } + ] + } +};