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;AA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;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',
+ }
+ ]
+ }
+};