aframe-particle-system-component
Version:
Particle systems for A-Frame.
1,567 lines (1,329 loc) • 142 kB
JavaScript
/******/ (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] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = 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;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 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: 'number',
default: 1
},
direction: {
type: 'number',
default: 1
},
duration: {
type: 'number',
default: null
},
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' ]
},
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
},
/*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); }) },
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.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,
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,
'vec4 mvPosition;',
'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 = 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( 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,
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 fail