diff --git a/assets/js/SPE.js b/assets/js/SPE.js new file mode 100644 index 00000000..0fbd3a06 --- /dev/null +++ b/assets/js/SPE.js @@ -0,0 +1,3543 @@ +/* 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, + }, + + + /** + * Set this value to however many 'steps' you + * want value-over-lifetime properties to have. + * + * It's adjustable to fix an interpolation problem: + * + * Assuming you specify an opacity value as [0, 1, 0] + * and the `valueOverLifetimeLength` is 4, then the + * opacity value array will be reinterpolated to + * be [0, 0.66, 0.66, 0]. + * This isn't ideal, as particles would never reach + * full opacity. + * + * NOTE: + * This property affects the length of ALL + * value-over-lifetime properties for ALL + * emitters and ALL groups. + * + * Only values >= 3 && <= 4 are allowed. + * + * @type {Number} + */ + valueOverLifetimeLength: 4 +}; + +// Module loader support: +if ( typeof define === 'function' && define.amd ) { + define( 'spe', SPE ); +} +else if ( typeof exports !== 'undefined' && typeof module !== 'undefined' ) { + module.exports = SPE; +} + +/** + * A helper class for TypedArrays. + * + * Allows for easy resizing, assignment of various component-based + * types (Vector2s, Vector3s, Vector4s, Mat3s, Mat4s), + * as well as Colors (where components are `r`, `g`, `b`), + * Numbers, and setting from other TypedArrays. + * + * @author Luke Moody + * @constructor + * @param {Function} TypedArrayConstructor The constructor to use (Float32Array, Uint8Array, etc.) + * @param {Number} size The size of the array to create + * @param {Number} componentSize The number of components per-value (ie. 3 for a vec3, 9 for a Mat3, etc.) + * @param {Number} indexOffset The index in the array from which to start assigning values. Default `0` if none provided + */ +SPE.TypedArrayHelper = function( TypedArrayConstructor, size, componentSize, indexOffset ) { + 'use strict'; + + this.componentSize = componentSize || 1; + this.size = ( size || 1 ); + this.TypedArrayConstructor = TypedArrayConstructor || Float32Array; + this.array = new TypedArrayConstructor( size * this.componentSize ); + this.indexOffset = indexOffset || 0; +}; + +SPE.TypedArrayHelper.constructor = SPE.TypedArrayHelper; + +/** + * Sets the size of the internal array. + * + * Delegates to `this.shrink` or `this.grow` depending on size + * argument's relation to the current size of the internal array. + * + * Note that if the array is to be shrunk, data will be lost. + * + * @param {Number} size The new size of the array. + */ +SPE.TypedArrayHelper.prototype.setSize = function( size, noComponentMultiply ) { + 'use strict'; + + var currentArraySize = this.array.length; + + if ( !noComponentMultiply ) { + size = size * this.componentSize; + } + + if ( size < currentArraySize ) { + return this.shrink( size ); + } + else if ( size > currentArraySize ) { + return this.grow( size ); + } + else { + console.info( 'TypedArray is already of size:', size + '.', 'Will not resize.' ); + } +}; + +/** + * Shrinks the internal array. + * + * @param {Number} size The new size of the typed array. Must be smaller than `this.array.length`. + * @return {SPE.TypedArrayHelper} Instance of this class. + */ +SPE.TypedArrayHelper.prototype.shrink = function( size ) { + 'use strict'; + + this.array = this.array.subarray( 0, size ); + this.size = size; + return this; +}; + +/** + * Grows the internal array. + * @param {Number} size The new size of the typed array. Must be larger than `this.array.length`. + * @return {SPE.TypedArrayHelper} Instance of this class. + */ +SPE.TypedArrayHelper.prototype.grow = function( size ) { + 'use strict'; + + var existingArray = this.array, + newArray = new this.TypedArrayConstructor( size ); + + newArray.set( existingArray ); + this.array = newArray; + this.size = size; + + return this; +}; + + +/** + * Perform a splice operation on this array's buffer. + * @param {Number} start The start index of the splice. Will be multiplied by the number of components for this attribute. + * @param {Number} end The end index of the splice. Will be multiplied by the number of components for this attribute. + * @returns {Object} The SPE.TypedArrayHelper instance. + */ +SPE.TypedArrayHelper.prototype.splice = function( start, end ) { + 'use strict'; + start *= this.componentSize; + end *= this.componentSize; + + var data = [], + array = this.array, + size = array.length; + + for ( var i = 0; i < size; ++i ) { + if ( i < start || i >= end ) { + data.push( array[ i ] ); + } + // array[ i ] = 0; + } + + this.setFromArray( 0, data ); + + return this; +}; + + +/** + * Copies from the given TypedArray into this one, using the index argument + * as the start position. Alias for `TypedArray.set`. Will automatically resize + * if the given source array is of a larger size than the internal array. + * + * @param {Number} index The start position from which to copy into this array. + * @param {TypedArray} array The array from which to copy; the source array. + * @return {SPE.TypedArrayHelper} Instance of this class. + */ +SPE.TypedArrayHelper.prototype.setFromArray = function( index, array ) { + 'use strict'; + + var sourceArraySize = array.length, + newSize = index + sourceArraySize; + + if ( newSize > this.array.length ) { + this.grow( newSize ); + } + else if ( newSize < this.array.length ) { + this.shrink( newSize ); + } + + this.array.set( array, this.indexOffset + index ); + + return this; +}; + +/** + * Set a Vector2 value at `index`. + * + * @param {Number} index The index at which to set the vec2 values from. + * @param {Vector2} vec2 Any object that has `x` and `y` properties. + * @return {SPE.TypedArrayHelper} Instance of this class. + */ +SPE.TypedArrayHelper.prototype.setVec2 = function( index, vec2 ) { + 'use strict'; + + return this.setVec2Components( index, vec2.x, vec2.y ); +}; + +/** + * Set a Vector2 value using raw components. + * + * @param {Number} index The index at which to set the vec2 values from. + * @param {Number} x The Vec2's `x` component. + * @param {Number} y The Vec2's `y` component. + * @return {SPE.TypedArrayHelper} Instance of this class. + */ +SPE.TypedArrayHelper.prototype.setVec2Components = function( index, x, y ) { + 'use strict'; + + var array = this.array, + i = this.indexOffset + ( index * this.componentSize ); + + array[ i ] = x; + array[ i + 1 ] = y; + return this; +}; + +/** + * Set a Vector3 value at `index`. + * + * @param {Number} index The index at which to set the vec3 values from. + * @param {Vector3} vec2 Any object that has `x`, `y`, and `z` properties. + * @return {SPE.TypedArrayHelper} Instance of this class. + */ +SPE.TypedArrayHelper.prototype.setVec3 = function( index, vec3 ) { + 'use strict'; + + return this.setVec3Components( index, vec3.x, vec3.y, vec3.z ); +}; + +/** + * Set a Vector3 value using raw components. + * + * @param {Number} index The index at which to set the vec3 values from. + * @param {Number} x The Vec3's `x` component. + * @param {Number} y The Vec3's `y` component. + * @param {Number} z The Vec3's `z` component. + * @return {SPE.TypedArrayHelper} Instance of this class. + */ +SPE.TypedArrayHelper.prototype.setVec3Components = function( index, x, y, z ) { + 'use strict'; + + var array = this.array, + i = this.indexOffset + ( index * this.componentSize ); + + array[ i ] = x; + array[ i + 1 ] = y; + array[ i + 2 ] = z; + return this; +}; + +/** + * Set a Vector4 value at `index`. + * + * @param {Number} index The index at which to set the vec4 values from. + * @param {Vector4} vec2 Any object that has `x`, `y`, `z`, and `w` properties. + * @return {SPE.TypedArrayHelper} Instance of this class. + */ +SPE.TypedArrayHelper.prototype.setVec4 = function( index, vec4 ) { + 'use strict'; + + return this.setVec4Components( index, vec4.x, vec4.y, vec4.z, vec4.w ); +}; + +/** + * Set a Vector4 value using raw components. + * + * @param {Number} index The index at which to set the vec4 values from. + * @param {Number} x The Vec4's `x` component. + * @param {Number} y The Vec4's `y` component. + * @param {Number} z The Vec4's `z` component. + * @param {Number} w The Vec4's `w` component. + * @return {SPE.TypedArrayHelper} Instance of this class. + */ +SPE.TypedArrayHelper.prototype.setVec4Components = function( index, x, y, z, w ) { + 'use strict'; + + var array = this.array, + i = this.indexOffset + ( index * this.componentSize ); + + array[ i ] = x; + array[ i + 1 ] = y; + array[ i + 2 ] = z; + array[ i + 3 ] = w; + return this; +}; + +/** + * Set a Matrix3 value at `index`. + * + * @param {Number} index The index at which to set the matrix values from. + * @param {Matrix3} mat3 The 3x3 matrix to set from. Must have a TypedArray property named `elements` to copy from. + * @return {SPE.TypedArrayHelper} Instance of this class. + */ +SPE.TypedArrayHelper.prototype.setMat3 = function( index, mat3 ) { + 'use strict'; + + return this.setFromArray( this.indexOffset + ( index * this.componentSize ), mat3.elements ); +}; + +/** + * Set a Matrix4 value at `index`. + * + * @param {Number} index The index at which to set the matrix values from. + * @param {Matrix4} mat3 The 4x4 matrix to set from. Must have a TypedArray property named `elements` to copy from. + * @return {SPE.TypedArrayHelper} Instance of this class. + */ +SPE.TypedArrayHelper.prototype.setMat4 = function( index, mat4 ) { + 'use strict'; + + return this.setFromArray( this.indexOffset + ( index * this.componentSize ), mat4.elements ); +}; + +/** + * Set a Color value at `index`. + * + * @param {Number} index The index at which to set the vec3 values from. + * @param {Color} color Any object that has `r`, `g`, and `b` properties. + * @return {SPE.TypedArrayHelper} Instance of this class. + */ +SPE.TypedArrayHelper.prototype.setColor = function( index, color ) { + 'use strict'; + + return this.setVec3Components( index, color.r, color.g, color.b ); +}; + +/** + * Set a Number value at `index`. + * + * @param {Number} index The index at which to set the vec3 values from. + * @param {Number} numericValue The number to assign to this index in the array. + * @return {SPE.TypedArrayHelper} Instance of this class. + */ +SPE.TypedArrayHelper.prototype.setNumber = function( index, numericValue ) { + 'use strict'; + + this.array[ this.indexOffset + ( index * this.componentSize ) ] = numericValue; + return this; +}; + +/** + * Returns the value of the array at the given index, taking into account + * the `indexOffset` property of this class. + * + * Note that this function ignores the component size and will just return a + * single value. + * + * @param {Number} index The index in the array to fetch. + * @return {Number} The value at the given index. + */ +SPE.TypedArrayHelper.prototype.getValueAtIndex = function( index ) { + 'use strict'; + + return this.array[ this.indexOffset + index ]; +}; + +/** + * Returns the component value of the array at the given index, taking into account + * the `indexOffset` property of this class. + * + * If the componentSize is set to 3, then it will return a new TypedArray + * of length 3. + * + * @param {Number} index The index in the array to fetch. + * @return {TypedArray} The component value at the given index. + */ +SPE.TypedArrayHelper.prototype.getComponentValueAtIndex = function( index ) { + 'use strict'; + + return this.array.subarray( this.indexOffset + ( index * this.componentSize ) ); +}; + +/** + * A helper to handle creating and updating a THREE.BufferAttribute instance. + * + * @author Luke Moody + * @constructor + * @param {String} type The buffer attribute type. See SPE.ShaderAttribute.typeSizeMap for valid values. + * @param {Boolean=} dynamicBuffer Whether this buffer attribute should be marked as dynamic or not. + * @param {Function=} arrayType A reference to a TypedArray constructor. Defaults to Float32Array if none provided. + */ +SPE.ShaderAttribute = function( type, dynamicBuffer, arrayType ) { + 'use strict'; + + var typeMap = SPE.ShaderAttribute.typeSizeMap; + + this.type = typeof type === 'string' && typeMap.hasOwnProperty( type ) ? type : 'f'; + this.componentSize = typeMap[ this.type ]; + this.arrayType = arrayType || Float32Array; + this.typedArray = null; + this.bufferAttribute = null; + this.dynamicBuffer = !!dynamicBuffer; + + this.updateMin = 0; + this.updateMax = 0; +}; + +SPE.ShaderAttribute.constructor = SPE.ShaderAttribute; + +/** + * A map of uniform types to their component size. + * @enum {Number} + */ +SPE.ShaderAttribute.typeSizeMap = { + /** + * Float + * @type {Number} + */ + f: 1, + + /** + * Vec2 + * @type {Number} + */ + v2: 2, + + /** + * Vec3 + * @type {Number} + */ + v3: 3, + + /** + * Vec4 + * @type {Number} + */ + v4: 4, + + /** + * Color + * @type {Number} + */ + c: 3, + + /** + * Mat3 + * @type {Number} + */ + m3: 9, + + /** + * Mat4 + * @type {Number} + */ + m4: 16 +}; + +/** + * Calculate the minimum and maximum update range for this buffer attribute using + * component size independant min and max values. + * + * @param {Number} min The start of the range to mark as needing an update. + * @param {Number} max The end of the range to mark as needing an update. + */ +SPE.ShaderAttribute.prototype.setUpdateRange = function( min, max ) { + 'use strict'; + + this.updateMin = Math.min( min * this.componentSize, this.updateMin * this.componentSize ); + this.updateMax = Math.max( max * this.componentSize, this.updateMax * this.componentSize ); +}; + +/** + * Calculate the number of indices that this attribute should mark as needing + * updating. Also marks the attribute as needing an update. + */ +SPE.ShaderAttribute.prototype.flagUpdate = function() { + 'use strict'; + + var attr = this.bufferAttribute, + range = attr.updateRange; + + range.offset = this.updateMin; + range.count = Math.min( ( this.updateMax - this.updateMin ) + this.componentSize, this.typedArray.array.length ); + // console.log( range.offset, range.count, this.typedArray.array.length ); + // console.log( 'flagUpdate:', range.offset, range.count ); + attr.needsUpdate = true; +}; + + + +/** + * Reset the index update counts for this attribute + */ +SPE.ShaderAttribute.prototype.resetUpdateRange = function() { + 'use strict'; + + this.updateMin = 0; + this.updateMax = 0; +}; + +SPE.ShaderAttribute.prototype.resetDynamic = function() { + 'use strict'; + this.bufferAttribute.dynamic = this.dynamicBuffer; +}; + +/** + * Perform a splice operation on this attribute's buffer. + * @param {Number} start The start index of the splice. Will be multiplied by the number of components for this attribute. + * @param {Number} end The end index of the splice. Will be multiplied by the number of components for this attribute. + */ +SPE.ShaderAttribute.prototype.splice = function( start, end ) { + 'use strict'; + + this.typedArray.splice( start, end ); + + // Reset the reference to the attribute's typed array + // since it has probably changed. + this.forceUpdateAll(); +}; + +SPE.ShaderAttribute.prototype.forceUpdateAll = function() { + 'use strict'; + + this.bufferAttribute.array = this.typedArray.array; + this.bufferAttribute.updateRange.offset = 0; + this.bufferAttribute.updateRange.count = -1; + this.bufferAttribute.dynamic = false; + this.bufferAttribute.needsUpdate = true; +}; + +/** + * Make sure this attribute has a typed array associated with it. + * + * If it does, then it will ensure the typed array is of the correct size. + * + * If not, a new SPE.TypedArrayHelper instance will be created. + * + * @param {Number} size The size of the typed array to create or update to. + */ +SPE.ShaderAttribute.prototype._ensureTypedArray = function( size ) { + 'use strict'; + + // Condition that's most likely to be true at the top: no change. + if ( this.typedArray !== null && this.typedArray.size === size * this.componentSize ) { + return; + } + + // Resize the array if we need to, telling the TypedArrayHelper to + // ignore it's component size when evaluating size. + else if ( this.typedArray !== null && this.typedArray.size !== size ) { + this.typedArray.setSize( size ); + } + + // This condition should only occur once in an attribute's lifecycle. + else if ( this.typedArray === null ) { + this.typedArray = new SPE.TypedArrayHelper( this.arrayType, size, this.componentSize ); + } +}; + + +/** + * Creates a THREE.BufferAttribute instance if one doesn't exist already. + * + * Ensures a typed array is present by calling _ensureTypedArray() first. + * + * If a buffer attribute exists already, then it will be marked as needing an update. + * + * @param {Number} size The size of the typed array to create if one doesn't exist, or resize existing array to. + */ +SPE.ShaderAttribute.prototype._createBufferAttribute = function( size ) { + 'use strict'; + + // Make sure the typedArray is present and correct. + this._ensureTypedArray( size ); + + // Don't create it if it already exists, but do + // flag that it needs updating on the next render + // cycle. + if ( this.bufferAttribute !== null ) { + this.bufferAttribute.array = this.typedArray.array; + + // Since THREE.js version 81, dynamic count calculation was removed + // so I need to do it manually here. + // + // In the next minor release, I may well remove this check and force + // dependency on THREE r81+. + if ( parseFloat( THREE.REVISION ) >= 81 ) { + this.bufferAttribute.count = this.bufferAttribute.array.length / this.bufferAttribute.itemSize; + } + + this.bufferAttribute.needsUpdate = true; + return; + } + + this.bufferAttribute = new THREE.BufferAttribute( this.typedArray.array, this.componentSize ); + this.bufferAttribute.dynamic = this.dynamicBuffer; +}; + +/** + * Returns the length of the typed array associated with this attribute. + * @return {Number} The length of the typed array. Will be 0 if no typed array has been created yet. + */ +SPE.ShaderAttribute.prototype.getLength = function() { + 'use strict'; + + if ( this.typedArray === null ) { + return 0; + } + + return this.typedArray.array.length; +}; + + +SPE.shaderChunks = { + // Register color-packing define statements. + defines: [ + '#define PACKED_COLOR_SIZE 256.0', + '#define PACKED_COLOR_DIVISOR 255.0' + ].join( '\n' ), + + // All uniforms used by vertex / fragment shaders + uniforms: [ + 'uniform float deltaTime;', + 'uniform float runTime;', + 'uniform sampler2D texture;', + 'uniform vec4 textureAnimation;', + 'uniform float scale;', + ].join( '\n' ), + + // All attributes used by the vertex shader. + // + // Note that some attributes are squashed into other ones: + // + // * Drag is acceleration.w + attributes: [ + 'attribute vec4 acceleration;', + 'attribute vec3 velocity;', + 'attribute vec4 rotation;', + 'attribute vec3 rotationCenter;', + 'attribute vec4 params;', + 'attribute vec4 size;', + 'attribute vec4 angle;', + 'attribute vec4 color;', + 'attribute vec4 opacity;' + ].join( '\n' ), + + // + varyings: [ + 'varying vec4 vColor;', + '#ifdef SHOULD_ROTATE_TEXTURE', + ' varying float vAngle;', + '#endif', + + '#ifdef SHOULD_CALCULATE_SPRITE', + ' varying vec4 vSpriteSheet;', + '#endif' + ].join( '\n' ), + + + // Branch-avoiding comparison fns + // - http://theorangeduck.com/page/avoiding-shader-conditionals + branchAvoidanceFunctions: [ + 'float when_gt(float x, float y) {', + ' return max(sign(x - y), 0.0);', + '}', + + 'float when_lt(float x, float y) {', + ' return min( max(1.0 - sign(x - y), 0.0), 1.0 );', + '}', + + 'float when_eq( float x, float y ) {', + ' return 1.0 - abs( sign( x - y ) );', + '}', + + 'float when_ge(float x, float y) {', + ' return 1.0 - when_lt(x, y);', + '}', + + 'float when_le(float x, float y) {', + ' return 1.0 - when_gt(x, y);', + '}', + + // Branch-avoiding logical operators + // (to be used with above comparison fns) + 'float and(float a, float b) {', + ' return a * b;', + '}', + + 'float or(float a, float b) {', + ' return min(a + b, 1.0);', + '}', + ].join( '\n' ), + + + // From: + // - http://stackoverflow.com/a/12553149 + // - https://stackoverflow.com/questions/22895237/hexadecimal-to-rgb-values-in-webgl-shader + unpackColor: [ + 'vec3 unpackColor( in float hex ) {', + ' vec3 c = vec3( 0.0 );', + + ' float r = mod( (hex / PACKED_COLOR_SIZE / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );', + ' float g = mod( (hex / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );', + ' float b = mod( hex, PACKED_COLOR_SIZE );', + + ' c.r = r / PACKED_COLOR_DIVISOR;', + ' c.g = g / PACKED_COLOR_DIVISOR;', + ' c.b = b / PACKED_COLOR_DIVISOR;', + + ' return c;', + '}', + ].join( '\n' ), + + unpackRotationAxis: [ + 'vec3 unpackRotationAxis( in float hex ) {', + ' vec3 c = vec3( 0.0 );', + + ' float r = mod( (hex / PACKED_COLOR_SIZE / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );', + ' float g = mod( (hex / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );', + ' float b = mod( hex, PACKED_COLOR_SIZE );', + + ' c.r = r / PACKED_COLOR_DIVISOR;', + ' c.g = g / PACKED_COLOR_DIVISOR;', + ' c.b = b / PACKED_COLOR_DIVISOR;', + + ' c *= vec3( 2.0 );', + ' c -= vec3( 1.0 );', + + ' return c;', + '}', + ].join( '\n' ), + + floatOverLifetime: [ + 'float getFloatOverLifetime( in float positionInTime, in vec4 attr ) {', + ' highp float value = 0.0;', + ' float deltaAge = positionInTime * float( VALUE_OVER_LIFETIME_LENGTH - 1 );', + ' float fIndex = 0.0;', + ' float shouldApplyValue = 0.0;', + + // This might look a little odd, but it's faster in the testing I've done than using branches. + // Uses basic maths to avoid branching. + // + // Take a look at the branch-avoidance functions defined above, + // and be sure to check out The Orange Duck site where I got this + // from (link above). + + // Fix for static emitters (age is always zero). + ' value += attr[ 0 ] * when_eq( deltaAge, 0.0 );', + '', + ' for( int i = 0; i < VALUE_OVER_LIFETIME_LENGTH - 1; ++i ) {', + ' fIndex = float( i );', + ' shouldApplyValue = and( when_gt( deltaAge, fIndex ), when_le( deltaAge, fIndex + 1.0 ) );', + ' value += shouldApplyValue * mix( attr[ i ], attr[ i + 1 ], deltaAge - fIndex );', + ' }', + '', + ' return value;', + '}', + ].join( '\n' ), + + colorOverLifetime: [ + 'vec3 getColorOverLifetime( in float positionInTime, in vec3 color1, in vec3 color2, in vec3 color3, in vec3 color4 ) {', + ' vec3 value = vec3( 0.0 );', + ' value.x = getFloatOverLifetime( positionInTime, vec4( color1.x, color2.x, color3.x, color4.x ) );', + ' value.y = getFloatOverLifetime( positionInTime, vec4( color1.y, color2.y, color3.y, color4.y ) );', + ' value.z = getFloatOverLifetime( positionInTime, vec4( color1.z, color2.z, color3.z, color4.z ) );', + ' return value;', + '}', + ].join( '\n' ), + + paramFetchingFunctions: [ + 'float getAlive() {', + ' return params.x;', + '}', + + 'float getAge() {', + ' return params.y;', + '}', + + 'float getMaxAge() {', + ' return params.z;', + '}', + + 'float getWiggle() {', + ' return params.w;', + '}', + ].join( '\n' ), + + forceFetchingFunctions: [ + 'vec4 getPosition( in float age ) {', + ' return modelViewMatrix * vec4( position, 1.0 );', + '}', + + 'vec3 getVelocity( in float age ) {', + ' return velocity * age;', + '}', + + 'vec3 getAcceleration( in float age ) {', + ' return acceleration.xyz * age;', + '}', + ].join( '\n' ), + + + rotationFunctions: [ + // Huge thanks to: + // - http://www.neilmendoza.com/glsl-rotation-about-an-arbitrary-axis/ + '#ifdef SHOULD_ROTATE_PARTICLES', + ' mat4 getRotationMatrix( in vec3 axis, in float angle) {', + ' axis = normalize(axis);', + ' float s = sin(angle);', + ' float c = cos(angle);', + ' float oc = 1.0 - c;', + '', + ' return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0,', + ' oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0,', + ' oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0,', + ' 0.0, 0.0, 0.0, 1.0);', + ' }', + '', + ' vec3 getRotation( in vec3 pos, in float positionInTime ) {', + ' if( rotation.y == 0.0 ) {', + ' return pos;', + ' }', + '', + ' vec3 axis = unpackRotationAxis( rotation.x );', + ' vec3 center = rotationCenter;', + ' vec3 translated;', + ' mat4 rotationMatrix;', + + ' float angle = 0.0;', + ' angle += when_eq( rotation.z, 0.0 ) * rotation.y;', + ' angle += when_gt( rotation.z, 0.0 ) * mix( 0.0, rotation.y, positionInTime );', + ' translated = rotationCenter - pos;', + ' rotationMatrix = getRotationMatrix( axis, angle );', + ' return center - vec3( rotationMatrix * vec4( translated, 0.0 ) );', + ' }', + '#endif' + ].join( '\n' ), + + + // Fragment chunks + rotateTexture: [ + ' vec2 vUv = vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y );', + '', + ' #ifdef SHOULD_ROTATE_TEXTURE', + ' float x = gl_PointCoord.x - 0.5;', + ' float y = 1.0 - gl_PointCoord.y - 0.5;', + ' float c = cos( -vAngle );', + ' float s = sin( -vAngle );', + + ' vUv = vec2( c * x + s * y + 0.5, c * y - s * x + 0.5 );', + ' #endif', + '', + + // Spritesheets overwrite angle calculations. + ' #ifdef SHOULD_CALCULATE_SPRITE', + ' float framesX = vSpriteSheet.x;', + ' float framesY = vSpriteSheet.y;', + ' float columnNorm = vSpriteSheet.z;', + ' float rowNorm = vSpriteSheet.w;', + + ' vUv.x = gl_PointCoord.x * framesX + columnNorm;', + ' vUv.y = 1.0 - (gl_PointCoord.y * framesY + rowNorm);', + ' #endif', + + '', + ' vec4 rotatedTexture = texture2D( texture, vUv );', + ].join( '\n' ) +}; + +SPE.shaders = { + vertex: [ + SPE.shaderChunks.defines, + SPE.shaderChunks.uniforms, + SPE.shaderChunks.attributes, + SPE.shaderChunks.varyings, + + THREE.ShaderChunk.common, + THREE.ShaderChunk.logdepthbuf_pars_vertex, + THREE.ShaderChunk.fog_pars_vertex, + + SPE.shaderChunks.branchAvoidanceFunctions, + SPE.shaderChunks.unpackColor, + SPE.shaderChunks.unpackRotationAxis, + SPE.shaderChunks.floatOverLifetime, + SPE.shaderChunks.colorOverLifetime, + SPE.shaderChunks.paramFetchingFunctions, + SPE.shaderChunks.forceFetchingFunctions, + SPE.shaderChunks.rotationFunctions, + + + 'void main() {', + + + // + // Setup... + // + ' highp float age = getAge();', + ' highp float alive = getAlive();', + ' highp float maxAge = getMaxAge();', + ' highp float positionInTime = (age / maxAge);', + ' highp float isAlive = when_gt( alive, 0.0 );', + + ' #ifdef SHOULD_WIGGLE_PARTICLES', + ' float wiggleAmount = positionInTime * getWiggle();', + ' float wiggleSin = isAlive * sin( wiggleAmount );', + ' float wiggleCos = isAlive * cos( wiggleAmount );', + ' #endif', + + // + // Forces + // + + // Get forces & position + ' vec3 vel = getVelocity( age );', + ' vec3 accel = getAcceleration( age );', + ' vec3 force = vec3( 0.0 );', + ' vec3 pos = vec3( position );', + + // Calculate the required drag to apply to the forces. + ' float drag = 1.0 - (positionInTime * 0.5) * acceleration.w;', + + // Integrate forces... + ' force += vel;', + ' force *= drag;', + ' force += accel * age;', + ' pos += force;', + + + // Wiggly wiggly wiggle! + ' #ifdef SHOULD_WIGGLE_PARTICLES', + ' pos.x += wiggleSin;', + ' pos.y += wiggleCos;', + ' pos.z += wiggleSin;', + ' #endif', + + + // Rotate the emitter around it's central point + ' #ifdef SHOULD_ROTATE_PARTICLES', + ' pos = getRotation( pos, positionInTime );', + ' #endif', + + // Convert pos to a world-space value + ' vec4 mvPosition = modelViewMatrix * vec4( pos, 1.0 );', + + // Determine point size. + ' highp float pointSize = getFloatOverLifetime( positionInTime, size ) * isAlive;', + + // Determine perspective + ' #ifdef HAS_PERSPECTIVE', + ' float perspective = scale / length( mvPosition.xyz );', + ' #else', + ' float perspective = 1.0;', + ' #endif', + + // Apply perpective to pointSize value + ' float pointSizePerspective = pointSize * perspective;', + + + // + // Appearance + // + + // Determine color and opacity for this particle + ' #ifdef COLORIZE', + ' vec3 c = isAlive * getColorOverLifetime(', + ' positionInTime,', + ' unpackColor( color.x ),', + ' unpackColor( color.y ),', + ' unpackColor( color.z ),', + ' unpackColor( color.w )', + ' );', + ' #else', + ' vec3 c = vec3(1.0);', + ' #endif', + + ' float o = isAlive * getFloatOverLifetime( positionInTime, opacity );', + + // Assign color to vColor varying. + ' vColor = vec4( c, o );', + + // Determine angle + ' #ifdef SHOULD_ROTATE_TEXTURE', + ' vAngle = isAlive * getFloatOverLifetime( positionInTime, angle );', + ' #endif', + + // If this particle is using a sprite-sheet as a texture, we'll have to figure out + // what frame of the texture the particle is using at it's current position in time. + ' #ifdef SHOULD_CALCULATE_SPRITE', + ' float framesX = textureAnimation.x;', + ' float framesY = textureAnimation.y;', + ' float loopCount = textureAnimation.w;', + ' float totalFrames = textureAnimation.z;', + ' float frameNumber = mod( (positionInTime * loopCount) * totalFrames, totalFrames );', + + ' float column = floor(mod( frameNumber, framesX ));', + ' float row = floor( (frameNumber - column) / framesX );', + + ' float columnNorm = column / framesX;', + ' float rowNorm = row / framesY;', + + ' vSpriteSheet.x = 1.0 / framesX;', + ' vSpriteSheet.y = 1.0 / framesY;', + ' vSpriteSheet.z = columnNorm;', + ' vSpriteSheet.w = rowNorm;', + ' #endif', + + // + // Write values + // + + // Set PointSize according to size at current point in time. + ' gl_PointSize = pointSizePerspective;', + ' gl_Position = projectionMatrix * mvPosition;', + + THREE.ShaderChunk.logdepthbuf_vertex, + THREE.ShaderChunk.fog_vertex, + + '}' + ].join( '\n' ), + + fragment: [ + SPE.shaderChunks.uniforms, + + THREE.ShaderChunk.common, + THREE.ShaderChunk.fog_pars_fragment, + THREE.ShaderChunk.logdepthbuf_pars_fragment, + + SPE.shaderChunks.varyings, + + SPE.shaderChunks.branchAvoidanceFunctions, + + 'void main() {', + ' vec3 outgoingLight = vColor.xyz;', + ' ', + ' #ifdef ALPHATEST', + ' if ( vColor.w < float(ALPHATEST) ) discard;', + ' #endif', + + SPE.shaderChunks.rotateTexture, + + THREE.ShaderChunk.logdepthbuf_fragment, + + ' outgoingLight = vColor.xyz * rotatedTexture.xyz;', + ' gl_FragColor = vec4( outgoingLight.xyz, rotatedTexture.w * vColor.w );', + + THREE.ShaderChunk.fog_fragment, + + '}' + ].join( '\n' ) +}; + + +/** + * A bunch of utility functions used throughout the library. + * @namespace + * @type {Object} + */ +SPE.utils = { + /** + * A map of types used by `SPE.utils.ensureTypedArg` and + * `SPE.utils.ensureArrayTypedArg` to compare types against. + * + * @enum {String} + */ + types: { + /** + * Boolean type. + * @type {String} + */ + BOOLEAN: 'boolean', + + /** + * String type. + * @type {String} + */ + STRING: 'string', + + /** + * Number type. + * @type {String} + */ + NUMBER: 'number', + + /** + * Object type. + * @type {String} + */ + OBJECT: 'object' + }, + + /** + * Given a value, a type, and a default value to fallback to, + * ensure the given argument adheres to the type requesting, + * returning the default value if type check is false. + * + * @param {(boolean|string|number|object)} arg The value to perform a type-check on. + * @param {String} type The type the `arg` argument should adhere to. + * @param {(boolean|string|number|object)} defaultValue A default value to fallback on if the type check fails. + * @return {(boolean|string|number|object)} The given value if type check passes, or the default value if it fails. + */ + ensureTypedArg: function( arg, type, defaultValue ) { + 'use strict'; + + if ( typeof arg === type ) { + return arg; + } + else { + return defaultValue; + } + }, + + /** + * Given an array of values, a type, and a default value, + * ensure the given array's contents ALL adhere to the provided type, + * returning the default value if type check fails. + * + * If the given value to check isn't an Array, delegates to SPE.utils.ensureTypedArg. + * + * @param {Array|boolean|string|number|object} arg The array of values to check type of. + * @param {String} type The type that should be adhered to. + * @param {(boolean|string|number|object)} defaultValue A default fallback value. + * @return {(boolean|string|number|object)} The given value if type check passes, or the default value if it fails. + */ + ensureArrayTypedArg: function( arg, type, defaultValue ) { + 'use strict'; + + // If the argument being checked is an array, loop through + // it and ensure all the values are of the correct type, + // falling back to the defaultValue if any aren't. + if ( Array.isArray( arg ) ) { + for ( var i = arg.length - 1; i >= 0; --i ) { + if ( typeof arg[ i ] !== type ) { + return defaultValue; + } + } + + return arg; + } + + // If the arg isn't an array then just fallback to + // checking the type. + return this.ensureTypedArg( arg, type, defaultValue ); + }, + + /** + * Ensures the given value is an instance of a constructor function. + * + * @param {Object} arg The value to check instance of. + * @param {Function} instance The constructor of the instance to check against. + * @param {Object} defaultValue A default fallback value if instance check fails + * @return {Object} The given value if type check passes, or the default value if it fails. + */ + ensureInstanceOf: function( arg, instance, defaultValue ) { + 'use strict'; + + if ( instance !== undefined && arg instanceof instance ) { + return arg; + } + else { + return defaultValue; + } + }, + + /** + * Given an array of values, ensure the instances of all items in the array + * matches the given instance constructor falling back to a default value if + * the check fails. + * + * If given value isn't an Array, delegates to `SPE.utils.ensureInstanceOf`. + * + * @param {Array|Object} arg The value to perform the instanceof check on. + * @param {Function} instance The constructor of the instance to check against. + * @param {Object} defaultValue A default fallback value if instance check fails + * @return {Object} The given value if type check passes, or the default value if it fails. + */ + ensureArrayInstanceOf: function( arg, instance, defaultValue ) { + 'use strict'; + + // If the argument being checked is an array, loop through + // it and ensure all the values are of the correct type, + // falling back to the defaultValue if any aren't. + if ( Array.isArray( arg ) ) { + for ( var i = arg.length - 1; i >= 0; --i ) { + if ( instance !== undefined && arg[ i ] instanceof instance === false ) { + return defaultValue; + } + } + + return arg; + } + + // If the arg isn't an array then just fallback to + // checking the type. + return this.ensureInstanceOf( arg, instance, defaultValue ); + }, + + /** + * Ensures that any "value-over-lifetime" properties of an emitter are + * of the correct length (as dictated by `SPE.valueOverLifetimeLength`). + * + * Delegates to `SPE.utils.interpolateArray` for array resizing. + * + * If properties aren't arrays, then property values are put into one. + * + * @param {Object} property The property of an SPE.Emitter instance to check compliance of. + * @param {Number} minLength The minimum length of the array to create. + * @param {Number} maxLength The maximum length of the array to create. + */ + ensureValueOverLifetimeCompliance: function( property, minLength, maxLength ) { + 'use strict'; + + minLength = minLength || 3; + maxLength = maxLength || 3; + + // First, ensure both properties are arrays. + if ( Array.isArray( property._value ) === false ) { + property._value = [ property._value ]; + } + + if ( Array.isArray( property._spread ) === false ) { + property._spread = [ property._spread ]; + } + + var valueLength = this.clamp( property._value.length, minLength, maxLength ), + spreadLength = this.clamp( property._spread.length, minLength, maxLength ), + desiredLength = Math.max( valueLength, spreadLength ); + + if ( property._value.length !== desiredLength ) { + property._value = this.interpolateArray( property._value, desiredLength ); + } + + if ( property._spread.length !== desiredLength ) { + property._spread = this.interpolateArray( property._spread, desiredLength ); + } + }, + + /** + * Performs linear interpolation (lerp) on an array. + * + * For example, lerping [1, 10], with a `newLength` of 10 will produce [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]. + * + * Delegates to `SPE.utils.lerpTypeAgnostic` to perform the actual + * interpolation. + * + * @param {Array} srcArray The array to lerp. + * @param {Number} newLength The length the array should be interpolated to. + * @return {Array} The interpolated array. + */ + interpolateArray: function( srcArray, newLength ) { + 'use strict'; + + var sourceLength = srcArray.length, + newArray = [ typeof srcArray[ 0 ].clone === 'function' ? srcArray[ 0 ].clone() : srcArray[ 0 ] ], + factor = ( sourceLength - 1 ) / ( newLength - 1 ); + + + for ( var i = 1; i < newLength - 1; ++i ) { + var f = i * factor, + before = Math.floor( f ), + after = Math.ceil( f ), + delta = f - before; + + newArray[ i ] = this.lerpTypeAgnostic( srcArray[ before ], srcArray[ after ], delta ); + } + + newArray.push( + typeof srcArray[ sourceLength - 1 ].clone === 'function' ? + srcArray[ sourceLength - 1 ].clone() : + srcArray[ sourceLength - 1 ] + ); + + return newArray; + }, + + /** + * Clamp a number to between the given min and max values. + * @param {Number} value The number to clamp. + * @param {Number} min The minimum value. + * @param {Number} max The maximum value. + * @return {Number} The clamped number. + */ + clamp: function( value, min, max ) { + 'use strict'; + + return Math.max( min, Math.min( value, max ) ); + }, + + /** + * If the given value is less than the epsilon value, then return + * a randomised epsilon value if specified, or just the epsilon value if not. + * Works for negative numbers as well as positive. + * + * @param {Number} value The value to perform the operation on. + * @param {Boolean} randomise Whether the value should be randomised. + * @return {Number} The result of the operation. + */ + zeroToEpsilon: function( value, randomise ) { + 'use strict'; + + var epsilon = 0.00001, + result = value; + + result = randomise ? Math.random() * epsilon * 10 : epsilon; + + if ( value < 0 && value > -epsilon ) { + result = -result; + } + + // if ( value === 0 ) { + // result = randomise ? Math.random() * epsilon * 10 : epsilon; + // } + // else if ( value > 0 && value < epsilon ) { + // result = randomise ? Math.random() * epsilon * 10 : epsilon; + // } + // else if ( value < 0 && value > -epsilon ) { + // result = -( randomise ? Math.random() * epsilon * 10 : epsilon ); + // } + + return result; + }, + + /** + * Linearly interpolates two values of various types. The given values + * must be of the same type for the interpolation to work. + * @param {(number|Object)} start The start value of the lerp. + * @param {(number|object)} end The end value of the lerp. + * @param {Number} delta The delta posiiton of the lerp operation. Ideally between 0 and 1 (inclusive). + * @return {(number|object|undefined)} The result of the operation. Result will be undefined if + * the start and end arguments aren't a supported type, or + * if their types do not match. + */ + lerpTypeAgnostic: function( start, end, delta ) { + 'use strict'; + + var types = this.types, + out; + + if ( typeof start === types.NUMBER && typeof end === types.NUMBER ) { + return start + ( ( end - start ) * delta ); + } + else if ( start instanceof THREE.Vector2 && end instanceof THREE.Vector2 ) { + out = start.clone(); + out.x = this.lerp( start.x, end.x, delta ); + out.y = this.lerp( start.y, end.y, delta ); + return out; + } + else if ( start instanceof THREE.Vector3 && end instanceof THREE.Vector3 ) { + out = start.clone(); + out.x = this.lerp( start.x, end.x, delta ); + out.y = this.lerp( start.y, end.y, delta ); + out.z = this.lerp( start.z, end.z, delta ); + return out; + } + else if ( start instanceof THREE.Vector4 && end instanceof THREE.Vector4 ) { + out = start.clone(); + out.x = this.lerp( start.x, end.x, delta ); + out.y = this.lerp( start.y, end.y, delta ); + out.z = this.lerp( start.z, end.z, delta ); + out.w = this.lerp( start.w, end.w, delta ); + return out; + } + else if ( start instanceof THREE.Color && end instanceof THREE.Color ) { + out = start.clone(); + out.r = this.lerp( start.r, end.r, delta ); + out.g = this.lerp( start.g, end.g, delta ); + out.b = this.lerp( start.b, end.b, delta ); + return out; + } + else { + console.warn( 'Invalid argument types, or argument types do not match:', start, end ); + } + }, + + /** + * Perform a linear interpolation operation on two numbers. + * @param {Number} start The start value. + * @param {Number} end The end value. + * @param {Number} delta The position to interpolate to. + * @return {Number} The result of the lerp operation. + */ + lerp: function( start, end, delta ) { + 'use strict'; + return start + ( ( end - start ) * delta ); + }, + + /** + * Rounds a number to a nearest multiple. + * + * @param {Number} n The number to round. + * @param {Number} multiple The multiple to round to. + * @return {Number} The result of the round operation. + */ + roundToNearestMultiple: function( n, multiple ) { + 'use strict'; + + var remainder = 0; + + if ( multiple === 0 ) { + return n; + } + + remainder = Math.abs( n ) % multiple; + + if ( remainder === 0 ) { + return n; + } + + if ( n < 0 ) { + return -( Math.abs( n ) - remainder ); + } + + return n + multiple - remainder; + }, + + /** + * Check if all items in an array are equal. Uses strict equality. + * + * @param {Array} array The array of values to check equality of. + * @return {Boolean} Whether the array's values are all equal or not. + */ + arrayValuesAreEqual: function( array ) { + 'use strict'; + + for ( var i = 0; i < array.length - 1; ++i ) { + if ( array[ i ] !== array[ i + 1 ] ) { + return false; + } + } + + return true; + }, + + // colorsAreEqual: function() { + // var colors = Array.prototype.slice.call( arguments ), + // numColors = colors.length; + + // for ( var i = 0, color1, color2; i < numColors - 1; ++i ) { + // color1 = colors[ i ]; + // color2 = colors[ i + 1 ]; + + // if ( + // color1.r !== color2.r || + // color1.g !== color2.g || + // color1.b !== color2.b + // ) { + // return false + // } + // } + + // return true; + // }, + + + /** + * Given a start value and a spread value, create and return a random + * number. + * @param {Number} base The start value. + * @param {Number} spread The size of the random variance to apply. + * @return {Number} A randomised number. + */ + randomFloat: function( base, spread ) { + 'use strict'; + return base + spread * ( Math.random() - 0.5 ); + }, + + + + /** + * Given an SPE.ShaderAttribute instance, and various other settings, + * assign values to the attribute's array in a `vec3` format. + * + * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to. + * @param {Number} index The offset in the attribute's TypedArray to save the result from. + * @param {Object} base THREE.Vector3 instance describing the start value. + * @param {Object} spread THREE.Vector3 instance describing the random variance to apply to the start value. + * @param {Object} spreadClamp THREE.Vector3 instance describing the multiples to clamp the randomness to. + */ + randomVector3: function( attribute, index, base, spread, spreadClamp ) { + 'use strict'; + + var x = base.x + ( Math.random() * spread.x - ( spread.x * 0.5 ) ), + y = base.y + ( Math.random() * spread.y - ( spread.y * 0.5 ) ), + z = base.z + ( Math.random() * spread.z - ( spread.z * 0.5 ) ); + + // var x = this.randomFloat( base.x, spread.x ), + // y = this.randomFloat( base.y, spread.y ), + // z = this.randomFloat( base.z, spread.z ); + + if ( spreadClamp ) { + x = -spreadClamp.x * 0.5 + this.roundToNearestMultiple( x, spreadClamp.x ); + y = -spreadClamp.y * 0.5 + this.roundToNearestMultiple( y, spreadClamp.y ); + z = -spreadClamp.z * 0.5 + this.roundToNearestMultiple( z, spreadClamp.z ); + } + + attribute.typedArray.setVec3Components( index, x, y, z ); + }, + + /** + * Given an SPE.Shader attribute instance, and various other settings, + * assign Color values to the attribute. + * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to. + * @param {Number} index The offset in the attribute's TypedArray to save the result from. + * @param {Object} base THREE.Color instance describing the start color. + * @param {Object} spread THREE.Vector3 instance describing the random variance to apply to the start color. + */ + randomColor: function( attribute, index, base, spread ) { + 'use strict'; + + var r = base.r + ( Math.random() * spread.x ), + g = base.g + ( Math.random() * spread.y ), + b = base.b + ( Math.random() * spread.z ); + + r = this.clamp( r, 0, 1 ); + g = this.clamp( g, 0, 1 ); + b = this.clamp( b, 0, 1 ); + + + attribute.typedArray.setVec3Components( index, r, g, b ); + }, + + + randomColorAsHex: ( function() { + 'use strict'; + + var workingColor = new THREE.Color(); + + /** + * Assigns a random color value, encoded as a hex value in decimal + * format, to a SPE.ShaderAttribute instance. + * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to. + * @param {Number} index The offset in the attribute's TypedArray to save the result from. + * @param {Object} base THREE.Color instance describing the start color. + * @param {Object} spread THREE.Vector3 instance describing the random variance to apply to the start color. + */ + return function( attribute, index, base, spread ) { + var numItems = base.length, + colors = []; + + for ( var i = 0; i < numItems; ++i ) { + var spreadVector = spread[ i ]; + + workingColor.copy( base[ i ] ); + + workingColor.r += ( Math.random() * spreadVector.x ) - ( spreadVector.x * 0.5 ); + workingColor.g += ( Math.random() * spreadVector.y ) - ( spreadVector.y * 0.5 ); + workingColor.b += ( Math.random() * spreadVector.z ) - ( spreadVector.z * 0.5 ); + + workingColor.r = this.clamp( workingColor.r, 0, 1 ); + workingColor.g = this.clamp( workingColor.g, 0, 1 ); + workingColor.b = this.clamp( workingColor.b, 0, 1 ); + + colors.push( workingColor.getHex() ); + } + + attribute.typedArray.setVec4Components( index, colors[ 0 ], colors[ 1 ], colors[ 2 ], colors[ 3 ] ); + }; + }() ), + + /** + * Assigns a random vector 3 value to an SPE.ShaderAttribute instance, projecting the + * given values onto a sphere. + * + * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to. + * @param {Number} index The offset in the attribute's TypedArray to save the result from. + * @param {Object} base THREE.Vector3 instance describing the origin of the transform. + * @param {Number} radius The radius of the sphere to project onto. + * @param {Number} radiusSpread The amount of randomness to apply to the projection result + * @param {Object} radiusScale THREE.Vector3 instance describing the scale of each axis of the sphere. + * @param {Number} radiusSpreadClamp What numeric multiple the projected value should be clamped to. + */ + randomVector3OnSphere: function( + attribute, index, base, radius, radiusSpread, radiusScale, radiusSpreadClamp, distributionClamp + ) { + 'use strict'; + + var depth = 2 * Math.random() - 1, + t = 6.2832 * Math.random(), + r = Math.sqrt( 1 - depth * depth ), + rand = this.randomFloat( radius, radiusSpread ), + x = 0, + y = 0, + z = 0; + + + if ( radiusSpreadClamp ) { + rand = Math.round( rand / radiusSpreadClamp ) * radiusSpreadClamp; + } + + + + // Set position on sphere + x = r * Math.cos( t ) * rand; + y = r * Math.sin( t ) * rand; + z = depth * rand; + + // Apply radius scale to this position + x *= radiusScale.x; + y *= radiusScale.y; + z *= radiusScale.z; + + // Translate to the base position. + x += base.x; + y += base.y; + z += base.z; + + // Set the values in the typed array. + attribute.typedArray.setVec3Components( index, x, y, z ); + }, + + seededRandom: function( seed ) { + var x = Math.sin( seed ) * 10000; + return x - ( x | 0 ); + }, + + + + /** + * Assigns a random vector 3 value to an SPE.ShaderAttribute instance, projecting the + * given values onto a 2d-disc. + * + * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to. + * @param {Number} index The offset in the attribute's TypedArray to save the result from. + * @param {Object} base THREE.Vector3 instance describing the origin of the transform. + * @param {Number} radius The radius of the sphere to project onto. + * @param {Number} radiusSpread The amount of randomness to apply to the projection result + * @param {Object} radiusScale THREE.Vector3 instance describing the scale of each axis of the disc. The z-component is ignored. + * @param {Number} radiusSpreadClamp What numeric multiple the projected value should be clamped to. + */ + randomVector3OnDisc: function( attribute, index, base, radius, radiusSpread, radiusScale, radiusSpreadClamp ) { + 'use strict'; + + var t = 6.2832 * Math.random(), + rand = Math.abs( this.randomFloat( radius, radiusSpread ) ), + x = 0, + y = 0, + z = 0; + + if ( radiusSpreadClamp ) { + rand = Math.round( rand / radiusSpreadClamp ) * radiusSpreadClamp; + } + + // Set position on sphere + x = Math.cos( t ) * rand; + y = Math.sin( t ) * rand; + + // Apply radius scale to this position + x *= radiusScale.x; + y *= radiusScale.y; + + // Translate to the base position. + x += base.x; + y += base.y; + z += base.z; + + // Set the values in the typed array. + attribute.typedArray.setVec3Components( index, x, y, z ); + }, + + randomDirectionVector3OnSphere: ( function() { + 'use strict'; + + var v = new THREE.Vector3(); + + /** + * Given an SPE.ShaderAttribute instance, create a direction vector from the given + * position, using `speed` as the magnitude. Values are saved to the attribute. + * + * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to. + * @param {Number} index The offset in the attribute's TypedArray to save the result from. + * @param {Number} posX The particle's x coordinate. + * @param {Number} posY The particle's y coordinate. + * @param {Number} posZ The particle's z coordinate. + * @param {Object} emitterPosition THREE.Vector3 instance describing the emitter's base position. + * @param {Number} speed The magnitude to apply to the vector. + * @param {Number} speedSpread The amount of randomness to apply to the magnitude. + */ + return function( attribute, index, posX, posY, posZ, emitterPosition, speed, speedSpread ) { + v.copy( emitterPosition ); + + v.x -= posX; + v.y -= posY; + v.z -= posZ; + + v.normalize().multiplyScalar( -this.randomFloat( speed, speedSpread ) ); + + attribute.typedArray.setVec3Components( index, v.x, v.y, v.z ); + }; + }() ), + + + randomDirectionVector3OnDisc: ( function() { + 'use strict'; + + var v = new THREE.Vector3(); + + /** + * Given an SPE.ShaderAttribute instance, create a direction vector from the given + * position, using `speed` as the magnitude. Values are saved to the attribute. + * + * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to. + * @param {Number} index The offset in the attribute's TypedArray to save the result from. + * @param {Number} posX The particle's x coordinate. + * @param {Number} posY The particle's y coordinate. + * @param {Number} posZ The particle's z coordinate. + * @param {Object} emitterPosition THREE.Vector3 instance describing the emitter's base position. + * @param {Number} speed The magnitude to apply to the vector. + * @param {Number} speedSpread The amount of randomness to apply to the magnitude. + */ + return function( attribute, index, posX, posY, posZ, emitterPosition, speed, speedSpread ) { + v.copy( emitterPosition ); + + v.x -= posX; + v.y -= posY; + v.z -= posZ; + + v.normalize().multiplyScalar( -this.randomFloat( speed, speedSpread ) ); + + attribute.typedArray.setVec3Components( index, v.x, v.y, 0 ); + }; + }() ), + + getPackedRotationAxis: ( function() { + 'use strict'; + + var v = new THREE.Vector3(), + vSpread = new THREE.Vector3(), + c = new THREE.Color(), + addOne = new THREE.Vector3( 1, 1, 1 ); + + /** + * Given a rotation axis, and a rotation axis spread vector, + * calculate a randomised rotation axis, and pack it into + * a hexadecimal value represented in decimal form. + * @param {Object} axis THREE.Vector3 instance describing the rotation axis. + * @param {Object} axisSpread THREE.Vector3 instance describing the amount of randomness to apply to the rotation axis. + * @return {Number} The packed rotation axis, with randomness. + */ + return function( axis, axisSpread ) { + v.copy( axis ).normalize(); + vSpread.copy( axisSpread ).normalize(); + + v.x += ( -axisSpread.x * 0.5 ) + ( Math.random() * axisSpread.x ); + v.y += ( -axisSpread.y * 0.5 ) + ( Math.random() * axisSpread.y ); + v.z += ( -axisSpread.z * 0.5 ) + ( Math.random() * axisSpread.z ); + + // v.x = Math.abs( v.x ); + // v.y = Math.abs( v.y ); + // v.z = Math.abs( v.z ); + + v.normalize().add( addOne ).multiplyScalar( 0.5 ); + + c.setRGB( v.x, v.y, v.z ); + + return c.getHex(); + }; + }() ) +}; + +/** + * An SPE.Group instance. + * @typedef {Object} Group + * @see SPE.Group + */ + +/** + * A map of options to configure an SPE.Group instance. + * @typedef {Object} GroupOptions + * + * @property {Object} texture An object describing the texture used by the group. + * + * @property {Object} texture.value An instance of THREE.Texture. + * + * @property {Object=} texture.frames A THREE.Vector2 instance describing the number + * of frames on the x- and y-axis of the given texture. + * If not provided, the texture will NOT be treated as + * a sprite-sheet and as such will NOT be animated. + * + * @property {Number} [texture.frameCount=texture.frames.x * texture.frames.y] The total number of frames in the sprite-sheet. + * Allows for sprite-sheets that don't fill the entire + * texture. + * + * @property {Number} texture.loop The number of loops through the sprite-sheet that should + * be performed over the course of a single particle's lifetime. + * + * @property {Number} fixedTimeStep If no `dt` (or `deltaTime`) value is passed to this group's + * `tick()` function, this number will be used to move the particle + * simulation forward. Value in SECONDS. + * + * @property {Boolean} hasPerspective Whether the distance a particle is from the camera should affect + * the particle's size. + * + * @property {Boolean} colorize Whether the particles in this group should be rendered with color, or + * whether the only color of particles will come from the provided texture. + * + * @property {Number} blending One of Three.js's blending modes to apply to this group's `ShaderMaterial`. + * + * @property {Boolean} transparent Whether these particle's should be rendered with transparency. + * + * @property {Number} alphaTest Sets the alpha value to be used when running an alpha test on the `texture.value` property. Value between 0 and 1. + * + * @property {Boolean} depthWrite Whether rendering the group has any effect on the depth buffer. + * + * @property {Boolean} depthTest Whether to have depth test enabled when rendering this group. + * + * @property {Boolean} fog Whether this group's particles should be affected by their scene's fog. + * + * @property {Number} scale The scale factor to apply to this group's particle sizes. Useful for + * setting particle sizes to be relative to renderer size. + */ + + +/** + * The SPE.Group class. Creates a new group, containing a material, geometry, and mesh. + * + * @constructor + * @param {GroupOptions} options A map of options to configure the group instance. + */ +SPE.Group = function( options ) { + 'use strict'; + + var utils = SPE.utils, + types = utils.types; + + // Ensure we have a map of options to play with + options = utils.ensureTypedArg( options, types.OBJECT, {} ); + options.texture = utils.ensureTypedArg( options.texture, types.OBJECT, {} ); + + // Assign a UUID to this instance + this.uuid = THREE.Math.generateUUID(); + + // If no `deltaTime` value is passed to the `SPE.Group.tick` function, + // the value of this property will be used to advance the simulation. + this.fixedTimeStep = utils.ensureTypedArg( options.fixedTimeStep, types.NUMBER, 0.016 ); + + // Set properties used in the uniforms map, starting with the + // texture stuff. + this.texture = utils.ensureInstanceOf( options.texture.value, THREE.Texture, null ); + this.textureFrames = utils.ensureInstanceOf( options.texture.frames, THREE.Vector2, new THREE.Vector2( 1, 1 ) ); + this.textureFrameCount = utils.ensureTypedArg( options.texture.frameCount, types.NUMBER, this.textureFrames.x * this.textureFrames.y ); + this.textureLoop = utils.ensureTypedArg( options.texture.loop, types.NUMBER, 1 ); + this.textureFrames.max( new THREE.Vector2( 1, 1 ) ); + + this.hasPerspective = utils.ensureTypedArg( options.hasPerspective, types.BOOLEAN, true ); + this.colorize = utils.ensureTypedArg( options.colorize, types.BOOLEAN, true ); + + this.maxParticleCount = utils.ensureTypedArg( options.maxParticleCount, types.NUMBER, null ); + + + // Set properties used to define the ShaderMaterial's appearance. + this.blending = utils.ensureTypedArg( options.blending, types.NUMBER, THREE.AdditiveBlending ); + this.transparent = utils.ensureTypedArg( options.transparent, types.BOOLEAN, true ); + this.alphaTest = parseFloat( utils.ensureTypedArg( options.alphaTest, types.NUMBER, 0.0 ) ); + this.depthWrite = utils.ensureTypedArg( options.depthWrite, types.BOOLEAN, false ); + this.depthTest = utils.ensureTypedArg( options.depthTest, types.BOOLEAN, true ); + this.fog = utils.ensureTypedArg( options.fog, types.BOOLEAN, true ); + this.scale = utils.ensureTypedArg( options.scale, types.NUMBER, 300 ); + + // Where emitter's go to curl up in a warm blanket and live + // out their days. + this.emitters = []; + this.emitterIDs = []; + + // Create properties for use by the emitter pooling functions. + this._pool = []; + this._poolCreationSettings = null; + this._createNewWhenPoolEmpty = 0; + + // Whether all attributes should be forced to updated + // their entire buffer contents on the next tick. + // + // Used when an emitter is removed. + this._attributesNeedRefresh = false; + this._attributesNeedDynamicReset = false; + + this.particleCount = 0; + + + // Map of uniforms to be applied to the ShaderMaterial instance. + this.uniforms = { + texture: { + type: 't', + value: this.texture + }, + textureAnimation: { + type: 'v4', + value: new THREE.Vector4( + this.textureFrames.x, + this.textureFrames.y, + this.textureFrameCount, + Math.max( Math.abs( this.textureLoop ), 1.0 ) + ) + }, + fogColor: { + type: 'c', + value: null + }, + fogNear: { + type: 'f', + value: 10 + }, + fogFar: { + type: 'f', + value: 200 + }, + fogDensity: { + type: 'f', + value: 0.5 + }, + deltaTime: { + type: 'f', + value: 0 + }, + runTime: { + type: 'f', + value: 0 + }, + scale: { + type: 'f', + value: this.scale + } + }; + + // Add some defines into the mix... + this.defines = { + HAS_PERSPECTIVE: this.hasPerspective, + COLORIZE: this.colorize, + VALUE_OVER_LIFETIME_LENGTH: SPE.valueOverLifetimeLength, + + SHOULD_ROTATE_TEXTURE: false, + SHOULD_ROTATE_PARTICLES: false, + SHOULD_WIGGLE_PARTICLES: false, + + SHOULD_CALCULATE_SPRITE: this.textureFrames.x > 1 || this.textureFrames.y > 1 + }; + + // Map of all attributes to be applied to the particles. + // + // See SPE.ShaderAttribute for a bit more info on this bit. + this.attributes = { + position: new SPE.ShaderAttribute( 'v3', true ), + acceleration: new SPE.ShaderAttribute( 'v4', true ), // w component is drag + velocity: new SPE.ShaderAttribute( 'v3', true ), + rotation: new SPE.ShaderAttribute( 'v4', true ), + rotationCenter: new SPE.ShaderAttribute( 'v3', true ), + params: new SPE.ShaderAttribute( 'v4', true ), // Holds (alive, age, delay, wiggle) + size: new SPE.ShaderAttribute( 'v4', true ), + angle: new SPE.ShaderAttribute( 'v4', true ), + color: new SPE.ShaderAttribute( 'v4', true ), + opacity: new SPE.ShaderAttribute( 'v4', true ) + }; + + this.attributeKeys = Object.keys( this.attributes ); + this.attributeCount = this.attributeKeys.length; + + // Create the ShaderMaterial instance that'll help render the + // particles. + this.material = new THREE.ShaderMaterial( { + uniforms: this.uniforms, + vertexShader: SPE.shaders.vertex, + fragmentShader: SPE.shaders.fragment, + blending: this.blending, + transparent: this.transparent, + alphaTest: this.alphaTest, + depthWrite: this.depthWrite, + depthTest: this.depthTest, + defines: this.defines, + fog: this.fog + } ); + + // Create the BufferGeometry and Points instances, ensuring + // the geometry and material are given to the latter. + this.geometry = new THREE.BufferGeometry(); + this.mesh = new THREE.Points( this.geometry, this.material ); + + if ( this.maxParticleCount === null ) { + console.warn( 'SPE.Group: No maxParticleCount specified. Adding emitters after rendering will probably cause errors.' ); + } +}; + +SPE.Group.constructor = SPE.Group; + + +SPE.Group.prototype._updateDefines = function() { + 'use strict'; + + var emitters = this.emitters, + i = emitters.length - 1, + emitter, + defines = this.defines; + + for ( i; i >= 0; --i ) { + emitter = emitters[ i ]; + + // Only do angle calculation if there's no spritesheet defined. + // + // Saves calculations being done and then overwritten in the shaders. + if ( !defines.SHOULD_CALCULATE_SPRITE ) { + defines.SHOULD_ROTATE_TEXTURE = defines.SHOULD_ROTATE_TEXTURE || !!Math.max( + Math.max.apply( null, emitter.angle.value ), + Math.max.apply( null, emitter.angle.spread ) + ); + } + + defines.SHOULD_ROTATE_PARTICLES = defines.SHOULD_ROTATE_PARTICLES || !!Math.max( + emitter.rotation.angle, + emitter.rotation.angleSpread + ); + + defines.SHOULD_WIGGLE_PARTICLES = defines.SHOULD_WIGGLE_PARTICLES || !!Math.max( + emitter.wiggle.value, + emitter.wiggle.spread + ); + } + + this.material.needsUpdate = true; +}; + +SPE.Group.prototype._applyAttributesToGeometry = function() { + 'use strict'; + + var attributes = this.attributes, + geometry = this.geometry, + geometryAttributes = geometry.attributes, + attribute, + geometryAttribute; + + // Loop through all the shader attributes and assign (or re-assign) + // typed array buffers to each one. + for ( var attr in attributes ) { + if ( attributes.hasOwnProperty( attr ) ) { + attribute = attributes[ attr ]; + geometryAttribute = geometryAttributes[ attr ]; + + // Update the array if this attribute exists on the geometry. + // + // This needs to be done because the attribute's typed array might have + // been resized and reinstantiated, and might now be looking at a + // different ArrayBuffer, so reference needs updating. + if ( geometryAttribute ) { + geometryAttribute.array = attribute.typedArray.array; + } + + // // Add the attribute to the geometry if it doesn't already exist. + else { + geometry.addAttribute( attr, attribute.bufferAttribute ); + } + + // Mark the attribute as needing an update the next time a frame is rendered. + attribute.bufferAttribute.needsUpdate = true; + } + } + + // Mark the draw range on the geometry. This will ensure + // only the values in the attribute buffers that are + // associated with a particle will be used in THREE's + // render cycle. + this.geometry.setDrawRange( 0, this.particleCount ); +}; + +/** + * Adds an SPE.Emitter instance to this group, creating particle values and + * assigning them to this group's shader attributes. + * + * @param {Emitter} emitter The emitter to add to this group. + */ +SPE.Group.prototype.addEmitter = function( emitter ) { + 'use strict'; + + // Ensure an actual emitter instance is passed here. + // + // Decided not to throw here, just in case a scene's + // rendering would be paused. Logging an error instead + // of stopping execution if exceptions aren't caught. + if ( emitter instanceof SPE.Emitter === false ) { + console.error( '`emitter` argument must be instance of SPE.Emitter. Was provided with:', emitter ); + return; + } + + // If the emitter already exists as a member of this group, then + // stop here, we don't want to add it again. + else if ( this.emitterIDs.indexOf( emitter.uuid ) > -1 ) { + console.error( 'Emitter already exists in this group. Will not add again.' ); + return; + } + + // And finally, if the emitter is a member of another group, + // don't add it to this group. + else if ( emitter.group !== null ) { + console.error( 'Emitter already belongs to another group. Will not add to requested group.' ); + return; + } + + var attributes = this.attributes, + start = this.particleCount, + end = start + emitter.particleCount; + + // Update this group's particle count. + this.particleCount = end; + + // Emit a warning if the emitter being added will exceed the buffer sizes specified. + if ( this.maxParticleCount !== null && this.particleCount > this.maxParticleCount ) { + console.warn( 'SPE.Group: maxParticleCount exceeded. Requesting', this.particleCount, 'particles, can support only', this.maxParticleCount ); + } + + + // Set the `particlesPerSecond` value (PPS) on the emitter. + // It's used to determine how many particles to release + // on a per-frame basis. + emitter._calculatePPSValue( emitter.maxAge._value + emitter.maxAge._spread ); + emitter._setBufferUpdateRanges( this.attributeKeys ); + + // Store the offset value in the TypedArray attributes for this emitter. + emitter._setAttributeOffset( start ); + + // Save a reference to this group on the emitter so it knows + // where it belongs. + emitter.group = this; + + // Store reference to the attributes on the emitter for + // easier access during the emitter's tick function. + emitter.attributes = this.attributes; + + + + // Ensure the attributes and their BufferAttributes exist, and their + // TypedArrays are of the correct size. + for ( var attr in attributes ) { + if ( attributes.hasOwnProperty( attr ) ) { + // When creating a buffer, pass through the maxParticle count + // if one is specified. + attributes[ attr ]._createBufferAttribute( + this.maxParticleCount !== null ? + this.maxParticleCount : + this.particleCount + ); + } + } + + // Loop through each particle this emitter wants to have, and create the attributes values, + // storing them in the TypedArrays that each attribute holds. + for ( var i = start; i < end; ++i ) { + emitter._assignPositionValue( i ); + emitter._assignForceValue( i, 'velocity' ); + emitter._assignForceValue( i, 'acceleration' ); + emitter._assignAbsLifetimeValue( i, 'opacity' ); + emitter._assignAbsLifetimeValue( i, 'size' ); + emitter._assignAngleValue( i ); + emitter._assignRotationValue( i ); + emitter._assignParamsValue( i ); + emitter._assignColorValue( i ); + } + + // Update the geometry and make sure the attributes are referencing + // the typed arrays properly. + this._applyAttributesToGeometry(); + + // Store this emitter in this group's emitter's store. + this.emitters.push( emitter ); + this.emitterIDs.push( emitter.uuid ); + + // Update certain flags to enable shader calculations only if they're necessary. + this._updateDefines( emitter ); + + // Update the material since defines might have changed + this.material.needsUpdate = true; + this.geometry.needsUpdate = true; + this._attributesNeedRefresh = true; + + // Return the group to enable chaining. + return this; +}; + +/** + * Removes an SPE.Emitter instance from this group. When called, + * all particle's belonging to the given emitter will be instantly + * removed from the scene. + * + * @param {Emitter} emitter The emitter to add to this group. + */ +SPE.Group.prototype.removeEmitter = function( emitter ) { + 'use strict'; + + var emitterIndex = this.emitterIDs.indexOf( emitter.uuid ); + + // Ensure an actual emitter instance is passed here. + // + // Decided not to throw here, just in case a scene's + // rendering would be paused. Logging an error instead + // of stopping execution if exceptions aren't caught. + if ( emitter instanceof SPE.Emitter === false ) { + console.error( '`emitter` argument must be instance of SPE.Emitter. Was provided with:', emitter ); + return; + } + + // Issue an error if the emitter isn't a member of this group. + else if ( emitterIndex === -1 ) { + console.error( 'Emitter does not exist in this group. Will not remove.' ); + return; + } + + // Kill all particles by marking them as dead + // and their age as 0. + var start = emitter.attributeOffset, + end = start + emitter.particleCount, + params = this.attributes.params.typedArray; + + // Set alive and age to zero. + for ( var i = start; i < end; ++i ) { + params.array[ i * 4 ] = 0.0; + params.array[ i * 4 + 1 ] = 0.0; + } + + // Remove the emitter from this group's "store". + this.emitters.splice( emitterIndex, 1 ); + this.emitterIDs.splice( emitterIndex, 1 ); + + // Remove this emitter's attribute values from all shader attributes. + // The `.splice()` call here also marks each attribute's buffer + // as needing to update it's entire contents. + for ( var attr in this.attributes ) { + if ( this.attributes.hasOwnProperty( attr ) ) { + this.attributes[ attr ].splice( start, end ); + } + } + + // Ensure this group's particle count is correct. + this.particleCount -= emitter.particleCount; + + // Call the emitter's remove method. + emitter._onRemove(); + + // Set a flag to indicate that the attribute buffers should + // be updated in their entirety on the next frame. + this._attributesNeedRefresh = true; +}; + + +/** + * Fetch a single emitter instance from the pool. + * If there are no objects in the pool, a new emitter will be + * created if specified. + * + * @return {Emitter|null} + */ +SPE.Group.prototype.getFromPool = function() { + 'use strict'; + + var pool = this._pool, + createNew = this._createNewWhenPoolEmpty; + + if ( pool.length ) { + return pool.pop(); + } + else if ( createNew ) { + var emitter = new SPE.Emitter( this._poolCreationSettings ); + + this.addEmitter( emitter ); + + return emitter; + } + + return null; +}; + + +/** + * Release an emitter into the pool. + * + * @param {ShaderParticleEmitter} emitter + * @return {Group} This group instance. + */ +SPE.Group.prototype.releaseIntoPool = function( emitter ) { + 'use strict'; + + if ( emitter instanceof SPE.Emitter === false ) { + console.error( 'Argument is not instanceof SPE.Emitter:', emitter ); + return; + } + + emitter.reset(); + this._pool.unshift( emitter ); + + return this; +}; + + +/** + * Get the pool array + * + * @return {Array} + */ +SPE.Group.prototype.getPool = function() { + 'use strict'; + return this._pool; +}; + + +/** + * Add a pool of emitters to this particle group + * + * @param {Number} numEmitters The number of emitters to add to the pool. + * @param {EmitterOptions|Array} emitterOptions An object, or array of objects, describing the options to pass to each emitter. + * @param {Boolean} createNew Should a new emitter be created if the pool runs out? + * @return {Group} This group instance. + */ +SPE.Group.prototype.addPool = function( numEmitters, emitterOptions, createNew ) { + 'use strict'; + + var emitter; + + // Save relevant settings and flags. + this._poolCreationSettings = emitterOptions; + this._createNewWhenPoolEmpty = !!createNew; + + // Create the emitters, add them to this group and the pool. + for ( var i = 0; i < numEmitters; ++i ) { + if ( Array.isArray( emitterOptions ) ) { + emitter = new SPE.Emitter( emitterOptions[ i ] ); + } + else { + emitter = new SPE.Emitter( emitterOptions ); + } + this.addEmitter( emitter ); + this.releaseIntoPool( emitter ); + } + + return this; +}; + + + +SPE.Group.prototype._triggerSingleEmitter = function( pos ) { + 'use strict'; + + var emitter = this.getFromPool(), + self = this; + + if ( emitter === null ) { + console.log( 'SPE.Group pool ran out.' ); + return; + } + + // TODO: + // - Make sure buffers are update with thus new position. + if ( pos instanceof THREE.Vector3 ) { + emitter.position.value.copy( pos ); + + // Trigger the setter for this property to force an + // update to the emitter's position attribute. + emitter.position.value = emitter.position.value; + } + + emitter.enable(); + + setTimeout( function() { + emitter.disable(); + self.releaseIntoPool( emitter ); + }, ( Math.max( emitter.duration, ( emitter.maxAge.value + emitter.maxAge.spread ) ) ) * 1000 ); + + return this; +}; + + +/** + * Set a given number of emitters as alive, with an optional position + * vector3 to move them to. + * + * @param {Number} numEmitters The number of emitters to activate + * @param {Object} [position=undefined] A THREE.Vector3 instance describing the position to activate the emitter(s) at. + * @return {Group} This group instance. + */ +SPE.Group.prototype.triggerPoolEmitter = function( numEmitters, position ) { + 'use strict'; + + if ( typeof numEmitters === 'number' && numEmitters > 1 ) { + for ( var i = 0; i < numEmitters; ++i ) { + this._triggerSingleEmitter( position ); + } + } + else { + this._triggerSingleEmitter( position ); + } + + return this; +}; + + + +SPE.Group.prototype._updateUniforms = function( dt ) { + 'use strict'; + + this.uniforms.runTime.value += dt; + this.uniforms.deltaTime.value = dt; +}; + +SPE.Group.prototype._resetBufferRanges = function() { + 'use strict'; + + var keys = this.attributeKeys, + i = this.attributeCount - 1, + attrs = this.attributes; + + for ( i; i >= 0; --i ) { + attrs[ keys[ i ] ].resetUpdateRange(); + } +}; + + +SPE.Group.prototype._updateBuffers = function( emitter ) { + 'use strict'; + + var keys = this.attributeKeys, + i = this.attributeCount - 1, + attrs = this.attributes, + emitterRanges = emitter.bufferUpdateRanges, + key, + emitterAttr, + attr; + + for ( i; i >= 0; --i ) { + key = keys[ i ]; + emitterAttr = emitterRanges[ key ]; + attr = attrs[ key ]; + attr.setUpdateRange( emitterAttr.min, emitterAttr.max ); + attr.flagUpdate(); + } +}; + + +/** + * Simulate all the emitter's belonging to this group, updating + * attribute values along the way. + * @param {Number} [dt=Group's `fixedTimeStep` value] The number of seconds to simulate the group's emitters for (deltaTime) + */ +SPE.Group.prototype.tick = function( dt ) { + 'use strict'; + + var emitters = this.emitters, + numEmitters = emitters.length, + deltaTime = dt || this.fixedTimeStep, + keys = this.attributeKeys, + i, + attrs = this.attributes; + + // Update uniform values. + this._updateUniforms( deltaTime ); + + // Reset buffer update ranges on the shader attributes. + this._resetBufferRanges(); + + + // If nothing needs updating, then stop here. + if ( + numEmitters === 0 && + this._attributesNeedRefresh === false && + this._attributesNeedDynamicReset === false + ) { + return; + } + + // Loop through each emitter in this group and + // simulate it, then update the shader attribute + // buffers. + for ( var i = 0, emitter; i < numEmitters; ++i ) { + emitter = emitters[ i ]; + emitter.tick( deltaTime ); + this._updateBuffers( emitter ); + } + + // If the shader attributes have been refreshed, + // then the dynamic properties of each buffer + // attribute will need to be reset back to + // what they should be. + if ( this._attributesNeedDynamicReset === true ) { + i = this.attributeCount - 1; + + for ( i; i >= 0; --i ) { + attrs[ keys[ i ] ].resetDynamic(); + } + + this._attributesNeedDynamicReset = false; + } + + // If this group's shader attributes need a full refresh + // then mark each attribute's buffer attribute as + // needing so. + if ( this._attributesNeedRefresh === true ) { + i = this.attributeCount - 1; + + for ( i; i >= 0; --i ) { + attrs[ keys[ i ] ].forceUpdateAll(); + } + + this._attributesNeedRefresh = false; + this._attributesNeedDynamicReset = true; + } +}; + + +/** + * Dipose the geometry and material for the group. + * + * @return {Group} Group instance. + */ +SPE.Group.prototype.dispose = function() { + 'use strict'; + this.geometry.dispose(); + this.material.dispose(); + return this; +}; + + +/** + * An SPE.Emitter instance. + * @typedef {Object} Emitter + * @see SPE.Emitter + */ + +/** + * A map of options to configure an SPE.Emitter instance. + * + * @typedef {Object} EmitterOptions + * + * @property {distribution} [type=BOX] The default distribution this emitter should use to control + * its particle's spawn position and force behaviour. + * Must be an SPE.distributions.* value. + * + * + * @property {Number} [particleCount=100] The total number of particles this emitter will hold. NOTE: this is not the number + * of particles emitted in a second, or anything like that. The number of particles + * emitted per-second is calculated by particleCount / maxAge (approximately!) + * + * @property {Number|null} [duration=null] The duration in seconds that this emitter should live for. If not specified, the emitter + * will emit particles indefinitely. + * NOTE: When an emitter is older than a specified duration, the emitter is NOT removed from + * it's group, but rather is just marked as dead, allowing it to be reanimated at a later time + * using `SPE.Emitter.prototype.enable()`. + * + * @property {Boolean} [isStatic=false] Whether this emitter should be not be simulated (true). + * @property {Boolean} [activeMultiplier=1] A value between 0 and 1 describing what percentage of this emitter's particlesPerSecond should be + * emitted, where 0 is 0%, and 1 is 100%. + * For example, having an emitter with 100 particles, a maxAge of 2, yields a particlesPerSecond + * value of 50. Setting `activeMultiplier` to 0.5, then, will only emit 25 particles per second (0.5 = 50%). + * Values greater than 1 will emulate a burst of particles, causing the emitter to run out of particles + * before it's next activation cycle. + * + * @property {Boolean} [direction=1] The direction of the emitter. If value is `1`, emitter will start at beginning of particle's lifecycle. + * If value is `-1`, emitter will start at end of particle's lifecycle and work it's way backwards. + * + * @property {Object} [maxAge={}] An object describing the particle's maximum age in seconds. + * @property {Number} [maxAge.value=2] A number between 0 and 1 describing the amount of maxAge to apply to all particles. + * @property {Number} [maxAge.spread=0] A number describing the maxAge variance on a per-particle basis. + * + * + * @property {Object} [position={}] An object describing this emitter's position. + * @property {Object} [position.value=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's base position. + * @property {Object} [position.spread=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's position variance on a per-particle basis. + * Note that when using a SPHERE or DISC distribution, only the x-component + * of this vector is used. + * @property {Object} [position.spreadClamp=new THREE.Vector3()] A THREE.Vector3 instance describing the numeric multiples the particle's should + * be spread out over. + * Note that when using a SPHERE or DISC distribution, only the x-component + * of this vector is used. + * @property {Number} [position.radius=10] This emitter's base radius. + * @property {Object} [position.radiusScale=new THREE.Vector3()] A THREE.Vector3 instance describing the radius's scale in all three axes. Allows a SPHERE or DISC to be squashed or stretched. + * @property {distribution} [position.distribution=value of the `type` option.] A specific distribution to use when radiusing particles. Overrides the `type` option. + * @property {Boolean} [position.randomise=false] When a particle is re-spawned, whether it's position should be re-randomised or not. Can incur a performance hit. + * + * + * @property {Object} [velocity={}] An object describing this particle velocity. + * @property {Object} [velocity.value=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's base velocity. + * @property {Object} [velocity.spread=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's velocity variance on a per-particle basis. + * Note that when using a SPHERE or DISC distribution, only the x-component + * of this vector is used. + * @property {distribution} [velocity.distribution=value of the `type` option.] A specific distribution to use when calculating a particle's velocity. Overrides the `type` option. + * @property {Boolean} [velocity.randomise=false] When a particle is re-spawned, whether it's velocity should be re-randomised or not. Can incur a performance hit. + * + * + * @property {Object} [acceleration={}] An object describing this particle's acceleration. + * @property {Object} [acceleration.value=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's base acceleration. + * @property {Object} [acceleration.spread=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's acceleration variance on a per-particle basis. + * Note that when using a SPHERE or DISC distribution, only the x-component + * of this vector is used. + * @property {distribution} [acceleration.distribution=value of the `type` option.] A specific distribution to use when calculating a particle's acceleration. Overrides the `type` option. + * @property {Boolean} [acceleration.randomise=false] When a particle is re-spawned, whether it's acceleration should be re-randomised or not. Can incur a performance hit. + * + * + * @property {Object} [drag={}] An object describing this particle drag. Drag is applied to both velocity and acceleration values. + * @property {Number} [drag.value=0] A number between 0 and 1 describing the amount of drag to apply to all particles. + * @property {Number} [drag.spread=0] A number describing the drag variance on a per-particle basis. + * @property {Boolean} [drag.randomise=false] When a particle is re-spawned, whether it's drag should be re-randomised or not. Can incur a performance hit. + * + * + * @property {Object} [wiggle={}] This is quite a fun one! The values of this object will determine whether a particle will wiggle, or jiggle, or wave, + * or shimmy, or waggle, or... Well you get the idea. The wiggle is calculated over-time, meaning that a particle will + * start off with no wiggle, and end up wiggling about with the distance of the `value` specified by the time it dies. + * It's quite handy to simulate fire embers, or similar effects where the particle's position should slightly change over + * time, and such change isn't easily controlled by rotation, velocity, or acceleration. The wiggle is a combination of sin and cos calculations, so is circular in nature. + * @property {Number} [wiggle.value=0] A number describing the amount of wiggle to apply to all particles. It's measured in distance. + * @property {Number} [wiggle.spread=0] A number describing the wiggle variance on a per-particle basis. + * + * + * @property {Object} [rotation={}] An object describing this emitter's rotation. It can either be static, or set to rotate from 0radians to the value of `rotation.value` + * over a particle's lifetime. Rotation values affect both a particle's position and the forces applied to it. + * @property {Object} [rotation.axis=new THREE.Vector3(0, 1, 0)] A THREE.Vector3 instance describing this emitter's axis of rotation. + * @property {Object} [rotation.axisSpread=new THREE.Vector3()] A THREE.Vector3 instance describing the amount of variance to apply to the axis of rotation on + * a per-particle basis. + * @property {Number} [rotation.angle=0] The angle of rotation, given in radians. If `rotation.static` is true, the emitter will start off rotated at this angle, and stay as such. + * Otherwise, the particles will rotate from 0radians to this value over their lifetimes. + * @property {Number} [rotation.angleSpread=0] The amount of variance in each particle's rotation angle. + * @property {Boolean} [rotation.static=false] Whether the rotation should be static or not. + * @property {Object} [rotation.center=The value of `position.value`] A THREE.Vector3 instance describing the center point of rotation. + * @property {Boolean} [rotation.randomise=false] When a particle is re-spawned, whether it's rotation should be re-randomised or not. Can incur a performance hit. + * + * + * @property {Object} [color={}] An object describing a particle's color. This property is a "value-over-lifetime" property, meaning an array of values and spreads can be + * given to describe specific value changes over a particle's lifetime. + * Depending on the value of SPE.valueOverLifetimeLength, if arrays of THREE.Color instances are given, then the array will be interpolated to + * have a length matching the value of SPE.valueOverLifetimeLength. + * @property {Object} [color.value=new THREE.Color()] Either a single THREE.Color instance, or an array of THREE.Color instances to describe the color of a particle over it's lifetime. + * @property {Object} [color.spread=new THREE.Vector3()] Either a single THREE.Vector3 instance, or an array of THREE.Vector3 instances to describe the color variance of a particle over it's lifetime. + * @property {Boolean} [color.randomise=false] When a particle is re-spawned, whether it's color should be re-randomised or not. Can incur a performance hit. + * + * + * @property {Object} [opacity={}] An object describing a particle's opacity. This property is a "value-over-lifetime" property, meaning an array of values and spreads can be + * given to describe specific value changes over a particle's lifetime. + * Depending on the value of SPE.valueOverLifetimeLength, if arrays of numbers are given, then the array will be interpolated to + * have a length matching the value of SPE.valueOverLifetimeLength. + * @property {Number} [opacity.value=1] Either a single number, or an array of numbers to describe the opacity of a particle over it's lifetime. + * @property {Number} [opacity.spread=0] Either a single number, or an array of numbers to describe the opacity variance of a particle over it's lifetime. + * @property {Boolean} [opacity.randomise=false] When a particle is re-spawned, whether it's opacity should be re-randomised or not. Can incur a performance hit. + * + * + * @property {Object} [size={}] An object describing a particle's size. This property is a "value-over-lifetime" property, meaning an array of values and spreads can be + * given to describe specific value changes over a particle's lifetime. + * Depending on the value of SPE.valueOverLifetimeLength, if arrays of numbers are given, then the array will be interpolated to + * have a length matching the value of SPE.valueOverLifetimeLength. + * @property {Number} [size.value=1] Either a single number, or an array of numbers to describe the size of a particle over it's lifetime. + * @property {Number} [size.spread=0] Either a single number, or an array of numbers to describe the size variance of a particle over it's lifetime. + * @property {Boolean} [size.randomise=false] When a particle is re-spawned, whether it's size should be re-randomised or not. Can incur a performance hit. + * + * + * @property {Object} [angle={}] An object describing a particle's angle. The angle is a 2d-rotation, measured in radians, applied to the particle's texture. + * NOTE: if a particle's texture is a sprite-sheet, this value IS IGNORED. + * This property is a "value-over-lifetime" property, meaning an array of values and spreads can be + * given to describe specific value changes over a particle's lifetime. + * Depending on the value of SPE.valueOverLifetimeLength, if arrays of numbers are given, then the array will be interpolated to + * have a length matching the value of SPE.valueOverLifetimeLength. + * @property {Number} [angle.value=0] Either a single number, or an array of numbers to describe the angle of a particle over it's lifetime. + * @property {Number} [angle.spread=0] Either a single number, or an array of numbers to describe the angle variance of a particle over it's lifetime. + * @property {Boolean} [angle.randomise=false] When a particle is re-spawned, whether it's angle should be re-randomised or not. Can incur a performance hit. + * + */ + +/** + * The SPE.Emitter class. + * + * @constructor + * + * @param {EmitterOptions} options A map of options to configure the emitter. + */ +SPE.Emitter = function( options ) { + 'use strict'; + + var utils = SPE.utils, + types = utils.types, + lifetimeLength = SPE.valueOverLifetimeLength; + + // Ensure we have a map of options to play with, + // and that each option is in the correct format. + options = utils.ensureTypedArg( options, types.OBJECT, {} ); + options.position = utils.ensureTypedArg( options.position, types.OBJECT, {} ); + options.velocity = utils.ensureTypedArg( options.velocity, types.OBJECT, {} ); + options.acceleration = utils.ensureTypedArg( options.acceleration, types.OBJECT, {} ); + options.radius = utils.ensureTypedArg( options.radius, types.OBJECT, {} ); + options.drag = utils.ensureTypedArg( options.drag, types.OBJECT, {} ); + options.rotation = utils.ensureTypedArg( options.rotation, types.OBJECT, {} ); + options.color = utils.ensureTypedArg( options.color, types.OBJECT, {} ); + options.opacity = utils.ensureTypedArg( options.opacity, types.OBJECT, {} ); + options.size = utils.ensureTypedArg( options.size, types.OBJECT, {} ); + options.angle = utils.ensureTypedArg( options.angle, types.OBJECT, {} ); + options.wiggle = utils.ensureTypedArg( options.wiggle, types.OBJECT, {} ); + options.maxAge = utils.ensureTypedArg( options.maxAge, types.OBJECT, {} ); + + if ( options.onParticleSpawn ) { + console.warn( 'onParticleSpawn has been removed. Please set properties directly to alter values at runtime.' ); + } + + this.uuid = THREE.Math.generateUUID(); + + this.type = utils.ensureTypedArg( options.type, types.NUMBER, SPE.distributions.BOX ); + + // Start assigning properties...kicking it off with props that DON'T support values over + // lifetimes. + // + // Btw, values over lifetimes are just the new way of referring to *Start, *Middle, and *End. + this.position = { + _value: utils.ensureInstanceOf( options.position.value, THREE.Vector3, new THREE.Vector3() ), + _spread: utils.ensureInstanceOf( options.position.spread, THREE.Vector3, new THREE.Vector3() ), + _spreadClamp: utils.ensureInstanceOf( options.position.spreadClamp, THREE.Vector3, new THREE.Vector3() ), + _distribution: utils.ensureTypedArg( options.position.distribution, types.NUMBER, this.type ), + _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false ), + _radius: utils.ensureTypedArg( options.position.radius, types.NUMBER, 10 ), + _radiusScale: utils.ensureInstanceOf( options.position.radiusScale, THREE.Vector3, new THREE.Vector3( 1, 1, 1 ) ), + _distributionClamp: utils.ensureTypedArg( options.position.distributionClamp, types.NUMBER, 0 ), + }; + + this.velocity = { + _value: utils.ensureInstanceOf( options.velocity.value, THREE.Vector3, new THREE.Vector3() ), + _spread: utils.ensureInstanceOf( options.velocity.spread, THREE.Vector3, new THREE.Vector3() ), + _distribution: utils.ensureTypedArg( options.velocity.distribution, types.NUMBER, this.type ), + _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false ) + }; + + this.acceleration = { + _value: utils.ensureInstanceOf( options.acceleration.value, THREE.Vector3, new THREE.Vector3() ), + _spread: utils.ensureInstanceOf( options.acceleration.spread, THREE.Vector3, new THREE.Vector3() ), + _distribution: utils.ensureTypedArg( options.acceleration.distribution, types.NUMBER, this.type ), + _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false ) + }; + + this.drag = { + _value: utils.ensureTypedArg( options.drag.value, types.NUMBER, 0 ), + _spread: utils.ensureTypedArg( options.drag.spread, types.NUMBER, 0 ), + _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false ) + }; + + this.wiggle = { + _value: utils.ensureTypedArg( options.wiggle.value, types.NUMBER, 0 ), + _spread: utils.ensureTypedArg( options.wiggle.spread, types.NUMBER, 0 ) + }; + + this.rotation = { + _axis: utils.ensureInstanceOf( options.rotation.axis, THREE.Vector3, new THREE.Vector3( 0.0, 1.0, 0.0 ) ), + _axisSpread: utils.ensureInstanceOf( options.rotation.axisSpread, THREE.Vector3, new THREE.Vector3() ), + _angle: utils.ensureTypedArg( options.rotation.angle, types.NUMBER, 0 ), + _angleSpread: utils.ensureTypedArg( options.rotation.angleSpread, types.NUMBER, 0 ), + _static: utils.ensureTypedArg( options.rotation.static, types.BOOLEAN, false ), + _center: utils.ensureInstanceOf( options.rotation.center, THREE.Vector3, this.position._value.clone() ), + _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false ) + }; + + + this.maxAge = { + _value: utils.ensureTypedArg( options.maxAge.value, types.NUMBER, 2 ), + _spread: utils.ensureTypedArg( options.maxAge.spread, types.NUMBER, 0 ) + }; + + + + // The following properties can support either single values, or an array of values that change + // the property over a particle's lifetime (value over lifetime). + this.color = { + _value: utils.ensureArrayInstanceOf( options.color.value, THREE.Color, new THREE.Color() ), + _spread: utils.ensureArrayInstanceOf( options.color.spread, THREE.Vector3, new THREE.Vector3() ), + _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false ) + }; + + this.opacity = { + _value: utils.ensureArrayTypedArg( options.opacity.value, types.NUMBER, 1 ), + _spread: utils.ensureArrayTypedArg( options.opacity.spread, types.NUMBER, 0 ), + _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false ) + }; + + this.size = { + _value: utils.ensureArrayTypedArg( options.size.value, types.NUMBER, 1 ), + _spread: utils.ensureArrayTypedArg( options.size.spread, types.NUMBER, 0 ), + _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false ) + }; + + this.angle = { + _value: utils.ensureArrayTypedArg( options.angle.value, types.NUMBER, 0 ), + _spread: utils.ensureArrayTypedArg( options.angle.spread, types.NUMBER, 0 ), + _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false ) + }; + + + // Assign renaining option values. + this.particleCount = utils.ensureTypedArg( options.particleCount, types.NUMBER, 100 ); + this.duration = utils.ensureTypedArg( options.duration, types.NUMBER, null ); + this.isStatic = utils.ensureTypedArg( options.isStatic, types.BOOLEAN, false ); + this.activeMultiplier = utils.ensureTypedArg( options.activeMultiplier, types.NUMBER, 1 ); + this.direction = utils.ensureTypedArg( options.direction, types.NUMBER, 1 ); + + // Whether this emitter is alive or not. + this.alive = utils.ensureTypedArg( options.alive, types.BOOLEAN, true ); + + + // The following properties are set internally and are not + // user-controllable. + this.particlesPerSecond = 0; + + // The current particle index for which particles should + // be marked as active on the next update cycle. + this.activationIndex = 0; + + // The offset in the typed arrays this emitter's + // particle's values will start at + this.attributeOffset = 0; + + // The end of the range in the attribute buffers + this.attributeEnd = 0; + + + + // Holds the time the emitter has been alive for. + this.age = 0.0; + + // Holds the number of currently-alive particles + this.activeParticleCount = 0.0; + + // Holds a reference to this emitter's group once + // it's added to one. + this.group = null; + + // Holds a reference to this emitter's group's attributes object + // for easier access. + this.attributes = null; + + // Holds a reference to the params attribute's typed array + // for quicker access. + this.paramsArray = null; + + // A set of flags to determine whether particular properties + // should be re-randomised when a particle is reset. + // + // If a `randomise` property is given, this is preferred. + // Otherwise, it looks at whether a spread value has been + // given. + // + // It allows randomization to be turned off as desired. If + // all randomization is turned off, then I'd expect a performance + // boost as no attribute buffers (excluding the `params`) + // would have to be re-passed to the GPU each frame (since nothing + // except the `params` attribute would have changed). + this.resetFlags = { + // params: utils.ensureTypedArg( options.maxAge.randomise, types.BOOLEAN, !!options.maxAge.spread ) || + // utils.ensureTypedArg( options.wiggle.randomise, types.BOOLEAN, !!options.wiggle.spread ), + position: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false ) || + utils.ensureTypedArg( options.radius.randomise, types.BOOLEAN, false ), + velocity: utils.ensureTypedArg( options.velocity.randomise, types.BOOLEAN, false ), + acceleration: utils.ensureTypedArg( options.acceleration.randomise, types.BOOLEAN, false ) || + utils.ensureTypedArg( options.drag.randomise, types.BOOLEAN, false ), + rotation: utils.ensureTypedArg( options.rotation.randomise, types.BOOLEAN, false ), + rotationCenter: utils.ensureTypedArg( options.rotation.randomise, types.BOOLEAN, false ), + size: utils.ensureTypedArg( options.size.randomise, types.BOOLEAN, false ), + color: utils.ensureTypedArg( options.color.randomise, types.BOOLEAN, false ), + opacity: utils.ensureTypedArg( options.opacity.randomise, types.BOOLEAN, false ), + angle: utils.ensureTypedArg( options.angle.randomise, types.BOOLEAN, false ) + }; + + this.updateFlags = {}; + this.updateCounts = {}; + + // A map to indicate which emitter parameters should update + // which attribute. + this.updateMap = { + maxAge: 'params', + position: 'position', + velocity: 'velocity', + acceleration: 'acceleration', + drag: 'acceleration', + wiggle: 'params', + rotation: 'rotation', + size: 'size', + color: 'color', + opacity: 'opacity', + angle: 'angle' + }; + + for ( var i in this.updateMap ) { + if ( this.updateMap.hasOwnProperty( i ) ) { + this.updateCounts[ this.updateMap[ i ] ] = 0.0; + this.updateFlags[ this.updateMap[ i ] ] = false; + this._createGetterSetters( this[ i ], i ); + } + } + + this.bufferUpdateRanges = {}; + this.attributeKeys = null; + this.attributeCount = 0; + + + // Ensure that the value-over-lifetime property objects above + // have value and spread properties that are of the same length. + // + // Also, for now, make sure they have a length of 3 (min/max arguments here). + utils.ensureValueOverLifetimeCompliance( this.color, lifetimeLength, lifetimeLength ); + utils.ensureValueOverLifetimeCompliance( this.opacity, lifetimeLength, lifetimeLength ); + utils.ensureValueOverLifetimeCompliance( this.size, lifetimeLength, lifetimeLength ); + utils.ensureValueOverLifetimeCompliance( this.angle, lifetimeLength, lifetimeLength ); +}; + +SPE.Emitter.constructor = SPE.Emitter; + +SPE.Emitter.prototype._createGetterSetters = function( propObj, propName ) { + 'use strict'; + + var self = this; + + for ( var i in propObj ) { + if ( propObj.hasOwnProperty( i ) ) { + + var name = i.replace( '_', '' ); + + Object.defineProperty( propObj, name, { + get: ( function( prop ) { + return function() { + return this[ prop ]; + }; + }( i ) ), + + set: ( function( prop ) { + return function( value ) { + var mapName = self.updateMap[ propName ], + prevValue = this[ prop ], + length = SPE.valueOverLifetimeLength; + + if ( prop === '_rotationCenter' ) { + self.updateFlags.rotationCenter = true; + self.updateCounts.rotationCenter = 0.0; + } + else if ( prop === '_randomise' ) { + self.resetFlags[ mapName ] = value; + } + else { + self.updateFlags[ mapName ] = true; + self.updateCounts[ mapName ] = 0.0; + } + + self.group._updateDefines(); + + this[ prop ] = value; + + // If the previous value was an array, then make + // sure the provided value is interpolated correctly. + if ( Array.isArray( prevValue ) ) { + SPE.utils.ensureValueOverLifetimeCompliance( self[ propName ], length, length ); + } + }; + }( i ) ) + } ); + } + } +}; + +SPE.Emitter.prototype._setBufferUpdateRanges = function( keys ) { + 'use strict'; + + this.attributeKeys = keys; + this.attributeCount = keys.length; + + for ( var i = this.attributeCount - 1; i >= 0; --i ) { + this.bufferUpdateRanges[ keys[ i ] ] = { + min: Number.POSITIVE_INFINITY, + max: Number.NEGATIVE_INFINITY + }; + } +}; + +SPE.Emitter.prototype._calculatePPSValue = function( groupMaxAge ) { + 'use strict'; + + var particleCount = this.particleCount; + + + // Calculate the `particlesPerSecond` value for this emitter. It's used + // when determining which particles should die and which should live to + // see another day. Or be born, for that matter. The "God" property. + if ( this.duration ) { + this.particlesPerSecond = particleCount / ( groupMaxAge < this.duration ? groupMaxAge : this.duration ); + } + else { + this.particlesPerSecond = particleCount / groupMaxAge; + } +}; + +SPE.Emitter.prototype._setAttributeOffset = function( startIndex ) { + this.attributeOffset = startIndex; + this.activationIndex = startIndex; + this.activationEnd = startIndex + this.particleCount; +}; + + +SPE.Emitter.prototype._assignValue = function( prop, index ) { + 'use strict'; + + switch ( prop ) { + case 'position': + this._assignPositionValue( index ); + break; + + case 'velocity': + case 'acceleration': + this._assignForceValue( index, prop ); + break; + + case 'size': + case 'opacity': + this._assignAbsLifetimeValue( index, prop ); + break; + + case 'angle': + this._assignAngleValue( index ); + break; + + case 'params': + this._assignParamsValue( index ); + break; + + case 'rotation': + this._assignRotationValue( index ); + break; + + case 'color': + this._assignColorValue( index ); + break; + } +}; + +SPE.Emitter.prototype._assignPositionValue = function( index ) { + 'use strict'; + + var distributions = SPE.distributions, + utils = SPE.utils, + prop = this.position, + attr = this.attributes.position, + value = prop._value, + spread = prop._spread, + distribution = prop._distribution; + + switch ( distribution ) { + case distributions.BOX: + utils.randomVector3( attr, index, value, spread, prop._spreadClamp ); + break; + + case distributions.SPHERE: + utils.randomVector3OnSphere( attr, index, value, prop._radius, prop._spread.x, prop._radiusScale, prop._spreadClamp.x, prop._distributionClamp || this.particleCount ); + break; + + case distributions.DISC: + utils.randomVector3OnDisc( attr, index, value, prop._radius, prop._spread.x, prop._radiusScale, prop._spreadClamp.x ); + break; + } +}; + +SPE.Emitter.prototype._assignForceValue = function( index, attrName ) { + 'use strict'; + + var distributions = SPE.distributions, + utils = SPE.utils, + prop = this[ attrName ], + value = prop._value, + spread = prop._spread, + distribution = prop._distribution, + pos, + positionX, + positionY, + positionZ, + i; + + switch ( distribution ) { + case distributions.BOX: + utils.randomVector3( this.attributes[ attrName ], index, value, spread ); + break; + + case distributions.SPHERE: + pos = this.attributes.position.typedArray.array; + i = index * 3; + + // Ensure position values aren't zero, otherwise no force will be + // applied. + // positionX = utils.zeroToEpsilon( pos[ i ], true ); + // positionY = utils.zeroToEpsilon( pos[ i + 1 ], true ); + // positionZ = utils.zeroToEpsilon( pos[ i + 2 ], true ); + positionX = pos[ i ]; + positionY = pos[ i + 1 ]; + positionZ = pos[ i + 2 ]; + + utils.randomDirectionVector3OnSphere( + this.attributes[ attrName ], index, + positionX, positionY, positionZ, + this.position._value, + prop._value.x, + prop._spread.x + ); + break; + + case distributions.DISC: + pos = this.attributes.position.typedArray.array; + i = index * 3; + + // Ensure position values aren't zero, otherwise no force will be + // applied. + // positionX = utils.zeroToEpsilon( pos[ i ], true ); + // positionY = utils.zeroToEpsilon( pos[ i + 1 ], true ); + // positionZ = utils.zeroToEpsilon( pos[ i + 2 ], true ); + positionX = pos[ i ]; + positionY = pos[ i + 1 ]; + positionZ = pos[ i + 2 ]; + + utils.randomDirectionVector3OnDisc( + this.attributes[ attrName ], index, + positionX, positionY, positionZ, + this.position._value, + prop._value.x, + prop._spread.x + ); + break; + } + + if ( attrName === 'acceleration' ) { + var drag = utils.clamp( utils.randomFloat( this.drag._value, this.drag._spread ), 0, 1 ); + this.attributes.acceleration.typedArray.array[ index * 4 + 3 ] = drag; + } +}; + +SPE.Emitter.prototype._assignAbsLifetimeValue = function( index, propName ) { + 'use strict'; + + var array = this.attributes[ propName ].typedArray, + prop = this[ propName ], + utils = SPE.utils, + value; + + if ( utils.arrayValuesAreEqual( prop._value ) && utils.arrayValuesAreEqual( prop._spread ) ) { + value = Math.abs( utils.randomFloat( prop._value[ 0 ], prop._spread[ 0 ] ) ); + array.setVec4Components( index, value, value, value, value ); + } + else { + array.setVec4Components( index, + Math.abs( utils.randomFloat( prop._value[ 0 ], prop._spread[ 0 ] ) ), + Math.abs( utils.randomFloat( prop._value[ 1 ], prop._spread[ 1 ] ) ), + Math.abs( utils.randomFloat( prop._value[ 2 ], prop._spread[ 2 ] ) ), + Math.abs( utils.randomFloat( prop._value[ 3 ], prop._spread[ 3 ] ) ) + ); + } +}; + +SPE.Emitter.prototype._assignAngleValue = function( index ) { + 'use strict'; + + var array = this.attributes.angle.typedArray, + prop = this.angle, + utils = SPE.utils, + value; + + if ( utils.arrayValuesAreEqual( prop._value ) && utils.arrayValuesAreEqual( prop._spread ) ) { + value = utils.randomFloat( prop._value[ 0 ], prop._spread[ 0 ] ); + array.setVec4Components( index, value, value, value, value ); + } + else { + array.setVec4Components( index, + utils.randomFloat( prop._value[ 0 ], prop._spread[ 0 ] ), + utils.randomFloat( prop._value[ 1 ], prop._spread[ 1 ] ), + utils.randomFloat( prop._value[ 2 ], prop._spread[ 2 ] ), + utils.randomFloat( prop._value[ 3 ], prop._spread[ 3 ] ) + ); + } +}; + +SPE.Emitter.prototype._assignParamsValue = function( index ) { + 'use strict'; + + this.attributes.params.typedArray.setVec4Components( index, + this.isStatic ? 1 : 0, + 0.0, + Math.abs( SPE.utils.randomFloat( this.maxAge._value, this.maxAge._spread ) ), + SPE.utils.randomFloat( this.wiggle._value, this.wiggle._spread ) + ); +}; + +SPE.Emitter.prototype._assignRotationValue = function( index ) { + 'use strict'; + + this.attributes.rotation.typedArray.setVec3Components( index, + SPE.utils.getPackedRotationAxis( this.rotation._axis, this.rotation._axisSpread ), + SPE.utils.randomFloat( this.rotation._angle, this.rotation._angleSpread ), + this.rotation._static ? 0 : 1 + ); + + this.attributes.rotationCenter.typedArray.setVec3( index, this.rotation._center ); +}; + +SPE.Emitter.prototype._assignColorValue = function( index ) { + 'use strict'; + SPE.utils.randomColorAsHex( this.attributes.color, index, this.color._value, this.color._spread ); +}; + +SPE.Emitter.prototype._resetParticle = function( index ) { + 'use strict'; + + var resetFlags = this.resetFlags, + updateFlags = this.updateFlags, + updateCounts = this.updateCounts, + keys = this.attributeKeys, + key, + updateFlag; + + for ( var i = this.attributeCount - 1; i >= 0; --i ) { + key = keys[ i ]; + updateFlag = updateFlags[ key ]; + + if ( resetFlags[ key ] === true || updateFlag === true ) { + this._assignValue( key, index ); + this._updateAttributeUpdateRange( key, index ); + + if ( updateFlag === true && updateCounts[ key ] === this.particleCount ) { + updateFlags[ key ] = false; + updateCounts[ key ] = 0.0; + } + else if ( updateFlag == true ) { + ++updateCounts[ key ]; + } + } + } +}; + +SPE.Emitter.prototype._updateAttributeUpdateRange = function( attr, i ) { + 'use strict'; + + var ranges = this.bufferUpdateRanges[ attr ]; + + ranges.min = Math.min( i, ranges.min ); + ranges.max = Math.max( i, ranges.max ); +}; + +SPE.Emitter.prototype._resetBufferRanges = function() { + 'use strict'; + + var ranges = this.bufferUpdateRanges, + keys = this.bufferUpdateKeys, + i = this.bufferUpdateCount - 1, + key; + + for ( i; i >= 0; --i ) { + key = keys[ i ]; + ranges[ key ].min = Number.POSITIVE_INFINITY; + ranges[ key ].max = Number.NEGATIVE_INFINITY; + } +}; + +SPE.Emitter.prototype._onRemove = function() { + 'use strict'; + // Reset any properties of the emitter that were set by + // a group when it was added. + this.particlesPerSecond = 0; + this.attributeOffset = 0; + this.activationIndex = 0; + this.activeParticleCount = 0; + this.group = null; + this.attributes = null; + this.paramsArray = null; + this.age = 0.0; +}; + +SPE.Emitter.prototype._decrementParticleCount = function() { + 'use strict'; + --this.activeParticleCount; + + // TODO: + // - Trigger event if count === 0. +}; + +SPE.Emitter.prototype._incrementParticleCount = function() { + 'use strict'; + ++this.activeParticleCount; + + // TODO: + // - Trigger event if count === this.particleCount. +}; + +SPE.Emitter.prototype._checkParticleAges = function( start, end, params, dt ) { + 'use strict'; + for ( var i = end - 1, index, maxAge, age, alive; i >= start; --i ) { + index = i * 4; + + alive = params[ index ]; + + if ( alive === 0.0 ) { + continue; + } + + // Increment age + age = params[ index + 1 ]; + maxAge = params[ index + 2 ]; + + if ( this.direction === 1 ) { + age += dt; + + if ( age >= maxAge ) { + age = 0.0; + alive = 0.0; + this._decrementParticleCount(); + } + } + else { + age -= dt; + + if ( age <= 0.0 ) { + age = maxAge; + alive = 0.0; + this._decrementParticleCount(); + } + } + + params[ index ] = alive; + params[ index + 1 ] = age; + + this._updateAttributeUpdateRange( 'params', i ); + } +}; + +SPE.Emitter.prototype._activateParticles = function( activationStart, activationEnd, params, dtPerParticle ) { + 'use strict'; + var direction = this.direction; + + for ( var i = activationStart, index, dtValue; i < activationEnd; ++i ) { + index = i * 4; + + // Don't re-activate particles that aren't dead yet. + // if ( params[ index ] !== 0.0 && ( this.particleCount !== 1 || this.activeMultiplier !== 1 ) ) { + // continue; + // } + + if ( params[ index ] != 0.0 && this.particleCount !== 1 ) { + continue; + } + + // Increment the active particle count. + this._incrementParticleCount(); + + // Mark the particle as alive. + params[ index ] = 1.0; + + // Reset the particle + this._resetParticle( i ); + + // Move each particle being activated to + // it's actual position in time. + // + // This stops particles being 'clumped' together + // when frame rates are on the lower side of 60fps + // or not constant (a very real possibility!) + dtValue = dtPerParticle * ( i - activationStart ) + params[ index + 1 ] = direction === -1 ? params[ index + 2 ] - dtValue : dtValue; + + this._updateAttributeUpdateRange( 'params', i ); + } +}; + +/** + * Simulates one frame's worth of particles, updating particles + * that are already alive, and marking ones that are currently dead + * but should be alive as alive. + * + * If the emitter is marked as static, then this function will do nothing. + * + * @param {Number} dt The number of seconds to simulate (deltaTime) + */ +SPE.Emitter.prototype.tick = function( dt ) { + 'use strict'; + + if ( this.isStatic ) { + return; + } + + if ( this.paramsArray === null ) { + this.paramsArray = this.attributes.params.typedArray.array; + } + + var start = this.attributeOffset, + end = start + this.particleCount, + params = this.paramsArray, // vec3( alive, age, maxAge, wiggle ) + ppsDt = this.particlesPerSecond * this.activeMultiplier * dt, + activationIndex = this.activationIndex; + + // Reset the buffer update indices. + this._resetBufferRanges(); + + // Increment age for those particles that are alive, + // and kill off any particles whose age is over the limit. + this._checkParticleAges( start, end, params, dt ); + + // If the emitter is dead, reset the age of the emitter to zero, + // ready to go again if required + if ( this.alive === false ) { + this.age = 0.0; + return; + } + + // If the emitter has a specified lifetime and we've exceeded it, + // mark the emitter as dead. + if ( this.duration !== null && this.age > this.duration ) { + this.alive = false; + this.age = 0.0; + return; + } + + + var activationStart = this.particleCount === 1 ? activationIndex : ( activationIndex | 0 ), + activationEnd = Math.min( activationStart + ppsDt, this.activationEnd ), + activationCount = activationEnd - this.activationIndex | 0, + dtPerParticle = activationCount > 0 ? dt / activationCount : 0; + + this._activateParticles( activationStart, activationEnd, params, dtPerParticle ); + + // Move the activation window forward, soldier. + this.activationIndex += ppsDt; + + if ( this.activationIndex > end ) { + this.activationIndex = start; + } + + + // Increment the age of the emitter. + this.age += dt; +}; + +/** + * Resets all the emitter's particles to their start positions + * and marks the particles as dead if the `force` argument is + * true. + * + * @param {Boolean} [force=undefined] If true, all particles will be marked as dead instantly. + * @return {Emitter} This emitter instance. + */ +SPE.Emitter.prototype.reset = function( force ) { + 'use strict'; + + this.age = 0.0; + this.alive = false; + + if ( force === true ) { + var start = this.attributeOffset, + end = start + this.particleCount, + array = this.paramsArray, + attr = this.attributes.params.bufferAttribute; + + for ( var i = end - 1, index; i >= start; --i ) { + index = i * 4; + + array[ index ] = 0.0; + array[ index + 1 ] = 0.0; + } + + attr.updateRange.offset = 0; + attr.updateRange.count = -1; + attr.needsUpdate = true; + } + + return this; +}; + +/** + * Enables the emitter. If not already enabled, the emitter + * will start emitting particles. + * + * @return {Emitter} This emitter instance. + */ +SPE.Emitter.prototype.enable = function() { + 'use strict'; + this.alive = true; + return this; +}; + +/** + * Disables th emitter, but does not instantly remove it's + * particles fromt the scene. When called, the emitter will be + * 'switched off' and just stop emitting. Any particle's alive will + * be allowed to finish their lifecycle. + * + * @return {Emitter} This emitter instance. + */ +SPE.Emitter.prototype.disable = function() { + 'use strict'; + + this.alive = false; + return this; +}; + +/** + * Remove this emitter from it's parent group (if it has been added to one). + * Delgates to SPE.group.prototype.removeEmitter(). + * + * When called, all particle's belonging to this emitter will be instantly + * removed from the scene. + * + * @return {Emitter} This emitter instance. + * + * @see SPE.Group.prototype.removeEmitter + */ +SPE.Emitter.prototype.remove = function() { + 'use strict'; + if ( this.group !== null ) { + this.group.removeEmitter( this ); + } + else { + console.error( 'Emitter does not belong to a group, cannot remove.' ); + } + + return this; +}; \ No newline at end of file diff --git a/assets/js/SPE.min.js b/assets/js/SPE.min.js new file mode 100644 index 00000000..083dc26b --- /dev/null +++ b/assets/js/SPE.min.js @@ -0,0 +1,45 @@ +/* shader-particle-engine 1.0.6 + * + * (c) 2015 Luke Moody (http://www.github.com/squarefeet) + * Originally based on Lee Stemkoski's original work (https://github.com/stemkoski/stemkoski.github.com/blob/master/Three.js/js/ParticleEngine.js). + * + * shader-particle-engine may be freely distributed under the MIT license (See LICENSE at root of this repository.) + */ +var SPE={distributions:{BOX:1,SPHERE:2,DISC:3},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),c>a?this.shrink(a):a>c?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;e>f;++f)(a>f||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),void(this.bufferAttribute.dynamic=this.dynamicBuffer))},SPE.ShaderAttribute.prototype.getLength=function(){"use strict";return null===this.typedArray?0:this.typedArray.array.length},SPE.shaderChunks={defines:["#define PACKED_COLOR_SIZE 256.0","#define PACKED_COLOR_DIVISOR 255.0"].join("\n"),uniforms:["uniform float deltaTime;","uniform float runTime;","uniform sampler2D texture;","uniform vec4 textureAnimation;","uniform float scale;"].join("\n"),attributes:["attribute vec4 acceleration;","attribute vec3 velocity;","attribute vec4 rotation;","attribute vec3 rotationCenter;","attribute vec4 params;","attribute vec4 size;","attribute vec4 angle;","attribute vec4 color;","attribute vec4 opacity;"].join("\n"),varyings:["varying vec4 vColor;","#ifdef SHOULD_ROTATE_TEXTURE"," varying float vAngle;","#endif","#ifdef SHOULD_CALCULATE_SPRITE"," varying vec4 vSpriteSheet;","#endif"].join("\n"), +branchAvoidanceFunctions:["float when_gt(float x, float y) {"," return max(sign(x - y), 0.0);","}","float when_lt(float x, float y) {"," return min( max(1.0 - sign(x - y), 0.0), 1.0 );","}","float when_eq( float x, float y ) {"," return 1.0 - abs( sign( x - y ) );","}","float when_ge(float x, float y) {"," return 1.0 - when_lt(x, y);","}","float when_le(float x, float y) {"," return 1.0 - when_gt(x, y);","}","float and(float a, float b) {"," return a * b;","}","float or(float a, float b) {"," return min(a + b, 1.0);","}"].join("\n"),unpackColor:["vec3 unpackColor( in float hex ) {"," vec3 c = vec3( 0.0 );"," float r = mod( (hex / PACKED_COLOR_SIZE / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );"," float g = mod( (hex / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );"," float b = mod( hex, PACKED_COLOR_SIZE );"," c.r = r / PACKED_COLOR_DIVISOR;"," c.g = g / PACKED_COLOR_DIVISOR;"," c.b = b / PACKED_COLOR_DIVISOR;"," return c;","}"].join("\n"),unpackRotationAxis:["vec3 unpackRotationAxis( in float hex ) {"," vec3 c = vec3( 0.0 );"," float r = mod( (hex / PACKED_COLOR_SIZE / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );"," float g = mod( (hex / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );"," float b = mod( hex, PACKED_COLOR_SIZE );"," c.r = r / PACKED_COLOR_DIVISOR;"," c.g = g / PACKED_COLOR_DIVISOR;"," c.b = b / PACKED_COLOR_DIVISOR;"," c *= vec3( 2.0 );"," c -= vec3( 1.0 );"," return c;","}"].join("\n"), +floatOverLifetime:["float getFloatOverLifetime( in float positionInTime, in vec4 attr ) {"," highp float value = 0.0;"," float deltaAge = positionInTime * float( VALUE_OVER_LIFETIME_LENGTH - 1 );"," float fIndex = 0.0;"," float shouldApplyValue = 0.0;"," value += attr[ 0 ] * when_eq( deltaAge, 0.0 );",""," for( int i = 0; i < VALUE_OVER_LIFETIME_LENGTH - 1; ++i ) {"," fIndex = float( i );"," shouldApplyValue = and( when_gt( deltaAge, fIndex ), when_le( deltaAge, fIndex + 1.0 ) );"," value += shouldApplyValue * mix( attr[ i ], attr[ i + 1 ], deltaAge - fIndex );"," }",""," return value;","}"].join("\n"),colorOverLifetime:["vec3 getColorOverLifetime( in float positionInTime, in vec3 color1, in vec3 color2, in vec3 color3, in vec3 color4 ) {"," vec3 value = vec3( 0.0 );"," value.x = getFloatOverLifetime( positionInTime, vec4( color1.x, color2.x, color3.x, color4.x ) );"," value.y = getFloatOverLifetime( positionInTime, vec4( color1.y, color2.y, color3.y, color4.y ) );"," value.z = getFloatOverLifetime( positionInTime, vec4( color1.z, color2.z, color3.z, color4.z ) );"," return value;","}"].join("\n"), +paramFetchingFunctions:["float getAlive() {"," return params.x;","}","float getAge() {"," return params.y;","}","float getMaxAge() {"," return params.z;","}","float getWiggle() {"," return params.w;","}"].join("\n"),forceFetchingFunctions:["vec4 getPosition( in float age ) {"," return modelViewMatrix * vec4( position, 1.0 );","}","vec3 getVelocity( in float age ) {"," return velocity * age;","}","vec3 getAcceleration( in float age ) {"," return acceleration.xyz * age;","}"].join("\n"),rotationFunctions:["#ifdef SHOULD_ROTATE_PARTICLES"," mat4 getRotationMatrix( in vec3 axis, in float angle) {"," axis = normalize(axis);"," float s = sin(angle);"," float c = cos(angle);"," float oc = 1.0 - c;",""," return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0,"," oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0,"," oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0,"," 0.0, 0.0, 0.0, 1.0);"," }",""," vec3 getRotation( in vec3 pos, in float positionInTime ) {"," if( rotation.y == 0.0 ) {"," return pos;"," }",""," vec3 axis = unpackRotationAxis( rotation.x );"," vec3 center = rotationCenter;"," vec3 translated;"," mat4 rotationMatrix;"," float angle = 0.0;"," angle += when_eq( rotation.z, 0.0 ) * rotation.y;"," angle += when_gt( rotation.z, 0.0 ) * mix( 0.0, rotation.y, positionInTime );"," translated = rotationCenter - pos;"," rotationMatrix = getRotationMatrix( axis, angle );"," return center - vec3( rotationMatrix * vec4( translated, 0.0 ) );"," }","#endif"].join("\n"), +rotateTexture:[" vec2 vUv = vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y );",""," #ifdef SHOULD_ROTATE_TEXTURE"," float x = gl_PointCoord.x - 0.5;"," float y = 1.0 - gl_PointCoord.y - 0.5;"," float c = cos( -vAngle );"," float s = sin( -vAngle );"," vUv = vec2( c * x + s * y + 0.5, c * y - s * x + 0.5 );"," #endif",""," #ifdef SHOULD_CALCULATE_SPRITE"," float framesX = vSpriteSheet.x;"," float framesY = vSpriteSheet.y;"," float columnNorm = vSpriteSheet.z;"," float rowNorm = vSpriteSheet.w;"," vUv.x = gl_PointCoord.x * framesX + columnNorm;"," vUv.y = 1.0 - (gl_PointCoord.y * framesY + rowNorm);"," #endif",""," vec4 rotatedTexture = texture2D( texture, vUv );"].join("\n")},SPE.shaders={vertex:[SPE.shaderChunks.defines,SPE.shaderChunks.uniforms,SPE.shaderChunks.attributes,SPE.shaderChunks.varyings,THREE.ShaderChunk.common,THREE.ShaderChunk.logdepthbuf_pars_vertex,THREE.ShaderChunk.fog_pars_vertex,SPE.shaderChunks.branchAvoidanceFunctions,SPE.shaderChunks.unpackColor,SPE.shaderChunks.unpackRotationAxis,SPE.shaderChunks.floatOverLifetime,SPE.shaderChunks.colorOverLifetime,SPE.shaderChunks.paramFetchingFunctions,SPE.shaderChunks.forceFetchingFunctions,SPE.shaderChunks.rotationFunctions,"void main() {"," highp float age = getAge();"," highp float alive = getAlive();"," highp float maxAge = getMaxAge();"," highp float positionInTime = (age / maxAge);"," highp float isAlive = when_gt( alive, 0.0 );"," #ifdef SHOULD_WIGGLE_PARTICLES"," float wiggleAmount = positionInTime * getWiggle();"," float wiggleSin = isAlive * sin( wiggleAmount );"," float wiggleCos = isAlive * cos( wiggleAmount );"," #endif"," vec3 vel = getVelocity( age );"," vec3 accel = getAcceleration( age );"," vec3 force = vec3( 0.0 );"," vec3 pos = vec3( position );"," float drag = 1.0 - (positionInTime * 0.5) * acceleration.w;"," force += vel;"," force *= drag;"," force += accel * age;"," pos += force;"," #ifdef SHOULD_WIGGLE_PARTICLES"," pos.x += wiggleSin;"," pos.y += wiggleCos;"," pos.z += wiggleSin;"," #endif"," #ifdef SHOULD_ROTATE_PARTICLES"," pos = getRotation( pos, positionInTime );"," #endif"," vec4 mvPosition = modelViewMatrix * vec4( pos, 1.0 );"," highp float pointSize = getFloatOverLifetime( positionInTime, size ) * isAlive;"," #ifdef HAS_PERSPECTIVE"," float perspective = scale / length( mvPosition.xyz );"," #else"," float perspective = 1.0;"," #endif"," float pointSizePerspective = pointSize * perspective;"," #ifdef COLORIZE"," vec3 c = isAlive * getColorOverLifetime("," positionInTime,"," unpackColor( color.x ),"," unpackColor( color.y ),"," unpackColor( color.z ),"," unpackColor( color.w )"," );"," #else"," vec3 c = vec3(1.0);"," #endif"," float o = isAlive * getFloatOverLifetime( positionInTime, opacity );"," vColor = vec4( c, o );"," #ifdef SHOULD_ROTATE_TEXTURE"," vAngle = isAlive * getFloatOverLifetime( positionInTime, angle );"," #endif"," #ifdef SHOULD_CALCULATE_SPRITE"," float framesX = textureAnimation.x;"," float framesY = textureAnimation.y;"," float loopCount = textureAnimation.w;"," float totalFrames = textureAnimation.z;"," float frameNumber = mod( (positionInTime * loopCount) * totalFrames, totalFrames );"," float column = floor(mod( frameNumber, framesX ));"," float row = floor( (frameNumber - column) / framesX );"," float columnNorm = column / framesX;"," float rowNorm = row / framesY;"," vSpriteSheet.x = 1.0 / framesX;"," vSpriteSheet.y = 1.0 / framesY;"," vSpriteSheet.z = columnNorm;"," vSpriteSheet.w = rowNorm;"," #endif"," gl_PointSize = pointSizePerspective;"," gl_Position = projectionMatrix * mvPosition;",THREE.ShaderChunk.logdepthbuf_vertex,THREE.ShaderChunk.fog_vertex,"}"].join("\n"), +fragment:[SPE.shaderChunks.uniforms,THREE.ShaderChunk.common,THREE.ShaderChunk.fog_pars_fragment,THREE.ShaderChunk.logdepthbuf_pars_fragment,SPE.shaderChunks.varyings,SPE.shaderChunks.branchAvoidanceFunctions,"void main() {"," vec3 outgoingLight = vColor.xyz;"," "," #ifdef ALPHATEST"," if ( vColor.w < float(ALPHATEST) ) discard;"," #endif",SPE.shaderChunks.rotateTexture,THREE.ShaderChunk.logdepthbuf_fragment," outgoingLight = vColor.xyz * rotatedTexture.xyz;"," gl_FragColor = vec4( outgoingLight.xyz, rotatedTexture.w * vColor.w );",THREE.ShaderChunk.fog_fragment,"}"].join("\n")},SPE.utils={types:{BOOLEAN:"boolean",STRING:"string",NUMBER:"number",OBJECT:"object"},ensureTypedArg:function(a,b,c){"use strict";return typeof a===b?a:c},ensureArrayTypedArg:function(a,b,c){"use strict";if(Array.isArray(a)){for(var d=a.length-1;d>=0;--d)if(typeof a[d]!==b)return c;return a}return this.ensureTypedArg(a,b,c)},ensureInstanceOf:function(a,b,c){"use strict";return void 0!==b&&a instanceof b?a:c; +},ensureArrayInstanceOf:function(a,b,c){"use strict";if(Array.isArray(a)){for(var d=a.length-1;d>=0;--d)if(void 0!==b&&a[d]instanceof b==!1)return c;return a}return this.ensureInstanceOf(a,b,c)},ensureValueOverLifetimeCompliance:function(a,b,c){"use strict";b=b||3,c=c||3,Array.isArray(a._value)===!1&&(a._value=[a._value]),Array.isArray(a._spread)===!1&&(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;b-1>f;++f){var g=f*e,h=Math.floor(g),i=Math.ceil(g),j=g-h;d[f]=this.lerpTypeAgnostic(a[h],a[i],j)}return d.push("function"==typeof a[c-1].clone?a[c-1].clone():a[c-1]),d},clamp:function(a,b,c){"use strict";return Math.max(b,Math.min(a,c))},zeroToEpsilon:function(a,b){ +"use strict";var c=1e-5,d=a;return d=b?Math.random()*c*10:c,0>a&&a>-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:0>a?-(Math.abs(a)-c):a+b-c)},arrayValuesAreEqual:function(a){"use strict";for(var b=0;bh;++h){var i=e[h];a.copy(d[h]),a.r+=Math.random()*i.x-.5*i.x,a.g+=Math.random()*i.y-.5*i.y,a.b+=Math.random()*i.z-.5*i.z, +a.r=this.clamp(a.r,0,1),a.g=this.clamp(a.g,0,1),a.b=this.clamp(a.b,0,1),g.push(a.getHex())}b.typedArray.setVec4Components(c,g[0],g[1],g[2],g[3])}}(),randomVector3OnSphere:function(a,b,c,d,e,f,g,h){"use strict";var i=2*Math.random()-1,j=6.2832*Math.random(),k=Math.sqrt(1-i*i),l=this.randomFloat(d,e),m=0,n=0,o=0;g&&(l=Math.round(l/g)*g),m=k*Math.cos(j)*l,n=k*Math.sin(j)*l,o=i*l,m*=f.x,n*=f.y,o*=f.z,m+=c.x,n+=c.y,o+=c.z,a.typedArray.setVec3Components(b,m,n,o)},seededRandom:function(a){var b=1e4*Math.sin(a);return b-(0|b)},randomVector3OnDisc:function(a,b,c,d,e,f,g){"use strict";var h=6.2832*Math.random(),i=Math.abs(this.randomFloat(d,e)),j=0,k=0,l=0;g&&(i=Math.round(i/g)*g),j=Math.cos(h)*i,k=Math.sin(h)*i,j*=f.x,k*=f.y,j+=c.x,k+=c.y,l+=c.z,a.typedArray.setVec3Components(b,j,k,l)},randomDirectionVector3OnSphere:function(){"use strict";var a=new THREE.Vector3;return function(b,c,d,e,f,g,h,i){a.copy(g),a.x-=d,a.y-=e,a.z-=f,a.normalize().multiplyScalar(-this.randomFloat(h,i)),b.typedArray.setVec3Components(c,a.x,a.y,a.z); +}}(),randomDirectionVector3OnDisc:function(){"use strict";var a=new THREE.Vector3;return function(b,c,d,e,f,g,h,i){a.copy(g),a.x-=d,a.y-=e,a.z-=f,a.normalize().multiplyScalar(-this.randomFloat(h,i)),b.typedArray.setVec3Components(c,a.x,a.y,0)}}(),getPackedRotationAxis:function(){"use strict";var a=new THREE.Vector3,b=new THREE.Vector3,c=new THREE.Color,d=new THREE.Vector3(1,1,1);return function(e,f){return a.copy(e).normalize(),b.copy(f).normalize(),a.x+=.5*-f.x+Math.random()*f.x,a.y+=.5*-f.y+Math.random()*f.y,a.z+=.5*-f.z+Math.random()*f.z,a.normalize().add(d).multiplyScalar(.5),c.setRGB(a.x,a.y,a.z),c.getHex()}}()},SPE.Group=function(a){"use strict";var b=SPE.utils,c=b.types;a=b.ensureTypedArg(a,c.OBJECT,{}),a.texture=b.ensureTypedArg(a.texture,c.OBJECT,{}),this.uuid=THREE.Math.generateUUID(),this.fixedTimeStep=b.ensureTypedArg(a.fixedTimeStep,c.NUMBER,.016),this.texture=b.ensureInstanceOf(a.texture.value,THREE.Texture,null),this.textureFrames=b.ensureInstanceOf(a.texture.frames,THREE.Vector2,new THREE.Vector2(1,1)), +this.textureFrameCount=b.ensureTypedArg(a.texture.frameCount,c.NUMBER,this.textureFrames.x*this.textureFrames.y),this.textureLoop=b.ensureTypedArg(a.texture.loop,c.NUMBER,1),this.textureFrames.max(new THREE.Vector2(1,1)),this.hasPerspective=b.ensureTypedArg(a.hasPerspective,c.BOOLEAN,!0),this.colorize=b.ensureTypedArg(a.colorize,c.BOOLEAN,!0),this.maxParticleCount=b.ensureTypedArg(a.maxParticleCount,c.NUMBER,null),this.blending=b.ensureTypedArg(a.blending,c.NUMBER,THREE.AdditiveBlending),this.transparent=b.ensureTypedArg(a.transparent,c.BOOLEAN,!0),this.alphaTest=parseFloat(b.ensureTypedArg(a.alphaTest,c.NUMBER,0)),this.depthWrite=b.ensureTypedArg(a.depthWrite,c.BOOLEAN,!1),this.depthTest=b.ensureTypedArg(a.depthTest,c.BOOLEAN,!0),this.fog=b.ensureTypedArg(a.fog,c.BOOLEAN,!0),this.scale=b.ensureTypedArg(a.scale,c.NUMBER,300),this.emitters=[],this.emitterIDs=[],this._pool=[],this._poolCreationSettings=null,this._createNewWhenPoolEmpty=0,this._attributesNeedRefresh=!1,this._attributesNeedDynamicReset=!1, +this.particleCount=0,this.uniforms={texture:{type:"t",value:this.texture},textureAnimation:{type:"v4",value:new THREE.Vector4(this.textureFrames.x,this.textureFrames.y,this.textureFrameCount,Math.max(Math.abs(this.textureLoop),1))},fogColor:{type:"c",value:null},fogNear:{type:"f",value:10},fogFar:{type:"f",value:200},fogDensity:{type:"f",value:.5},deltaTime:{type:"f",value:0},runTime:{type:"f",value:0},scale:{type:"f",value:this.scale}},this.defines={HAS_PERSPECTIVE:this.hasPerspective,COLORIZE:this.colorize,VALUE_OVER_LIFETIME_LENGTH:SPE.valueOverLifetimeLength,SHOULD_ROTATE_TEXTURE:!1,SHOULD_ROTATE_PARTICLES:!1,SHOULD_WIGGLE_PARTICLES:!1,SHOULD_CALCULATE_SPRITE:this.textureFrames.x>1||this.textureFrames.y>1},this.attributes={position:new SPE.ShaderAttribute("v3",!0),acceleration:new SPE.ShaderAttribute("v4",!0),velocity:new SPE.ShaderAttribute("v3",!0),rotation:new SPE.ShaderAttribute("v4",!0),rotationCenter:new SPE.ShaderAttribute("v3",!0),params:new SPE.ShaderAttribute("v4",!0),size:new SPE.ShaderAttribute("v4",!0), +angle:new SPE.ShaderAttribute("v4",!0),color:new SPE.ShaderAttribute("v4",!0),opacity:new SPE.ShaderAttribute("v4",!0)},this.attributeKeys=Object.keys(this.attributes),this.attributeCount=this.attributeKeys.length,this.material=new THREE.ShaderMaterial({uniforms:this.uniforms,vertexShader:SPE.shaders.vertex,fragmentShader:SPE.shaders.fragment,blending:this.blending,transparent:this.transparent,alphaTest:this.alphaTest,depthWrite:this.depthWrite,depthTest:this.depthTest,defines:this.defines,fog:this.fog}),this.geometry=new THREE.BufferGeometry,this.mesh=new THREE.Points(this.geometry,this.material),null===this.maxParticleCount&&console.warn("SPE.Group: No maxParticleCount specified. Adding emitters after rendering will probably cause errors.")},SPE.Group.constructor=SPE.Group,SPE.Group.prototype._updateDefines=function(){"use strict";var a,b=this.emitters,c=b.length-1,d=this.defines;for(c;c>=0;--c)a=b[c],d.SHOULD_CALCULATE_SPRITE||(d.SHOULD_ROTATE_TEXTURE=d.SHOULD_ROTATE_TEXTURE||!!Math.max(Math.max.apply(null,a.angle.value),Math.max.apply(null,a.angle.spread))), +d.SHOULD_ROTATE_PARTICLES=d.SHOULD_ROTATE_PARTICLES||!!Math.max(a.rotation.angle,a.rotation.angleSpread),d.SHOULD_WIGGLE_PARTICLES=d.SHOULD_WIGGLE_PARTICLES||!!Math.max(a.wiggle.value,a.wiggle.spread);this.material.needsUpdate=!0},SPE.Group.prototype._applyAttributesToGeometry=function(){"use strict";var a,b,c=this.attributes,d=this.geometry,e=d.attributes;for(var f in c)c.hasOwnProperty(f)&&(a=c[f],b=e[f],b?b.array=a.typedArray.array:d.addAttribute(f,a.bufferAttribute),a.bufferAttribute.needsUpdate=!0);this.geometry.setDrawRange(0,this.particleCount)},SPE.Group.prototype.addEmitter=function(a){"use strict";if(a instanceof SPE.Emitter==!1)return void console.error("`emitter` argument must be instance of SPE.Emitter. Was provided with:",a);if(this.emitterIDs.indexOf(a.uuid)>-1)return void console.error("Emitter already exists in this group. Will not add again.");if(null!==a.group)return void console.error("Emitter already belongs to another group. Will not add to requested group.");var b=this.attributes,c=this.particleCount,d=c+a.particleCount; +this.particleCount=d,null!==this.maxParticleCount&&this.particleCount>this.maxParticleCount&&console.warn("SPE.Group: maxParticleCount exceeded. Requesting",this.particleCount,"particles, can support only",this.maxParticleCount),a._calculatePPSValue(a.maxAge._value+a.maxAge._spread),a._setBufferUpdateRanges(this.attributeKeys),a._setAttributeOffset(c),a.group=this,a.attributes=this.attributes;for(var e in b)b.hasOwnProperty(e)&&b[e]._createBufferAttribute(null!==this.maxParticleCount?this.maxParticleCount:this.particleCount);for(var f=c;d>f;++f)a._assignPositionValue(f),a._assignForceValue(f,"velocity"),a._assignForceValue(f,"acceleration"),a._assignAbsLifetimeValue(f,"opacity"),a._assignAbsLifetimeValue(f,"size"),a._assignAngleValue(f),a._assignRotationValue(f),a._assignParamsValue(f),a._assignColorValue(f);return this._applyAttributesToGeometry(),this.emitters.push(a),this.emitterIDs.push(a.uuid),this._updateDefines(a),this.material.needsUpdate=!0,this.geometry.needsUpdate=!0,this._attributesNeedRefresh=!0, +this},SPE.Group.prototype.removeEmitter=function(a){"use strict";var b=this.emitterIDs.indexOf(a.uuid);if(a instanceof SPE.Emitter==!1)return void console.error("`emitter` argument must be instance of SPE.Emitter. Was provided with:",a);if(-1===b)return void console.error("Emitter does not exist in this group. Will not remove.");for(var c=a.attributeOffset,d=c+a.particleCount,e=this.attributes.params.typedArray,f=c;d>f;++f)e.array[4*f]=0,e.array[4*f+1]=0;this.emitters.splice(b,1),this.emitterIDs.splice(b,1);for(var g in this.attributes)this.attributes.hasOwnProperty(g)&&this.attributes[g].splice(c,d);this.particleCount-=a.particleCount,a._onRemove(),this._attributesNeedRefresh=!0},SPE.Group.prototype.getFromPool=function(){"use strict";var a=this._pool,b=this._createNewWhenPoolEmpty;if(a.length)return a.pop();if(b){var c=new SPE.Emitter(this._poolCreationSettings);return this.addEmitter(c),c}return null},SPE.Group.prototype.releaseIntoPool=function(a){"use strict";return a instanceof SPE.Emitter==!1?void console.error("Argument is not instanceof SPE.Emitter:",a):(a.reset(), +this._pool.unshift(a),this)},SPE.Group.prototype.getPool=function(){"use strict";return this._pool},SPE.Group.prototype.addPool=function(a,b,c){"use strict";var d;this._poolCreationSettings=b,this._createNewWhenPoolEmpty=!!c;for(var e=0;a>e;++e)d=Array.isArray(b)?new SPE.Emitter(b[e]):new SPE.Emitter(b),this.addEmitter(d),this.releaseIntoPool(d);return this},SPE.Group.prototype._triggerSingleEmitter=function(a){"use strict";var b=this.getFromPool(),c=this;return null===b?void console.log("SPE.Group pool ran out."):(a instanceof THREE.Vector3&&(b.position.value.copy(a),b.position.value=b.position.value),b.enable(),setTimeout(function(){b.disable(),c.releaseIntoPool(b)},1e3*Math.max(b.duration,b.maxAge.value+b.maxAge.spread)),this)},SPE.Group.prototype.triggerPoolEmitter=function(a,b){"use strict";if("number"==typeof a&&a>1)for(var c=0;a>c;++c)this._triggerSingleEmitter(b);else this._triggerSingleEmitter(b);return this},SPE.Group.prototype._updateUniforms=function(a){"use strict";this.uniforms.runTime.value+=a, +this.uniforms.deltaTime.value=a},SPE.Group.prototype._resetBufferRanges=function(){"use strict";var a=this.attributeKeys,b=this.attributeCount-1,c=this.attributes;for(b;b>=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||this._attributesNeedRefresh!==!1||this._attributesNeedDynamicReset!==!1){for(var h,b=0;d>b;++b)h=c[b],h.tick(e),this._updateBuffers(h);if(this._attributesNeedDynamicReset===!0){for(b=this.attributeCount-1;b>=0;--b)g[f[b]].resetDynamic();this._attributesNeedDynamicReset=!1}if(this._attributesNeedRefresh===!0){for(b=this.attributeCount-1;b>=0;--b)g[f[b]].forceUpdateAll(); +this._attributesNeedRefresh=!1,this._attributesNeedDynamicReset=!0}}},SPE.Group.prototype.dispose=function(){"use strict";return this.geometry.dispose(),this.material.dispose(),this},SPE.Emitter=function(a){"use strict";var b=SPE.utils,c=b.types,d=SPE.valueOverLifetimeLength;a=b.ensureTypedArg(a,c.OBJECT,{}),a.position=b.ensureTypedArg(a.position,c.OBJECT,{}),a.velocity=b.ensureTypedArg(a.velocity,c.OBJECT,{}),a.acceleration=b.ensureTypedArg(a.acceleration,c.OBJECT,{}),a.radius=b.ensureTypedArg(a.radius,c.OBJECT,{}),a.drag=b.ensureTypedArg(a.drag,c.OBJECT,{}),a.rotation=b.ensureTypedArg(a.rotation,c.OBJECT,{}),a.color=b.ensureTypedArg(a.color,c.OBJECT,{}),a.opacity=b.ensureTypedArg(a.opacity,c.OBJECT,{}),a.size=b.ensureTypedArg(a.size,c.OBJECT,{}),a.angle=b.ensureTypedArg(a.angle,c.OBJECT,{}),a.wiggle=b.ensureTypedArg(a.wiggle,c.OBJECT,{}),a.maxAge=b.ensureTypedArg(a.maxAge,c.OBJECT,{}),a.onParticleSpawn&&console.warn("onParticleSpawn has been removed. Please set properties directly to alter values at runtime."), +this.uuid=THREE.Math.generateUUID(),this.type=b.ensureTypedArg(a.type,c.NUMBER,SPE.distributions.BOX),this.position={_value:b.ensureInstanceOf(a.position.value,THREE.Vector3,new THREE.Vector3),_spread:b.ensureInstanceOf(a.position.spread,THREE.Vector3,new THREE.Vector3),_spreadClamp:b.ensureInstanceOf(a.position.spreadClamp,THREE.Vector3,new THREE.Vector3),_distribution:b.ensureTypedArg(a.position.distribution,c.NUMBER,this.type),_randomise:b.ensureTypedArg(a.position.randomise,c.BOOLEAN,!1),_radius:b.ensureTypedArg(a.position.radius,c.NUMBER,10),_radiusScale:b.ensureInstanceOf(a.position.radiusScale,THREE.Vector3,new THREE.Vector3(1,1,1)),_distributionClamp:b.ensureTypedArg(a.position.distributionClamp,c.NUMBER,0)},this.velocity={_value:b.ensureInstanceOf(a.velocity.value,THREE.Vector3,new THREE.Vector3),_spread:b.ensureInstanceOf(a.velocity.spread,THREE.Vector3,new THREE.Vector3),_distribution:b.ensureTypedArg(a.velocity.distribution,c.NUMBER,this.type),_randomise:b.ensureTypedArg(a.position.randomise,c.BOOLEAN,!1) +},this.acceleration={_value:b.ensureInstanceOf(a.acceleration.value,THREE.Vector3,new THREE.Vector3),_spread:b.ensureInstanceOf(a.acceleration.spread,THREE.Vector3,new THREE.Vector3),_distribution:b.ensureTypedArg(a.acceleration.distribution,c.NUMBER,this.type),_randomise:b.ensureTypedArg(a.position.randomise,c.BOOLEAN,!1)},this.drag={_value:b.ensureTypedArg(a.drag.value,c.NUMBER,0),_spread:b.ensureTypedArg(a.drag.spread,c.NUMBER,0),_randomise:b.ensureTypedArg(a.position.randomise,c.BOOLEAN,!1)},this.wiggle={_value:b.ensureTypedArg(a.wiggle.value,c.NUMBER,0),_spread:b.ensureTypedArg(a.wiggle.spread,c.NUMBER,0)},this.rotation={_axis:b.ensureInstanceOf(a.rotation.axis,THREE.Vector3,new THREE.Vector3(0,1,0)),_axisSpread:b.ensureInstanceOf(a.rotation.axisSpread,THREE.Vector3,new THREE.Vector3),_angle:b.ensureTypedArg(a.rotation.angle,c.NUMBER,0),_angleSpread:b.ensureTypedArg(a.rotation.angleSpread,c.NUMBER,0),_static:b.ensureTypedArg(a.rotation["static"],c.BOOLEAN,!1),_center:b.ensureInstanceOf(a.rotation.center,THREE.Vector3,this.position._value.clone()), +_randomise:b.ensureTypedArg(a.position.randomise,c.BOOLEAN,!1)},this.maxAge={_value:b.ensureTypedArg(a.maxAge.value,c.NUMBER,2),_spread:b.ensureTypedArg(a.maxAge.spread,c.NUMBER,0)},this.color={_value:b.ensureArrayInstanceOf(a.color.value,THREE.Color,new THREE.Color),_spread:b.ensureArrayInstanceOf(a.color.spread,THREE.Vector3,new THREE.Vector3),_randomise:b.ensureTypedArg(a.position.randomise,c.BOOLEAN,!1)},this.opacity={_value:b.ensureArrayTypedArg(a.opacity.value,c.NUMBER,1),_spread:b.ensureArrayTypedArg(a.opacity.spread,c.NUMBER,0),_randomise:b.ensureTypedArg(a.position.randomise,c.BOOLEAN,!1)},this.size={_value:b.ensureArrayTypedArg(a.size.value,c.NUMBER,1),_spread:b.ensureArrayTypedArg(a.size.spread,c.NUMBER,0),_randomise:b.ensureTypedArg(a.position.randomise,c.BOOLEAN,!1)},this.angle={_value:b.ensureArrayTypedArg(a.angle.value,c.NUMBER,0),_spread:b.ensureArrayTypedArg(a.angle.spread,c.NUMBER,0),_randomise:b.ensureTypedArg(a.position.randomise,c.BOOLEAN,!1)},this.particleCount=b.ensureTypedArg(a.particleCount,c.NUMBER,100), +this.duration=b.ensureTypedArg(a.duration,c.NUMBER,null),this.isStatic=b.ensureTypedArg(a.isStatic,c.BOOLEAN,!1),this.activeMultiplier=b.ensureTypedArg(a.activeMultiplier,c.NUMBER,1),this.direction=b.ensureTypedArg(a.direction,c.NUMBER,1),this.alive=b.ensureTypedArg(a.alive,c.BOOLEAN,!0),this.particlesPerSecond=0,this.activationIndex=0,this.attributeOffset=0,this.attributeEnd=0,this.age=0,this.activeParticleCount=0,this.group=null,this.attributes=null,this.paramsArray=null,this.resetFlags={position:b.ensureTypedArg(a.position.randomise,c.BOOLEAN,!1)||b.ensureTypedArg(a.radius.randomise,c.BOOLEAN,!1),velocity:b.ensureTypedArg(a.velocity.randomise,c.BOOLEAN,!1),acceleration:b.ensureTypedArg(a.acceleration.randomise,c.BOOLEAN,!1)||b.ensureTypedArg(a.drag.randomise,c.BOOLEAN,!1),rotation:b.ensureTypedArg(a.rotation.randomise,c.BOOLEAN,!1),rotationCenter:b.ensureTypedArg(a.rotation.randomise,c.BOOLEAN,!1),size:b.ensureTypedArg(a.size.randomise,c.BOOLEAN,!1),color:b.ensureTypedArg(a.color.randomise,c.BOOLEAN,!1), +opacity:b.ensureTypedArg(a.opacity.randomise,c.BOOLEAN,!1),angle:b.ensureTypedArg(a.angle.randomise,c.BOOLEAN,!1)},this.updateFlags={},this.updateCounts={},this.updateMap={maxAge:"params",position:"position",velocity:"velocity",acceleration:"acceleration",drag:"acceleration",wiggle:"params",rotation:"rotation",size:"size",color:"color",opacity:"opacity",angle:"angle"};for(var e in this.updateMap)this.updateMap.hasOwnProperty(e)&&(this.updateCounts[this.updateMap[e]]=0,this.updateFlags[this.updateMap[e]]=!1,this._createGetterSetters(this[e],e));this.bufferUpdateRanges={},this.attributeKeys=null,this.attributeCount=0,b.ensureValueOverLifetimeCompliance(this.color,d,d),b.ensureValueOverLifetimeCompliance(this.opacity,d,d),b.ensureValueOverLifetimeCompliance(this.size,d,d),b.ensureValueOverLifetimeCompliance(this.angle,d,d)},SPE.Emitter.constructor=SPE.Emitter,SPE.Emitter.prototype._createGetterSetters=function(a,b){"use strict";var c=this;for(var d in a)if(a.hasOwnProperty(d)){var e=d.replace("_",""); +Object.defineProperty(a,e,{get:function(a){return function(){return this[a]}}(d),set:function(a){return function(d){var e=c.updateMap[b],f=this[a],g=SPE.valueOverLifetimeLength;"_rotationCenter"===a?(c.updateFlags.rotationCenter=!0,c.updateCounts.rotationCenter=0):"_randomise"===a?c.resetFlags[e]=d:(c.updateFlags[e]=!0,c.updateCounts[e]=0),c.group._updateDefines(),this[a]=d,Array.isArray(f)&&SPE.utils.ensureValueOverLifetimeCompliance(c[b],g,g)}}(d)})}},SPE.Emitter.prototype._setBufferUpdateRanges=function(a){"use strict";this.attributeKeys=a,this.attributeCount=a.length;for(var b=this.attributeCount-1;b>=0;--b)this.bufferUpdateRanges[a[b]]={min:Number.POSITIVE_INFINITY,max:Number.NEGATIVE_INFINITY}},SPE.Emitter.prototype._calculatePPSValue=function(a){"use strict";var b=this.particleCount;this.duration?this.particlesPerSecond=b/(a=0;--h)b=g[h],c=e[b],d[b]!==!0&&c!==!0||(this._assignValue(b,a),this._updateAttributeUpdateRange(b,a),c===!0&&f[b]===this.particleCount?(e[b]=!1,f[b]=0):1==c&&++f[b])},SPE.Emitter.prototype._updateAttributeUpdateRange=function(a,b){"use strict";var c=this.bufferUpdateRanges[a];c.min=Math.min(b,c.min),c.max=Math.max(b,c.max)},SPE.Emitter.prototype._resetBufferRanges=function(){"use strict";var a,b=this.bufferUpdateRanges,c=this.bufferUpdateKeys,d=this.bufferUpdateCount-1;for(d;d>=0;--d)a=c[d],b[a].min=Number.POSITIVE_INFINITY,b[a].max=Number.NEGATIVE_INFINITY},SPE.Emitter.prototype._onRemove=function(){ +"use strict";this.particlesPerSecond=0,this.attributeOffset=0,this.activationIndex=0,this.activeParticleCount=0,this.group=null,this.attributes=null,this.paramsArray=null,this.age=0},SPE.Emitter.prototype._decrementParticleCount=function(){"use strict";--this.activeParticleCount},SPE.Emitter.prototype._incrementParticleCount=function(){"use strict";++this.activeParticleCount},SPE.Emitter.prototype._checkParticleAges=function(a,b,c,d){"use strict";for(var e,f,g,h,i=b-1;i>=a;--i)e=4*i,h=c[e],0!==h&&(g=c[e+1],f=c[e+2],1===this.direction?(g+=d,g>=f&&(g=0,h=0,this._decrementParticleCount())):(g-=d,0>=g&&(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;b>h;++h)e=4*h,0!=c[e]&&1!==this.particleCount||(this._incrementParticleCount(),c[e]=1,this._resetParticle(h),f=d*(h-a),c[e+1]=-1===g?c[e+2]-f:f,this._updateAttributeUpdateRange("params",h)); +},SPE.Emitter.prototype.tick=function(a){"use strict";if(!this.isStatic){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),this.alive===!1)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=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,a===!0){for(var b,c=this.attributeOffset,d=c+this.particleCount,e=this.paramsArray,f=this.attributes.params.bufferAttribute,g=d-1;g>=c;--g)b=4*g,e[b]=0,e[b+1]=0;f.updateRange.offset=0,f.updateRange.count=-1, +f.needsUpdate=!0}return this},SPE.Emitter.prototype.enable=function(){"use strict";return this.alive=!0,this},SPE.Emitter.prototype.disable=function(){"use strict";return this.alive=!1,this},SPE.Emitter.prototype.remove=function(){"use strict";return null!==this.group?this.group.removeEmitter(this):console.error("Emitter does not belong to a group, cannot remove."),this}; \ No newline at end of file diff --git a/assets/js/three.bas.js b/assets/js/three.bas.js new file mode 100644 index 00000000..768951d0 --- /dev/null +++ b/assets/js/three.bas.js @@ -0,0 +1,1595 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('three')) : + typeof define === 'function' && define.amd ? define(['exports', 'three'], factory) : + (factory((global.BAS = {}),global.THREE)); +}(this, (function (exports,three) { 'use strict'; + +function BaseAnimationMaterial(parameters, uniforms) { + three.ShaderMaterial.call(this); + + var uniformValues = parameters.uniformValues; + delete parameters.uniformValues; + + this.setValues(parameters); + + this.uniforms = three.UniformsUtils.merge([uniforms, this.uniforms]); + + this.setUniformValues(uniformValues); + + if (uniformValues) { + uniformValues.map && (this.defines['USE_MAP'] = ''); + uniformValues.normalMap && (this.defines['USE_NORMALMAP'] = ''); + uniformValues.envMap && (this.defines['USE_ENVMAP'] = ''); + uniformValues.aoMap && (this.defines['USE_AOMAP'] = ''); + uniformValues.specularMap && (this.defines['USE_SPECULARMAP'] = ''); + uniformValues.alphaMap && (this.defines['USE_ALPHAMAP'] = ''); + uniformValues.lightMap && (this.defines['USE_LIGHTMAP'] = ''); + uniformValues.emissiveMap && (this.defines['USE_EMISSIVEMAP'] = ''); + uniformValues.bumpMap && (this.defines['USE_BUMPMAP'] = ''); + uniformValues.displacementMap && (this.defines['USE_DISPLACEMENTMAP'] = ''); + uniformValues.roughnessMap && (this.defines['USE_DISPLACEMENTMAP'] = ''); + uniformValues.roughnessMap && (this.defines['USE_ROUGHNESSMAP'] = ''); + uniformValues.metalnessMap && (this.defines['USE_METALNESSMAP'] = ''); + + if (uniformValues.envMap) { + this.defines['USE_ENVMAP'] = ''; + + var envMapTypeDefine = 'ENVMAP_TYPE_CUBE'; + var envMapModeDefine = 'ENVMAP_MODE_REFLECTION'; + var envMapBlendingDefine = 'ENVMAP_BLENDING_MULTIPLY'; + + switch (uniformValues.envMap.mapping) { + case three.CubeReflectionMapping: + case three.CubeRefractionMapping: + envMapTypeDefine = 'ENVMAP_TYPE_CUBE'; + break; + case three.CubeUVReflectionMapping: + case three.CubeUVRefractionMapping: + envMapTypeDefine = 'ENVMAP_TYPE_CUBE_UV'; + break; + case three.EquirectangularReflectionMapping: + case three.EquirectangularRefractionMapping: + envMapTypeDefine = 'ENVMAP_TYPE_EQUIREC'; + break; + case three.SphericalReflectionMapping: + envMapTypeDefine = 'ENVMAP_TYPE_SPHERE'; + break; + } + + switch (uniformValues.envMap.mapping) { + case three.CubeRefractionMapping: + case three.EquirectangularRefractionMapping: + envMapModeDefine = 'ENVMAP_MODE_REFRACTION'; + break; + } + + switch (uniformValues.combine) { + case three.MixOperation: + envMapBlendingDefine = 'ENVMAP_BLENDING_MIX'; + break; + case three.AddOperation: + envMapBlendingDefine = 'ENVMAP_BLENDING_ADD'; + break; + case three.MultiplyOperation: + default: + envMapBlendingDefine = 'ENVMAP_BLENDING_MULTIPLY'; + break; + } + + this.defines[envMapTypeDefine] = ''; + this.defines[envMapBlendingDefine] = ''; + this.defines[envMapModeDefine] = ''; + } + } +} + +BaseAnimationMaterial.prototype = Object.assign(Object.create(three.ShaderMaterial.prototype), { + constructor: BaseAnimationMaterial, + + setUniformValues: function setUniformValues(values) { + var _this = this; + + if (!values) return; + + var keys = Object.keys(values); + + keys.forEach(function (key) { + key in _this.uniforms && (_this.uniforms[key].value = values[key]); + }); + }, + stringifyChunk: function stringifyChunk(name) { + var value = void 0; + + if (!this[name]) { + value = ''; + } else if (typeof this[name] === 'string') { + value = this[name]; + } else { + value = this[name].join('\n'); + } + + return value; + } +}); + +/** + * Extends THREE.MeshBasicMaterial with custom shader chunks. + * + * @see http://three-bas-examples.surge.sh/examples/materials_basic/ + * + * @param {Object} parameters Object containing material properties and custom shader chunks. + * @constructor + */ +function BasicAnimationMaterial(parameters) { + this.varyingParameters = []; + + this.vertexParameters = []; + this.vertexFunctions = []; + this.vertexInit = []; + this.vertexNormal = []; + this.vertexPosition = []; + this.vertexColor = []; + this.vertexPostMorph = []; + this.vertexPostSkinning = []; + + this.fragmentFunctions = []; + this.fragmentParameters = []; + this.fragmentInit = []; + this.fragmentMap = []; + this.fragmentDiffuse = []; + + BaseAnimationMaterial.call(this, parameters, three.ShaderLib['basic'].uniforms); + + this.lights = false; + this.vertexShader = this.concatVertexShader(); + this.fragmentShader = this.concatFragmentShader(); +} +BasicAnimationMaterial.prototype = Object.create(BaseAnimationMaterial.prototype); +BasicAnimationMaterial.prototype.constructor = BasicAnimationMaterial; + +BasicAnimationMaterial.prototype.concatVertexShader = function () { + return '\n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n \n ' + this.stringifyChunk('vertexParameters') + '\n ' + this.stringifyChunk('varyingParameters') + '\n ' + this.stringifyChunk('vertexFunctions') + '\n \n void main() {\n\n ' + this.stringifyChunk('vertexInit') + '\n \n #include \n #include \n #include \n #include \n \n #ifdef USE_ENVMAP\n \n #include \n \n ' + this.stringifyChunk('vertexNormal') + '\n \n #include \n #include \n #include \n \n #endif\n \n #include \n \n ' + this.stringifyChunk('vertexPosition') + '\n ' + this.stringifyChunk('vertexColor') + '\n \n #include \n \n ' + this.stringifyChunk('vertexPostMorph') + '\n \n #include \n\n ' + this.stringifyChunk('vertexPostSkinning') + '\n\n #include \n #include \n \n #include \n #include \n #include \n #include \n }'; +}; + +BasicAnimationMaterial.prototype.concatFragmentShader = function () { + return '\n uniform vec3 diffuse;\n uniform float opacity;\n \n ' + this.stringifyChunk('fragmentParameters') + '\n ' + this.stringifyChunk('varyingParameters') + '\n ' + this.stringifyChunk('fragmentFunctions') + '\n \n #ifndef FLAT_SHADED\n \n varying vec3 vNormal;\n \n #endif\n \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n \n void main() {\n \n ' + this.stringifyChunk('fragmentInit') + '\n \n #include \n\n vec4 diffuseColor = vec4( diffuse, opacity );\n\n ' + this.stringifyChunk('fragmentDiffuse') + '\n \n #include \n \n ' + (this.stringifyChunk('fragmentMap') || '#include ') + '\n \n #include \n #include \n #include \n #include \n \n ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n \n // accumulation (baked indirect lighting only)\n #ifdef USE_LIGHTMAP\n \n reflectedLight.indirectDiffuse += texture2D( lightMap, vUv2 ).xyz * lightMapIntensity;\n \n #else\n \n reflectedLight.indirectDiffuse += vec3( 1.0 );\n \n #endif\n \n // modulation\n #include \n \n reflectedLight.indirectDiffuse *= diffuseColor.rgb;\n \n vec3 outgoingLight = reflectedLight.indirectDiffuse;\n \n #include \n \n gl_FragColor = vec4( outgoingLight, diffuseColor.a );\n \n #include \n #include \n #include \n #include \n }'; +}; + +/** + * Extends THREE.MeshLambertMaterial with custom shader chunks. + * + * @see http://three-bas-examples.surge.sh/examples/materials_lambert/ + * + * @param {Object} parameters Object containing material properties and custom shader chunks. + * @constructor + */ +function LambertAnimationMaterial(parameters) { + this.varyingParameters = []; + + this.vertexFunctions = []; + this.vertexParameters = []; + this.vertexInit = []; + this.vertexNormal = []; + this.vertexPosition = []; + this.vertexColor = []; + this.vertexPostMorph = []; + this.vertexPostSkinning = []; + + this.fragmentFunctions = []; + this.fragmentParameters = []; + this.fragmentInit = []; + this.fragmentMap = []; + this.fragmentDiffuse = []; + this.fragmentEmissive = []; + this.fragmentSpecular = []; + + BaseAnimationMaterial.call(this, parameters, three.ShaderLib['lambert'].uniforms); + + this.lights = true; + this.vertexShader = this.concatVertexShader(); + this.fragmentShader = this.concatFragmentShader(); +} +LambertAnimationMaterial.prototype = Object.create(BaseAnimationMaterial.prototype); +LambertAnimationMaterial.prototype.constructor = LambertAnimationMaterial; + +LambertAnimationMaterial.prototype.concatVertexShader = function () { + return '\n #define LAMBERT\n\n varying vec3 vLightFront;\n \n #ifdef DOUBLE_SIDED\n \n varying vec3 vLightBack;\n \n #endif\n \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n \n ' + this.stringifyChunk('vertexParameters') + '\n ' + this.stringifyChunk('varyingParameters') + '\n ' + this.stringifyChunk('vertexFunctions') + '\n \n void main() {\n \n ' + this.stringifyChunk('vertexInit') + '\n \n #include \n #include \n #include \n \n #include \n \n ' + this.stringifyChunk('vertexNormal') + '\n \n #include \n #include \n #include \n #include \n \n #include \n \n ' + this.stringifyChunk('vertexPosition') + '\n ' + this.stringifyChunk('vertexColor') + '\n \n #include \n \n ' + this.stringifyChunk('vertexPostMorph') + '\n \n #include \n\n ' + this.stringifyChunk('vertexPostSkinning') + '\n \n #include \n #include \n #include \n \n #include \n #include \n #include \n #include \n #include \n }'; +}; + +LambertAnimationMaterial.prototype.concatFragmentShader = function () { + return '\n uniform vec3 diffuse;\n uniform vec3 emissive;\n uniform float opacity;\n \n varying vec3 vLightFront;\n \n #ifdef DOUBLE_SIDED\n \n varying vec3 vLightBack;\n \n #endif\n \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n \n ' + this.stringifyChunk('fragmentParameters') + '\n ' + this.stringifyChunk('varyingParameters') + '\n ' + this.stringifyChunk('fragmentFunctions') + '\n \n void main() {\n \n ' + this.stringifyChunk('fragmentInit') + '\n \n #include \n\n vec4 diffuseColor = vec4( diffuse, opacity );\n ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n vec3 totalEmissiveRadiance = emissive;\n\t\n ' + this.stringifyChunk('fragmentDiffuse') + '\n \n #include \n\n ' + (this.stringifyChunk('fragmentMap') || '#include ') + '\n\n #include \n #include \n #include \n #include \n\n ' + this.stringifyChunk('fragmentEmissive') + '\n\n #include \n \n // accumulation\n reflectedLight.indirectDiffuse = getAmbientLightIrradiance( ambientLightColor );\n \n #include \n \n reflectedLight.indirectDiffuse *= BRDF_Diffuse_Lambert( diffuseColor.rgb );\n \n #ifdef DOUBLE_SIDED\n \n reflectedLight.directDiffuse = ( gl_FrontFacing ) ? vLightFront : vLightBack;\n \n #else\n \n reflectedLight.directDiffuse = vLightFront;\n \n #endif\n \n reflectedLight.directDiffuse *= BRDF_Diffuse_Lambert( diffuseColor.rgb ) * getShadowMask();\n \n // modulation\n #include \n \n vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n \n #include \n \n gl_FragColor = vec4( outgoingLight, diffuseColor.a );\n \n #include \n #include \n #include \n #include \n #include \n }'; +}; + +/** + * Extends THREE.MeshPhongMaterial with custom shader chunks. + * + * @see http://three-bas-examples.surge.sh/examples/materials_phong/ + * + * @param {Object} parameters Object containing material properties and custom shader chunks. + * @constructor + */ +function PhongAnimationMaterial(parameters) { + this.varyingParameters = []; + + this.vertexFunctions = []; + this.vertexParameters = []; + this.vertexInit = []; + this.vertexNormal = []; + this.vertexPosition = []; + this.vertexColor = []; + + this.fragmentFunctions = []; + this.fragmentParameters = []; + this.fragmentInit = []; + this.fragmentMap = []; + this.fragmentDiffuse = []; + this.fragmentEmissive = []; + this.fragmentSpecular = []; + + BaseAnimationMaterial.call(this, parameters, three.ShaderLib['phong'].uniforms); + + this.lights = true; + this.vertexShader = this.concatVertexShader(); + this.fragmentShader = this.concatFragmentShader(); +} +PhongAnimationMaterial.prototype = Object.create(BaseAnimationMaterial.prototype); +PhongAnimationMaterial.prototype.constructor = PhongAnimationMaterial; + +PhongAnimationMaterial.prototype.concatVertexShader = function () { + return '\n #define PHONG\n\n varying vec3 vViewPosition;\n \n #ifndef FLAT_SHADED\n \n varying vec3 vNormal;\n \n #endif\n \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n \n ' + this.stringifyChunk('vertexParameters') + '\n ' + this.stringifyChunk('varyingParameters') + '\n ' + this.stringifyChunk('vertexFunctions') + '\n \n void main() {\n \n ' + this.stringifyChunk('vertexInit') + '\n \n #include \n #include \n #include \n \n #include \n \n ' + this.stringifyChunk('vertexNormal') + '\n \n #include \n #include \n #include \n #include \n \n #ifndef FLAT_SHADED // Normal computed with derivatives when FLAT_SHADED\n \n vNormal = normalize( transformedNormal );\n \n #endif\n \n #include \n \n ' + this.stringifyChunk('vertexPosition') + '\n ' + this.stringifyChunk('vertexColor') + '\n \n #include \n #include \n #include \n #include \n #include \n #include \n \n vViewPosition = - mvPosition.xyz;\n \n #include \n #include \n #include \n #include \n }'; +}; + +PhongAnimationMaterial.prototype.concatFragmentShader = function () { + return '\n #define PHONG\n\n uniform vec3 diffuse;\n uniform vec3 emissive;\n uniform vec3 specular;\n uniform float shininess;\n uniform float opacity;\n \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n \n ' + this.stringifyChunk('fragmentParameters') + '\n ' + this.stringifyChunk('varyingParameters') + '\n ' + this.stringifyChunk('fragmentFunctions') + '\n \n void main() {\n \n ' + this.stringifyChunk('fragmentInit') + '\n \n #include \n \n vec4 diffuseColor = vec4( diffuse, opacity );\n ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n vec3 totalEmissiveRadiance = emissive;\n \n ' + this.stringifyChunk('fragmentDiffuse') + '\n \n #include \n\n ' + (this.stringifyChunk('fragmentMap') || '#include ') + '\n\n #include \n #include \n #include \n #include \n #include \n #include \n \n ' + this.stringifyChunk('fragmentEmissive') + '\n \n #include \n \n // accumulation\n #include \n #include \n #include \n #include \n \n ' + this.stringifyChunk('fragmentSpecular') + '\n \n // modulation\n #include \n \n vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\n \n #include \n \n gl_FragColor = vec4( outgoingLight, diffuseColor.a );\n \n #include \n #include \n #include \n #include \n #include \n \n }'; +}; + +/** + * Extends THREE.MeshStandardMaterial with custom shader chunks. + * + * @see http://three-bas-examples.surge.sh/examples/materials_standard/ + * + * @param {Object} parameters Object containing material properties and custom shader chunks. + * @constructor + */ +function StandardAnimationMaterial(parameters) { + this.varyingParameters = []; + + this.vertexFunctions = []; + this.vertexParameters = []; + this.vertexInit = []; + this.vertexNormal = []; + this.vertexPosition = []; + this.vertexColor = []; + this.vertexPostMorph = []; + this.vertexPostSkinning = []; + + this.fragmentFunctions = []; + this.fragmentParameters = []; + this.fragmentInit = []; + this.fragmentMap = []; + this.fragmentDiffuse = []; + this.fragmentRoughness = []; + this.fragmentMetalness = []; + this.fragmentEmissive = []; + + BaseAnimationMaterial.call(this, parameters, three.ShaderLib['standard'].uniforms); + + this.lights = true; + this.vertexShader = this.concatVertexShader(); + this.fragmentShader = this.concatFragmentShader(); +} +StandardAnimationMaterial.prototype = Object.create(BaseAnimationMaterial.prototype); +StandardAnimationMaterial.prototype.constructor = StandardAnimationMaterial; + +StandardAnimationMaterial.prototype.concatVertexShader = function () { + return '\n #define PHYSICAL\n\n varying vec3 vViewPosition;\n \n #ifndef FLAT_SHADED\n \n varying vec3 vNormal;\n \n #endif\n \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n \n ' + this.stringifyChunk('vertexParameters') + '\n ' + this.stringifyChunk('varyingParameters') + '\n ' + this.stringifyChunk('vertexFunctions') + '\n \n void main() {\n\n ' + this.stringifyChunk('vertexInit') + '\n\n #include \n #include \n #include \n \n #include \n \n ' + this.stringifyChunk('vertexNormal') + '\n \n #include \n #include \n #include \n #include \n \n #ifndef FLAT_SHADED // Normal computed with derivatives when FLAT_SHADED\n \n vNormal = normalize( transformedNormal );\n \n #endif\n \n #include \n \n ' + this.stringifyChunk('vertexPosition') + '\n ' + this.stringifyChunk('vertexColor') + '\n \n #include \n \n ' + this.stringifyChunk('vertexPostMorph') + '\n \n #include \n\n ' + this.stringifyChunk('vertexPostSkinning') + '\n \n #include \n #include \n #include \n #include \n \n vViewPosition = - mvPosition.xyz;\n \n #include \n #include \n #include \n }'; +}; + +StandardAnimationMaterial.prototype.concatFragmentShader = function () { + return '\n #define PHYSICAL\n \n uniform vec3 diffuse;\n uniform vec3 emissive;\n uniform float roughness;\n uniform float metalness;\n uniform float opacity;\n \n #ifndef STANDARD\n uniform float clearCoat;\n uniform float clearCoatRoughness;\n #endif\n \n varying vec3 vViewPosition;\n \n #ifndef FLAT_SHADED\n \n varying vec3 vNormal;\n \n #endif\n \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n \n ' + this.stringifyChunk('fragmentParameters') + '\n ' + this.stringifyChunk('varyingParameters') + '\n ' + this.stringifyChunk('fragmentFunctions') + '\n \n void main() {\n \n ' + this.stringifyChunk('fragmentInit') + '\n \n #include \n \n vec4 diffuseColor = vec4( diffuse, opacity );\n ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n vec3 totalEmissiveRadiance = emissive;\n \n ' + this.stringifyChunk('fragmentDiffuse') + '\n \n #include \n\n ' + (this.stringifyChunk('fragmentMap') || '#include ') + '\n\n #include \n #include \n #include \n \n float roughnessFactor = roughness;\n ' + this.stringifyChunk('fragmentRoughness') + '\n #ifdef USE_ROUGHNESSMAP\n \n vec4 texelRoughness = texture2D( roughnessMap, vUv );\n \n // reads channel G, compatible with a combined OcclusionRoughnessMetallic (RGB) texture\n roughnessFactor *= texelRoughness.g;\n \n #endif\n \n float metalnessFactor = metalness;\n ' + this.stringifyChunk('fragmentMetalness') + '\n #ifdef USE_METALNESSMAP\n \n vec4 texelMetalness = texture2D( metalnessMap, vUv );\n metalnessFactor *= texelMetalness.b;\n \n #endif\n \n #include \n #include \n \n ' + this.stringifyChunk('fragmentEmissive') + '\n \n #include \n \n // accumulation\n #include \n #include \n #include \n #include \n \n // modulation\n #include \n \n vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\n \n gl_FragColor = vec4( outgoingLight, diffuseColor.a );\n \n #include \n #include \n #include \n #include \n #include \n \n }'; +}; + +/** + * Extends THREE.PointsMaterial with custom shader chunks. + * + * @param {Object} parameters Object containing material properties and custom shader chunks. + * @constructor + */ +function PointsAnimationMaterial(parameters) { + this.varyingParameters = []; + + this.vertexFunctions = []; + this.vertexParameters = []; + this.vertexInit = []; + this.vertexPosition = []; + this.vertexColor = []; + + this.fragmentFunctions = []; + this.fragmentParameters = []; + this.fragmentInit = []; + this.fragmentMap = []; + this.fragmentDiffuse = []; + // use fragment shader to shape to point, reference: https://thebookofshaders.com/07/ + this.fragmentShape = []; + + BaseAnimationMaterial.call(this, parameters, three.ShaderLib['points'].uniforms); + + this.vertexShader = this.concatVertexShader(); + this.fragmentShader = this.concatFragmentShader(); +} + +PointsAnimationMaterial.prototype = Object.create(BaseAnimationMaterial.prototype); +PointsAnimationMaterial.prototype.constructor = PointsAnimationMaterial; + +PointsAnimationMaterial.prototype.concatVertexShader = function () { + return '\n uniform float size;\n uniform float scale;\n \n #include \n #include \n #include \n #include \n #include \n #include \n \n ' + this.stringifyChunk('vertexParameters') + '\n ' + this.stringifyChunk('varyingParameters') + '\n ' + this.stringifyChunk('vertexFunctions') + '\n \n void main() {\n \n ' + this.stringifyChunk('vertexInit') + '\n \n #include \n #include \n \n ' + this.stringifyChunk('vertexPosition') + '\n ' + this.stringifyChunk('vertexColor') + '\n \n #include \n \n #ifdef USE_SIZEATTENUATION\n gl_PointSize = size * ( scale / - mvPosition.z );\n #else\n gl_PointSize = size;\n #endif\n \n #include \n #include \n #include \n #include \n #include \n }'; +}; + +PointsAnimationMaterial.prototype.concatFragmentShader = function () { + return '\n uniform vec3 diffuse;\n uniform float opacity;\n \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n \n ' + this.stringifyChunk('fragmentParameters') + '\n ' + this.stringifyChunk('varyingParameters') + '\n ' + this.stringifyChunk('fragmentFunctions') + '\n \n void main() {\n \n ' + this.stringifyChunk('fragmentInit') + '\n \n #include \n \n vec3 outgoingLight = vec3( 0.0 );\n vec4 diffuseColor = vec4( diffuse, opacity );\n \n ' + this.stringifyChunk('fragmentDiffuse') + '\n \n #include \n\n ' + (this.stringifyChunk('fragmentMap') || '#include ') + '\n\n #include \n #include \n \n outgoingLight = diffuseColor.rgb;\n \n gl_FragColor = vec4( outgoingLight, diffuseColor.a );\n \n ' + this.stringifyChunk('fragmentShape') + '\n \n #include \n #include \n #include \n #include \n }'; +}; + +function DepthAnimationMaterial(parameters) { + this.depthPacking = three.RGBADepthPacking; + this.clipping = true; + + this.vertexFunctions = []; + this.vertexParameters = []; + this.vertexInit = []; + this.vertexPosition = []; + this.vertexPostMorph = []; + this.vertexPostSkinning = []; + + BaseAnimationMaterial.call(this, parameters); + + this.uniforms = three.UniformsUtils.merge([three.ShaderLib['depth'].uniforms, this.uniforms]); + this.vertexShader = this.concatVertexShader(); + this.fragmentShader = three.ShaderLib['depth'].fragmentShader; +} +DepthAnimationMaterial.prototype = Object.create(BaseAnimationMaterial.prototype); +DepthAnimationMaterial.prototype.constructor = DepthAnimationMaterial; + +DepthAnimationMaterial.prototype.concatVertexShader = function () { + + return '\n #include \n #include \n #include \n #include \n #include \n #include \n #include \n \n ' + this.stringifyChunk('vertexParameters') + '\n ' + this.stringifyChunk('vertexFunctions') + '\n \n void main() {\n \n ' + this.stringifyChunk('vertexInit') + '\n \n #include \n \n #include \n \n #ifdef USE_DISPLACEMENTMAP\n \n #include \n #include \n #include \n \n #endif\n \n #include \n \n ' + this.stringifyChunk('vertexPosition') + '\n\n #include \n \n ' + this.stringifyChunk('vertexPostMorph') + '\n \n #include \n\n ' + this.stringifyChunk('vertexPostSkinning') + '\n \n #include \n #include \n #include \n #include \n }'; +}; + +function DistanceAnimationMaterial(parameters) { + this.depthPacking = three.RGBADepthPacking; + this.clipping = true; + + this.vertexFunctions = []; + this.vertexParameters = []; + this.vertexInit = []; + this.vertexPosition = []; + this.vertexPostMorph = []; + this.vertexPostSkinning = []; + + BaseAnimationMaterial.call(this, parameters); + + this.uniforms = three.UniformsUtils.merge([three.ShaderLib['distanceRGBA'].uniforms, this.uniforms]); + this.vertexShader = this.concatVertexShader(); + this.fragmentShader = three.ShaderLib['distanceRGBA'].fragmentShader; +} +DistanceAnimationMaterial.prototype = Object.create(BaseAnimationMaterial.prototype); +DistanceAnimationMaterial.prototype.constructor = DistanceAnimationMaterial; + +DistanceAnimationMaterial.prototype.concatVertexShader = function () { + return '\n #define DISTANCE\n\n varying vec3 vWorldPosition;\n \n #include \n #include \n #include \n #include \n #include \n #include \n \n ' + this.stringifyChunk('vertexParameters') + '\n ' + this.stringifyChunk('vertexFunctions') + '\n \n void main() {\n\n ' + this.stringifyChunk('vertexInit') + '\n \n #include \n \n #include \n \n #ifdef USE_DISPLACEMENTMAP\n \n #include \n #include \n #include \n \n #endif\n \n #include \n \n ' + this.stringifyChunk('vertexPosition') + '\n\n #include \n \n ' + this.stringifyChunk('vertexPostMorph') + '\n \n #include \n\n ' + this.stringifyChunk('vertexPostSkinning') + '\n \n #include \n #include \n #include \n #include \n \n vWorldPosition = worldPosition.xyz;\n \n }'; +}; + +/** + * A BufferGeometry where a 'prefab' geometry is repeated a number of times. + * + * @param {Geometry|BufferGeometry} prefab The Geometry instance to repeat. + * @param {Number} count The number of times to repeat the geometry. + * @constructor + */ +function PrefabBufferGeometry(prefab, count) { + three.BufferGeometry.call(this); + + /** + * A reference to the prefab geometry used to create this instance. + * @type {Geometry|BufferGeometry} + */ + this.prefabGeometry = prefab; + this.isPrefabBufferGeometry = prefab.isBufferGeometry; + + /** + * Number of prefabs. + * @type {Number} + */ + this.prefabCount = count; + + /** + * Number of vertices of the prefab. + * @type {Number} + */ + if (this.isPrefabBufferGeometry) { + this.prefabVertexCount = prefab.attributes.position.count; + } else { + this.prefabVertexCount = prefab.vertices.length; + } + + this.bufferIndices(); + this.bufferPositions(); +} +PrefabBufferGeometry.prototype = Object.create(three.BufferGeometry.prototype); +PrefabBufferGeometry.prototype.constructor = PrefabBufferGeometry; + +PrefabBufferGeometry.prototype.bufferIndices = function () { + var prefabIndices = []; + var prefabIndexCount = void 0; + + if (this.isPrefabBufferGeometry) { + if (this.prefabGeometry.index) { + prefabIndexCount = this.prefabGeometry.index.count; + prefabIndices = this.prefabGeometry.index.array; + } else { + prefabIndexCount = this.prefabVertexCount; + + for (var i = 0; i < prefabIndexCount; i++) { + prefabIndices.push(i); + } + } + } else { + var prefabFaceCount = this.prefabGeometry.faces.length; + prefabIndexCount = prefabFaceCount * 3; + + for (var _i = 0; _i < prefabFaceCount; _i++) { + var face = this.prefabGeometry.faces[_i]; + prefabIndices.push(face.a, face.b, face.c); + } + } + + var indexBuffer = new Uint32Array(this.prefabCount * prefabIndexCount); + + this.setIndex(new three.BufferAttribute(indexBuffer, 1)); + + for (var _i2 = 0; _i2 < this.prefabCount; _i2++) { + for (var k = 0; k < prefabIndexCount; k++) { + indexBuffer[_i2 * prefabIndexCount + k] = prefabIndices[k] + _i2 * this.prefabVertexCount; + } + } +}; + +PrefabBufferGeometry.prototype.bufferPositions = function () { + var positionBuffer = this.createAttribute('position', 3).array; + + if (this.isPrefabBufferGeometry) { + var positions = this.prefabGeometry.attributes.position.array; + + for (var i = 0, offset = 0; i < this.prefabCount; i++) { + for (var j = 0; j < this.prefabVertexCount; j++, offset += 3) { + positionBuffer[offset] = positions[j * 3]; + positionBuffer[offset + 1] = positions[j * 3 + 1]; + positionBuffer[offset + 2] = positions[j * 3 + 2]; + } + } + } else { + for (var _i3 = 0, _offset = 0; _i3 < this.prefabCount; _i3++) { + for (var _j = 0; _j < this.prefabVertexCount; _j++, _offset += 3) { + var prefabVertex = this.prefabGeometry.vertices[_j]; + + positionBuffer[_offset] = prefabVertex.x; + positionBuffer[_offset + 1] = prefabVertex.y; + positionBuffer[_offset + 2] = prefabVertex.z; + } + } + } +}; + +/** + * Creates a BufferAttribute with UV coordinates. + */ +PrefabBufferGeometry.prototype.bufferUvs = function () { + var prefabUvs = []; + + if (this.isPrefabBufferGeometry) { + var uv = this.prefabGeometry.attributes.uv.array; + + for (var i = 0; i < this.prefabVertexCount; i++) { + prefabUvs.push(new three.Vector2(uv[i * 2], uv[i * 2 + 1])); + } + } else { + var prefabFaceCount = this.prefabGeometry.faces.length; + + for (var _i4 = 0; _i4 < prefabFaceCount; _i4++) { + var face = this.prefabGeometry.faces[_i4]; + var _uv = this.prefabGeometry.faceVertexUvs[0][_i4]; + + prefabUvs[face.a] = _uv[0]; + prefabUvs[face.b] = _uv[1]; + prefabUvs[face.c] = _uv[2]; + } + } + + var uvBuffer = this.createAttribute('uv', 2); + + for (var _i5 = 0, offset = 0; _i5 < this.prefabCount; _i5++) { + for (var j = 0; j < this.prefabVertexCount; j++, offset += 2) { + var prefabUv = prefabUvs[j]; + + uvBuffer.array[offset] = prefabUv.x; + uvBuffer.array[offset + 1] = prefabUv.y; + } + } +}; + +/** + * Creates a BufferAttribute on this geometry instance. + * + * @param {String} name Name of the attribute. + * @param {Number} itemSize Number of floats per vertex (typically 1, 2, 3 or 4). + * @param {function=} factory Function that will be called for each prefab upon creation. Accepts 3 arguments: data[], index and prefabCount. Calls setPrefabData. + * + * @returns {BufferAttribute} + */ +PrefabBufferGeometry.prototype.createAttribute = function (name, itemSize, factory) { + var buffer = new Float32Array(this.prefabCount * this.prefabVertexCount * itemSize); + var attribute = new three.BufferAttribute(buffer, itemSize); + + this.addAttribute(name, attribute); + + if (factory) { + var data = []; + + for (var i = 0; i < this.prefabCount; i++) { + factory(data, i, this.prefabCount); + this.setPrefabData(attribute, i, data); + } + } + + return attribute; +}; + +/** + * Sets data for all vertices of a prefab at a given index. + * Usually called in a loop. + * + * @param {String|BufferAttribute} attribute The attribute or attribute name where the data is to be stored. + * @param {Number} prefabIndex Index of the prefab in the buffer geometry. + * @param {Array} data Array of data. Length should be equal to item size of the attribute. + */ +PrefabBufferGeometry.prototype.setPrefabData = function (attribute, prefabIndex, data) { + attribute = typeof attribute === 'string' ? this.attributes[attribute] : attribute; + + var offset = prefabIndex * this.prefabVertexCount * attribute.itemSize; + + for (var i = 0; i < this.prefabVertexCount; i++) { + for (var j = 0; j < attribute.itemSize; j++) { + attribute.array[offset++] = data[j]; + } + } +}; + +/** + * A BufferGeometry where a 'prefab' geometry array is repeated a number of times. + * + * @param {Array} prefabs An array with Geometry instances to repeat. + * @param {Number} repeatCount The number of times to repeat the array of Geometries. + * @constructor + */ +function MultiPrefabBufferGeometry(prefabs, repeatCount) { + three.BufferGeometry.call(this); + + if (Array.isArray(prefabs)) { + this.prefabGeometries = prefabs; + } else { + this.prefabGeometries = [prefabs]; + } + + this.prefabGeometriesCount = this.prefabGeometries.length; + + /** + * Number of prefabs. + * @type {Number} + */ + this.prefabCount = repeatCount * this.prefabGeometriesCount; + /** + * How often the prefab array is repeated. + * @type {Number} + */ + this.repeatCount = repeatCount; + + /** + * Array of vertex counts per prefab. + * @type {Array} + */ + this.prefabVertexCounts = this.prefabGeometries.map(function (p) { + return p.isBufferGeometry ? p.attributes.position.count : p.vertices.length; + }); + /** + * Total number of vertices for one repetition of the prefabs + * @type {number} + */ + this.repeatVertexCount = this.prefabVertexCounts.reduce(function (r, v) { + return r + v; + }, 0); + + this.bufferIndices(); + this.bufferPositions(); +} +MultiPrefabBufferGeometry.prototype = Object.create(three.BufferGeometry.prototype); +MultiPrefabBufferGeometry.prototype.constructor = MultiPrefabBufferGeometry; + +MultiPrefabBufferGeometry.prototype.bufferIndices = function () { + var repeatIndexCount = 0; + + this.prefabIndices = this.prefabGeometries.map(function (geometry) { + var indices = []; + + if (geometry.isBufferGeometry) { + if (geometry.index) { + indices = geometry.index.array; + } else { + for (var i = 0; i < geometry.attributes.position.count; i++) { + indices.push(i); + } + } + } else { + for (var _i = 0; _i < geometry.faces.length; _i++) { + var face = geometry.faces[_i]; + indices.push(face.a, face.b, face.c); + } + } + + repeatIndexCount += indices.length; + + return indices; + }); + + var indexBuffer = new Uint32Array(repeatIndexCount * this.repeatCount); + var indexOffset = 0; + var prefabOffset = 0; + + for (var i = 0; i < this.prefabCount; i++) { + var index = i % this.prefabGeometriesCount; + var indices = this.prefabIndices[index]; + var vertexCount = this.prefabVertexCounts[index]; + + for (var j = 0; j < indices.length; j++) { + indexBuffer[indexOffset++] = indices[j] + prefabOffset; + } + + prefabOffset += vertexCount; + } + + this.setIndex(new three.BufferAttribute(indexBuffer, 1)); +}; + +MultiPrefabBufferGeometry.prototype.bufferPositions = function () { + var _this = this; + + var positionBuffer = this.createAttribute('position', 3).array; + + var prefabPositions = this.prefabGeometries.map(function (geometry, i) { + var positions = void 0; + + if (geometry.isBufferGeometry) { + positions = geometry.attributes.position.array; + } else { + + var vertexCount = _this.prefabVertexCounts[i]; + + positions = []; + + for (var j = 0, offset = 0; j < vertexCount; j++) { + var prefabVertex = geometry.vertices[j]; + + positions[offset++] = prefabVertex.x; + positions[offset++] = prefabVertex.y; + positions[offset++] = prefabVertex.z; + } + } + + return positions; + }); + + for (var i = 0, offset = 0; i < this.prefabCount; i++) { + var index = i % this.prefabGeometries.length; + var vertexCount = this.prefabVertexCounts[index]; + var positions = prefabPositions[index]; + + for (var j = 0; j < vertexCount; j++) { + positionBuffer[offset++] = positions[j * 3]; + positionBuffer[offset++] = positions[j * 3 + 1]; + positionBuffer[offset++] = positions[j * 3 + 2]; + } + } +}; + +/** + * Creates a BufferAttribute with UV coordinates. + */ +MultiPrefabBufferGeometry.prototype.bufferUvs = function () { + var _this2 = this; + + var uvBuffer = this.createAttribute('uv', 2).array; + + var prefabUvs = this.prefabGeometries.map(function (geometry, i) { + var uvs = void 0; + + if (geometry.isBufferGeometry) { + if (!geometry.attributes.uv) { + console.error('No UV found in prefab geometry', geometry); + } + + uvs = geometry.attributes.uv.array; + } else { + var prefabFaceCount = _this2.prefabIndices[i].length / 3; + var uvObjects = []; + + for (var j = 0; j < prefabFaceCount; j++) { + var face = geometry.faces[j]; + var uv = geometry.faceVertexUvs[0][j]; + + uvObjects[face.a] = uv[0]; + uvObjects[face.b] = uv[1]; + uvObjects[face.c] = uv[2]; + } + + uvs = []; + + for (var k = 0; k < uvObjects.length; k++) { + uvs[k * 2] = uvObjects[k].x; + uvs[k * 2 + 1] = uvObjects[k].y; + } + } + + return uvs; + }); + + for (var i = 0, offset = 0; i < this.prefabCount; i++) { + + var index = i % this.prefabGeometries.length; + var vertexCount = this.prefabVertexCounts[index]; + var uvs = prefabUvs[index]; + + for (var j = 0; j < vertexCount; j++) { + uvBuffer[offset++] = uvs[j * 2]; + uvBuffer[offset++] = uvs[j * 2 + 1]; + } + } +}; + +/** + * Creates a BufferAttribute on this geometry instance. + * + * @param {String} name Name of the attribute. + * @param {Number} itemSize Number of floats per vertex (typically 1, 2, 3 or 4). + * @param {function=} factory Function that will be called for each prefab upon creation. Accepts 3 arguments: data[], index and prefabCount. Calls setPrefabData. + * + * @returns {BufferAttribute} + */ +MultiPrefabBufferGeometry.prototype.createAttribute = function (name, itemSize, factory) { + var buffer = new Float32Array(this.repeatCount * this.repeatVertexCount * itemSize); + var attribute = new three.BufferAttribute(buffer, itemSize); + + this.addAttribute(name, attribute); + + if (factory) { + var data = []; + + for (var i = 0; i < this.prefabCount; i++) { + factory(data, i, this.prefabCount); + this.setPrefabData(attribute, i, data); + } + } + + return attribute; +}; + +/** + * Sets data for all vertices of a prefab at a given index. + * Usually called in a loop. + * + * @param {String|BufferAttribute} attribute The attribute or attribute name where the data is to be stored. + * @param {Number} prefabIndex Index of the prefab in the buffer geometry. + * @param {Array} data Array of data. Length should be equal to item size of the attribute. + */ +MultiPrefabBufferGeometry.prototype.setPrefabData = function (attribute, prefabIndex, data) { + attribute = typeof attribute === 'string' ? this.attributes[attribute] : attribute; + + var prefabGeometryIndex = prefabIndex % this.prefabGeometriesCount; + var prefabGeometryVertexCount = this.prefabVertexCounts[prefabGeometryIndex]; + var whole = (prefabIndex / this.prefabGeometriesCount | 0) * this.prefabGeometriesCount; + var wholeOffset = whole * this.repeatVertexCount; + var part = prefabIndex - whole; + var partOffset = 0; + var i = 0; + + while (i < part) { + partOffset += this.prefabVertexCounts[i++]; + } + + var offset = (wholeOffset + partOffset) * attribute.itemSize; + + for (var _i2 = 0; _i2 < prefabGeometryVertexCount; _i2++) { + for (var j = 0; j < attribute.itemSize; j++) { + attribute.array[offset++] = data[j]; + } + } +}; + +/** + * Collection of utility functions. + * @namespace + */ +var Utils = { + /** + * Duplicates vertices so each face becomes separate. + * Same as THREE.ExplodeModifier. + * + * @param {THREE.Geometry} geometry Geometry instance to modify. + */ + separateFaces: function separateFaces(geometry) { + var vertices = []; + + for (var i = 0, il = geometry.faces.length; i < il; i++) { + var n = vertices.length; + var face = geometry.faces[i]; + + var a = face.a; + var b = face.b; + var c = face.c; + + var va = geometry.vertices[a]; + var vb = geometry.vertices[b]; + var vc = geometry.vertices[c]; + + vertices.push(va.clone()); + vertices.push(vb.clone()); + vertices.push(vc.clone()); + + face.a = n; + face.b = n + 1; + face.c = n + 2; + } + + geometry.vertices = vertices; + }, + + /** + * Compute the centroid (center) of a THREE.Face3. + * + * @param {THREE.Geometry} geometry Geometry instance the face is in. + * @param {THREE.Face3} face Face object from the THREE.Geometry.faces array + * @param {THREE.Vector3=} v Optional vector to store result in. + * @returns {THREE.Vector3} + */ + computeCentroid: function computeCentroid(geometry, face, v) { + var a = geometry.vertices[face.a]; + var b = geometry.vertices[face.b]; + var c = geometry.vertices[face.c]; + + v = v || new three.Vector3(); + + v.x = (a.x + b.x + c.x) / 3; + v.y = (a.y + b.y + c.y) / 3; + v.z = (a.z + b.z + c.z) / 3; + + return v; + }, + + /** + * Get a random vector between box.min and box.max. + * + * @param {THREE.Box3} box THREE.Box3 instance. + * @param {THREE.Vector3=} v Optional vector to store result in. + * @returns {THREE.Vector3} + */ + randomInBox: function randomInBox(box, v) { + v = v || new three.Vector3(); + + v.x = three.Math.randFloat(box.min.x, box.max.x); + v.y = three.Math.randFloat(box.min.y, box.max.y); + v.z = three.Math.randFloat(box.min.z, box.max.z); + + return v; + }, + + /** + * Get a random axis for quaternion rotation. + * + * @param {THREE.Vector3=} v Option vector to store result in. + * @returns {THREE.Vector3} + */ + randomAxis: function randomAxis(v) { + v = v || new three.Vector3(); + + v.x = three.Math.randFloatSpread(2.0); + v.y = three.Math.randFloatSpread(2.0); + v.z = three.Math.randFloatSpread(2.0); + v.normalize(); + + return v; + }, + + /** + * Create a THREE.BAS.DepthAnimationMaterial for shadows from a THREE.SpotLight or THREE.DirectionalLight by copying relevant shader chunks. + * Uniform values must be manually synced between the source material and the depth material. + * + * @see {@link http://three-bas-examples.surge.sh/examples/shadows/} + * + * @param {THREE.BAS.BaseAnimationMaterial} sourceMaterial Instance to get the shader chunks from. + * @returns {THREE.BAS.DepthAnimationMaterial} + */ + createDepthAnimationMaterial: function createDepthAnimationMaterial(sourceMaterial) { + return new DepthAnimationMaterial({ + uniforms: sourceMaterial.uniforms, + defines: sourceMaterial.defines, + vertexFunctions: sourceMaterial.vertexFunctions, + vertexParameters: sourceMaterial.vertexParameters, + vertexInit: sourceMaterial.vertexInit, + vertexPosition: sourceMaterial.vertexPosition + }); + }, + + /** + * Create a THREE.BAS.DistanceAnimationMaterial for shadows from a THREE.PointLight by copying relevant shader chunks. + * Uniform values must be manually synced between the source material and the distance material. + * + * @see {@link http://three-bas-examples.surge.sh/examples/shadows/} + * + * @param {THREE.BAS.BaseAnimationMaterial} sourceMaterial Instance to get the shader chunks from. + * @returns {THREE.BAS.DistanceAnimationMaterial} + */ + createDistanceAnimationMaterial: function createDistanceAnimationMaterial(sourceMaterial) { + return new DistanceAnimationMaterial({ + uniforms: sourceMaterial.uniforms, + defines: sourceMaterial.defines, + vertexFunctions: sourceMaterial.vertexFunctions, + vertexParameters: sourceMaterial.vertexParameters, + vertexInit: sourceMaterial.vertexInit, + vertexPosition: sourceMaterial.vertexPosition + }); + } +}; + +/** + * A THREE.BufferGeometry for animating individual faces of a THREE.Geometry. + * + * @param {THREE.Geometry} model The THREE.Geometry to base this geometry on. + * @param {Object=} options + * @param {Boolean=} options.computeCentroids If true, a centroids will be computed for each face and stored in THREE.BAS.ModelBufferGeometry.centroids. + * @param {Boolean=} options.localizeFaces If true, the positions for each face will be stored relative to the centroid. This is useful if you want to rotate or scale faces around their center. + * @constructor + */ +function ModelBufferGeometry(model, options) { + three.BufferGeometry.call(this); + + /** + * A reference to the geometry used to create this instance. + * @type {THREE.Geometry} + */ + this.modelGeometry = model; + + /** + * Number of faces of the model. + * @type {Number} + */ + this.faceCount = this.modelGeometry.faces.length; + + /** + * Number of vertices of the model. + * @type {Number} + */ + this.vertexCount = this.modelGeometry.vertices.length; + + options = options || {}; + options.computeCentroids && this.computeCentroids(); + + this.bufferIndices(); + this.bufferPositions(options.localizeFaces); +} +ModelBufferGeometry.prototype = Object.create(three.BufferGeometry.prototype); +ModelBufferGeometry.prototype.constructor = ModelBufferGeometry; + +/** + * Computes a centroid for each face and stores it in THREE.BAS.ModelBufferGeometry.centroids. + */ +ModelBufferGeometry.prototype.computeCentroids = function () { + /** + * An array of centroids corresponding to the faces of the model. + * + * @type {Array} + */ + this.centroids = []; + + for (var i = 0; i < this.faceCount; i++) { + this.centroids[i] = Utils.computeCentroid(this.modelGeometry, this.modelGeometry.faces[i]); + } +}; + +ModelBufferGeometry.prototype.bufferIndices = function () { + var indexBuffer = new Uint32Array(this.faceCount * 3); + + this.setIndex(new three.BufferAttribute(indexBuffer, 1)); + + for (var i = 0, offset = 0; i < this.faceCount; i++, offset += 3) { + var face = this.modelGeometry.faces[i]; + + indexBuffer[offset] = face.a; + indexBuffer[offset + 1] = face.b; + indexBuffer[offset + 2] = face.c; + } +}; + +ModelBufferGeometry.prototype.bufferPositions = function (localizeFaces) { + var positionBuffer = this.createAttribute('position', 3).array; + var i = void 0, + offset = void 0; + + if (localizeFaces === true) { + for (i = 0; i < this.faceCount; i++) { + var face = this.modelGeometry.faces[i]; + var centroid = this.centroids ? this.centroids[i] : Utils.computeCentroid(this.modelGeometry, face); + + var a = this.modelGeometry.vertices[face.a]; + var b = this.modelGeometry.vertices[face.b]; + var c = this.modelGeometry.vertices[face.c]; + + positionBuffer[face.a * 3] = a.x - centroid.x; + positionBuffer[face.a * 3 + 1] = a.y - centroid.y; + positionBuffer[face.a * 3 + 2] = a.z - centroid.z; + + positionBuffer[face.b * 3] = b.x - centroid.x; + positionBuffer[face.b * 3 + 1] = b.y - centroid.y; + positionBuffer[face.b * 3 + 2] = b.z - centroid.z; + + positionBuffer[face.c * 3] = c.x - centroid.x; + positionBuffer[face.c * 3 + 1] = c.y - centroid.y; + positionBuffer[face.c * 3 + 2] = c.z - centroid.z; + } + } else { + for (i = 0, offset = 0; i < this.vertexCount; i++, offset += 3) { + var vertex = this.modelGeometry.vertices[i]; + + positionBuffer[offset] = vertex.x; + positionBuffer[offset + 1] = vertex.y; + positionBuffer[offset + 2] = vertex.z; + } + } +}; + +/** + * Creates a THREE.BufferAttribute with UV coordinates. + */ +ModelBufferGeometry.prototype.bufferUVs = function () { + var uvBuffer = this.createAttribute('uv', 2).array; + + for (var i = 0; i < this.faceCount; i++) { + + var face = this.modelGeometry.faces[i]; + var uv = void 0; + + uv = this.modelGeometry.faceVertexUvs[0][i][0]; + uvBuffer[face.a * 2] = uv.x; + uvBuffer[face.a * 2 + 1] = uv.y; + + uv = this.modelGeometry.faceVertexUvs[0][i][1]; + uvBuffer[face.b * 2] = uv.x; + uvBuffer[face.b * 2 + 1] = uv.y; + + uv = this.modelGeometry.faceVertexUvs[0][i][2]; + uvBuffer[face.c * 2] = uv.x; + uvBuffer[face.c * 2 + 1] = uv.y; + } +}; + +/** + * Creates two THREE.BufferAttributes: skinIndex and skinWeight. Both are required for skinning. + */ +ModelBufferGeometry.prototype.bufferSkinning = function () { + var skinIndexBuffer = this.createAttribute('skinIndex', 4).array; + var skinWeightBuffer = this.createAttribute('skinWeight', 4).array; + + for (var i = 0; i < this.vertexCount; i++) { + var skinIndex = this.modelGeometry.skinIndices[i]; + var skinWeight = this.modelGeometry.skinWeights[i]; + + skinIndexBuffer[i * 4] = skinIndex.x; + skinIndexBuffer[i * 4 + 1] = skinIndex.y; + skinIndexBuffer[i * 4 + 2] = skinIndex.z; + skinIndexBuffer[i * 4 + 3] = skinIndex.w; + + skinWeightBuffer[i * 4] = skinWeight.x; + skinWeightBuffer[i * 4 + 1] = skinWeight.y; + skinWeightBuffer[i * 4 + 2] = skinWeight.z; + skinWeightBuffer[i * 4 + 3] = skinWeight.w; + } +}; + +/** + * Creates a THREE.BufferAttribute on this geometry instance. + * + * @param {String} name Name of the attribute. + * @param {int} itemSize Number of floats per vertex (typically 1, 2, 3 or 4). + * @param {function=} factory Function that will be called for each face upon creation. Accepts 3 arguments: data[], index and faceCount. Calls setFaceData. + * + * @returns {BufferAttribute} + */ +ModelBufferGeometry.prototype.createAttribute = function (name, itemSize, factory) { + var buffer = new Float32Array(this.vertexCount * itemSize); + var attribute = new three.BufferAttribute(buffer, itemSize); + + this.addAttribute(name, attribute); + + if (factory) { + var data = []; + + for (var i = 0; i < this.faceCount; i++) { + factory(data, i, this.faceCount); + this.setFaceData(attribute, i, data); + } + } + + return attribute; +}; + +/** + * Sets data for all vertices of a face at a given index. + * Usually called in a loop. + * + * @param {String|THREE.BufferAttribute} attribute The attribute or attribute name where the data is to be stored. + * @param {int} faceIndex Index of the face in the buffer geometry. + * @param {Array} data Array of data. Length should be equal to item size of the attribute. + */ +ModelBufferGeometry.prototype.setFaceData = function (attribute, faceIndex, data) { + attribute = typeof attribute === 'string' ? this.attributes[attribute] : attribute; + + var offset = faceIndex * 3 * attribute.itemSize; + + for (var i = 0; i < 3; i++) { + for (var j = 0; j < attribute.itemSize; j++) { + attribute.array[offset++] = data[j]; + } + } +}; + +/** + * A THREE.BufferGeometry consists of points. + * @param {Number} count The number of points. + * @constructor + */ +function PointBufferGeometry(count) { + three.BufferGeometry.call(this); + + /** + * Number of points. + * @type {Number} + */ + this.pointCount = count; + + this.bufferPositions(); +} +PointBufferGeometry.prototype = Object.create(three.BufferGeometry.prototype); +PointBufferGeometry.prototype.constructor = PointBufferGeometry; + +PointBufferGeometry.prototype.bufferPositions = function () { + this.createAttribute('position', 3); +}; + +/** + * Creates a THREE.BufferAttribute on this geometry instance. + * + * @param {String} name Name of the attribute. + * @param {Number} itemSize Number of floats per vertex (typically 1, 2, 3 or 4). + * @param {function=} factory Function that will be called for each point upon creation. Accepts 3 arguments: data[], index and prefabCount. Calls setPointData. + * + * @returns {THREE.BufferAttribute} + */ +PointBufferGeometry.prototype.createAttribute = function (name, itemSize, factory) { + var buffer = new Float32Array(this.pointCount * itemSize); + var attribute = new three.BufferAttribute(buffer, itemSize); + + this.addAttribute(name, attribute); + + if (factory) { + var data = []; + for (var i = 0; i < this.pointCount; i++) { + factory(data, i, this.pointCount); + this.setPointData(attribute, i, data); + } + } + + return attribute; +}; + +PointBufferGeometry.prototype.setPointData = function (attribute, pointIndex, data) { + attribute = typeof attribute === 'string' ? this.attributes[attribute] : attribute; + + var offset = pointIndex * attribute.itemSize; + + for (var j = 0; j < attribute.itemSize; j++) { + attribute.array[offset++] = data[j]; + } +}; + +var catmull_rom_spline = "vec4 catmullRomSpline(vec4 p0, vec4 p1, vec4 p2, vec4 p3, float t, vec2 c) {\r\n vec4 v0 = (p2 - p0) * c.x;\r\n vec4 v1 = (p3 - p1) * c.y;\r\n float t2 = t * t;\r\n float t3 = t * t * t;\r\n\r\n return vec4((2.0 * p1 - 2.0 * p2 + v0 + v1) * t3 + (-3.0 * p1 + 3.0 * p2 - 2.0 * v0 - v1) * t2 + v0 * t + p1);\r\n}\r\nvec4 catmullRomSpline(vec4 p0, vec4 p1, vec4 p2, vec4 p3, float t) {\r\n return catmullRomSpline(p0, p1, p2, p3, t, vec2(0.5, 0.5));\r\n}\r\n\r\nvec3 catmullRomSpline(vec3 p0, vec3 p1, vec3 p2, vec3 p3, float t, vec2 c) {\r\n vec3 v0 = (p2 - p0) * c.x;\r\n vec3 v1 = (p3 - p1) * c.y;\r\n float t2 = t * t;\r\n float t3 = t * t * t;\r\n\r\n return vec3((2.0 * p1 - 2.0 * p2 + v0 + v1) * t3 + (-3.0 * p1 + 3.0 * p2 - 2.0 * v0 - v1) * t2 + v0 * t + p1);\r\n}\r\nvec3 catmullRomSpline(vec3 p0, vec3 p1, vec3 p2, vec3 p3, float t) {\r\n return catmullRomSpline(p0, p1, p2, p3, t, vec2(0.5, 0.5));\r\n}\r\n\r\nvec2 catmullRomSpline(vec2 p0, vec2 p1, vec2 p2, vec2 p3, float t, vec2 c) {\r\n vec2 v0 = (p2 - p0) * c.x;\r\n vec2 v1 = (p3 - p1) * c.y;\r\n float t2 = t * t;\r\n float t3 = t * t * t;\r\n\r\n return vec2((2.0 * p1 - 2.0 * p2 + v0 + v1) * t3 + (-3.0 * p1 + 3.0 * p2 - 2.0 * v0 - v1) * t2 + v0 * t + p1);\r\n}\r\nvec2 catmullRomSpline(vec2 p0, vec2 p1, vec2 p2, vec2 p3, float t) {\r\n return catmullRomSpline(p0, p1, p2, p3, t, vec2(0.5, 0.5));\r\n}\r\n\r\nfloat catmullRomSpline(float p0, float p1, float p2, float p3, float t, vec2 c) {\r\n float v0 = (p2 - p0) * c.x;\r\n float v1 = (p3 - p1) * c.y;\r\n float t2 = t * t;\r\n float t3 = t * t * t;\r\n\r\n return float((2.0 * p1 - 2.0 * p2 + v0 + v1) * t3 + (-3.0 * p1 + 3.0 * p2 - 2.0 * v0 - v1) * t2 + v0 * t + p1);\r\n}\r\nfloat catmullRomSpline(float p0, float p1, float p2, float p3, float t) {\r\n return catmullRomSpline(p0, p1, p2, p3, t, vec2(0.5, 0.5));\r\n}\r\n\r\nivec4 getCatmullRomSplineIndices(float l, float p) {\r\n float index = floor(p);\r\n int i0 = int(max(0.0, index - 1.0));\r\n int i1 = int(index);\r\n int i2 = int(min(index + 1.0, l));\r\n int i3 = int(min(index + 2.0, l));\r\n\r\n return ivec4(i0, i1, i2, i3);\r\n}\r\n\r\nivec4 getCatmullRomSplineIndicesClosed(float l, float p) {\r\n float index = floor(p);\r\n int i0 = int(index == 0.0 ? l : index - 1.0);\r\n int i1 = int(index);\r\n int i2 = int(mod(index + 1.0, l));\r\n int i3 = int(mod(index + 2.0, l));\r\n\r\n return ivec4(i0, i1, i2, i3);\r\n}\r\n"; + +var cubic_bezier = "vec3 cubicBezier(vec3 p0, vec3 c0, vec3 c1, vec3 p1, float t) {\r\n float tn = 1.0 - t;\r\n\r\n return tn * tn * tn * p0 + 3.0 * tn * tn * t * c0 + 3.0 * tn * t * t * c1 + t * t * t * p1;\r\n}\r\n\r\nvec2 cubicBezier(vec2 p0, vec2 c0, vec2 c1, vec2 p1, float t) {\r\n float tn = 1.0 - t;\r\n\r\n return tn * tn * tn * p0 + 3.0 * tn * tn * t * c0 + 3.0 * tn * t * t * c1 + t * t * t * p1;\r\n}\r\n"; + +var ease_back_in = "float easeBackIn(float p, float amplitude) {\r\n return p * p * ((amplitude + 1.0) * p - amplitude);\r\n}\r\n\r\nfloat easeBackIn(float p) {\r\n return easeBackIn(p, 1.70158);\r\n}\r\n\r\nfloat easeBackIn(float t, float b, float c, float d, float amplitude) {\r\n return b + easeBackIn(t / d, amplitude) * c;\r\n}\r\n\r\nfloat easeBackIn(float t, float b, float c, float d) {\r\n return b + easeBackIn(t / d) * c;\r\n}\r\n"; + +var ease_back_in_out = "float easeBackInOut(float p, float amplitude) {\r\n amplitude *= 1.525;\r\n\r\n return ((p *= 2.0) < 1.0) ? 0.5 * p * p * ((amplitude + 1.0) * p - amplitude) : 0.5 * ((p -= 2.0) * p * ((amplitude + 1.0) * p + amplitude) + 2.0);\r\n}\r\n\r\nfloat easeBackInOut(float p) {\r\n return easeBackInOut(p, 1.70158);\r\n}\r\n\r\nfloat easeBackInOut(float t, float b, float c, float d, float amplitude) {\r\n return b + easeBackInOut(t / d, amplitude) * c;\r\n}\r\n\r\nfloat easeBackInOut(float t, float b, float c, float d) {\r\n return b + easeBackInOut(t / d) * c;\r\n}\r\n"; + +var ease_back_out = "float easeBackOut(float p, float amplitude) {\r\n return ((p = p - 1.0) * p * ((amplitude + 1.0) * p + amplitude) + 1.0);\r\n}\r\n\r\nfloat easeBackOut(float p) {\r\n return easeBackOut(p, 1.70158);\r\n}\r\n\r\nfloat easeBackOut(float t, float b, float c, float d, float amplitude) {\r\n return b + easeBackOut(t / d, amplitude) * c;\r\n}\r\n\r\nfloat easeBackOut(float t, float b, float c, float d) {\r\n return b + easeBackOut(t / d) * c;\r\n}\r\n"; + +var ease_bezier = "float easeBezier(float p, vec4 curve) {\r\n float ip = 1.0 - p;\r\n return (3.0 * ip * ip * p * curve.xy + 3.0 * ip * p * p * curve.zw + p * p * p).y;\r\n}\r\n\r\nfloat easeBezier(float t, float b, float c, float d, vec4 curve) {\r\n return b + easeBezier(t / d, curve) * c;\r\n}\r\n"; + +var ease_bounce_in = "float easeBounceIn(float p) {\r\n if ((p = 1.0 - p) < 1.0 / 2.75) {\r\n return 1.0 - (7.5625 * p * p);\r\n } else if (p < 2.0 / 2.75) {\r\n return 1.0 - (7.5625 * (p -= 1.5 / 2.75) * p + 0.75);\r\n } else if (p < 2.5 / 2.75) {\r\n return 1.0 - (7.5625 * (p -= 2.25 / 2.75) * p + 0.9375);\r\n }\r\n return 1.0 - (7.5625 * (p -= 2.625 / 2.75) * p + 0.984375);\r\n}\r\n\r\nfloat easeBounceIn(float t, float b, float c, float d) {\r\n return b + easeBounceIn(t / d) * c;\r\n}\r\n"; + +var ease_bounce_in_out = "float easeBounceInOut(float p) {\r\n bool invert = (p < 0.5);\r\n\r\n p = invert ? (1.0 - (p * 2.0)) : ((p * 2.0) - 1.0);\r\n\r\n if (p < 1.0 / 2.75) {\r\n p = 7.5625 * p * p;\r\n } else if (p < 2.0 / 2.75) {\r\n p = 7.5625 * (p -= 1.5 / 2.75) * p + 0.75;\r\n } else if (p < 2.5 / 2.75) {\r\n p = 7.5625 * (p -= 2.25 / 2.75) * p + 0.9375;\r\n } else {\r\n p = 7.5625 * (p -= 2.625 / 2.75) * p + 0.984375;\r\n }\r\n\r\n return invert ? (1.0 - p) * 0.5 : p * 0.5 + 0.5;\r\n}\r\n\r\nfloat easeBounceInOut(float t, float b, float c, float d) {\r\n return b + easeBounceInOut(t / d) * c;\r\n}\r\n"; + +var ease_bounce_out = "float easeBounceOut(float p) {\r\n if (p < 1.0 / 2.75) {\r\n return 7.5625 * p * p;\r\n } else if (p < 2.0 / 2.75) {\r\n return 7.5625 * (p -= 1.5 / 2.75) * p + 0.75;\r\n } else if (p < 2.5 / 2.75) {\r\n return 7.5625 * (p -= 2.25 / 2.75) * p + 0.9375;\r\n }\r\n return 7.5625 * (p -= 2.625 / 2.75) * p + 0.984375;\r\n}\r\n\r\nfloat easeBounceOut(float t, float b, float c, float d) {\r\n return b + easeBounceOut(t / d) * c;\r\n}\r\n"; + +var ease_circ_in = "float easeCircIn(float p) {\r\n return -(sqrt(1.0 - p * p) - 1.0);\r\n}\r\n\r\nfloat easeCircIn(float t, float b, float c, float d) {\r\n return b + easeCircIn(t / d) * c;\r\n}\r\n"; + +var ease_circ_in_out = "float easeCircInOut(float p) {\r\n return ((p *= 2.0) < 1.0) ? -0.5 * (sqrt(1.0 - p * p) - 1.0) : 0.5 * (sqrt(1.0 - (p -= 2.0) * p) + 1.0);\r\n}\r\n\r\nfloat easeCircInOut(float t, float b, float c, float d) {\r\n return b + easeCircInOut(t / d) * c;\r\n}\r\n"; + +var ease_circ_out = "float easeCircOut(float p) {\r\n return sqrt(1.0 - (p = p - 1.0) * p);\r\n}\r\n\r\nfloat easeCircOut(float t, float b, float c, float d) {\r\n return b + easeCircOut(t / d) * c;\r\n}\r\n"; + +var ease_cubic_in = "float easeCubicIn(float t) {\r\n return t * t * t;\r\n}\r\n\r\nfloat easeCubicIn(float t, float b, float c, float d) {\r\n return b + easeCubicIn(t / d) * c;\r\n}\r\n"; + +var ease_cubic_in_out = "float easeCubicInOut(float t) {\r\n return (t /= 0.5) < 1.0 ? 0.5 * t * t * t : 0.5 * ((t-=2.0) * t * t + 2.0);\r\n}\r\n\r\nfloat easeCubicInOut(float t, float b, float c, float d) {\r\n return b + easeCubicInOut(t / d) * c;\r\n}\r\n"; + +var ease_cubic_out = "float easeCubicOut(float t) {\r\n float f = t - 1.0;\r\n return f * f * f + 1.0;\r\n}\r\n\r\nfloat easeCubicOut(float t, float b, float c, float d) {\r\n return b + easeCubicOut(t / d) * c;\r\n}\r\n"; + +var ease_elastic_in = "float easeElasticIn(float p, float amplitude, float period) {\r\n float p1 = max(amplitude, 1.0);\r\n float p2 = period / min(amplitude, 1.0);\r\n float p3 = p2 / PI2 * (asin(1.0 / p1));\r\n\r\n return -(p1 * pow(2.0, 10.0 * (p -= 1.0)) * sin((p - p3) * PI2 / p2));\r\n}\r\n\r\nfloat easeElasticIn(float p) {\r\n return easeElasticIn(p, 1.0, 0.3);\r\n}\r\n\r\nfloat easeElasticIn(float t, float b, float c, float d, float amplitude, float period) {\r\n return b + easeElasticIn(t / d, amplitude, period) * c;\r\n}\r\n\r\nfloat easeElasticIn(float t, float b, float c, float d) {\r\n return b + easeElasticIn(t / d) * c;\r\n}\r\n"; + +var ease_elastic_in_out = "float easeElasticInOut(float p, float amplitude, float period) {\r\n float p1 = max(amplitude, 1.0);\r\n float p2 = period / min(amplitude, 1.0);\r\n float p3 = p2 / PI2 * (asin(1.0 / p1));\r\n\r\n return ((p *= 2.0) < 1.0) ? -0.5 * (p1 * pow(2.0, 10.0 * (p -= 1.0)) * sin((p - p3) * PI2 / p2)) : p1 * pow(2.0, -10.0 * (p -= 1.0)) * sin((p - p3) * PI2 / p2) * 0.5 + 1.0;\r\n}\r\n\r\nfloat easeElasticInOut(float p) {\r\n return easeElasticInOut(p, 1.0, 0.3);\r\n}\r\n\r\nfloat easeElasticInOut(float t, float b, float c, float d, float amplitude, float period) {\r\n return b + easeElasticInOut(t / d, amplitude, period) * c;\r\n}\r\n\r\nfloat easeElasticInOut(float t, float b, float c, float d) {\r\n return b + easeElasticInOut(t / d) * c;\r\n}\r\n"; + +var ease_elastic_out = "float easeElasticOut(float p, float amplitude, float period) {\r\n float p1 = max(amplitude, 1.0);\r\n float p2 = period / min(amplitude, 1.0);\r\n float p3 = p2 / PI2 * (asin(1.0 / p1));\r\n\r\n return p1 * pow(2.0, -10.0 * p) * sin((p - p3) * PI2 / p2) + 1.0;\r\n}\r\n\r\nfloat easeElasticOut(float p) {\r\n return easeElasticOut(p, 1.0, 0.3);\r\n}\r\n\r\nfloat easeElasticOut(float t, float b, float c, float d, float amplitude, float period) {\r\n return b + easeElasticOut(t / d, amplitude, period) * c;\r\n}\r\n\r\nfloat easeElasticOut(float t, float b, float c, float d) {\r\n return b + easeElasticOut(t / d) * c;\r\n}\r\n"; + +var ease_expo_in = "float easeExpoIn(float p) {\r\n return pow(2.0, 10.0 * (p - 1.0));\r\n}\r\n\r\nfloat easeExpoIn(float t, float b, float c, float d) {\r\n return b + easeExpoIn(t / d) * c;\r\n}\r\n"; + +var ease_expo_in_out = "float easeExpoInOut(float p) {\r\n return ((p *= 2.0) < 1.0) ? 0.5 * pow(2.0, 10.0 * (p - 1.0)) : 0.5 * (2.0 - pow(2.0, -10.0 * (p - 1.0)));\r\n}\r\n\r\nfloat easeExpoInOut(float t, float b, float c, float d) {\r\n return b + easeExpoInOut(t / d) * c;\r\n}\r\n"; + +var ease_expo_out = "float easeExpoOut(float p) {\r\n return 1.0 - pow(2.0, -10.0 * p);\r\n}\r\n\r\nfloat easeExpoOut(float t, float b, float c, float d) {\r\n return b + easeExpoOut(t / d) * c;\r\n}\r\n"; + +var ease_quad_in = "float easeQuadIn(float t) {\r\n return t * t;\r\n}\r\n\r\nfloat easeQuadIn(float t, float b, float c, float d) {\r\n return b + easeQuadIn(t / d) * c;\r\n}\r\n"; + +var ease_quad_in_out = "float easeQuadInOut(float t) {\r\n float p = 2.0 * t * t;\r\n return t < 0.5 ? p : -p + (4.0 * t) - 1.0;\r\n}\r\n\r\nfloat easeQuadInOut(float t, float b, float c, float d) {\r\n return b + easeQuadInOut(t / d) * c;\r\n}\r\n"; + +var ease_quad_out = "float easeQuadOut(float t) {\r\n return -t * (t - 2.0);\r\n}\r\n\r\nfloat easeQuadOut(float t, float b, float c, float d) {\r\n return b + easeQuadOut(t / d) * c;\r\n}\r\n"; + +var ease_quart_in = "float easeQuartIn(float t) {\r\n return t * t * t * t;\r\n}\r\n\r\nfloat easeQuartIn(float t, float b, float c, float d) {\r\n return b + easeQuartIn(t / d) * c;\r\n}\r\n"; + +var ease_quart_in_out = "float easeQuartInOut(float t) {\r\n return t < 0.5 ? 8.0 * pow(t, 4.0) : -8.0 * pow(t - 1.0, 4.0) + 1.0;\r\n}\r\n\r\nfloat easeQuartInOut(float t, float b, float c, float d) {\r\n return b + easeQuartInOut(t / d) * c;\r\n}\r\n"; + +var ease_quart_out = "float easeQuartOut(float t) {\r\n return 1.0 - pow(1.0 - t, 4.0);\r\n}\r\n\r\nfloat easeQuartOut(float t, float b, float c, float d) {\r\n return b + easeQuartOut(t / d) * c;\r\n}\r\n"; + +var ease_quint_in = "float easeQuintIn(float t) {\r\n return pow(t, 5.0);\r\n}\r\n\r\nfloat easeQuintIn(float t, float b, float c, float d) {\r\n return b + easeQuintIn(t / d) * c;\r\n}\r\n"; + +var ease_quint_in_out = "float easeQuintInOut(float t) {\r\n return (t /= 0.5) < 1.0 ? 0.5 * t * t * t * t * t : 0.5 * ((t -= 2.0) * t * t * t * t + 2.0);\r\n}\r\n\r\nfloat easeQuintInOut(float t, float b, float c, float d) {\r\n return b + easeQuintInOut(t / d) * c;\r\n}\r\n"; + +var ease_quint_out = "float easeQuintOut(float t) {\r\n return (t -= 1.0) * t * t * t * t + 1.0;\r\n}\r\n\r\nfloat easeQuintOut(float t, float b, float c, float d) {\r\n return b + easeQuintOut(t / d) * c;\r\n}\r\n"; + +var ease_sine_in = "float easeSineIn(float p) {\r\n return -cos(p * 1.57079632679) + 1.0;\r\n}\r\n\r\nfloat easeSineIn(float t, float b, float c, float d) {\r\n return b + easeSineIn(t / d) * c;\r\n}\r\n"; + +var ease_sine_in_out = "float easeSineInOut(float p) {\r\n return -0.5 * (cos(PI * p) - 1.0);\r\n}\r\n\r\nfloat easeSineInOut(float t, float b, float c, float d) {\r\n return b + easeSineInOut(t / d) * c;\r\n}\r\n"; + +var ease_sine_out = "float easeSineOut(float p) {\r\n return sin(p * 1.57079632679);\r\n}\r\n\r\nfloat easeSineOut(float t, float b, float c, float d) {\r\n return b + easeSineOut(t / d) * c;\r\n}\r\n"; + +var quadratic_bezier = "vec3 quadraticBezier(vec3 p0, vec3 c0, vec3 p1, float t) {\r\n float tn = 1.0 - t;\r\n\r\n return tn * tn * p0 + 2.0 * tn * t * c0 + t * t * p1;\r\n}\r\n\r\nvec2 quadraticBezier(vec2 p0, vec2 c0, vec2 p1, float t) {\r\n float tn = 1.0 - t;\r\n\r\n return tn * tn * p0 + 2.0 * tn * t * c0 + t * t * p1;\r\n}"; + +var quaternion_rotation = "vec3 rotateVector(vec4 q, vec3 v) {\r\n return v + 2.0 * cross(q.xyz, cross(q.xyz, v) + q.w * v);\r\n}\r\n\r\nvec4 quatFromAxisAngle(vec3 axis, float angle) {\r\n float halfAngle = angle * 0.5;\r\n return vec4(axis.xyz * sin(halfAngle), cos(halfAngle));\r\n}\r\n"; + +var quaternion_slerp = "vec4 quatSlerp(vec4 q0, vec4 q1, float t) {\r\n float s = 1.0 - t;\r\n float c = dot(q0, q1);\r\n float dir = -1.0; //c >= 0.0 ? 1.0 : -1.0;\r\n float sqrSn = 1.0 - c * c;\r\n\r\n if (sqrSn > 2.220446049250313e-16) {\r\n float sn = sqrt(sqrSn);\r\n float len = atan(sn, c * dir);\r\n\r\n s = sin(s * len) / sn;\r\n t = sin(t * len) / sn;\r\n }\r\n\r\n float tDir = t * dir;\r\n\r\n return normalize(q0 * s + q1 * tDir);\r\n}\r\n"; + +// generated by scripts/build_shader_chunks.js + +var ShaderChunk = { + catmull_rom_spline: catmull_rom_spline, + cubic_bezier: cubic_bezier, + ease_back_in: ease_back_in, + ease_back_in_out: ease_back_in_out, + ease_back_out: ease_back_out, + ease_bezier: ease_bezier, + ease_bounce_in: ease_bounce_in, + ease_bounce_in_out: ease_bounce_in_out, + ease_bounce_out: ease_bounce_out, + ease_circ_in: ease_circ_in, + ease_circ_in_out: ease_circ_in_out, + ease_circ_out: ease_circ_out, + ease_cubic_in: ease_cubic_in, + ease_cubic_in_out: ease_cubic_in_out, + ease_cubic_out: ease_cubic_out, + ease_elastic_in: ease_elastic_in, + ease_elastic_in_out: ease_elastic_in_out, + ease_elastic_out: ease_elastic_out, + ease_expo_in: ease_expo_in, + ease_expo_in_out: ease_expo_in_out, + ease_expo_out: ease_expo_out, + ease_quad_in: ease_quad_in, + ease_quad_in_out: ease_quad_in_out, + ease_quad_out: ease_quad_out, + ease_quart_in: ease_quart_in, + ease_quart_in_out: ease_quart_in_out, + ease_quart_out: ease_quart_out, + ease_quint_in: ease_quint_in, + ease_quint_in_out: ease_quint_in_out, + ease_quint_out: ease_quint_out, + ease_sine_in: ease_sine_in, + ease_sine_in_out: ease_sine_in_out, + ease_sine_out: ease_sine_out, + quadratic_bezier: quadratic_bezier, + quaternion_rotation: quaternion_rotation, + quaternion_slerp: quaternion_slerp + +}; + +/** + * A timeline transition segment. An instance of this class is created internally when calling {@link THREE.BAS.Timeline.add}, so you should not use this class directly. + * The instance is also passed the the compiler function if you register a transition through {@link THREE.BAS.Timeline.register}. There you can use the public properties of the segment to compile the glsl string. + * @param {string} key A string key generated by the timeline to which this segment belongs. Keys are unique. + * @param {number} start Start time of this segment in a timeline in seconds. + * @param {number} duration Duration of this segment in seconds. + * @param {object} transition Object describing the transition. + * @param {function} compiler A reference to the compiler function from a transition definition. + * @constructor + */ +function TimelineSegment(key, start, duration, transition, compiler) { + this.key = key; + this.start = start; + this.duration = duration; + this.transition = transition; + this.compiler = compiler; + + this.trail = 0; +} + +TimelineSegment.prototype.compile = function () { + return this.compiler(this); +}; + +Object.defineProperty(TimelineSegment.prototype, 'end', { + get: function get() { + return this.start + this.duration; + } +}); + +/** + * A utility class to create an animation timeline which can be baked into a (vertex) shader. + * By default the timeline supports translation, scale and rotation. This can be extended or overridden. + * @constructor + */ +function Timeline() { + /** + * The total duration of the timeline in seconds. + * @type {number} + */ + this.duration = 0; + + /** + * The name of the value that segments will use to read the time. Defaults to 'tTime'. + * @type {string} + */ + this.timeKey = 'tTime'; + + this.segments = {}; + this.__key = 0; +} + +// static definitions map +Timeline.segmentDefinitions = {}; + +/** + * Registers a transition definition for use with {@link THREE.BAS.Timeline.add}. + * @param {String} key Name of the transition. Defaults include 'scale', 'rotate' and 'translate'. + * @param {Object} definition + * @param {Function} definition.compiler A function that generates a glsl string for a transition segment. Accepts a THREE.BAS.TimelineSegment as the sole argument. + * @param {*} definition.defaultFrom The initial value for a transform.from. For example, the defaultFrom for a translation is THREE.Vector3(0, 0, 0). + * @static + */ +Timeline.register = function (key, definition) { + Timeline.segmentDefinitions[key] = definition; + + return definition; +}; + +/** + * Add a transition to the timeline. + * @param {number} duration Duration in seconds + * @param {object} transitions An object containing one or several transitions. The keys should match transform definitions. + * The transition object for each key will be passed to the matching definition's compiler. It can have arbitrary properties, but the Timeline expects at least a 'to', 'from' and an optional 'ease'. + * @param {number|string} [positionOffset] Position in the timeline. Defaults to the end of the timeline. If a number is provided, the transition will be inserted at that time in seconds. Strings ('+=x' or '-=x') can be used for a value relative to the end of timeline. + */ +Timeline.prototype.add = function (duration, transitions, positionOffset) { + // stop rollup from complaining about eval + var _eval = eval; + + var start = this.duration; + + if (positionOffset !== undefined) { + if (typeof positionOffset === 'number') { + start = positionOffset; + } else if (typeof positionOffset === 'string') { + _eval('start' + positionOffset); + } + + this.duration = Math.max(this.duration, start + duration); + } else { + this.duration += duration; + } + + var keys = Object.keys(transitions), + key = void 0; + + for (var i = 0; i < keys.length; i++) { + key = keys[i]; + + this.processTransition(key, transitions[key], start, duration); + } +}; + +Timeline.prototype.processTransition = function (key, transition, start, duration) { + var definition = Timeline.segmentDefinitions[key]; + + var segments = this.segments[key]; + if (!segments) segments = this.segments[key] = []; + + if (transition.from === undefined) { + if (segments.length === 0) { + transition.from = definition.defaultFrom; + } else { + transition.from = segments[segments.length - 1].transition.to; + } + } + + segments.push(new TimelineSegment((this.__key++).toString(), start, duration, transition, definition.compiler)); +}; + +/** + * Compiles the timeline into a glsl string array that can be injected into a (vertex) shader. + * @returns {Array} + */ +Timeline.prototype.compile = function () { + var c = []; + + var keys = Object.keys(this.segments); + var segments = void 0; + + for (var i = 0; i < keys.length; i++) { + segments = this.segments[keys[i]]; + + this.fillGaps(segments); + + segments.forEach(function (s) { + c.push(s.compile()); + }); + } + + return c; +}; +Timeline.prototype.fillGaps = function (segments) { + if (segments.length === 0) return; + + var s0 = void 0, + s1 = void 0; + + for (var i = 0; i < segments.length - 1; i++) { + s0 = segments[i]; + s1 = segments[i + 1]; + + s0.trail = s1.start - s0.end; + } + + // pad last segment until end of timeline + s0 = segments[segments.length - 1]; + s0.trail = this.duration - s0.end; +}; + +/** + * Get a compiled glsl string with calls to transform functions for a given key. + * The order in which these transitions are applied matters because they all operate on the same value. + * @param {string} key A key matching a transform definition. + * @returns {string} + */ +Timeline.prototype.getTransformCalls = function (key) { + var t = this.timeKey; + + return this.segments[key] ? this.segments[key].map(function (s) { + return 'applyTransform' + s.key + '(' + t + ', transformed);'; + }).join('\n') : ''; +}; + +var TimelineChunks = { + vec3: function vec3(n, v, p) { + var x = (v.x || 0).toPrecision(p); + var y = (v.y || 0).toPrecision(p); + var z = (v.z || 0).toPrecision(p); + + return "vec3 " + n + " = vec3(" + x + ", " + y + ", " + z + ");"; + }, + vec4: function vec4(n, v, p) { + var x = (v.x || 0).toPrecision(p); + var y = (v.y || 0).toPrecision(p); + var z = (v.z || 0).toPrecision(p); + var w = (v.w || 0).toPrecision(p); + + return "vec4 " + n + " = vec4(" + x + ", " + y + ", " + z + ", " + w + ");"; + }, + delayDuration: function delayDuration(segment) { + return "\n float cDelay" + segment.key + " = " + segment.start.toPrecision(4) + ";\n float cDuration" + segment.key + " = " + segment.duration.toPrecision(4) + ";\n "; + }, + progress: function progress(segment) { + // zero duration segments should always render complete + if (segment.duration === 0) { + return "float progress = 1.0;"; + } else { + return "\n float progress = clamp(time - cDelay" + segment.key + ", 0.0, cDuration" + segment.key + ") / cDuration" + segment.key + ";\n " + (segment.transition.ease ? "progress = " + segment.transition.ease + "(progress" + (segment.transition.easeParams ? ", " + segment.transition.easeParams.map(function (v) { + return v.toPrecision(4); + }).join(", ") : "") + ");" : "") + "\n "; + } + }, + renderCheck: function renderCheck(segment) { + var startTime = segment.start.toPrecision(4); + var endTime = (segment.end + segment.trail).toPrecision(4); + + return "if (time < " + startTime + " || time > " + endTime + ") return;"; + } +}; + +var TranslationSegment = { + compiler: function compiler(segment) { + return '\n ' + TimelineChunks.delayDuration(segment) + '\n ' + TimelineChunks.vec3('cTranslateFrom' + segment.key, segment.transition.from, 2) + '\n ' + TimelineChunks.vec3('cTranslateTo' + segment.key, segment.transition.to, 2) + '\n \n void applyTransform' + segment.key + '(float time, inout vec3 v) {\n \n ' + TimelineChunks.renderCheck(segment) + '\n ' + TimelineChunks.progress(segment) + '\n \n v += mix(cTranslateFrom' + segment.key + ', cTranslateTo' + segment.key + ', progress);\n }\n '; + }, + defaultFrom: new three.Vector3(0, 0, 0) +}; + +Timeline.register('translate', TranslationSegment); + +var ScaleSegment = { + compiler: function compiler(segment) { + var origin = segment.transition.origin; + + return '\n ' + TimelineChunks.delayDuration(segment) + '\n ' + TimelineChunks.vec3('cScaleFrom' + segment.key, segment.transition.from, 2) + '\n ' + TimelineChunks.vec3('cScaleTo' + segment.key, segment.transition.to, 2) + '\n ' + (origin ? TimelineChunks.vec3('cOrigin' + segment.key, origin, 2) : '') + '\n \n void applyTransform' + segment.key + '(float time, inout vec3 v) {\n \n ' + TimelineChunks.renderCheck(segment) + '\n ' + TimelineChunks.progress(segment) + '\n \n ' + (origin ? 'v -= cOrigin' + segment.key + ';' : '') + '\n v *= mix(cScaleFrom' + segment.key + ', cScaleTo' + segment.key + ', progress);\n ' + (origin ? 'v += cOrigin' + segment.key + ';' : '') + '\n }\n '; + }, + defaultFrom: new three.Vector3(1, 1, 1) +}; + +Timeline.register('scale', ScaleSegment); + +var RotationSegment = { + compiler: function compiler(segment) { + var fromAxisAngle = new three.Vector4(segment.transition.from.axis.x, segment.transition.from.axis.y, segment.transition.from.axis.z, segment.transition.from.angle); + + var toAxis = segment.transition.to.axis || segment.transition.from.axis; + var toAxisAngle = new three.Vector4(toAxis.x, toAxis.y, toAxis.z, segment.transition.to.angle); + + var origin = segment.transition.origin; + + return '\n ' + TimelineChunks.delayDuration(segment) + '\n ' + TimelineChunks.vec4('cRotationFrom' + segment.key, fromAxisAngle, 8) + '\n ' + TimelineChunks.vec4('cRotationTo' + segment.key, toAxisAngle, 8) + '\n ' + (origin ? TimelineChunks.vec3('cOrigin' + segment.key, origin, 2) : '') + '\n \n void applyTransform' + segment.key + '(float time, inout vec3 v) {\n ' + TimelineChunks.renderCheck(segment) + '\n ' + TimelineChunks.progress(segment) + '\n\n ' + (origin ? 'v -= cOrigin' + segment.key + ';' : '') + '\n vec3 axis = normalize(mix(cRotationFrom' + segment.key + '.xyz, cRotationTo' + segment.key + '.xyz, progress));\n float angle = mix(cRotationFrom' + segment.key + '.w, cRotationTo' + segment.key + '.w, progress);\n vec4 q = quatFromAxisAngle(axis, angle);\n v = rotateVector(q, v);\n ' + (origin ? 'v += cOrigin' + segment.key + ';' : '') + '\n }\n '; + }, + + defaultFrom: { axis: new three.Vector3(), angle: 0 } +}; + +Timeline.register('rotate', RotationSegment); + +exports.BasicAnimationMaterial = BasicAnimationMaterial; +exports.LambertAnimationMaterial = LambertAnimationMaterial; +exports.PhongAnimationMaterial = PhongAnimationMaterial; +exports.StandardAnimationMaterial = StandardAnimationMaterial; +exports.PointsAnimationMaterial = PointsAnimationMaterial; +exports.DepthAnimationMaterial = DepthAnimationMaterial; +exports.DistanceAnimationMaterial = DistanceAnimationMaterial; +exports.PrefabBufferGeometry = PrefabBufferGeometry; +exports.MultiPrefabBufferGeometry = MultiPrefabBufferGeometry; +exports.ModelBufferGeometry = ModelBufferGeometry; +exports.PointBufferGeometry = PointBufferGeometry; +exports.ShaderChunk = ShaderChunk; +exports.Timeline = Timeline; +exports.TimelineSegment = TimelineSegment; +exports.TimelineChunks = TimelineChunks; +exports.TranslationSegment = TranslationSegment; +exports.ScaleSegment = ScaleSegment; +exports.RotationSegment = RotationSegment; +exports.Utils = Utils; + +Object.defineProperty(exports, '__esModule', { value: true }); + +}))); diff --git a/assets/js/three.bas.min.js b/assets/js/three.bas.min.js new file mode 100644 index 00000000..f92e6d7d --- /dev/null +++ b/assets/js/three.bas.min.js @@ -0,0 +1 @@ +!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports,require("three")):"function"==typeof define&&define.amd?define(["exports","three"],n):n(e.BAS={},e.THREE)}(this,function(e,n){"use strict";function t(e,t){n.ShaderMaterial.call(this);var r=e.uniformValues;if(delete e.uniformValues,this.setValues(e),this.uniforms=n.UniformsUtils.merge([t,this.uniforms]),this.setUniformValues(r),r&&(r.map&&(this.defines.USE_MAP=""),r.normalMap&&(this.defines.USE_NORMALMAP=""),r.envMap&&(this.defines.USE_ENVMAP=""),r.aoMap&&(this.defines.USE_AOMAP=""),r.specularMap&&(this.defines.USE_SPECULARMAP=""),r.alphaMap&&(this.defines.USE_ALPHAMAP=""),r.lightMap&&(this.defines.USE_LIGHTMAP=""),r.emissiveMap&&(this.defines.USE_EMISSIVEMAP=""),r.bumpMap&&(this.defines.USE_BUMPMAP=""),r.displacementMap&&(this.defines.USE_DISPLACEMENTMAP=""),r.roughnessMap&&(this.defines.USE_DISPLACEMENTMAP=""),r.roughnessMap&&(this.defines.USE_ROUGHNESSMAP=""),r.metalnessMap&&(this.defines.USE_METALNESSMAP=""),r.envMap)){this.defines.USE_ENVMAP="";var i="ENVMAP_TYPE_CUBE",a="ENVMAP_MODE_REFLECTION",o="ENVMAP_BLENDING_MULTIPLY";switch(r.envMap.mapping){case n.CubeReflectionMapping:case n.CubeRefractionMapping:i="ENVMAP_TYPE_CUBE";break;case n.CubeUVReflectionMapping:case n.CubeUVRefractionMapping:i="ENVMAP_TYPE_CUBE_UV";break;case n.EquirectangularReflectionMapping:case n.EquirectangularRefractionMapping:i="ENVMAP_TYPE_EQUIREC";break;case n.SphericalReflectionMapping:i="ENVMAP_TYPE_SPHERE"}switch(r.envMap.mapping){case n.CubeRefractionMapping:case n.EquirectangularRefractionMapping:a="ENVMAP_MODE_REFRACTION"}switch(r.combine){case n.MixOperation:o="ENVMAP_BLENDING_MIX";break;case n.AddOperation:o="ENVMAP_BLENDING_ADD";break;case n.MultiplyOperation:default:o="ENVMAP_BLENDING_MULTIPLY"}this.defines[i]="",this.defines[o]="",this.defines[a]=""}}function r(e){this.varyingParameters=[],this.vertexParameters=[],this.vertexFunctions=[],this.vertexInit=[],this.vertexNormal=[],this.vertexPosition=[],this.vertexColor=[],this.vertexPostMorph=[],this.vertexPostSkinning=[],this.fragmentFunctions=[],this.fragmentParameters=[],this.fragmentInit=[],this.fragmentMap=[],this.fragmentDiffuse=[],t.call(this,e,n.ShaderLib.basic.uniforms),this.lights=!1,this.vertexShader=this.concatVertexShader(),this.fragmentShader=this.concatFragmentShader()}function i(e){this.varyingParameters=[],this.vertexFunctions=[],this.vertexParameters=[],this.vertexInit=[],this.vertexNormal=[],this.vertexPosition=[],this.vertexColor=[],this.vertexPostMorph=[],this.vertexPostSkinning=[],this.fragmentFunctions=[],this.fragmentParameters=[],this.fragmentInit=[],this.fragmentMap=[],this.fragmentDiffuse=[],this.fragmentEmissive=[],this.fragmentSpecular=[],t.call(this,e,n.ShaderLib.lambert.uniforms),this.lights=!0,this.vertexShader=this.concatVertexShader(),this.fragmentShader=this.concatFragmentShader()}function a(e){this.varyingParameters=[],this.vertexFunctions=[],this.vertexParameters=[],this.vertexInit=[],this.vertexNormal=[],this.vertexPosition=[],this.vertexColor=[],this.fragmentFunctions=[],this.fragmentParameters=[],this.fragmentInit=[],this.fragmentMap=[],this.fragmentDiffuse=[],this.fragmentEmissive=[],this.fragmentSpecular=[],t.call(this,e,n.ShaderLib.phong.uniforms),this.lights=!0,this.vertexShader=this.concatVertexShader(),this.fragmentShader=this.concatFragmentShader()}function o(e){this.varyingParameters=[],this.vertexFunctions=[],this.vertexParameters=[],this.vertexInit=[],this.vertexNormal=[],this.vertexPosition=[],this.vertexColor=[],this.vertexPostMorph=[],this.vertexPostSkinning=[],this.fragmentFunctions=[],this.fragmentParameters=[],this.fragmentInit=[],this.fragmentMap=[],this.fragmentDiffuse=[],this.fragmentRoughness=[],this.fragmentMetalness=[],this.fragmentEmissive=[],t.call(this,e,n.ShaderLib.standard.uniforms),this.lights=!0,this.vertexShader=this.concatVertexShader(),this.fragmentShader=this.concatFragmentShader()}function s(e){this.varyingParameters=[],this.vertexFunctions=[],this.vertexParameters=[],this.vertexInit=[],this.vertexPosition=[],this.vertexColor=[],this.fragmentFunctions=[],this.fragmentParameters=[],this.fragmentInit=[],this.fragmentMap=[],this.fragmentDiffuse=[],this.fragmentShape=[],t.call(this,e,n.ShaderLib.points.uniforms),this.vertexShader=this.concatVertexShader(),this.fragmentShader=this.concatFragmentShader()}function f(e){this.depthPacking=n.RGBADepthPacking,this.clipping=!0,this.vertexFunctions=[],this.vertexParameters=[],this.vertexInit=[],this.vertexPosition=[],this.vertexPostMorph=[],this.vertexPostSkinning=[],t.call(this,e),this.uniforms=n.UniformsUtils.merge([n.ShaderLib.depth.uniforms,this.uniforms]),this.vertexShader=this.concatVertexShader(),this.fragmentShader=n.ShaderLib.depth.fragmentShader}function l(e){this.depthPacking=n.RGBADepthPacking,this.clipping=!0,this.vertexFunctions=[],this.vertexParameters=[],this.vertexInit=[],this.vertexPosition=[],this.vertexPostMorph=[],this.vertexPostSkinning=[],t.call(this,e),this.uniforms=n.UniformsUtils.merge([n.ShaderLib.distanceRGBA.uniforms,this.uniforms]),this.vertexShader=this.concatVertexShader(),this.fragmentShader=n.ShaderLib.distanceRGBA.fragmentShader}function c(e,t){n.BufferGeometry.call(this),this.prefabGeometry=e,this.isPrefabBufferGeometry=e.isBufferGeometry,this.prefabCount=t,this.isPrefabBufferGeometry?this.prefabVertexCount=e.attributes.position.count:this.prefabVertexCount=e.vertices.length,this.bufferIndices(),this.bufferPositions()}function u(e,t){n.BufferGeometry.call(this),Array.isArray(e)?this.prefabGeometries=e:this.prefabGeometries=[e],this.prefabGeometriesCount=this.prefabGeometries.length,this.prefabCount=t*this.prefabGeometriesCount,this.repeatCount=t,this.prefabVertexCounts=this.prefabGeometries.map(function(e){return e.isBufferGeometry?e.attributes.position.count:e.vertices.length}),this.repeatVertexCount=this.prefabVertexCounts.reduce(function(e,n){return e+n},0),this.bufferIndices(),this.bufferPositions()}function p(e,t){n.BufferGeometry.call(this),this.modelGeometry=e,this.faceCount=this.modelGeometry.faces.length,this.vertexCount=this.modelGeometry.vertices.length,(t=t||{}).computeCentroids&&this.computeCentroids(),this.bufferIndices(),this.bufferPositions(t.localizeFaces)}function d(e){n.BufferGeometry.call(this),this.pointCount=e,this.bufferPositions()}function m(e,n,t,r,i){this.key=e,this.start=n,this.duration=t,this.transition=r,this.compiler=i,this.trail=0}function h(){this.duration=0,this.timeKey="tTime",this.segments={},this.__key=0}t.prototype=Object.assign(Object.create(n.ShaderMaterial.prototype),{constructor:t,setUniformValues:function(e){var n=this;if(e){Object.keys(e).forEach(function(t){t in n.uniforms&&(n.uniforms[t].value=e[t])})}},stringifyChunk:function(e){return this[e]?"string"==typeof this[e]?this[e]:this[e].join("\n"):""}}),(r.prototype=Object.create(t.prototype)).constructor=r,r.prototype.concatVertexShader=function(){return"\n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n \n "+this.stringifyChunk("vertexParameters")+"\n "+this.stringifyChunk("varyingParameters")+"\n "+this.stringifyChunk("vertexFunctions")+"\n \n void main() {\n\n "+this.stringifyChunk("vertexInit")+"\n \n #include \n #include \n #include \n #include \n \n #ifdef USE_ENVMAP\n \n #include \n \n "+this.stringifyChunk("vertexNormal")+"\n \n #include \n #include \n #include \n \n #endif\n \n #include \n \n "+this.stringifyChunk("vertexPosition")+"\n "+this.stringifyChunk("vertexColor")+"\n \n #include \n \n "+this.stringifyChunk("vertexPostMorph")+"\n \n #include \n\n "+this.stringifyChunk("vertexPostSkinning")+"\n\n #include \n #include \n \n #include \n #include \n #include \n #include \n }"},r.prototype.concatFragmentShader=function(){return"\n uniform vec3 diffuse;\n uniform float opacity;\n \n "+this.stringifyChunk("fragmentParameters")+"\n "+this.stringifyChunk("varyingParameters")+"\n "+this.stringifyChunk("fragmentFunctions")+"\n \n #ifndef FLAT_SHADED\n \n varying vec3 vNormal;\n \n #endif\n \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n \n void main() {\n \n "+this.stringifyChunk("fragmentInit")+"\n \n #include \n\n vec4 diffuseColor = vec4( diffuse, opacity );\n\n "+this.stringifyChunk("fragmentDiffuse")+"\n \n #include \n \n "+(this.stringifyChunk("fragmentMap")||"#include ")+"\n \n #include \n #include \n #include \n #include \n \n ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n \n // accumulation (baked indirect lighting only)\n #ifdef USE_LIGHTMAP\n \n reflectedLight.indirectDiffuse += texture2D( lightMap, vUv2 ).xyz * lightMapIntensity;\n \n #else\n \n reflectedLight.indirectDiffuse += vec3( 1.0 );\n \n #endif\n \n // modulation\n #include \n \n reflectedLight.indirectDiffuse *= diffuseColor.rgb;\n \n vec3 outgoingLight = reflectedLight.indirectDiffuse;\n \n #include \n \n gl_FragColor = vec4( outgoingLight, diffuseColor.a );\n \n #include \n #include \n #include \n #include \n }"},(i.prototype=Object.create(t.prototype)).constructor=i,i.prototype.concatVertexShader=function(){return"\n #define LAMBERT\n\n varying vec3 vLightFront;\n \n #ifdef DOUBLE_SIDED\n \n varying vec3 vLightBack;\n \n #endif\n \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n \n "+this.stringifyChunk("vertexParameters")+"\n "+this.stringifyChunk("varyingParameters")+"\n "+this.stringifyChunk("vertexFunctions")+"\n \n void main() {\n \n "+this.stringifyChunk("vertexInit")+"\n \n #include \n #include \n #include \n \n #include \n \n "+this.stringifyChunk("vertexNormal")+"\n \n #include \n #include \n #include \n #include \n \n #include \n \n "+this.stringifyChunk("vertexPosition")+"\n "+this.stringifyChunk("vertexColor")+"\n \n #include \n \n "+this.stringifyChunk("vertexPostMorph")+"\n \n #include \n\n "+this.stringifyChunk("vertexPostSkinning")+"\n \n #include \n #include \n #include \n \n #include \n #include \n #include \n #include \n #include \n }"},i.prototype.concatFragmentShader=function(){return"\n uniform vec3 diffuse;\n uniform vec3 emissive;\n uniform float opacity;\n \n varying vec3 vLightFront;\n \n #ifdef DOUBLE_SIDED\n \n varying vec3 vLightBack;\n \n #endif\n \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n \n "+this.stringifyChunk("fragmentParameters")+"\n "+this.stringifyChunk("varyingParameters")+"\n "+this.stringifyChunk("fragmentFunctions")+"\n \n void main() {\n \n "+this.stringifyChunk("fragmentInit")+"\n \n #include \n\n vec4 diffuseColor = vec4( diffuse, opacity );\n ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n vec3 totalEmissiveRadiance = emissive;\n\t\n "+this.stringifyChunk("fragmentDiffuse")+"\n \n #include \n\n "+(this.stringifyChunk("fragmentMap")||"#include ")+"\n\n #include \n #include \n #include \n #include \n\n "+this.stringifyChunk("fragmentEmissive")+"\n\n #include \n \n // accumulation\n reflectedLight.indirectDiffuse = getAmbientLightIrradiance( ambientLightColor );\n \n #include \n \n reflectedLight.indirectDiffuse *= BRDF_Diffuse_Lambert( diffuseColor.rgb );\n \n #ifdef DOUBLE_SIDED\n \n reflectedLight.directDiffuse = ( gl_FrontFacing ) ? vLightFront : vLightBack;\n \n #else\n \n reflectedLight.directDiffuse = vLightFront;\n \n #endif\n \n reflectedLight.directDiffuse *= BRDF_Diffuse_Lambert( diffuseColor.rgb ) * getShadowMask();\n \n // modulation\n #include \n \n vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n \n #include \n \n gl_FragColor = vec4( outgoingLight, diffuseColor.a );\n \n #include \n #include \n #include \n #include \n #include \n }"},(a.prototype=Object.create(t.prototype)).constructor=a,a.prototype.concatVertexShader=function(){return"\n #define PHONG\n\n varying vec3 vViewPosition;\n \n #ifndef FLAT_SHADED\n \n varying vec3 vNormal;\n \n #endif\n \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n \n "+this.stringifyChunk("vertexParameters")+"\n "+this.stringifyChunk("varyingParameters")+"\n "+this.stringifyChunk("vertexFunctions")+"\n \n void main() {\n \n "+this.stringifyChunk("vertexInit")+"\n \n #include \n #include \n #include \n \n #include \n \n "+this.stringifyChunk("vertexNormal")+"\n \n #include \n #include \n #include \n #include \n \n #ifndef FLAT_SHADED // Normal computed with derivatives when FLAT_SHADED\n \n vNormal = normalize( transformedNormal );\n \n #endif\n \n #include \n \n "+this.stringifyChunk("vertexPosition")+"\n "+this.stringifyChunk("vertexColor")+"\n \n #include \n #include \n #include \n #include \n #include \n #include \n \n vViewPosition = - mvPosition.xyz;\n \n #include \n #include \n #include \n #include \n }"},a.prototype.concatFragmentShader=function(){return"\n #define PHONG\n\n uniform vec3 diffuse;\n uniform vec3 emissive;\n uniform vec3 specular;\n uniform float shininess;\n uniform float opacity;\n \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n \n "+this.stringifyChunk("fragmentParameters")+"\n "+this.stringifyChunk("varyingParameters")+"\n "+this.stringifyChunk("fragmentFunctions")+"\n \n void main() {\n \n "+this.stringifyChunk("fragmentInit")+"\n \n #include \n \n vec4 diffuseColor = vec4( diffuse, opacity );\n ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n vec3 totalEmissiveRadiance = emissive;\n \n "+this.stringifyChunk("fragmentDiffuse")+"\n \n #include \n\n "+(this.stringifyChunk("fragmentMap")||"#include ")+"\n\n #include \n #include \n #include \n #include \n #include \n #include \n \n "+this.stringifyChunk("fragmentEmissive")+"\n \n #include \n \n // accumulation\n #include \n #include \n #include \n #include \n \n "+this.stringifyChunk("fragmentSpecular")+"\n \n // modulation\n #include \n \n vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\n \n #include \n \n gl_FragColor = vec4( outgoingLight, diffuseColor.a );\n \n #include \n #include \n #include \n #include \n #include \n \n }"},(o.prototype=Object.create(t.prototype)).constructor=o,o.prototype.concatVertexShader=function(){return"\n #define PHYSICAL\n\n varying vec3 vViewPosition;\n \n #ifndef FLAT_SHADED\n \n varying vec3 vNormal;\n \n #endif\n \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n \n "+this.stringifyChunk("vertexParameters")+"\n "+this.stringifyChunk("varyingParameters")+"\n "+this.stringifyChunk("vertexFunctions")+"\n \n void main() {\n\n "+this.stringifyChunk("vertexInit")+"\n\n #include \n #include \n #include \n \n #include \n \n "+this.stringifyChunk("vertexNormal")+"\n \n #include \n #include \n #include \n #include \n \n #ifndef FLAT_SHADED // Normal computed with derivatives when FLAT_SHADED\n \n vNormal = normalize( transformedNormal );\n \n #endif\n \n #include \n \n "+this.stringifyChunk("vertexPosition")+"\n "+this.stringifyChunk("vertexColor")+"\n \n #include \n \n "+this.stringifyChunk("vertexPostMorph")+"\n \n #include \n\n "+this.stringifyChunk("vertexPostSkinning")+"\n \n #include \n #include \n #include \n #include \n \n vViewPosition = - mvPosition.xyz;\n \n #include \n #include \n #include \n }"},o.prototype.concatFragmentShader=function(){return"\n #define PHYSICAL\n \n uniform vec3 diffuse;\n uniform vec3 emissive;\n uniform float roughness;\n uniform float metalness;\n uniform float opacity;\n \n #ifndef STANDARD\n uniform float clearCoat;\n uniform float clearCoatRoughness;\n #endif\n \n varying vec3 vViewPosition;\n \n #ifndef FLAT_SHADED\n \n varying vec3 vNormal;\n \n #endif\n \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n \n "+this.stringifyChunk("fragmentParameters")+"\n "+this.stringifyChunk("varyingParameters")+"\n "+this.stringifyChunk("fragmentFunctions")+"\n \n void main() {\n \n "+this.stringifyChunk("fragmentInit")+"\n \n #include \n \n vec4 diffuseColor = vec4( diffuse, opacity );\n ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n vec3 totalEmissiveRadiance = emissive;\n \n "+this.stringifyChunk("fragmentDiffuse")+"\n \n #include \n\n "+(this.stringifyChunk("fragmentMap")||"#include ")+"\n\n #include \n #include \n #include \n \n float roughnessFactor = roughness;\n "+this.stringifyChunk("fragmentRoughness")+"\n #ifdef USE_ROUGHNESSMAP\n \n vec4 texelRoughness = texture2D( roughnessMap, vUv );\n \n // reads channel G, compatible with a combined OcclusionRoughnessMetallic (RGB) texture\n roughnessFactor *= texelRoughness.g;\n \n #endif\n \n float metalnessFactor = metalness;\n "+this.stringifyChunk("fragmentMetalness")+"\n #ifdef USE_METALNESSMAP\n \n vec4 texelMetalness = texture2D( metalnessMap, vUv );\n metalnessFactor *= texelMetalness.b;\n \n #endif\n \n #include \n #include \n \n "+this.stringifyChunk("fragmentEmissive")+"\n \n #include \n \n // accumulation\n #include \n #include \n #include \n #include \n \n // modulation\n #include \n \n vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\n \n gl_FragColor = vec4( outgoingLight, diffuseColor.a );\n \n #include \n #include \n #include \n #include \n #include \n \n }"},(s.prototype=Object.create(t.prototype)).constructor=s,s.prototype.concatVertexShader=function(){return"\n uniform float size;\n uniform float scale;\n \n #include \n #include \n #include \n #include \n #include \n #include \n \n "+this.stringifyChunk("vertexParameters")+"\n "+this.stringifyChunk("varyingParameters")+"\n "+this.stringifyChunk("vertexFunctions")+"\n \n void main() {\n \n "+this.stringifyChunk("vertexInit")+"\n \n #include \n #include \n \n "+this.stringifyChunk("vertexPosition")+"\n "+this.stringifyChunk("vertexColor")+"\n \n #include \n \n #ifdef USE_SIZEATTENUATION\n gl_PointSize = size * ( scale / - mvPosition.z );\n #else\n gl_PointSize = size;\n #endif\n \n #include \n #include \n #include \n #include \n #include \n }"},s.prototype.concatFragmentShader=function(){return"\n uniform vec3 diffuse;\n uniform float opacity;\n \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n \n "+this.stringifyChunk("fragmentParameters")+"\n "+this.stringifyChunk("varyingParameters")+"\n "+this.stringifyChunk("fragmentFunctions")+"\n \n void main() {\n \n "+this.stringifyChunk("fragmentInit")+"\n \n #include \n \n vec3 outgoingLight = vec3( 0.0 );\n vec4 diffuseColor = vec4( diffuse, opacity );\n \n "+this.stringifyChunk("fragmentDiffuse")+"\n \n #include \n\n "+(this.stringifyChunk("fragmentMap")||"#include ")+"\n\n #include \n #include \n \n outgoingLight = diffuseColor.rgb;\n \n gl_FragColor = vec4( outgoingLight, diffuseColor.a );\n \n "+this.stringifyChunk("fragmentShape")+"\n \n #include \n #include \n #include \n #include \n }"},(f.prototype=Object.create(t.prototype)).constructor=f,f.prototype.concatVertexShader=function(){return"\n #include \n #include \n #include \n #include \n #include \n #include \n #include \n \n "+this.stringifyChunk("vertexParameters")+"\n "+this.stringifyChunk("vertexFunctions")+"\n \n void main() {\n \n "+this.stringifyChunk("vertexInit")+"\n \n #include \n \n #include \n \n #ifdef USE_DISPLACEMENTMAP\n \n #include \n #include \n #include \n \n #endif\n \n #include \n \n "+this.stringifyChunk("vertexPosition")+"\n\n #include \n \n "+this.stringifyChunk("vertexPostMorph")+"\n \n #include \n\n "+this.stringifyChunk("vertexPostSkinning")+"\n \n #include \n #include \n #include \n #include \n }"},(l.prototype=Object.create(t.prototype)).constructor=l,l.prototype.concatVertexShader=function(){return"\n #define DISTANCE\n\n varying vec3 vWorldPosition;\n \n #include \n #include \n #include \n #include \n #include \n #include \n \n "+this.stringifyChunk("vertexParameters")+"\n "+this.stringifyChunk("vertexFunctions")+"\n \n void main() {\n\n "+this.stringifyChunk("vertexInit")+"\n \n #include \n \n #include \n \n #ifdef USE_DISPLACEMENTMAP\n \n #include \n #include \n #include \n \n #endif\n \n #include \n \n "+this.stringifyChunk("vertexPosition")+"\n\n #include \n \n "+this.stringifyChunk("vertexPostMorph")+"\n \n #include \n\n "+this.stringifyChunk("vertexPostSkinning")+"\n \n #include \n #include \n #include \n #include \n \n vWorldPosition = worldPosition.xyz;\n \n }"},(c.prototype=Object.create(n.BufferGeometry.prototype)).constructor=c,c.prototype.bufferIndices=function(){var e=[],t=void 0;if(this.isPrefabBufferGeometry)if(this.prefabGeometry.index)t=this.prefabGeometry.index.count,e=this.prefabGeometry.index.array;else{t=this.prefabVertexCount;for(var r=0;r= 0.0 ? 1.0 : -1.0;\r\n float sqrSn = 1.0 - c * c;\r\n\r\n if (sqrSn > 2.220446049250313e-16) {\r\n float sn = sqrt(sqrSn);\r\n float len = atan(sn, c * dir);\r\n\r\n s = sin(s * len) / sn;\r\n t = sin(t * len) / sn;\r\n }\r\n\r\n float tDir = t * dir;\r\n\r\n return normalize(q0 * s + q1 * tDir);\r\n}\r\n"};m.prototype.compile=function(){return this.compiler(this)},Object.defineProperty(m.prototype,"end",{get:function(){return this.start+this.duration}}),h.segmentDefinitions={},h.register=function(e,n){return h.segmentDefinitions[e]=n,n},h.prototype.add=function(e,n,t){var r=eval,i=this.duration;void 0!==t?("number"==typeof t?i=t:"string"==typeof t&&r("start"+t),this.duration=Math.max(this.duration,i+e)):this.duration+=e;for(var a=Object.keys(n),o=void 0,s=0;s "+(e.end+e.trail).toPrecision(4)+") return;"}},x={compiler:function(e){return"\n "+_.delayDuration(e)+"\n "+_.vec3("cTranslateFrom"+e.key,e.transition.from,2)+"\n "+_.vec3("cTranslateTo"+e.key,e.transition.to,2)+"\n \n void applyTransform"+e.key+"(float time, inout vec3 v) {\n \n "+_.renderCheck(e)+"\n "+_.progress(e)+"\n \n v += mix(cTranslateFrom"+e.key+", cTranslateTo"+e.key+", progress);\n }\n "},defaultFrom:new n.Vector3(0,0,0)};h.register("translate",x);var y={compiler:function(e){var n=e.transition.origin;return"\n "+_.delayDuration(e)+"\n "+_.vec3("cScaleFrom"+e.key,e.transition.from,2)+"\n "+_.vec3("cScaleTo"+e.key,e.transition.to,2)+"\n "+(n?_.vec3("cOrigin"+e.key,n,2):"")+"\n \n void applyTransform"+e.key+"(float time, inout vec3 v) {\n \n "+_.renderCheck(e)+"\n "+_.progress(e)+"\n \n "+(n?"v -= cOrigin"+e.key+";":"")+"\n v *= mix(cScaleFrom"+e.key+", cScaleTo"+e.key+", progress);\n "+(n?"v += cOrigin"+e.key+";":"")+"\n }\n "},defaultFrom:new n.Vector3(1,1,1)};h.register("scale",y);var b={compiler:function(e){var t=new n.Vector4(e.transition.from.axis.x,e.transition.from.axis.y,e.transition.from.axis.z,e.transition.from.angle),r=e.transition.to.axis||e.transition.from.axis,i=new n.Vector4(r.x,r.y,r.z,e.transition.to.angle),a=e.transition.origin;return"\n "+_.delayDuration(e)+"\n "+_.vec4("cRotationFrom"+e.key,t,8)+"\n "+_.vec4("cRotationTo"+e.key,i,8)+"\n "+(a?_.vec3("cOrigin"+e.key,a,2):"")+"\n \n void applyTransform"+e.key+"(float time, inout vec3 v) {\n "+_.renderCheck(e)+"\n "+_.progress(e)+"\n\n "+(a?"v -= cOrigin"+e.key+";":"")+"\n vec3 axis = normalize(mix(cRotationFrom"+e.key+".xyz, cRotationTo"+e.key+".xyz, progress));\n float angle = mix(cRotationFrom"+e.key+".w, cRotationTo"+e.key+".w, progress);\n vec4 q = quatFromAxisAngle(axis, angle);\n v = rotateVector(q, v);\n "+(a?"v += cOrigin"+e.key+";":"")+"\n }\n "},defaultFrom:{axis:new n.Vector3,angle:0}};h.register("rotate",b),e.BasicAnimationMaterial=r,e.LambertAnimationMaterial=i,e.PhongAnimationMaterial=a,e.StandardAnimationMaterial=o,e.PointsAnimationMaterial=s,e.DepthAnimationMaterial=f,e.DistanceAnimationMaterial=l,e.PrefabBufferGeometry=c,e.MultiPrefabBufferGeometry=u,e.ModelBufferGeometry=p,e.PointBufferGeometry=d,e.ShaderChunk=g,e.Timeline=h,e.TimelineSegment=m,e.TimelineChunks=_,e.TranslationSegment=x,e.ScaleSegment=y,e.RotationSegment=b,e.Utils=v,Object.defineProperty(e,"__esModule",{value:!0})}); diff --git a/index.html b/index.html index b97b2ef2..d0c45e00 100644 --- a/index.html +++ b/index.html @@ -27,6 +27,8 @@ + + diff --git a/src/event/menu/animation/AddFireEvent.js b/src/event/menu/animation/AddFireEvent.js index 64dfbed2..c06d09af 100644 --- a/src/event/menu/animation/AddFireEvent.js +++ b/src/event/menu/animation/AddFireEvent.js @@ -45,7 +45,7 @@ AddFireEvent.prototype.onAddFire = function () { camera ); - fire.mesh.name = 'Fire ' + ID++; + fire.mesh.name = '火焰' + ID++; fire.mesh.useSelectionBox = false; fire.mesh.geometry.boundingBox = new THREE.Box3( new THREE.Vector3(-fireWidth, -fireHeight, -fireDepth), diff --git a/src/event/menu/animation/AddPersonEvent.js b/src/event/menu/animation/AddPersonEvent.js index 8475b8f4..4c483e91 100644 --- a/src/event/menu/animation/AddPersonEvent.js +++ b/src/event/menu/animation/AddPersonEvent.js @@ -49,7 +49,7 @@ AddPersonEvent.prototype.onAddPerson = function () { mesh.scale.set(1, 1, 1); mesh.rotation.y = - 135 * Math.PI / 180; - mesh.name = 'Person ' + ID++; + mesh.name = '人' + ID++; editor.execute(new AddObjectCommand(mesh)); mesh.mixer = new THREE.AnimationMixer(mesh); diff --git a/src/event/menu/animation/AddSmokeEvent.js b/src/event/menu/animation/AddSmokeEvent.js index 0185ba8c..992a8fe6 100644 --- a/src/event/menu/animation/AddSmokeEvent.js +++ b/src/event/menu/animation/AddSmokeEvent.js @@ -34,7 +34,7 @@ AddSmokeEvent.prototype.onAddSmoke = function () { var smoke = new Smoke(camera, renderer); - smoke.mesh.name = 'Smoke ' + ID++; + smoke.mesh.name = '烟' + ID++; smoke.mesh.useSelectionBox = false; smoke.mesh.position.y = 3; smoke.mesh.scale