├── .gitignore ├── .gitattributes ├── assets ├── blob.png ├── fog.png ├── square.png ├── screenshot.jpg ├── explosion_sheet.png └── fireworks_sheet.png ├── package.json ├── index.html ├── README.md ├── index.js └── dist └── aframe-spe-particles-component.min.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /assets/blob.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harlyq/aframe-spe-particles-component/HEAD/assets/blob.png -------------------------------------------------------------------------------- /assets/fog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harlyq/aframe-spe-particles-component/HEAD/assets/fog.png -------------------------------------------------------------------------------- /assets/square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harlyq/aframe-spe-particles-component/HEAD/assets/square.png -------------------------------------------------------------------------------- /assets/screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harlyq/aframe-spe-particles-component/HEAD/assets/screenshot.jpg -------------------------------------------------------------------------------- /assets/explosion_sheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harlyq/aframe-spe-particles-component/HEAD/assets/explosion_sheet.png -------------------------------------------------------------------------------- /assets/fireworks_sheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harlyq/aframe-spe-particles-component/HEAD/assets/fireworks_sheet.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aframe-spe-particles-component", 3 | "version": "1.0.4", 4 | "description": "A-frame particle system using the Shader Particle Engine from squarefeet.", 5 | "files": [ 6 | "dist" 7 | ], 8 | "directories": { 9 | "lib": "lib" 10 | }, 11 | "scripts": { 12 | "dist": "browserify index.js -o dist/aframe-spe-particles-component.js && browserify index.js | uglifyjs -c -m -o dist/aframe-spe-particles-component.min.js" 13 | }, 14 | "author": "harlyq", 15 | "keywords": [ 16 | "a-frame", 17 | "aframe", 18 | "aframe-vr", 19 | "aframe-component" 20 | ], 21 | "repository": "github:harlyq/aframe-spe-particles-component", 22 | "license": "MIT", 23 | "devDependencies": {}, 24 | "peerDependencies": { 25 | "aframe": "^0.8.0" 26 | }, 27 | "dependencies": {} 28 | } 29 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aframe-spe-particles-component 2 | The spe-particles component provides a wrapper around the [Shader Particle Engine by SquareFeet](http://squarefeet.github.io/ShaderParticleEngine/), for generating GPU based particle systems in A-Frame. The system supports single textures and spritesheets, settings for position, velocity, acceleration, color, opacity, rotation, size, drag and wiggle. 3 | 4 | ## [Demo](https://harlyq.github.io/aframe-spe-particles-component/) 5 | 6 | ![Screenshot](assets/screenshot.jpg) 7 | 8 | ## Example 9 | ```html 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | ``` 19 | ## Value 20 | | Property | Description | Default Value | Type | 21 | | -------- | ----------- | ------------- | ---- | 22 | |acceleration|for sphere and disc distributions, only the x axis is used|{x: 0, y: 0, z: 0}|vec3| 23 | |acceleration-distribution|distribution of particle acceleration, for disc and sphere, only the x component will be used. if set to NONE use the 'distribution' attribute for accelerationDistribution|'NONE'|['NONE', 'BOX', 'SPHERE', 'DISC']| 24 | |acceleration-spread|spread of the particle's acceleration. for sphere and disc distributions, only the x axis is used|{x: 0, y: 0, z: 0}|vec3| 25 | |active-multiplier|multiply the rate of particles emission, if larger than 1 then the particles will be emitted in bursts. note, very large numbers will emit all particles at once|1|number| 26 | |affected-by-fog|if true, the particles are affected by THREE js fog|true|boolean| 27 | |alpha-test|alpha values below the alphaTest threshold are considered invisible|0|number| 28 | |angle|2D rotation of the particle over the particle's lifetime, max 4 elements|[0]|array| 29 | |angle-spread|spread in angle over the particle's lifetime, max 4 elements|[0]|array| 30 | |blending|blending mode, when drawing particles|'normal'|['no', 'normal', 'additive', 'subtractive', 'multiply', 'custom']| 31 | |color|array of colors over the particle's lifetime, max 4 elements|['#fff']|array| 32 | |color-spread|spread to apply to colors, spread an array of vec3 (r g b) with 0 for no spread. note the spread will be re-applied through-out the lifetime of the particle|[]|array| 33 | |depth-test|if true, don't render a particle's pixels if something is closer in the depth buffer|true|boolean| 34 | |depth-write|if true, particles write their depth into the depth buffer. this should be false if we use transparent particles|false|boolean| 35 | |direction|make the emitter operate forward or backward in time|'forward'|['forward', 'backward']| 36 | |distribution|distribution for particle positions, velocities and acceleration. will be overriden by specific '...Distribution' attributes|'BOX'|['BOX', 'SPHERE', 'DISC']| 37 | |drag|apply resistance to moving the particle, 0 is no resistance, 1 is full resistance, particle will stop moving at half of it's maxAge|0|number| 38 | |drag-spread|spread to apply to the drag attribute|0|number| 39 | |duration|duration of the emitter (seconds), if less than 0 then continuously emit particles|-1|number| 40 | |emitter-scale|global scaling factor for all particles from the emitter|100|number| 41 | |enable-in-editor|enable the emitter while the editor is active (i.e. while scene is paused)|false|boolean| 42 | |enabled|enable/disable the emitter|true|boolean| 43 | |frustum-culled|enable/disable frustum culling|false|boolean| 44 | |has-perspective|if true, particles will be larger the closer they are to the camera|true|boolean| 45 | |max-age|maximum age of a particle before reusing|1|number| 46 | |max-age-spread|variance for the 'maxAge' attribute|0|number| 47 | |opacity|opacity over the particle's lifetime, max 4 elements|[1]|array| 48 | |opacity-spread|spread in opacity over the particle's lifetime, max 4 elements|[0]|array| 49 | |particle-count|maximum number of particles for this emitter|100|int| 50 | |position-distribution|distribution of particle positions, disc and sphere will use the radius attributes. For box particles emit at 0,0,0, for sphere they emit on the surface of the sphere and for disc on the edge of a 2D disc on the XY plane|'NONE'|['NONE', 'BOX', 'SPHERE', 'DISC']| 51 | |position-offset|fixed offset to the apply to the emitter relative to its parent entity|{x: 0, y: 0, z: 0}|vec3| 52 | |position-spread|particles are positioned within +- of these local bounds. for sphere and disc distributions only the x axis is used|{x: 0, y: 0, z: 0}|vec3| 53 | |radius|radius of the disc or sphere emitter (ignored for box). note radius of 0 will prevent velocity and acceleration if they use a sphere or disc distribution|1|number| 54 | |radius-scale|scales the emitter for sphere and disc shapes to form oblongs and ellipses|{x: 1, y: 1, z: 1}|vec3| 55 | |randomize-acceleration|if true, re-randomize acceleration when re-spawning a particle, can incur a performance hit|false|boolean| 56 | |randomize-angle|if true, re-randomize angle when re-spawning a particle, can incur a performance hit|false|boolean| 57 | |randomize-color|if true, re-randomize colour when re-spawning a particle, can incur a performance hit|false|boolean| 58 | |randomize-drag|if true, re-randomize drag when re-spawning a particle, can incur a performance hit|false|boolean| 59 | |randomize-opacity|if true, re-randomize opacity when re-spawning a particle, can incur a performance hit|false|boolean| 60 | |randomize-position|if true, re-randomize position when re-spawning a particle, can incur a performance hit|false|boolean| 61 | |randomize-rotation|if true, re-randomize rotation when re-spawning a particle, can incur a performance hit|false|boolean| 62 | |randomize-size|if true, re-randomize size when re-spawning a particle, can incur a performance hit|false|boolean| 63 | |randomize-velocity|if true, re-randomize velocity when re-spawning a particle, can incur a performance hit|false|boolean| 64 | |relative|world relative particles move relative to the world, while local particles move relative to the emitter (i.e. if the emitter moves, all particles move with it)|'local'|['local', 'world']| 65 | |rotation|rotation in degrees|0|number| 66 | |rotation-axis|local axis when using rotation|{x: 0, y: 0, z: 0}|vec3| 67 | |rotation-axis-spread|variance in the axis of rotation|{x: 0, y: 0, z: 0}|vec3| 68 | |rotation-spread|rotation variance in degrees|0|number| 69 | |rotation-static|if true, the particles are fixed at their initial rotation value. if false, the particle will rotate from 0 to the 'rotation' value|false|boolean| 70 | |size|array of sizes over the particle's lifetime, max 4 elements|[1]|array| 71 | |size-spread|spread in size over the particle's lifetime, max 4 elements|[0]|array| 72 | |texture|texture to be used for each particle, may be a spritesheet||map| 73 | |texture-frame-count|number of frames in the spritesheet, negative numbers default to textureFrames.x * textureFrames.y|-1|int| 74 | |texture-frame-loop|number of times the spritesheet should be looped over the lifetime of a particle|1|int| 75 | |texture-frames|x and y frames for a spritesheet. each particle will transition through every frame of the spritesheet over its lifetime (see textureFramesLoop)|{x: 1, y: 1}|vec2| 76 | |use-transparency|should the particles be rendered with transparency?|true|boolean| 77 | |velocity|for sphere and disc distributions, only the x axis is used|{x: 0, y: 0, z: 0}|vec3| 78 | |velocity-distribution|distribution of particle velocities, for disc and sphere, only the x component will be used. if set to NONE use the 'distribution' attribute for velocityDistribution|'NONE'|['NONE', 'BOX', 'SPHERE', 'DISC']| 79 | |velocity-spread|variance for the velocity|{x: 0, y: 0, z: 0}|vec3| 80 | |wiggle|extra distance the particle moves over its lifetime|0|number| 81 | |wiggle-spread|+- spread for the wiggle attribute|0|number| 82 | 83 | ## Limitations 84 | When using the disc or sphere distributions for velocity or acceleration, setting the radius to 0, will also disable the velocity and acceleration 85 | 86 | Enabling **randomizePosition** on sphere or disc distributions breaks the velocity and acceleration behavior 87 | 88 | Having an **activeMultiplier** greater than 1 provides a burst of particles, however the emitter will gradually get out of sync and emit more and more particles between each burst 89 | 90 | On Firefox when using a single particle with an infinite duration emitter, the particle may disappear 91 | 92 | If the **duration** is the same as **maxAge**, then particles may be emitted at the end of the duration 93 | 94 | If a **texture** is not provided then a 1x1 white texture will be used -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 harlyq 2 | // MIT license 3 | 4 | // A-frame component for the Shader Particle Engine 5 | let SPE = require("./lib/SPE.js") 6 | 7 | const toVector3 = a => new THREE.Vector3(a.x, a.y, a.z) 8 | const toColor = a => new THREE.Color(a) 9 | const toVector2 = a => new THREE.Vector2(a.x, a.y) 10 | const parseFloatArray = a => typeof a === "string" ? a.split(",").map(y => parseFloat(y)) : a 11 | const degToRad = a => parseFloat(a)/180*Math.PI 12 | const radToDeg = a => parseFloat(a)*180/Math.PI 13 | 14 | let uniqueEmitterID = 1 15 | 16 | AFRAME.registerComponent("spe-particles", { 17 | schema: { 18 | enableInEditor: { 19 | default: false, 20 | description: "enable the emitter while the editor is active (i.e. while scene is paused)", 21 | }, 22 | enabled: { 23 | default: true, 24 | description: "enable/disable the emitter", 25 | }, 26 | frustumCulled: { 27 | default: false, 28 | description: "enable/disable frustum culling", 29 | }, 30 | 31 | // GROUP ATTRIBUTES 32 | texture: { 33 | type: "map", 34 | description: "texture to be used for each particle, may be a spritesheet", 35 | }, 36 | textureFrames: { 37 | type: "vec2", 38 | default: {x: 1, y: 1}, 39 | description: "x and y frames for a spritesheet. each particle will transition through every frame of the spritesheet over its lifetime (see textureFramesLoop)", 40 | }, 41 | textureFrameCount: { 42 | type: "int", 43 | default: -1, 44 | description: "number of frames in the spritesheet, negative numbers default to textureFrames.x * textureFrames.y", 45 | }, 46 | textureFrameLoop: { 47 | type: "int", 48 | default: 1, 49 | description: "number of times the spritesheet should be looped over the lifetime of a particle", 50 | }, 51 | // maxParticleCount: { 52 | // default: 1000, 53 | // description: "maximum number of particles for all emitters in this group (currently only one emitter per group)", 54 | // }, 55 | blending: { 56 | default: "normal", 57 | oneOf: ["no", "normal", "additive", "subtractive", "multiply", "custom"], 58 | description: "blending mode, when drawing particles", 59 | parse: x => (x || "no").toLowerCase(), 60 | }, 61 | hasPerspective: { 62 | default: true, 63 | description: "if true, particles will be larger the closer they are to the camera. setting this to false cancels the effect of emitterScale", 64 | }, 65 | useTransparency: { 66 | default: true, 67 | description: "should the particles be rendered with transparency?", 68 | }, 69 | alphaTest: { 70 | default: 0, 71 | min: 0, 72 | max: 1, 73 | description: "alpha values below the alphaTest threshold are considered invisible", 74 | }, 75 | depthWrite: { 76 | default: false, 77 | description: "if true, particles write their depth into the depth buffer. this should be false if we use transparent particles", 78 | }, 79 | depthTest: { 80 | default: true, 81 | description: "if true, don't render a particle's pixels if something is closer in the depth buffer", 82 | }, 83 | affectedByFog: { 84 | default: true, 85 | description: "if true, the particles are affected by THREE js fog", 86 | }, 87 | emitterScale: { 88 | default: 100, 89 | description: "global scaling factor for all particles from the emitter", 90 | }, 91 | 92 | // EMITTER ATTRIBUTES 93 | relative: { 94 | default: "local", 95 | oneOf: ["local", "world"], 96 | description: "world relative particles move relative to the world, while local particles move relative to the emitter (i.e. if the emitter moves, all particles move with it)", 97 | parse: x => (x || "local").toLowerCase() 98 | }, 99 | particleCount: { 100 | type: "int", 101 | default: 100, 102 | description: "maximum number of particles for this emitter", 103 | }, 104 | duration: { 105 | default: -1, 106 | description: "duration of the emitter (seconds), if less than 0 then continuously emit particles", 107 | }, 108 | distribution: { 109 | default: "BOX", 110 | oneOf: ["BOX", "SPHERE", "DISC"], 111 | description: "distribution for particle positions, velocities and acceleration. will be overriden by specific '...Distribution' attributes", 112 | parse: x => (x || "BOX").toUpperCase(), 113 | }, 114 | activeMultiplier: { 115 | default: 1, 116 | min: 0, 117 | description: "multiply the rate of particles emission, if larger than 1 then the particles will be emitted in bursts. note, very large numbers will emit all particles at once", 118 | }, 119 | direction: { 120 | default: "forward", 121 | oneOf: ["forward", "backward"], 122 | description: "make the emitter operate forward or backward in time", 123 | parse: x => (x || "forward").toLowerCase(), 124 | }, 125 | maxAge: { 126 | default: 1, 127 | description: "maximum age of a particle before reusing", 128 | }, 129 | maxAgeSpread: { 130 | default: 0, 131 | description: "variance for the 'maxAge' attribute", 132 | }, 133 | positionDistribution: { 134 | default: "NONE", 135 | oneOf: ["NONE", "BOX", "SPHERE", "DISC"], 136 | description: "distribution of particle positions, disc and sphere will use the radius attributes. For box particles emit at 0,0,0, for sphere they emit on the surface of the sphere and for disc on the edge of a 2D disc on the XY plane", 137 | parse: x => (x || "NONE").toUpperCase(), 138 | }, 139 | positionSpread: { 140 | type: "vec3", 141 | description: "particles are positioned within +- of these local bounds. for sphere and disc distributions only the x axis is used", 142 | }, 143 | positionOffset: { 144 | type: "vec3", 145 | description: "fixed offset to the apply to the emitter relative to its parent entity", 146 | }, 147 | randomizePosition: { 148 | default: false, 149 | description: "if true, re-randomize position when re-spawning a particle, can incur a performance hit", 150 | }, 151 | radius: { 152 | default: 1, 153 | min: 0, 154 | description: "radius of the disc or sphere emitter (ignored for box). note radius of 0 will prevent velocity and acceleration if they use a sphere or disc distribution", 155 | }, 156 | radiusScale: { 157 | type: "vec3", 158 | default: { x: 1, y: 1, z: 1 }, 159 | description: "scales the emitter for sphere and disc shapes to form oblongs and ellipses", 160 | }, 161 | velocityDistribution: { 162 | default: "NONE", 163 | oneOf: ["NONE", "BOX", "SPHERE", "DISC"], 164 | description: "distribution of particle velocities, for disc and sphere, only the x component will be used. if set to NONE use the 'distribution' attribute for velocityDistribution", 165 | parse: x => (x || "NONE").toUpperCase(), 166 | }, 167 | velocity: { 168 | type: "vec3", 169 | description: "for sphere and disc distributions, only the x axis is used", 170 | }, 171 | velocitySpread: { 172 | type: "vec3", 173 | description: "variance for the velocity", 174 | }, 175 | randomizeVelocity: { 176 | default: false, 177 | description: "if true, re-randomize velocity when re-spawning a particle, can incur a performance hit", 178 | }, 179 | accelerationDistribution: { 180 | default: "NONE", 181 | oneOf: ["NONE", "BOX", "SPHERE", "DISC"], 182 | description: "distribution of particle acceleration, for disc and sphere, only the x component will be used. if set to NONE use the 'distribution' attribute for accelerationDistribution", 183 | parse: x => (x || "NONE").toUpperCase(), 184 | }, 185 | acceleration: { 186 | type: "vec3", 187 | description: "for sphere and disc distributions, only the x axis is used", 188 | }, 189 | accelerationSpread: { 190 | type: "vec3", 191 | description: "spread of the particle's acceleration. for sphere and disc distributions, only the x axis is used", 192 | }, 193 | randomizeAcceleration: { 194 | default: false, 195 | description: "if true, re-randomize acceleration when re-spawning a particle, can incur a performance hit", 196 | }, 197 | drag: { 198 | default: 0, 199 | min: 0, 200 | max: 1, 201 | description: "apply resistance to moving the particle, 0 is no resistance, 1 is full resistance, particle will stop moving at half of it's maxAge" 202 | }, 203 | dragSpread: { 204 | default: 0, 205 | description: "spread to apply to the drag attribute" 206 | }, 207 | randomizeDrag: { 208 | default: false, 209 | description: "if true, re-randomize drag when re-spawning a particle, can incur a performance hit", 210 | }, 211 | wiggle: { 212 | default: 0, 213 | description: "extra distance the particle moves over its lifetime", 214 | }, 215 | wiggleSpread: { 216 | default: 0, 217 | description: "+- spread for the wiggle attribute", 218 | }, 219 | rotation: { 220 | default: 0, 221 | description: "rotation in degrees", 222 | parse: x => degToRad(x), 223 | stringify: x => radToDeg(x), 224 | }, 225 | rotationSpread: { 226 | default: 0, 227 | description: "rotation variance in degrees", 228 | parse: x => degToRad(x), 229 | stringify: x => radToDeg(x), 230 | }, 231 | rotationAxis: { 232 | type: "vec3", 233 | description: "local axis when using rotation", 234 | }, 235 | rotationAxisSpread: { 236 | type: "vec3", 237 | description: "variance in the axis of rotation", 238 | }, 239 | rotationStatic: { 240 | default: false, 241 | description: "if true, the particles are fixed at their initial rotation value. if false, the particle will rotate from 0 to the 'rotation' value", 242 | }, 243 | // rotationPivot: { 244 | // default: {x: Number.MAX_VALUE, y: Number.MAX_VALUE, z: Number.MAX_VALUE, }, 245 | // description: "if set rotate about this pivot, otherwise rotate about the emitter", 246 | // }, 247 | randomizeRotation: { 248 | default: false, 249 | description: "if true, re-randomize rotation when re-spawning a particle, can incur a performance hit", 250 | }, 251 | color: { 252 | type: "array", 253 | default: ["#fff"], 254 | description: "array of colors over the particle's lifetime, max 4 elements", 255 | }, 256 | colorSpread: { 257 | type: "array", 258 | default: [], 259 | description: "spread to apply to colors, spread an array of vec3 (r g b) with 0 for no spread. note the spread will be re-applied through-out the lifetime of the particle", 260 | parse: x => typeof x === "string" ? x.split(",").map(AFRAME.utils.coordinates.parse) : x, 261 | stringify: x => x.map(AFRAME.utils.coordinates.stringify).join(","), 262 | }, 263 | randomizeColor: { 264 | default: false, 265 | description: "if true, re-randomize colour when re-spawning a particle, can incur a performance hit", 266 | }, 267 | opacity: { 268 | type: "array", 269 | default: [1], 270 | description: "opacity over the particle's lifetime, max 4 elements", 271 | parse: parseFloatArray, 272 | }, 273 | opacitySpread: { 274 | type: "array", 275 | default: [0], 276 | description: "spread in opacity over the particle's lifetime, max 4 elements", 277 | parse: parseFloatArray, 278 | }, 279 | randomizeOpacity: { 280 | default: false, 281 | description: "if true, re-randomize opacity when re-spawning a particle, can incur a performance hit", 282 | }, 283 | size: { 284 | type: "array", 285 | default: [1], 286 | description: "array of sizes over the particle's lifetime, max 4 elements", 287 | parse: parseFloatArray, 288 | }, 289 | sizeSpread: { 290 | type: "array", 291 | default: [0], 292 | description: "spread in size over the particle's lifetime, max 4 elements", 293 | parse: parseFloatArray, 294 | }, 295 | randomizeSize: { 296 | default: false, 297 | description: "if true, re-randomize size when re-spawning a particle, can incur a performance hit", 298 | }, 299 | angle: { 300 | type: "array", 301 | default: [0], 302 | description: "2D rotation of the particle over the particle's lifetime, max 4 elements", 303 | parse: parseFloatArray, 304 | }, 305 | angleSpread: { 306 | type: "array", 307 | default: [0], 308 | description: "spread in angle over the particle's lifetime, max 4 elements", 309 | parse: parseFloatArray, 310 | }, 311 | randomizeAngle: { 312 | default: false, 313 | description: "if true, re-randomize angle when re-spawning a particle, can incur a performance hit", 314 | }, 315 | }, 316 | 317 | multiple: true, 318 | 319 | init: function () { 320 | this.particleGroup = null 321 | this.referenceEl = null 322 | this.pauseTickId = undefined 323 | this.emitterID = uniqueEmitterID++ 324 | this.pauseTick = this.pauseTick.bind(this) 325 | this.defaultTexture = new THREE.DataTexture(new Uint8Array(3).fill(255), 1, 1, THREE.RGBFormat) 326 | this.defaultTexture.needsUpdate = true 327 | }, 328 | 329 | update: function (oldData) { 330 | // don't recreate the effect if just changing enabled or enableInEditor 331 | const delta = diff(oldData, this.data) 332 | if (Object.keys(delta).some(x => !["enabled", "enableInEditor"].includes(x))) { 333 | this.removeParticleSystem() 334 | this.addParticleSystem() 335 | } 336 | 337 | if (this.data.enabled) { 338 | this.startParticles() 339 | } else { 340 | this.stopParticles() 341 | } 342 | 343 | // HACK - assume editor is up if isPlaying on this component is false 344 | if (!this.isPlaying && this.data.enableInEditor) { 345 | this.setupPauseTick() 346 | } else { 347 | this.shutdownPauseTick() 348 | } 349 | }, 350 | 351 | remove: function () { 352 | this.removeParticleSystem() 353 | }, 354 | 355 | tick: function (time, timeDelta) { 356 | this.tickParticleSystem(timeDelta) 357 | }, 358 | 359 | pause: function () { 360 | this.setupPauseTick() 361 | }, 362 | 363 | play: function () { 364 | this.shutdownPauseTick() 365 | }, 366 | 367 | setupPauseTick: function () { 368 | if (!this.pauseTickId && this.data.enableInEditor) { 369 | this.pauseTick(true) 370 | } 371 | }, 372 | 373 | shutdownPauseTick: function () { 374 | if (this.pauseTickId) { 375 | clearTimeout(this.pauseTickId) 376 | this.pauseTickId = undefined 377 | } 378 | }, 379 | 380 | pauseTick: function() { 381 | this.tickParticleSystem(33) 382 | this.pauseTickId = setTimeout(this.pauseTick, 33) 383 | }, 384 | 385 | tickParticleSystem: function (dt) { 386 | if (this.data.relative === "world") { 387 | let newPosition = toVector3(this.data.positionOffset).applyMatrix4(this.el.object3D.matrixWorld) 388 | this.particleGroup.emitters[0].position.value = newPosition 389 | } 390 | 391 | this.particleGroup.tick(dt/1000) 392 | }, 393 | 394 | addParticleSystem: function() { 395 | let data = this.data 396 | let textureLoader = new THREE.TextureLoader() 397 | let particleTexture = data.texture ? textureLoader.load(data.texture) : this.defaultTexture 398 | 399 | let blending = data.blending || "No" 400 | blending = blending.charAt(0).toUpperCase() + blending.substring(1).toLowerCase() + "Blending" 401 | if(!blending in THREE) console.error(`unknown blending mode "${data.blending}"`) 402 | 403 | console.assert(this.particleGroup === null) 404 | let groupOptions = { 405 | texture: { 406 | value: particleTexture, 407 | frames: toVector2(data.textureFrames), 408 | frameCount: data.textureFrameCount >= 0 ? data.textureFrameCount : undefined, 409 | loop: data.textureFrameLoop, 410 | }, 411 | maxParticleCount: data.particleCount, //data.maxParticleCount, 412 | blending: THREE[blending], 413 | hasPerspective: data.hasPerspective, 414 | transparent: data.useTransparency, 415 | alphaTest: data.alphaTest, 416 | depthWrite: data.depthWrite, 417 | depthTest: data.depthTest, 418 | fog: data.affectedByFog, 419 | scale: data.emitterScale, 420 | } 421 | this.particleGroup = new SPE.Group(groupOptions) 422 | 423 | let emitterOptions = { 424 | type: SPE.distributions[data.distribution in SPE.distributions ? data.distribution : "BOX"], 425 | particleCount: data.particleCount, 426 | duration: data.duration >= 0 ? data.duration : null, 427 | // isStatic: true, 428 | activeMultiplier: data.activeMultiplier, 429 | direction: data.direction === "forward" ? 1 : -1, 430 | maxAge: { 431 | value: data.maxAge, 432 | spread: data.maxAgeSpread, 433 | }, 434 | position: { 435 | value: data.relative === "World" ? toVector3(this.data.positionOffset).applyMatrix4(this.el.object3D.matrixWorld) : toVector3(data.positionOffset).applyMatrix4(this.el.object3D.matrix), 436 | radius: data.radius, 437 | radiusScale: toVector3(data.radiusScale), 438 | spread: toVector3(data.positionSpread), 439 | distribution: SPE.distributions[data.positionDistribution in SPE.distributions ? data.positionDistribution : data.distribution], // default to the base distribution 440 | randomise: data.randomizePosition, 441 | }, 442 | velocity: { 443 | value: toVector3(data.velocity), 444 | spread: toVector3(data.velocitySpread), 445 | distribution: SPE.distributions[data.velocityDistribution in SPE.distributions ? data.velocityDistribution : data.distribution], // default to the base distribution 446 | randomise: data.randomizeVelocity, 447 | }, 448 | acceleration: { 449 | value: toVector3(data.acceleration), 450 | spread: toVector3(data.accelerationSpread), 451 | distribution: SPE.distributions[data.accelerationDistribution in SPE.distributions ? data.accelerationDistribution : data.distribution], // default to the base distribution 452 | randomise: data.randomizeAcceleration, 453 | }, 454 | drag: { 455 | value: data.drag, 456 | spread: data.dragSpread, 457 | randomise: data.randomizeDrag, 458 | }, 459 | wiggle: { 460 | value: data.wiggle, 461 | spread: data.wiggleSpread, 462 | }, 463 | rotation: { 464 | axis: toVector3(data.rotationAxis), 465 | axisSpread: toVector3(data.rotationAxisSpread), 466 | angle: data.rotation, 467 | angleSpread: data.rotationSpread, 468 | static: data.rotationStatic, 469 | // center: data.rotationPivot, 470 | randomise: data.randomizeRotation, 471 | }, 472 | color: { 473 | value: data.color.length > 0 ? data.color.map(x => toColor(x)) : [toColor("#fff")], 474 | spread: data.colorSpread.length > 0 ? data.colorSpread.map(x => toVector3(x)) : undefined, // doesn't accept an empty array 475 | randomise: data.randomizeColor, 476 | }, 477 | opacity: { 478 | value: data.opacity, 479 | spread: data.opacitySpread, 480 | randomise: data.randomizeOpacity, 481 | }, 482 | size: { 483 | value: data.size, 484 | spread: data.sizeSpread, 485 | randomise: data.randomizeSize, 486 | }, 487 | angle: { 488 | value: data.angle, 489 | spread: data.angleSpread, 490 | randomise: data.randomizeAngle, 491 | }, 492 | } 493 | let emitter = new SPE.Emitter(emitterOptions) 494 | 495 | this.particleGroup.addEmitter(emitter) 496 | this.particleGroup.mesh.frustumCulled = data.frustumCulled // TODO verify this 497 | 498 | // World emitters are parented to the world and we set their position each frame. 499 | // Local emitters are parented to the DOM entity 500 | this.referenceEl = data.relative === "world" ? this.el.sceneEl : this.el 501 | this.referenceEl.setObject3D(this.getEmitterName(), this.particleGroup.mesh) 502 | }, 503 | 504 | removeParticleSystem: function() { 505 | if (this.particleGroup) { 506 | this.referenceEl.removeObject3D(this.getEmitterName()) 507 | this.particleGroup = null 508 | } 509 | }, 510 | 511 | startParticles: function() { 512 | this.particleGroup.emitters.forEach(em => em.enable()) 513 | }, 514 | 515 | stopParticles: function() { 516 | this.particleGroup.emitters.forEach(em => em.disable()) 517 | }, 518 | 519 | getEmitterName: function() { 520 | return "spe-particles" + this.emitterID 521 | } 522 | }) 523 | 524 | // Use a custom diff because AFRAME.utils.diff() does not correctly diff arrays (https://github.com/aframevr/aframe/issues/3591) 525 | function diff(a, b) { 526 | let delta = {} 527 | let keys = Object.keys(a) 528 | for (let k in b) { 529 | if (!keys.includes[k]) { keys.push(k) } 530 | } 531 | 532 | for (let k of keys) { 533 | let av = a[k] 534 | let bv = b[k] 535 | let isObj = av && bv && ( (av.constructor == Object && bv.constructor === Object) || 536 | (Array.isArray(av) && Array.isArray(bv)) ) 537 | 538 | if ((isObj && !AFRAME.utils.deepEqual(av, bv)) || (!isObj && av !== bv)) { 539 | delta[k] = bv; 540 | } 541 | } 542 | 543 | return delta 544 | } 545 | -------------------------------------------------------------------------------- /dist/aframe-spe-particles-component.min.js: -------------------------------------------------------------------------------- 1 | !function(){return function e(t,r,i){function a(o,n){if(!r[o]){if(!t[o]){var u="function"==typeof require&&require;if(!n&&u)return u(o,!0);if(s)return s(o,!0);var l=new Error("Cannot find module '"+o+"'");throw l.code="MODULE_NOT_FOUND",l}var p=r[o]={exports:{}};t[o][0].call(p.exports,function(e){return a(t[o][1][e]||e)},p,p.exports,e,t,r,i)}return r[o].exports}for(var s="function"==typeof require&&require,o=0;onew THREE.Vector3(e.x,e.y,e.z),s=e=>new THREE.Color(e),o=e=>"string"==typeof e?e.split(",").map(e=>parseFloat(e)):e,n=e=>parseFloat(e)/180*Math.PI,u=e=>180*parseFloat(e)/Math.PI;let l=1;AFRAME.registerComponent("spe-particles",{schema:{enableInEditor:{default:!1,description:"enable the emitter while the editor is active (i.e. while scene is paused)"},enabled:{default:!0,description:"enable/disable the emitter"},frustumCulled:{default:!1,description:"enable/disable frustum culling"},texture:{type:"map",description:"texture to be used for each particle, may be a spritesheet"},textureFrames:{type:"vec2",default:{x:1,y:1},description:"x and y frames for a spritesheet. each particle will transition through every frame of the spritesheet over its lifetime (see textureFramesLoop)"},textureFrameCount:{type:"int",default:-1,description:"number of frames in the spritesheet, negative numbers default to textureFrames.x * textureFrames.y"},textureFrameLoop:{type:"int",default:1,description:"number of times the spritesheet should be looped over the lifetime of a particle"},blending:{default:"normal",oneOf:["no","normal","additive","subtractive","multiply","custom"],description:"blending mode, when drawing particles",parse:e=>(e||"no").toLowerCase()},hasPerspective:{default:!0,description:"if true, particles will be larger the closer they are to the camera. setting this to false cancels the effect of emitterScale"},useTransparency:{default:!0,description:"should the particles be rendered with transparency?"},alphaTest:{default:0,min:0,max:1,description:"alpha values below the alphaTest threshold are considered invisible"},depthWrite:{default:!1,description:"if true, particles write their depth into the depth buffer. this should be false if we use transparent particles"},depthTest:{default:!0,description:"if true, don't render a particle's pixels if something is closer in the depth buffer"},affectedByFog:{default:!0,description:"if true, the particles are affected by THREE js fog"},emitterScale:{default:100,description:"global scaling factor for all particles from the emitter"},relative:{default:"local",oneOf:["local","world"],description:"world relative particles move relative to the world, while local particles move relative to the emitter (i.e. if the emitter moves, all particles move with it)",parse:e=>(e||"local").toLowerCase()},particleCount:{type:"int",default:100,description:"maximum number of particles for this emitter"},duration:{default:-1,description:"duration of the emitter (seconds), if less than 0 then continuously emit particles"},distribution:{default:"BOX",oneOf:["BOX","SPHERE","DISC"],description:"distribution for particle positions, velocities and acceleration. will be overriden by specific '...Distribution' attributes",parse:e=>(e||"BOX").toUpperCase()},activeMultiplier:{default:1,min:0,description:"multiply the rate of particles emission, if larger than 1 then the particles will be emitted in bursts. note, very large numbers will emit all particles at once"},direction:{default:"forward",oneOf:["forward","backward"],description:"make the emitter operate forward or backward in time",parse:e=>(e||"forward").toLowerCase()},maxAge:{default:1,description:"maximum age of a particle before reusing"},maxAgeSpread:{default:0,description:"variance for the 'maxAge' attribute"},positionDistribution:{default:"NONE",oneOf:["NONE","BOX","SPHERE","DISC"],description:"distribution of particle positions, disc and sphere will use the radius attributes. For box particles emit at 0,0,0, for sphere they emit on the surface of the sphere and for disc on the edge of a 2D disc on the XY plane",parse:e=>(e||"NONE").toUpperCase()},positionSpread:{type:"vec3",description:"particles are positioned within +- of these local bounds. for sphere and disc distributions only the x axis is used"},positionOffset:{type:"vec3",description:"fixed offset to the apply to the emitter relative to its parent entity"},randomizePosition:{default:!1,description:"if true, re-randomize position when re-spawning a particle, can incur a performance hit"},radius:{default:1,min:0,description:"radius of the disc or sphere emitter (ignored for box). note radius of 0 will prevent velocity and acceleration if they use a sphere or disc distribution"},radiusScale:{type:"vec3",default:{x:1,y:1,z:1},description:"scales the emitter for sphere and disc shapes to form oblongs and ellipses"},velocityDistribution:{default:"NONE",oneOf:["NONE","BOX","SPHERE","DISC"],description:"distribution of particle velocities, for disc and sphere, only the x component will be used. if set to NONE use the 'distribution' attribute for velocityDistribution",parse:e=>(e||"NONE").toUpperCase()},velocity:{type:"vec3",description:"for sphere and disc distributions, only the x axis is used"},velocitySpread:{type:"vec3",description:"variance for the velocity"},randomizeVelocity:{default:!1,description:"if true, re-randomize velocity when re-spawning a particle, can incur a performance hit"},accelerationDistribution:{default:"NONE",oneOf:["NONE","BOX","SPHERE","DISC"],description:"distribution of particle acceleration, for disc and sphere, only the x component will be used. if set to NONE use the 'distribution' attribute for accelerationDistribution",parse:e=>(e||"NONE").toUpperCase()},acceleration:{type:"vec3",description:"for sphere and disc distributions, only the x axis is used"},accelerationSpread:{type:"vec3",description:"spread of the particle's acceleration. for sphere and disc distributions, only the x axis is used"},randomizeAcceleration:{default:!1,description:"if true, re-randomize acceleration when re-spawning a particle, can incur a performance hit"},drag:{default:0,min:0,max:1,description:"apply resistance to moving the particle, 0 is no resistance, 1 is full resistance, particle will stop moving at half of it's maxAge"},dragSpread:{default:0,description:"spread to apply to the drag attribute"},randomizeDrag:{default:!1,description:"if true, re-randomize drag when re-spawning a particle, can incur a performance hit"},wiggle:{default:0,description:"extra distance the particle moves over its lifetime"},wiggleSpread:{default:0,description:"+- spread for the wiggle attribute"},rotation:{default:0,description:"rotation in degrees",parse:e=>n(e),stringify:e=>u(e)},rotationSpread:{default:0,description:"rotation variance in degrees",parse:e=>n(e),stringify:e=>u(e)},rotationAxis:{type:"vec3",description:"local axis when using rotation"},rotationAxisSpread:{type:"vec3",description:"variance in the axis of rotation"},rotationStatic:{default:!1,description:"if true, the particles are fixed at their initial rotation value. if false, the particle will rotate from 0 to the 'rotation' value"},randomizeRotation:{default:!1,description:"if true, re-randomize rotation when re-spawning a particle, can incur a performance hit"},color:{type:"array",default:["#fff"],description:"array of colors over the particle's lifetime, max 4 elements"},colorSpread:{type:"array",default:[],description:"spread to apply to colors, spread an array of vec3 (r g b) with 0 for no spread. note the spread will be re-applied through-out the lifetime of the particle",parse:e=>"string"==typeof e?e.split(",").map(AFRAME.utils.coordinates.parse):e,stringify:e=>e.map(AFRAME.utils.coordinates.stringify).join(",")},randomizeColor:{default:!1,description:"if true, re-randomize colour when re-spawning a particle, can incur a performance hit"},opacity:{type:"array",default:[1],description:"opacity over the particle's lifetime, max 4 elements",parse:o},opacitySpread:{type:"array",default:[0],description:"spread in opacity over the particle's lifetime, max 4 elements",parse:o},randomizeOpacity:{default:!1,description:"if true, re-randomize opacity when re-spawning a particle, can incur a performance hit"},size:{type:"array",default:[1],description:"array of sizes over the particle's lifetime, max 4 elements",parse:o},sizeSpread:{type:"array",default:[0],description:"spread in size over the particle's lifetime, max 4 elements",parse:o},randomizeSize:{default:!1,description:"if true, re-randomize size when re-spawning a particle, can incur a performance hit"},angle:{type:"array",default:[0],description:"2D rotation of the particle over the particle's lifetime, max 4 elements",parse:o},angleSpread:{type:"array",default:[0],description:"spread in angle over the particle's lifetime, max 4 elements",parse:o},randomizeAngle:{default:!1,description:"if true, re-randomize angle when re-spawning a particle, can incur a performance hit"}},multiple:!0,init:function(){this.particleGroup=null,this.referenceEl=null,this.pauseTickId=void 0,this.emitterID=l++,this.pauseTick=this.pauseTick.bind(this),this.defaultTexture=new THREE.DataTexture(new Uint8Array(3).fill(255),1,1,THREE.RGBFormat),this.defaultTexture.needsUpdate=!0},update:function(e){const t=function(e,t){let r={},i=Object.keys(e);for(let e in t)i.includes[e]||i.push(e);for(let a of i){let i=e[a],s=t[a],o=i&&s&&(i.constructor==Object&&s.constructor===Object||Array.isArray(i)&&Array.isArray(s));(o&&!AFRAME.utils.deepEqual(i,s)||!o&&i!==s)&&(r[a]=s)}return r}(e,this.data);Object.keys(t).some(e=>!["enabled","enableInEditor"].includes(e))&&(this.removeParticleSystem(),this.addParticleSystem()),this.data.enabled?this.startParticles():this.stopParticles(),!this.isPlaying&&this.data.enableInEditor?this.setupPauseTick():this.shutdownPauseTick()},remove:function(){this.removeParticleSystem()},tick:function(e,t){this.tickParticleSystem(t)},pause:function(){this.setupPauseTick()},play:function(){this.shutdownPauseTick()},setupPauseTick:function(){!this.pauseTickId&&this.data.enableInEditor&&this.pauseTick(!0)},shutdownPauseTick:function(){this.pauseTickId&&(clearTimeout(this.pauseTickId),this.pauseTickId=void 0)},pauseTick:function(){this.tickParticleSystem(33),this.pauseTickId=setTimeout(this.pauseTick,33)},tickParticleSystem:function(e){if("world"===this.data.relative){let e=a(this.data.positionOffset).applyMatrix4(this.el.object3D.matrixWorld);this.particleGroup.emitters[0].position.value=e}this.particleGroup.tick(e/1e3)},addParticleSystem:function(){let e=this.data,t=new THREE.TextureLoader,r=e.texture?t.load(e.texture):this.defaultTexture,o=e.blending||"No";!(o=o.charAt(0).toUpperCase()+o.substring(1).toLowerCase()+"Blending")in THREE&&console.error(`unknown blending mode "${e.blending}"`),console.assert(null===this.particleGroup);let n={texture:{value:r,frames:(e=>new THREE.Vector2(e.x,e.y))(e.textureFrames),frameCount:e.textureFrameCount>=0?e.textureFrameCount:void 0,loop:e.textureFrameLoop},maxParticleCount:e.particleCount,blending:THREE[o],hasPerspective:e.hasPerspective,transparent:e.useTransparency,alphaTest:e.alphaTest,depthWrite:e.depthWrite,depthTest:e.depthTest,fog:e.affectedByFog,scale:e.emitterScale};this.particleGroup=new i.Group(n);let u={type:i.distributions[e.distribution in i.distributions?e.distribution:"BOX"],particleCount:e.particleCount,duration:e.duration>=0?e.duration:null,activeMultiplier:e.activeMultiplier,direction:"forward"===e.direction?1:-1,maxAge:{value:e.maxAge,spread:e.maxAgeSpread},position:{value:"World"===e.relative?a(this.data.positionOffset).applyMatrix4(this.el.object3D.matrixWorld):a(e.positionOffset).applyMatrix4(this.el.object3D.matrix),radius:e.radius,radiusScale:a(e.radiusScale),spread:a(e.positionSpread),distribution:i.distributions[e.positionDistribution in i.distributions?e.positionDistribution:e.distribution],randomise:e.randomizePosition},velocity:{value:a(e.velocity),spread:a(e.velocitySpread),distribution:i.distributions[e.velocityDistribution in i.distributions?e.velocityDistribution:e.distribution],randomise:e.randomizeVelocity},acceleration:{value:a(e.acceleration),spread:a(e.accelerationSpread),distribution:i.distributions[e.accelerationDistribution in i.distributions?e.accelerationDistribution:e.distribution],randomise:e.randomizeAcceleration},drag:{value:e.drag,spread:e.dragSpread,randomise:e.randomizeDrag},wiggle:{value:e.wiggle,spread:e.wiggleSpread},rotation:{axis:a(e.rotationAxis),axisSpread:a(e.rotationAxisSpread),angle:e.rotation,angleSpread:e.rotationSpread,static:e.rotationStatic,randomise:e.randomizeRotation},color:{value:e.color.length>0?e.color.map(e=>s(e)):[s("#fff")],spread:e.colorSpread.length>0?e.colorSpread.map(e=>a(e)):void 0,randomise:e.randomizeColor},opacity:{value:e.opacity,spread:e.opacitySpread,randomise:e.randomizeOpacity},size:{value:e.size,spread:e.sizeSpread,randomise:e.randomizeSize},angle:{value:e.angle,spread:e.angleSpread,randomise:e.randomizeAngle}},l=new i.Emitter(u);this.particleGroup.addEmitter(l),this.particleGroup.mesh.frustumCulled=e.frustumCulled,this.referenceEl="world"===e.relative?this.el.sceneEl:this.el,this.referenceEl.setObject3D(this.getEmitterName(),this.particleGroup.mesh)},removeParticleSystem:function(){this.particleGroup&&(this.referenceEl.removeObject3D(this.getEmitterName()),this.particleGroup=null)},startParticles:function(){this.particleGroup.emitters.forEach(e=>e.enable())},stopParticles:function(){this.particleGroup.emitters.forEach(e=>e.disable())},getEmitterName:function(){return"spe-particles"+this.emitterID}})},{"./lib/SPE.js":2}],2:[function(e,t,r){var i={distributions:{BOX:1,SPHERE:2,DISC:3},valueOverLifetimeLength:4};"function"==typeof define&&define.amd?define("spe",i):void 0!==r&&void 0!==t&&(t.exports=i),i.TypedArrayHelper=function(e,t,r,i){"use strict";this.componentSize=r||1,this.size=t||1,this.TypedArrayConstructor=e||Float32Array,this.array=new e(t*this.componentSize),this.indexOffset=i||0},i.TypedArrayHelper.constructor=i.TypedArrayHelper,i.TypedArrayHelper.prototype.setSize=function(e,t){"use strict";var r=this.array.length;return t||(e*=this.componentSize),er?this.grow(e):void console.info("TypedArray is already of size:",e+".","Will not resize.")},i.TypedArrayHelper.prototype.shrink=function(e){"use strict";return this.array=this.array.subarray(0,e),this.size=e,this},i.TypedArrayHelper.prototype.grow=function(e){"use strict";var t=this.array,r=new this.TypedArrayConstructor(e);return r.set(t),this.array=r,this.size=e,this},i.TypedArrayHelper.prototype.splice=function(e,t){"use strict";e*=this.componentSize,t*=this.componentSize;for(var r=[],i=this.array,a=i.length,s=0;s=t)&&r.push(i[s]);return this.setFromArray(0,r),this},i.TypedArrayHelper.prototype.setFromArray=function(e,t){"use strict";var r=e+t.length;return r>this.array.length?this.grow(r):r=81&&(this.bufferAttribute.count=this.bufferAttribute.array.length/this.bufferAttribute.itemSize),void(this.bufferAttribute.needsUpdate=!0);this.bufferAttribute=new THREE.BufferAttribute(this.typedArray.array,this.componentSize),this.bufferAttribute.dynamic=this.dynamicBuffer},i.ShaderAttribute.prototype.getLength=function(){"use strict";return null===this.typedArray?0:this.typedArray.array.length},i.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")},i.shaders={vertex:[i.shaderChunks.defines,i.shaderChunks.uniforms,i.shaderChunks.attributes,i.shaderChunks.varyings,THREE.ShaderChunk.common,THREE.ShaderChunk.logdepthbuf_pars_vertex,THREE.ShaderChunk.fog_pars_vertex,i.shaderChunks.branchAvoidanceFunctions,i.shaderChunks.unpackColor,i.shaderChunks.unpackRotationAxis,i.shaderChunks.floatOverLifetime,i.shaderChunks.colorOverLifetime,i.shaderChunks.paramFetchingFunctions,i.shaderChunks.forceFetchingFunctions,i.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:[i.shaderChunks.uniforms,THREE.ShaderChunk.common,THREE.ShaderChunk.fog_pars_fragment,THREE.ShaderChunk.logdepthbuf_pars_fragment,i.shaderChunks.varyings,i.shaderChunks.branchAvoidanceFunctions,"void main() {"," vec3 outgoingLight = vColor.xyz;"," "," #ifdef ALPHATEST"," if ( vColor.w < float(ALPHATEST) ) discard;"," #endif",i.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")},i.utils={types:{BOOLEAN:"boolean",STRING:"string",NUMBER:"number",OBJECT:"object"},ensureTypedArg:function(e,t,r){"use strict";return typeof e===t?e:r},ensureArrayTypedArg:function(e,t,r){"use strict";if(Array.isArray(e)){for(var i=e.length-1;i>=0;--i)if(typeof e[i]!==t)return r;return e}return this.ensureTypedArg(e,t,r)},ensureInstanceOf:function(e,t,r){"use strict";return void 0!==t&&e instanceof t?e:r},ensureArrayInstanceOf:function(e,t,r){"use strict";if(Array.isArray(e)){for(var i=e.length-1;i>=0;--i)if(void 0!==t&&e[i]instanceof t==!1)return r;return e}return this.ensureInstanceOf(e,t,r)},ensureValueOverLifetimeCompliance:function(e,t,r){"use strict";t=t||3,r=r||3,!1===Array.isArray(e._value)&&(e._value=[e._value]),!1===Array.isArray(e._spread)&&(e._spread=[e._spread]);var i=this.clamp(e._value.length,t,r),a=this.clamp(e._spread.length,t,r),s=Math.max(i,a);e._value.length!==s&&(e._value=this.interpolateArray(e._value,s)),e._spread.length!==s&&(e._spread=this.interpolateArray(e._spread,s))},interpolateArray:function(e,t){"use strict";for(var r=e.length,i=["function"==typeof e[0].clone?e[0].clone():e[0]],a=(r-1)/(t-1),s=1;s-1e-5&&(r=-r),r},lerpTypeAgnostic:function(e,t,r){"use strict";var i,a=this.types;return typeof e===a.NUMBER&&typeof t===a.NUMBER?e+(t-e)*r:e instanceof THREE.Vector2&&t instanceof THREE.Vector2?((i=e.clone()).x=this.lerp(e.x,t.x,r),i.y=this.lerp(e.y,t.y,r),i):e instanceof THREE.Vector3&&t instanceof THREE.Vector3?((i=e.clone()).x=this.lerp(e.x,t.x,r),i.y=this.lerp(e.y,t.y,r),i.z=this.lerp(e.z,t.z,r),i):e instanceof THREE.Vector4&&t instanceof THREE.Vector4?((i=e.clone()).x=this.lerp(e.x,t.x,r),i.y=this.lerp(e.y,t.y,r),i.z=this.lerp(e.z,t.z,r),i.w=this.lerp(e.w,t.w,r),i):e instanceof THREE.Color&&t instanceof THREE.Color?((i=e.clone()).r=this.lerp(e.r,t.r,r),i.g=this.lerp(e.g,t.g,r),i.b=this.lerp(e.b,t.b,r),i):void console.warn("Invalid argument types, or argument types do not match:",e,t)},lerp:function(e,t,r){"use strict";return e+(t-e)*r},roundToNearestMultiple:function(e,t){"use strict";var r;return 0===t?e:0===(r=Math.abs(e)%t)?e:e<0?-(Math.abs(e)-r):e+t-r},arrayValuesAreEqual:function(e){"use strict";for(var t=0;t1||this.textureFrames.y>1},this.attributes={position:new i.ShaderAttribute("v3",!0),acceleration:new i.ShaderAttribute("v4",!0),velocity:new i.ShaderAttribute("v3",!0),rotation:new i.ShaderAttribute("v4",!0),rotationCenter:new i.ShaderAttribute("v3",!0),params:new i.ShaderAttribute("v4",!0),size:new i.ShaderAttribute("v4",!0),angle:new i.ShaderAttribute("v4",!0),color:new i.ShaderAttribute("v4",!0),opacity:new i.ShaderAttribute("v4",!0)},this.attributeKeys=Object.keys(this.attributes),this.attributeCount=this.attributeKeys.length,this.material=new THREE.ShaderMaterial({uniforms:this.uniforms,vertexShader:i.shaders.vertex,fragmentShader:i.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.")},i.Group.constructor=i.Group,i.Group.prototype._updateDefines=function(){"use strict";for(var e,t=this.emitters,r=t.length-1,i=this.defines;r>=0;--r)e=t[r],i.SHOULD_CALCULATE_SPRITE||(i.SHOULD_ROTATE_TEXTURE=i.SHOULD_ROTATE_TEXTURE||!!Math.max(Math.max.apply(null,e.angle.value),Math.max.apply(null,e.angle.spread))),i.SHOULD_ROTATE_PARTICLES=i.SHOULD_ROTATE_PARTICLES||!!Math.max(e.rotation.angle,e.rotation.angleSpread),i.SHOULD_WIGGLE_PARTICLES=i.SHOULD_WIGGLE_PARTICLES||!!Math.max(e.wiggle.value,e.wiggle.spread);this.material.needsUpdate=!0},i.Group.prototype._applyAttributesToGeometry=function(){"use strict";var e,t,r=this.attributes,i=this.geometry,a=i.attributes;for(var s in r)r.hasOwnProperty(s)&&(e=r[s],(t=a[s])?t.array=e.typedArray.array:i.addAttribute(s,e.bufferAttribute),e.bufferAttribute.needsUpdate=!0);this.geometry.setDrawRange(0,this.particleCount)},i.Group.prototype.addEmitter=function(e){"use strict";if(e instanceof i.Emitter!=!1)if(this.emitterIDs.indexOf(e.uuid)>-1)console.error("Emitter already exists in this group. Will not add again.");else{if(null===e.group){var t=this.attributes,r=this.particleCount,a=r+e.particleCount;for(var s in this.particleCount=a,null!==this.maxParticleCount&&this.particleCount>this.maxParticleCount&&console.warn("SPE.Group: maxParticleCount exceeded. Requesting",this.particleCount,"particles, can support only",this.maxParticleCount),e._calculatePPSValue(e.maxAge._value+e.maxAge._spread),e._setBufferUpdateRanges(this.attributeKeys),e._setAttributeOffset(r),e.group=this,e.attributes=this.attributes,t)t.hasOwnProperty(s)&&t[s]._createBufferAttribute(null!==this.maxParticleCount?this.maxParticleCount:this.particleCount);for(var o=r;o1)for(var r=0;r=0;--t)r[e[t]].resetUpdateRange()},i.Group.prototype._updateBuffers=function(e){"use strict";for(var t,r,i,a=this.attributeKeys,s=this.attributeCount-1,o=this.attributes,n=e.bufferUpdateRanges;s>=0;--s)r=n[t=a[s]],(i=o[t]).setUpdateRange(r.min,r.max),i.flagUpdate()},i.Group.prototype.tick=function(e){"use strict";var t=this.emitters,r=t.length,i=e||this.fixedTimeStep,a=this.attributeKeys,s=this.attributes;if(this._updateUniforms(i),this._resetBufferRanges(),0!==r||!1!==this._attributesNeedRefresh||!1!==this._attributesNeedDynamicReset){for(var o,n=0;n=0;--n)s[a[n]].resetDynamic();this._attributesNeedDynamicReset=!1}if(!0===this._attributesNeedRefresh){for(n=this.attributeCount-1;n>=0;--n)s[a[n]].forceUpdateAll();this._attributesNeedRefresh=!1,this._attributesNeedDynamicReset=!0}}},i.Group.prototype.dispose=function(){"use strict";return this.geometry.dispose(),this.material.dispose(),this},i.Emitter=function(e){"use strict";var t=i.utils,r=t.types,a=i.valueOverLifetimeLength;for(var s in(e=t.ensureTypedArg(e,r.OBJECT,{})).position=t.ensureTypedArg(e.position,r.OBJECT,{}),e.velocity=t.ensureTypedArg(e.velocity,r.OBJECT,{}),e.acceleration=t.ensureTypedArg(e.acceleration,r.OBJECT,{}),e.radius=t.ensureTypedArg(e.radius,r.OBJECT,{}),e.drag=t.ensureTypedArg(e.drag,r.OBJECT,{}),e.rotation=t.ensureTypedArg(e.rotation,r.OBJECT,{}),e.color=t.ensureTypedArg(e.color,r.OBJECT,{}),e.opacity=t.ensureTypedArg(e.opacity,r.OBJECT,{}),e.size=t.ensureTypedArg(e.size,r.OBJECT,{}),e.angle=t.ensureTypedArg(e.angle,r.OBJECT,{}),e.wiggle=t.ensureTypedArg(e.wiggle,r.OBJECT,{}),e.maxAge=t.ensureTypedArg(e.maxAge,r.OBJECT,{}),e.onParticleSpawn&&console.warn("onParticleSpawn has been removed. Please set properties directly to alter values at runtime."),this.uuid=THREE.Math.generateUUID(),this.type=t.ensureTypedArg(e.type,r.NUMBER,i.distributions.BOX),this.position={_value:t.ensureInstanceOf(e.position.value,THREE.Vector3,new THREE.Vector3),_spread:t.ensureInstanceOf(e.position.spread,THREE.Vector3,new THREE.Vector3),_spreadClamp:t.ensureInstanceOf(e.position.spreadClamp,THREE.Vector3,new THREE.Vector3),_distribution:t.ensureTypedArg(e.position.distribution,r.NUMBER,this.type),_randomise:t.ensureTypedArg(e.position.randomise,r.BOOLEAN,!1),_radius:t.ensureTypedArg(e.position.radius,r.NUMBER,10),_radiusScale:t.ensureInstanceOf(e.position.radiusScale,THREE.Vector3,new THREE.Vector3(1,1,1)),_distributionClamp:t.ensureTypedArg(e.position.distributionClamp,r.NUMBER,0)},this.velocity={_value:t.ensureInstanceOf(e.velocity.value,THREE.Vector3,new THREE.Vector3),_spread:t.ensureInstanceOf(e.velocity.spread,THREE.Vector3,new THREE.Vector3),_distribution:t.ensureTypedArg(e.velocity.distribution,r.NUMBER,this.type),_randomise:t.ensureTypedArg(e.position.randomise,r.BOOLEAN,!1)},this.acceleration={_value:t.ensureInstanceOf(e.acceleration.value,THREE.Vector3,new THREE.Vector3),_spread:t.ensureInstanceOf(e.acceleration.spread,THREE.Vector3,new THREE.Vector3),_distribution:t.ensureTypedArg(e.acceleration.distribution,r.NUMBER,this.type),_randomise:t.ensureTypedArg(e.position.randomise,r.BOOLEAN,!1)},this.drag={_value:t.ensureTypedArg(e.drag.value,r.NUMBER,0),_spread:t.ensureTypedArg(e.drag.spread,r.NUMBER,0),_randomise:t.ensureTypedArg(e.position.randomise,r.BOOLEAN,!1)},this.wiggle={_value:t.ensureTypedArg(e.wiggle.value,r.NUMBER,0),_spread:t.ensureTypedArg(e.wiggle.spread,r.NUMBER,0)},this.rotation={_axis:t.ensureInstanceOf(e.rotation.axis,THREE.Vector3,new THREE.Vector3(0,1,0)),_axisSpread:t.ensureInstanceOf(e.rotation.axisSpread,THREE.Vector3,new THREE.Vector3),_angle:t.ensureTypedArg(e.rotation.angle,r.NUMBER,0),_angleSpread:t.ensureTypedArg(e.rotation.angleSpread,r.NUMBER,0),_static:t.ensureTypedArg(e.rotation.static,r.BOOLEAN,!1),_center:t.ensureInstanceOf(e.rotation.center,THREE.Vector3,this.position._value.clone()),_randomise:t.ensureTypedArg(e.position.randomise,r.BOOLEAN,!1)},this.maxAge={_value:t.ensureTypedArg(e.maxAge.value,r.NUMBER,2),_spread:t.ensureTypedArg(e.maxAge.spread,r.NUMBER,0)},this.color={_value:t.ensureArrayInstanceOf(e.color.value,THREE.Color,new THREE.Color),_spread:t.ensureArrayInstanceOf(e.color.spread,THREE.Vector3,new THREE.Vector3),_randomise:t.ensureTypedArg(e.position.randomise,r.BOOLEAN,!1)},this.opacity={_value:t.ensureArrayTypedArg(e.opacity.value,r.NUMBER,1),_spread:t.ensureArrayTypedArg(e.opacity.spread,r.NUMBER,0),_randomise:t.ensureTypedArg(e.position.randomise,r.BOOLEAN,!1)},this.size={_value:t.ensureArrayTypedArg(e.size.value,r.NUMBER,1),_spread:t.ensureArrayTypedArg(e.size.spread,r.NUMBER,0),_randomise:t.ensureTypedArg(e.position.randomise,r.BOOLEAN,!1)},this.angle={_value:t.ensureArrayTypedArg(e.angle.value,r.NUMBER,0),_spread:t.ensureArrayTypedArg(e.angle.spread,r.NUMBER,0),_randomise:t.ensureTypedArg(e.position.randomise,r.BOOLEAN,!1)},this.particleCount=t.ensureTypedArg(e.particleCount,r.NUMBER,100),this.duration=t.ensureTypedArg(e.duration,r.NUMBER,null),this.isStatic=t.ensureTypedArg(e.isStatic,r.BOOLEAN,!1),this.activeMultiplier=t.ensureTypedArg(e.activeMultiplier,r.NUMBER,1),this.direction=t.ensureTypedArg(e.direction,r.NUMBER,1),this.alive=t.ensureTypedArg(e.alive,r.BOOLEAN,!0),this.particlesPerSecond=0,this.activationIndex=0,this.attributeOffset=0,this.attributeEnd=0,this.age=0,this.activeParticleCount=0,this.group=null,this.attributes=null,this.paramsArray=null,this.resetFlags={position:t.ensureTypedArg(e.position.randomise,r.BOOLEAN,!1)||t.ensureTypedArg(e.radius.randomise,r.BOOLEAN,!1),velocity:t.ensureTypedArg(e.velocity.randomise,r.BOOLEAN,!1),acceleration:t.ensureTypedArg(e.acceleration.randomise,r.BOOLEAN,!1)||t.ensureTypedArg(e.drag.randomise,r.BOOLEAN,!1),rotation:t.ensureTypedArg(e.rotation.randomise,r.BOOLEAN,!1),rotationCenter:t.ensureTypedArg(e.rotation.randomise,r.BOOLEAN,!1),size:t.ensureTypedArg(e.size.randomise,r.BOOLEAN,!1),color:t.ensureTypedArg(e.color.randomise,r.BOOLEAN,!1),opacity:t.ensureTypedArg(e.opacity.randomise,r.BOOLEAN,!1),angle:t.ensureTypedArg(e.angle.randomise,r.BOOLEAN,!1)},this.updateFlags={},this.updateCounts={},this.updateMap={maxAge:"params",position:"position",velocity:"velocity",acceleration:"acceleration",drag:"acceleration",wiggle:"params",rotation:"rotation",size:"size",color:"color",opacity:"opacity",angle:"angle"},this.updateMap)this.updateMap.hasOwnProperty(s)&&(this.updateCounts[this.updateMap[s]]=0,this.updateFlags[this.updateMap[s]]=!1,this._createGetterSetters(this[s],s));this.bufferUpdateRanges={},this.attributeKeys=null,this.attributeCount=0,t.ensureValueOverLifetimeCompliance(this.color,a,a),t.ensureValueOverLifetimeCompliance(this.opacity,a,a),t.ensureValueOverLifetimeCompliance(this.size,a,a),t.ensureValueOverLifetimeCompliance(this.angle,a,a)},i.Emitter.constructor=i.Emitter,i.Emitter.prototype._createGetterSetters=function(e,t){"use strict";var r=this;for(var a in e)if(e.hasOwnProperty(a)){var s=a.replace("_","");Object.defineProperty(e,s,{get:function(e){return function(){return this[e]}}(a),set:function(e){return function(a){var s=r.updateMap[t],o=this[e],n=i.valueOverLifetimeLength;"_rotationCenter"===e?(r.updateFlags.rotationCenter=!0,r.updateCounts.rotationCenter=0):"_randomise"===e?r.resetFlags[s]=a:(r.updateFlags[s]=!0,r.updateCounts[s]=0),r.group._updateDefines(),this[e]=a,Array.isArray(o)&&i.utils.ensureValueOverLifetimeCompliance(r[t],n,n)}}(a)})}},i.Emitter.prototype._setBufferUpdateRanges=function(e){"use strict";this.attributeKeys=e,this.attributeCount=e.length;for(var t=this.attributeCount-1;t>=0;--t)this.bufferUpdateRanges[e[t]]={min:Number.POSITIVE_INFINITY,max:Number.NEGATIVE_INFINITY}},i.Emitter.prototype._calculatePPSValue=function(e){"use strict";var t=this.particleCount;this.duration?this.particlesPerSecond=t/(e=0;--n)r=a[t=o[n]],!0!==i[t]&&!0!==r||(this._assignValue(t,e),this._updateAttributeUpdateRange(t,e),!0===r&&s[t]===this.particleCount?(a[t]=!1,s[t]=0):1==r&&++s[t])},i.Emitter.prototype._updateAttributeUpdateRange=function(e,t){"use strict";var r=this.bufferUpdateRanges[e];r.min=Math.min(t,r.min),r.max=Math.max(t,r.max)},i.Emitter.prototype._resetBufferRanges=function(){"use strict";for(var e,t=this.bufferUpdateRanges,r=this.bufferUpdateKeys,i=this.bufferUpdateCount-1;i>=0;--i)t[e=r[i]].min=Number.POSITIVE_INFINITY,t[e].max=Number.NEGATIVE_INFINITY},i.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},i.Emitter.prototype._decrementParticleCount=function(){"use strict";--this.activeParticleCount},i.Emitter.prototype._incrementParticleCount=function(){"use strict";++this.activeParticleCount},i.Emitter.prototype._checkParticleAges=function(e,t,r,i){"use strict";for(var a,s,o,n,u=t-1;u>=e;--u)0!==(n=r[a=4*u])&&(o=r[a+1],s=r[a+2],1===this.direction?(o+=i)>=s&&(o=0,n=0,this._decrementParticleCount()):(o-=i)<=0&&(o=s,n=0,this._decrementParticleCount()),r[a]=n,r[a+1]=o,this._updateAttributeUpdateRange("params",u))},i.Emitter.prototype._activateParticles=function(e,t,r,i){"use strict";for(var a,s,o=this.direction,n=e;nthis.duration)return this.alive=!1,void(this.age=0);var o=1===this.particleCount?s:0|s,n=Math.min(o+a,this.activationEnd),u=n-this.activationIndex|0,l=u>0?e/u:0;this._activateParticles(o,n,i,l),this.activationIndex+=a,this.activationIndex>r&&(this.activationIndex=t),this.age+=e}else this.age=0}},i.Emitter.prototype.reset=function(e){"use strict";if(this.age=0,this.alive=!1,!0===e){for(var t,r=this.attributeOffset,i=r+this.particleCount,a=this.paramsArray,s=this.attributes.params.bufferAttribute,o=i-1;o>=r;--o)a[t=4*o]=0,a[t+1]=0;s.updateRange.offset=0,s.updateRange.count=-1,s.needsUpdate=!0}return this},i.Emitter.prototype.enable=function(){"use strict";return this.alive=!0,this},i.Emitter.prototype.disable=function(){"use strict";return this.alive=!1,this},i.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}},{}]},{},[1]); --------------------------------------------------------------------------------