UNPKG

aframe-particle-system-component

Version:
1,409 lines (1,170 loc) 148 kB
/******/ (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' }, positionSpread: { type: 'vec3' }, type: { type: 'number' }, rotationAxis: { type: 'string' }, rotationAngle: { type: 'number' }, accelerationValue: { type: 'vec3' }, accelerationSpread: { type: 'vec3' }, velocityValue: { type: 'vec3' }, velocitySpread: { type: 'vec3' }, color: { type: 'string' }, size: { type: 'number' }, direction: { type: 'number' }, duration: { type: 'number' }, particleCount: { type: 'number' }, texture: { type: 'src' }, randomize: { type: 'boolean' }, opacity: { type: 'number', }, maxParticleCount: { type: 'number', default: 250000 } }, init: function() { this.presets = []; /* preset settings can be overwritten */ this.presets['default'] = { maxAge: (this.data.maxAge!==0?this.data.maxAge:6), positionSpread: (this.data.positionSpread.x!==0||this.data.positionSpread.y!==0||this.data.positionSpread.z!==0?this.data.positionSpread:{x:0,y:0,z:0}), type: (this.data.type!==0?this.data.type:SPE.distributions.BOX), /* SPE.distributions.SPHERE, SPE.distributions.DISC */ rotationAxis: (this.data.rotationAxis!==''?this.data.rotationAxis:'x'), rotationAngle: (this.data.rotationAngle!==0?this.data.rotationAngle:0), accelerationValue: (this.data.accelerationValue.x!==0||this.data.accelerationValue.y!==0||this.data.accelerationValue.z!==0?this.data.accelerationValue:{x: 0, y: -10, z: 0}), accelerationSpread: (this.data.accelerationSpread.x!==0||this.data.accelerationSpread.y!==0||this.data.accelerationSpread.z!==0?this.data.accelerationSpread:{x: 10, y: 0, z: 10}), velocityValue: (this.data.velocityValue.x!==0||this.data.velocityValue.y!==0||this.data.velocityValue.z!==0?this.data.velocityValue:{x: 0, y: 25, z: 0}), velocitySpread: (this.data.velocitySpread.x!==0||this.data.velocitySpread.y!==0||this.data.velocitySpread.z!==0?this.data.velocitySpread:{x: 10, y: 7.5, z: 10}), color: (this.data.color!==''?this.data.color:'#0000FF,#FF0000'), size: (this.data.size!==0?this.data.size:1), opacity: { value: (this.data.opacity!=0?this.data.opacity:1) }, direction: (this.data.direction!==0?this.data.direction:1), duration: (this.data.duration!=0?this.data.duration:null), particleCount: (this.data.particleCount!==0?this.data.particleCount:1000), texture: (this.data.texture!==''?this.data.texture:'https://cdn.rawgit.com/IdeaSpaceVR/aframe-particle-system-component/master/dist/images/star2.png'), randomize: false }; this.presets['dust'] = { maxAge: (this.data.maxAge!==0?this.data.maxAge:20), positionSpread: (this.data.positionSpread.x!==0||this.data.positionSpread.y!==0||this.data.positionSpread.z!==0?this.data.positionSpread:{x:100,y:100,z:100}), type: (this.data.type!==0?this.data.type:SPE.distributions.BOX), /* SPE.distributions.SPHERE, SPE.distributions.DISC */ rotationAxis: (this.data.rotationAxis!==''?this.data.rotationAxis:'x'), rotationAngle: (this.data.rotationAngle!==0?this.data.rotationAngle:3.14), accelerationValue: (this.data.accelerationValue.x!==0||this.data.accelerationValue.y!==0||this.data.accelerationValue.z!==0?this.data.accelerationValue:{x: 0, y: 0, z: 0}), accelerationSpread: (this.data.accelerationSpread.x!==0||this.data.accelerationSpread.y!==0||this.data.accelerationSpread.z!==0?this.data.accelerationSpread:{x: 0, y: 0, z: 0}), velocityValue: (this.data.velocityValue.x!==0||this.data.velocityValue.y!==0||this.data.velocityValue.z!==0?this.data.velocityValue:{x: 1, y: 0.3, z: 1}), velocitySpread: (this.data.velocitySpread.x!==0||this.data.velocitySpread.y!==0||this.data.velocitySpread.z!==0?this.data.velocitySpread:{x: 0.5, y: 1, z: 0.5}), color: (this.data.color!==''?this.data.color:'#FFFFFF'), size: (this.data.size!==0?this.data.size:1), opacity: { value: (this.data.opacity!=0?this.data.opacity:1) }, direction: (this.data.direction!==0?this.data.direction:1), duration: (this.data.duration!=0?this.data.duration:null), particleCount: (this.data.particleCount!==0?this.data.particleCount:100), texture: (this.data.texture!==''?this.data.texture:'https://cdn.rawgit.com/IdeaSpaceVR/aframe-particle-system-component/master/dist/images/smokeparticle.png'), randomize: false }; this.presets['snow'] = { maxAge: (this.data.maxAge!==0?this.data.maxAge:20), positionSpread: (this.data.positionSpread.x!==0||this.data.positionSpread.y!==0||this.data.positionSpread.z!==0?this.data.positionSpread:{x:100,y:100,z:100}), type: (this.data.type!==0?this.data.type:SPE.distributions.BOX), /* SPE.distributions.SPHERE, SPE.distributions.DISC */ rotationAxis: (this.data.rotationAxis!==''?this.data.rotationAxis:'x'), rotationAngle: (this.data.rotationAngle!==0?this.data.rotationAngle:3.14), accelerationValue: (this.data.accelerationValue.x!==0||this.data.accelerationValue.y!==0||this.data.accelerationValue.z!==0?this.data.accelerationValue:{x: 0, y: 0, z: 0}), accelerationSpread: (this.data.accelerationSpread.x!==0||this.data.accelerationSpread.y!==0||this.data.accelerationSpread.z!==0?this.data.accelerationSpread:{x: 0.2, y: 0, z: 0.2}), velocityValue: (this.data.velocityValue.x!==0||this.data.velocityValue.y!==0||this.data.velocityValue.z!==0?this.data.velocityValue:{x: 0, y: 8, z: 0}), velocitySpread: (this.data.velocitySpread.x!==0||this.data.velocitySpread.y!==0||this.data.velocitySpread.z!==0?this.data.velocitySpread:{x: 2, y: 0, z: 2}), color: (this.data.color!==''?this.data.color:'#FFFFFF'), size: (this.data.size!==0?this.data.size:1), opacity: { value: (this.data.opacity!=0?this.data.opacity:1) }, direction: (this.data.direction!==0?this.data.direction:1), duration: (this.data.duration!=0?this.data.duration:null), particleCount: (this.data.particleCount!==0?this.data.particleCount:200), texture: (this.data.texture!==''?this.data.texture:'https://cdn.rawgit.com/IdeaSpaceVR/aframe-particle-system-component/master/dist/images/smokeparticle.png'), randomize: false }; this.presets['rain'] = { maxAge: (this.data.maxAge!==0?this.data.maxAge:1), positionSpread: (this.data.positionSpread.x!==0||this.data.positionSpread.y!==0||this.data.positionSpread.z!==0?this.data.positionSpread:{x:100,y:100,z:100}), type: (this.data.type!==0?this.data.type:SPE.distributions.BOX), /* SPE.distributions.SPHERE, SPE.distributions.DISC */ rotationAxis: (this.data.rotationAxis!==''?this.data.rotationAxis:'x'), rotationAngle: (this.data.rotationAngle!==0?this.data.rotationAngle:3.14), accelerationValue: (this.data.accelerationValue.x!==0||this.data.accelerationValue.y!==0||this.data.accelerationValue.z!==0?this.data.accelerationValue:{x: 0, y: 3, z: 0}), accelerationSpread: (this.data.accelerationSpread.x!==0||this.data.accelerationSpread.y!==0||this.data.accelerationSpread.z!==0?this.data.accelerationSpread:{x: 2, y: 1, z: 2}), velocityValue: (this.data.velocityValue.x!==0||this.data.velocityValue.y!==0||this.data.velocityValue.z!==0?this.data.velocityValue:{x: 0, y: 75, z: 0}), velocitySpread: (this.data.velocitySpread.x!==0||this.data.velocitySpread.y!==0||this.data.velocitySpread.z!==0?this.data.velocitySpread:{x: 10, y: 50, z: 10}), color: (this.data.color!==''?this.data.color:'#FFFFFF'), size: (this.data.size!==0?this.data.size:0.4), opacity: { value: (this.data.opacity!=0?this.data.opacity:1) }, direction: (this.data.direction!==0?this.data.direction:1), duration: (this.data.duration!=0?this.data.duration:null), particleCount: (this.data.particleCount!==0?this.data.particleCount:1000), texture: (this.data.texture!==''?this.data.texture:'https://cdn.rawgit.com/IdeaSpaceVR/aframe-particle-system-component/master/dist/images/raindrop.png'), randomize: false }; }, update: function (oldData) { // Remove old particle group. if (this.particleGroup) { this.el.removeObject3D('particle-system'); } // Remove old particle group. if (this.particleGroup) { this.el.removeObject3D('particle-system'); } if (this.data.preset != '' && this.data.preset in this.presets) { this.initParticleSystem(this.presets[this.data.preset]); } else { this.initParticleSystem(this.presets['default']); } }, tick: function(time, dt) { this.particleGroup.tick(dt / 1000); }, remove: function() { // Remove particle system. if (!this.particleGroup) { return; } this.el.removeObject3D('particle-system'); }, 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: this.data.maxParticleCount }); /* color */ var color_arr = []; settings.color.split(',').forEach((function(c) { color_arr.push(new THREE.Color(this.hexToRgb(c).r, this.hexToRgb(c).g, this.hexToRgb(c).b)); }).bind(this)); var emitter = new SPE.Emitter({ maxAge: { value: settings.maxAge }, type: { value: settings.type }, position: { value: this.el.object3D.position, spread: new THREE.Vector3(settings.positionSpread.x, settings.positionSpread.y, settings.positionSpread.z), randomize: settings.randomize //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, 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) }, color: { value: color_arr }, size: { value: settings.size }, /*wiggle: { value: 4, spread: 2 }, //settings.wiggle,*/ /*drag: { value: settings.drag },*/ direction: { value: settings.direction }, duration: settings.duration, opacity: settings.opacity, particleCount: settings.particleCount }); this.particleGroup.addEmitter(emitter); this.el.setObject3D('particle-system', this.particleGroup.mesh); }, hexToRgb: function(hex) { var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16) } : null; } }); /***/ }, /* 1 */ /***/ function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;/* shader-particle-engine 1.0.5 * * (c) 2015 Luke Moody (http://www.github.com/squarefeet) * Originally based on Lee Stemkoski's original work (https://github.com/stemkoski/stemkoski.github.com/blob/master/Three.js/js/ParticleEngine.js). * * shader-particle-engine may be freely distributed under the MIT license (See LICENSE at root of this repository.) */ /** * @typedef {Number} distribution * @property {Number} SPE.distributions.BOX Values will be distributed within a box. * @property {Number} SPE.distributions.SPHERE Values will be distributed within a sphere. * @property {Number} SPE.distributions.DISC Values will be distributed within a 2D disc. */ /** * Namespace for Shader Particle Engine. * * All SPE-related code sits under this namespace. * * @type {Object} * @namespace */ var SPE = { /** * A map of supported distribution types used * by SPE.Emitter instances. * * These distribution types can be applied to * an emitter globally, which will affect the * `position`, `velocity`, and `acceleration` * value calculations for an emitter, or they * can be applied on a per-property basis. * * @enum {Number} */ distributions: { /** * Values will be distributed within a box. * @type {Number} */ BOX: 1, /** * Values will be distributed on a sphere. * @type {Number} */ SPHERE: 2, /** * Values will be distributed on a 2d-disc shape. * @type {Number} */ DISC: 3, }, /** * Set this value to however many 'steps' you * want value-over-lifetime properties to have. * * It's adjustable to fix an interpolation problem: * * Assuming you specify an opacity value as [0, 1, 0] * and the `valueOverLifetimeLength` is 4, then the * opacity value array will be reinterpolated to * be [0, 0.66, 0.66, 0]. * This isn't ideal, as particles would never reach * full opacity. * * NOTE: * This property affects the length of ALL * value-over-lifetime properties for ALL * emitters and ALL groups. * * Only values >= 3 && <= 4 are allowed. * * @type {Number} */ valueOverLifetimeLength: 4 }; // Module loader support: if ( 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; } /** * A helper class for TypedArrays. * * Allows for easy resizing, assignment of various component-based * types (Vector2s, Vector3s, Vector4s, Mat3s, Mat4s), * as well as Colors (where components are `r`, `g`, `b`), * Numbers, and setting from other TypedArrays. * * @author Luke Moody * @constructor * @param {Function} TypedArrayConstructor The constructor to use (Float32Array, Uint8Array, etc.) * @param {Number} size The size of the array to create * @param {Number} componentSize The number of components per-value (ie. 3 for a vec3, 9 for a Mat3, etc.) * @param {Number} indexOffset The index in the array from which to start assigning values. Default `0` if none provided */ SPE.TypedArrayHelper = function( TypedArrayConstructor, size, componentSize, indexOffset ) { 'use strict'; this.componentSize = componentSize || 1; this.size = ( size || 1 ); this.TypedArrayConstructor = TypedArrayConstructor || Float32Array; this.array = new TypedArrayConstructor( size * this.componentSize ); this.indexOffset = indexOffset || 0; }; SPE.TypedArrayHelper.constructor = SPE.TypedArrayHelper; /** * Sets the size of the internal array. * * Delegates to `this.shrink` or `this.grow` depending on size * argument's relation to the current size of the internal array. * * Note that if the array is to be shrunk, data will be lost. * * @param {Number} size The new size of the array. */ SPE.TypedArrayHelper.prototype.setSize = function( size, noComponentMultiply ) { 'use strict'; var currentArraySize = this.array.length; if ( !noComponentMultiply ) { size = size * this.componentSize; } if ( size < currentArraySize ) { return this.shrink( size ); } else if ( size > currentArraySize ) { return this.grow( size ); } else { console.info( 'TypedArray is already of size:', size + '.', 'Will not resize.' ); } }; /** * Shrinks the internal array. * * @param {Number} size The new size of the typed array. Must be smaller than `this.array.length`. * @return {SPE.TypedArrayHelper} Instance of this class. */ SPE.TypedArrayHelper.prototype.shrink = function( size ) { 'use strict'; this.array = this.array.subarray( 0, size ); this.size = size; return this; }; /** * Grows the internal array. * @param {Number} size The new size of the typed array. Must be larger than `this.array.length`. * @return {SPE.TypedArrayHelper} Instance of this class. */ SPE.TypedArrayHelper.prototype.grow = function( size ) { 'use strict'; var existingArray = this.array, newArray = new this.TypedArrayConstructor( size ); newArray.set( existingArray ); this.array = newArray; this.size = size; return this; }; /** * Perform a splice operation on this array's buffer. * @param {Number} start The start index of the splice. Will be multiplied by the number of components for this attribute. * @param {Number} end The end index of the splice. Will be multiplied by the number of components for this attribute. * @returns {Object} The SPE.TypedArrayHelper instance. */ SPE.TypedArrayHelper.prototype.splice = function( start, end ) { 'use strict'; start *= this.componentSize; end *= this.componentSize; var data = [], array = this.array, size = array.length; for ( var i = 0; i < size; ++i ) { if ( i < start || i >= end ) { data.push( array[ i ] ); } // array[ i ] = 0; } this.setFromArray( 0, data ); return this; }; /** * Copies from the given TypedArray into this one, using the index argument * as the start position. Alias for `TypedArray.set`. Will automatically resize * if the given source array is of a larger size than the internal array. * * @param {Number} index The start position from which to copy into this array. * @param {TypedArray} array The array from which to copy; the source array. * @return {SPE.TypedArrayHelper} Instance of this class. */ SPE.TypedArrayHelper.prototype.setFromArray = function( index, array ) { 'use strict'; var sourceArraySize = array.length, newSize = index + sourceArraySize; if ( newSize > this.array.length ) { this.grow( newSize ); } else if ( newSize < this.array.length ) { this.shrink( newSize ); } this.array.set( array, this.indexOffset + index ); return this; }; /** * Set a Vector2 value at `index`. * * @param {Number} index The index at which to set the vec2 values from. * @param {Vector2} vec2 Any object that has `x` and `y` properties. * @return {SPE.TypedArrayHelper} Instance of this class. */ SPE.TypedArrayHelper.prototype.setVec2 = function( index, vec2 ) { 'use strict'; return this.setVec2Components( index, vec2.x, vec2.y ); }; /** * Set a Vector2 value using raw components. * * @param {Number} index The index at which to set the vec2 values from. * @param {Number} x The Vec2's `x` component. * @param {Number} y The Vec2's `y` component. * @return {SPE.TypedArrayHelper} Instance of this class. */ SPE.TypedArrayHelper.prototype.setVec2Components = function( index, x, y ) { 'use strict'; var array = this.array, i = this.indexOffset + ( index * this.componentSize ); array[ i ] = x; array[ i + 1 ] = y; return this; }; /** * Set a Vector3 value at `index`. * * @param {Number} index The index at which to set the vec3 values from. * @param {Vector3} vec2 Any object that has `x`, `y`, and `z` properties. * @return {SPE.TypedArrayHelper} Instance of this class. */ SPE.TypedArrayHelper.prototype.setVec3 = function( index, vec3 ) { 'use strict'; return this.setVec3Components( index, vec3.x, vec3.y, vec3.z ); }; /** * Set a Vector3 value using raw components. * * @param {Number} index The index at which to set the vec3 values from. * @param {Number} x The Vec3's `x` component. * @param {Number} y The Vec3's `y` component. * @param {Number} z The Vec3's `z` component. * @return {SPE.TypedArrayHelper} Instance of this class. */ SPE.TypedArrayHelper.prototype.setVec3Components = function( index, x, y, z ) { 'use strict'; var array = this.array, i = this.indexOffset + ( index * this.componentSize ); array[ i ] = x; array[ i + 1 ] = y; array[ i + 2 ] = z; return this; }; /** * Set a Vector4 value at `index`. * * @param {Number} index The index at which to set the vec4 values from. * @param {Vector4} vec2 Any object that has `x`, `y`, `z`, and `w` properties. * @return {SPE.TypedArrayHelper} Instance of this class. */ SPE.TypedArrayHelper.prototype.setVec4 = function( index, vec4 ) { 'use strict'; return this.setVec4Components( index, vec4.x, vec4.y, vec4.z, vec4.w ); }; /** * Set a Vector4 value using raw components. * * @param {Number} index The index at which to set the vec4 values from. * @param {Number} x The Vec4's `x` component. * @param {Number} y The Vec4's `y` component. * @param {Number} z The Vec4's `z` component. * @param {Number} w The Vec4's `w` component. * @return {SPE.TypedArrayHelper} Instance of this class. */ SPE.TypedArrayHelper.prototype.setVec4Components = function( index, x, y, z, w ) { 'use strict'; var array = this.array, i = this.indexOffset + ( index * this.componentSize ); array[ i ] = x; array[ i + 1 ] = y; array[ i + 2 ] = z; array[ i + 3 ] = w; return this; }; /** * Set a Matrix3 value at `index`. * * @param {Number} index The index at which to set the matrix values from. * @param {Matrix3} mat3 The 3x3 matrix to set from. Must have a TypedArray property named `elements` to copy from. * @return {SPE.TypedArrayHelper} Instance of this class. */ SPE.TypedArrayHelper.prototype.setMat3 = function( index, mat3 ) { 'use strict'; return this.setFromArray( this.indexOffset + ( index * this.componentSize ), mat3.elements ); }; /** * Set a Matrix4 value at `index`. * * @param {Number} index The index at which to set the matrix values from. * @param {Matrix4} mat3 The 4x4 matrix to set from. Must have a TypedArray property named `elements` to copy from. * @return {SPE.TypedArrayHelper} Instance of this class. */ SPE.TypedArrayHelper.prototype.setMat4 = function( index, mat4 ) { 'use strict'; return this.setFromArray( this.indexOffset + ( index * this.componentSize ), mat4.elements ); }; /** * Set a Color value at `index`. * * @param {Number} index The index at which to set the vec3 values from. * @param {Color} color Any object that has `r`, `g`, and `b` properties. * @return {SPE.TypedArrayHelper} Instance of this class. */ SPE.TypedArrayHelper.prototype.setColor = function( index, color ) { 'use strict'; return this.setVec3Components( index, color.r, color.g, color.b ); }; /** * Set a Number value at `index`. * * @param {Number} index The index at which to set the vec3 values from. * @param {Number} numericValue The number to assign to this index in the array. * @return {SPE.TypedArrayHelper} Instance of this class. */ SPE.TypedArrayHelper.prototype.setNumber = function( index, numericValue ) { 'use strict'; this.array[ this.indexOffset + ( index * this.componentSize ) ] = numericValue; return this; }; /** * Returns the value of the array at the given index, taking into account * the `indexOffset` property of this class. * * Note that this function ignores the component size and will just return a * single value. * * @param {Number} index The index in the array to fetch. * @return {Number} The value at the given index. */ SPE.TypedArrayHelper.prototype.getValueAtIndex = function( index ) { 'use strict'; return this.array[ this.indexOffset + index ]; }; /** * Returns the component value of the array at the given index, taking into account * the `indexOffset` property of this class. * * If the componentSize is set to 3, then it will return a new TypedArray * of length 3. * * @param {Number} index The index in the array to fetch. * @return {TypedArray} The component value at the given index. */ SPE.TypedArrayHelper.prototype.getComponentValueAtIndex = function( index ) { 'use strict'; return this.array.subarray( this.indexOffset + ( index * this.componentSize ) ); }; /** * A helper to handle creating and updating a THREE.BufferAttribute instance. * * @author Luke Moody * @constructor * @param {String} type The buffer attribute type. See SPE.ShaderAttribute.typeSizeMap for valid values. * @param {Boolean=} dynamicBuffer Whether this buffer attribute should be marked as dynamic or not. * @param {Function=} arrayType A reference to a TypedArray constructor. Defaults to Float32Array if none provided. */ SPE.ShaderAttribute = function( type, dynamicBuffer, arrayType ) { 'use strict'; var typeMap = SPE.ShaderAttribute.typeSizeMap; this.type = typeof type === 'string' && typeMap.hasOwnProperty( type ) ? type : 'f'; this.componentSize = typeMap[ this.type ]; this.arrayType = arrayType || Float32Array; this.typedArray = null; this.bufferAttribute = null; this.dynamicBuffer = !!dynamicBuffer; this.updateMin = 0; this.updateMax = 0; }; SPE.ShaderAttribute.constructor = SPE.ShaderAttribute; /** * A map of uniform types to their component size. * @enum {Number} */ SPE.ShaderAttribute.typeSizeMap = { /** * Float * @type {Number} */ f: 1, /** * Vec2 * @type {Number} */ v2: 2, /** * Vec3 * @type {Number} */ v3: 3, /** * Vec4 * @type {Number} */ v4: 4, /** * Color * @type {Number} */ c: 3, /** * Mat3 * @type {Number} */ m3: 9, /** * Mat4 * @type {Number} */ m4: 16 }; /** * Calculate the minimum and maximum update range for this buffer attribute using * component size independant min and max values. * * @param {Number} min The start of the range to mark as needing an update. * @param {Number} max The end of the range to mark as needing an update. */ SPE.ShaderAttribute.prototype.setUpdateRange = function( min, max ) { 'use strict'; this.updateMin = Math.min( min * this.componentSize, this.updateMin * this.componentSize ); this.updateMax = Math.max( max * this.componentSize, this.updateMax * this.componentSize ); }; /** * Calculate the number of indices that this attribute should mark as needing * updating. Also marks the attribute as needing an update. */ SPE.ShaderAttribute.prototype.flagUpdate = function() { 'use strict'; var attr = this.bufferAttribute, range = attr.updateRange; range.offset = this.updateMin; range.count = Math.min( ( this.updateMax - this.updateMin ) + this.componentSize, this.typedArray.array.length ); // console.log( range.offset, range.count, this.typedArray.array.length ); // console.log( 'flagUpdate:', range.offset, range.count ); attr.needsUpdate = true; }; /** * Reset the index update counts for this attribute */ SPE.ShaderAttribute.prototype.resetUpdateRange = function() { 'use strict'; this.updateMin = 0; this.updateMax = 0; }; SPE.ShaderAttribute.prototype.resetDynamic = function() { 'use strict'; this.bufferAttribute.dynamic = this.dynamicBuffer; }; /** * Perform a splice operation on this attribute's buffer. * @param {Number} start The start index of the splice. Will be multiplied by the number of components for this attribute. * @param {Number} end The end index of the splice. Will be multiplied by the number of components for this attribute. */ SPE.ShaderAttribute.prototype.splice = function( start, end ) { 'use strict'; this.typedArray.splice( start, end ); // Reset the reference to the attribute's typed array // since it has probably changed. this.forceUpdateAll(); }; SPE.ShaderAttribute.prototype.forceUpdateAll = function() { 'use strict'; this.bufferAttribute.array = this.typedArray.array; this.bufferAttribute.updateRange.offset = 0; this.bufferAttribute.updateRange.count = -1; this.bufferAttribute.dynamic = false; this.bufferAttribute.needsUpdate = true; }; /** * Make sure this attribute has a typed array associated with it. * * If it does, then it will ensure the typed array is of the correct size. * * If not, a new SPE.TypedArrayHelper instance will be created. * * @param {Number} size The size of the typed array to create or update to. */ SPE.ShaderAttribute.prototype._ensureTypedArray = function( size ) { 'use strict'; // Condition that's most likely to be true at the top: no change. if ( this.typedArray !== null && this.typedArray.size === size * this.componentSize ) { return; } // Resize the array if we need to, telling the TypedArrayHelper to // ignore it's component size when evaluating size. else if ( this.typedArray !== null && this.typedArray.size !== size ) { this.typedArray.setSize( size ); } // This condition should only occur once in an attribute's lifecycle. else if ( this.typedArray === null ) { this.typedArray = new SPE.TypedArrayHelper( this.arrayType, size, this.componentSize ); } }; /** * Creates a THREE.BufferAttribute instance if one doesn't exist already. * * Ensures a typed array is present by calling _ensureTypedArray() first. * * If a buffer attribute exists already, then it will be marked as needing an update. * * @param {Number} size The size of the typed array to create if one doesn't exist, or resize existing array to. */ SPE.ShaderAttribute.prototype._createBufferAttribute = function( size ) { 'use strict'; // Make sure the typedArray is present and correct. this._ensureTypedArray( size ); // Don't create it if it already exists, but do // flag that it needs updating on the next render // cycle. if ( this.bufferAttribute !== null ) { this.bufferAttribute.array = this.typedArray.array; this.bufferAttribute.needsUpdate = true; return; } this.bufferAttribute = new THREE.BufferAttribute( this.typedArray.array, this.componentSize ); this.bufferAttribute.dynamic = this.dynamicBuffer; }; /** * Returns the length of the typed array associated with this attribute. * @return {Number} The length of the typed array. Will be 0 if no typed array has been created yet. */ SPE.ShaderAttribute.prototype.getLength = function() { 'use strict'; if ( this.typedArray === null ) { return 0; } return this.typedArray.array.length; }; SPE.shaderChunks = { // Register color-packing define statements. defines: [ '#define PACKED_COLOR_SIZE 256.0', '#define PACKED_COLOR_DIVISOR 255.0' ].join( '\n' ), // All uniforms used by vertex / fragment shaders uniforms: [ 'uniform float deltaTime;', 'uniform float runTime;', 'uniform sampler2D texture;', 'uniform vec4 textureAnimation;', 'uniform float scale;', ].join( '\n' ), // All attributes used by the vertex shader. // // Note that some attributes are squashed into other ones: // // * Drag is acceleration.w attributes: [ 'attribute vec4 acceleration;', 'attribute vec3 velocity;', 'attribute vec4 rotation;', 'attribute vec3 rotationCenter;', 'attribute vec4 params;', 'attribute vec4 size;', 'attribute vec4 angle;', 'attribute vec4 color;', 'attribute vec4 opacity;' ].join( '\n' ), // varyings: [ 'varying vec4 vColor;', '#ifdef SHOULD_ROTATE_TEXTURE', ' varying float vAngle;', '#endif', '#ifdef SHOULD_CALCULATE_SPRITE', ' varying vec4 vSpriteSheet;', '#endif' ].join( '\n' ), // Branch-avoiding comparison fns // - http://theorangeduck.com/page/avoiding-shader-conditionals branchAvoidanceFunctions: [ 'float when_gt(float x, float y) {', ' return max(sign(x - y), 0.0);', '}', 'float when_lt(float x, float y) {', ' return min( max(1.0 - sign(x - y), 0.0), 1.0 );', '}', 'float when_eq( float x, float y ) {', ' return 1.0 - abs( sign( x - y ) );', '}', 'float when_ge(float x, float y) {', ' return 1.0 - when_lt(x, y);', '}', 'float when_le(float x, float y) {', ' return 1.0 - when_gt(x, y);', '}', // Branch-avoiding logical operators // (to be used with above comparison fns) 'float and(float a, float b) {', ' return a * b;', '}', 'float or(float a, float b) {', ' return min(a + b, 1.0);', '}', ].join( '\n' ), // From: // - http://stackoverflow.com/a/12553149 // - https://stackoverflow.com/questions/22895237/hexadecimal-to-rgb-values-in-webgl-shader unpackColor: [ 'vec3 unpackColor( in float hex ) {', ' vec3 c = vec3( 0.0 );', ' float r = mod( (hex / PACKED_COLOR_SIZE / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );', ' float g = mod( (hex / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );', ' float b = mod( hex, PACKED_COLOR_SIZE );', ' c.r = r / PACKED_COLOR_DIVISOR;', ' c.g = g / PACKED_COLOR_DIVISOR;', ' c.b = b / PACKED_COLOR_DIVISOR;', ' return c;', '}', ].join( '\n' ), unpackRotationAxis: [ 'vec3 unpackRotationAxis( in float hex ) {', ' vec3 c = vec3( 0.0 );', ' float r = mod( (hex / PACKED_COLOR_SIZE / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );', ' float g = mod( (hex / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );', ' float b = mod( hex, PACKED_COLOR_SIZE );', ' c.r = r / PACKED_COLOR_DIVISOR;', ' c.g = g / PACKED_COLOR_DIVISOR;', ' c.b = b / PACKED_COLOR_DIVISOR;', ' c *= vec3( 2.0 );', ' c -= vec3( 1.0 );', ' return c;', '}', ].join( '\n' ), floatOverLifetime: [ 'float getFloatOverLifetime( in float positionInTime, in vec4 attr ) {', ' highp float value = 0.0;', ' float deltaAge = positionInTime * float( VALUE_OVER_LIFETIME_LENGTH - 1 );', ' float fIndex = 0.0;', ' float shouldApplyValue = 0.0;', // This might look a little odd, but it's faster in the testing I've done than using branches. // Uses basic maths to avoid branching. // // Take a look at the branch-avoidance functions defined above, // and be sure to check out The Orange Duck site where I got this // from (link above). // Fix for static emitters (age is always zero). ' value += attr[ 0 ] * when_eq( deltaAge, 0.0 );', '', ' for( int i = 0; i < VALUE_OVER_LIFETIME_LENGTH - 1; ++i ) {', ' fIndex = float( i );', ' shouldApplyValue = and( when_gt( deltaAge, fIndex ), when_le( deltaAge, fIndex + 1.0 ) );', ' value += shouldApplyValue * mix( attr[ i ], attr[ i + 1 ], deltaAge - fIndex );', ' }', '', ' return value;', '}', ].join( '\n' ), colorOverLifetime: [ 'vec3 getColorOverLifetime( in float positionInTime, in vec3 color1, in vec3 color2, in vec3 color3, in vec3 color4 ) {', ' vec3 value = vec3( 0.0 );', ' value.x = getFloatOverLifetime( positionInTime, vec4( color1.x, color2.x, color3.x, color4.x ) );', ' value.y = getFloatOverLifetime( positionInTime, vec4( color1.y, color2.y, color3.y, color4.y ) );', ' value.z = getFloatOverLifetime( positionInTime, vec4( color1.z, color2.z, color3.z, color4.z ) );', ' return value;', '}', ].join( '\n' ), paramFetchingFunctions: [ 'float getAlive() {', ' return params.x;', '}', 'float getAge() {', ' return params.y;', '}', 'float getMaxAge() {', ' return params.z;', '}', 'float getWiggle() {', ' return params.w;', '}', ].join( '\n' ), forceFetchingFunctions: [ 'vec4 getPosition( in float age ) {', ' return modelViewMatrix * vec4( position, 1.0 );', '}', 'vec3 getVelocity( in float age ) {', ' return velocity * age;', '}', 'vec3 getAcceleration( in float age ) {', ' return acceleration.xyz * age;', '}', ].join( '\n' ), rotationFunctions: [ // Huge thanks to: // - http://www.neilmendoza.com/glsl-rotation-about-an-arbitrary-axis/ '#ifdef SHOULD_ROTATE_PARTICLES', ' mat4 getRotationMatrix( in vec3 axis, in float angle) {', ' axis = normalize(axis);', ' float s = sin(angle);', ' float c = cos(angle);', ' float oc = 1.0 - c;', '', ' return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0,', ' oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0,', ' oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0,', ' 0.0, 0.0, 0.0, 1.0);', ' }', '', ' vec3 getRotation( in vec3 pos, in float positionInTime ) {', ' if( rotation.y == 0.0 ) {', ' return pos;', ' }', '', ' vec3 axis = unpackRotationAxis( rotation.x );', ' vec3 center = rotationCenter;', ' vec3 translated;', ' mat4 rotationMatrix;', ' float angle = 0.0;', ' angle += when_eq( rotation.z, 0.0 ) * rotation.y;', ' angle += when_gt( rotation.z, 0.0 ) * mix( 0.0, rotation.y, positionInTime );', ' translated = rotationCenter - pos;', ' rotationMatrix = getRotationMatrix( axis, angle );', ' return center - vec3( rotationMatrix * vec4( translated, 0.0 ) );', ' }', '#endif' ].join( '\n' ), // Fragment chunks rotateTexture: [ ' vec2 vUv = vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y );', '', ' #ifdef SHOULD_ROTATE_TEXTURE', ' float x = gl_PointCoord.x - 0.5;', ' float y = 1.0 - gl_PointCoord.y - 0.5;', ' float c = cos( -vAngle );', ' float s = sin( -vAngle );', ' vUv = vec2( c * x + s * y + 0.5, c * y - s * x + 0.5 );', ' #endif', '', // Spritesheets overwrite angle calculations. ' #ifdef SHOULD_CALCULATE_SPRITE', ' float framesX = vSpriteSheet.x;', ' float framesY = vSpriteSheet.y;', ' float columnNorm = vSpriteSheet.z;', ' float rowNorm = vSpriteSheet.w;', ' vUv.x = gl_PointCoord.x * framesX + columnNorm;', ' vUv.y = 1.0 - (gl_PointCoord.y * framesY + rowNorm);', ' #endif', '', ' vec4 rotatedTexture = texture2D( texture, vUv );', ].join( '\n' ) }; SPE.shaders = { vertex: [ SPE.shaderChunks.defines, SPE.shaderChunks.uniforms, SPE.shaderChunks.attributes, SPE.shaderChunks.varyings, THREE.ShaderChunk.common, THREE.ShaderChunk.logdepthbuf_pars_vertex, SPE.shaderChunks.branchAvoidanceFunctions, SPE.shaderChunks.unpackColor, SPE.shaderChunks.unpackRotationAxis, SPE.shaderChunks.floatOverLifetime, SPE.shaderChunks.colorOverLifetime, SPE.shaderChunks.paramFetchingFunctions, SPE.shaderChunks.forceFetchingFunctions, SPE.shaderChunks.rotationFunctions, 'void main() {', // // Setup... // ' highp float age = getAge();', ' highp float alive = getAlive();', ' highp float maxAge = getMaxAge();', ' highp float positionInTime = (age / maxAge);', ' highp float isAlive = when_gt( alive, 0.0 );', ' #ifdef SHOULD_WIGGLE_PARTICLES', ' float wiggleAmount = positionInTime * getWiggle();', ' float wiggleSin = isAlive * sin( wiggleAmount );', ' float wiggleCos = isAlive * cos( wiggleAmount );', ' #endif', // // Forces // // Get forces & position ' vec3 vel = getVelocity( age );', ' vec3 accel = getAcceleration( age );', ' vec3 force = vec3( 0.0 );', ' vec3 pos = vec3( position );', // Calculate the required drag to apply to the forces. ' float drag = 1.0 - (positionInTime * 0.5) * acceleration.w;', // Integrate forces... ' force += vel;', ' force *= drag;', ' force += accel * age;', ' pos += force;', // Wiggly wiggly wiggle! ' #ifdef SHOULD_WIGGLE_PARTICLES', ' pos.x += wiggleSin;', ' pos.y += wiggleCos;', ' pos.z += wiggleSin;', ' #endif', // Rotate the emitter around it's central point ' #ifdef SHOULD_ROTATE_PARTICLES', ' pos = getRotation( pos, positionInTime );', ' #endif', // Convert pos to a world-space value ' vec4 mvPos = modelViewMatrix * vec4( pos, 1.0 );', // Determine point size. ' highp float pointSize = getFloatOverLifetime( positionInTime, size ) * isAlive;', // Determine perspective ' #ifdef HAS_PERSPECTIVE', ' float perspective = scale / length( mvPos.xyz );', ' #else', ' float perspective = 1.0;', ' #endif', // Apply perpective to pointSize value ' float pointSizePerspective = pointSize * perspective;', // // Appearance // // Determine color and opacity for this particle ' #ifdef COLORIZE', ' vec3 c = isAlive * getColorOverLifetime(', ' positionInTime,', ' unpackColor( color.x ),', ' unpackColor( color.y ),', ' unpackColor( color.z ),', ' unpackColor( color.w )', ' );', ' #else', ' vec3 c = vec3(1.0);', ' #endif', ' float o = isAlive * getFloatOverLifetime( positionInTime, opacity );', // Assign color to vColor varying. ' vColor = vec4( c, o );', // Determine angle ' #ifdef SHOULD_ROTATE_TEXTURE', ' vAngle = isAlive * getFloatOverLifetime( positionInTime, angle );', ' #endif', // If this particle is using a sprite-sheet as a texture, we'll have to figure out // what frame of the texture the particle is using at it's current position in time. ' #ifdef SHOULD_CALCULATE_SPRITE', ' float framesX = textureAnimation.x;', ' float framesY = textureAnimation.y;', ' float loopCount = textureAnimation.w;', ' float totalFrames = textureAnimation.z;', ' float frameNumber = mod( (positionInTime * loopCount) * totalFrames, totalFrames );', ' float column = floor(mod( frameNumber, framesX ));', ' float row = floor( (frameNumber - column) / framesX );', ' float columnNorm = column / framesX;', ' float rowNorm = row / framesY;', ' vSpriteSheet.x = 1.0 / framesX;', ' vSpriteSheet.y = 1.0 / framesY;', ' vSpriteSheet.z = columnNorm;', ' vSpriteSheet.w = rowNorm;', ' #endif', // // Write values // // Set PointSize according to size at current point in time. ' gl_PointSize = pointSizePerspective;', ' gl_Position = projectionMatrix * mvPos;', THREE.ShaderChunk.logdepthbuf_vertex, '}' ].join( '\n' ), fragment: [ SPE.shaderChunks.uniforms, THREE.ShaderChunk.common, THREE.ShaderChunk.fog_pars_fragment, THREE.ShaderChunk.logdepthbuf_pars_fragment, SPE.shaderChunks.varyings,