Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

aframe-particle-system-component.js 143KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971
  1. /******/ (function(modules) { // webpackBootstrap
  2. /******/ // The module cache
  3. /******/ var installedModules = {};
  4. /******/ // The require function
  5. /******/ function __webpack_require__(moduleId) {
  6. /******/ // Check if module is in cache
  7. /******/ if(installedModules[moduleId])
  8. /******/ return installedModules[moduleId].exports;
  9. /******/ // Create a new module (and put it into the cache)
  10. /******/ var module = installedModules[moduleId] = {
  11. /******/ exports: {},
  12. /******/ id: moduleId,
  13. /******/ loaded: false
  14. /******/ };
  15. /******/ // Execute the module function
  16. /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
  17. /******/ // Flag the module as loaded
  18. /******/ module.loaded = true;
  19. /******/ // Return the exports of the module
  20. /******/ return module.exports;
  21. /******/ }
  22. /******/ // expose the modules object (__webpack_modules__)
  23. /******/ __webpack_require__.m = modules;
  24. /******/ // expose the module cache
  25. /******/ __webpack_require__.c = installedModules;
  26. /******/ // __webpack_public_path__
  27. /******/ __webpack_require__.p = "";
  28. /******/ // Load entry module and return exports
  29. /******/ return __webpack_require__(0);
  30. /******/ })
  31. /************************************************************************/
  32. /******/ ([
  33. /* 0 */
  34. /***/ (function(module, exports, __webpack_require__) {
  35. /**
  36. * Particles component for A-Frame.
  37. *
  38. * ShaderParticleEngine by Squarefeet (https://github.com/squarefeet).
  39. */
  40. var SPE = __webpack_require__(1);
  41. if (typeof AFRAME === 'undefined') {
  42. throw new Error('Component attempted to register before AFRAME was available.');
  43. }
  44. AFRAME.registerComponent('particle-system', {
  45. schema: {
  46. preset: {
  47. type: 'string',
  48. default: '',
  49. oneOf: ['default', 'dust', 'snow', 'rain']
  50. },
  51. maxAge: {
  52. type: 'number',
  53. default: 6
  54. },
  55. positionSpread: {
  56. type: 'vec3',
  57. default: { x: 0, y: 0, z: 0 }
  58. },
  59. type: {
  60. type: 'number',
  61. default: SPE.distributions.BOX
  62. },
  63. rotationAxis: {
  64. type: 'string',
  65. default: 'x'
  66. },
  67. rotationAngle: {
  68. type: 'number',
  69. default: 0
  70. },
  71. rotationAngleSpread: {
  72. type: 'number',
  73. default: 0
  74. },
  75. accelerationValue: {
  76. type: 'vec3',
  77. default: { x: 0, y: -10, z: 0 }
  78. },
  79. accelerationSpread: {
  80. type: 'vec3',
  81. default: { x: 10, y: 0, z: 10 }
  82. },
  83. velocityValue: {
  84. type: 'vec3',
  85. default: { x: 0, y: 25, z: 0 }
  86. },
  87. velocitySpread: {
  88. type: 'vec3',
  89. default: { x: 10, y: 7.5, z: 10 }
  90. },
  91. dragValue: {
  92. type: 'number',
  93. default: 0
  94. },
  95. dragSpread: {
  96. type: 'number',
  97. default: 0
  98. },
  99. dragRandomise: {
  100. type: 'boolean',
  101. default: false
  102. },
  103. color: {
  104. type: 'array',
  105. default: [ '#0000FF', '#FF0000' ]
  106. },
  107. size: {
  108. type: 'array',
  109. default: [ '1' ]
  110. },
  111. sizeSpread: {
  112. type: 'array',
  113. default: [ '0' ]
  114. },
  115. direction: {
  116. type: 'number',
  117. default: 1
  118. },
  119. duration: {
  120. type: 'number',
  121. default: Infinity
  122. },
  123. particleCount: {
  124. type: 'number',
  125. default: 1000
  126. },
  127. texture: {
  128. type: 'asset',
  129. default: 'https://cdn.rawgit.com/IdeaSpaceVR/aframe-particle-system-component/master/dist/images/star2.png'
  130. },
  131. randomise: {
  132. type: 'boolean',
  133. default: false
  134. },
  135. opacity: {
  136. type: 'array',
  137. default: [ '1' ]
  138. },
  139. opacitySpread: {
  140. type: 'array',
  141. default: [ '0' ]
  142. },
  143. maxParticleCount: {
  144. type: 'number',
  145. default: 250000
  146. },
  147. blending: {
  148. type: 'number',
  149. default: THREE.AdditiveBlending,
  150. oneOf: [THREE.NoBlending,THREE.NormalBlending,THREE.AdditiveBlending,THREE.SubtractiveBlending,THREE.MultiplyBlending]
  151. },
  152. enabled: {
  153. type:'boolean',
  154. default:true
  155. }
  156. },
  157. init: function() {
  158. this.presets = {};
  159. /* preset settings can be overwritten */
  160. this.presets['dust'] = {
  161. maxAge: 20,
  162. positionSpread: {x:100,y:100,z:100},
  163. rotationAngle: 3.14,
  164. accelerationValue: {x: 0, y: 0, z: 0},
  165. accelerationSpread: {x: 0, y: 0, z: 0},
  166. velocityValue: {x: 1, y: 0.3, z: 1},
  167. velocitySpread: {x: 0.5, y: 1, z: 0.5},
  168. color: ['#FFFFFF'],
  169. particleCount: 100,
  170. texture: 'https://cdn.rawgit.com/IdeaSpaceVR/aframe-particle-system-component/master/dist/images/smokeparticle.png'
  171. };
  172. this.presets['snow'] = {
  173. maxAge: 20,
  174. positionSpread: {x:100,y:100,z:100},
  175. rotationAngle: 3.14,
  176. accelerationValue: {x: 0, y: 0, z: 0},
  177. accelerationSpread: {x: 0.2, y: 0, z: 0.2},
  178. velocityValue: {x: 0, y: 8, z: 0},
  179. velocitySpread: {x: 2, y: 0, z: 2},
  180. color: ['#FFFFFF'],
  181. particleCount: 200,
  182. texture: 'https://cdn.rawgit.com/IdeaSpaceVR/aframe-particle-system-component/master/dist/images/smokeparticle.png'
  183. };
  184. this.presets['rain'] = {
  185. maxAge: 1,
  186. positionSpread: {x:100,y:100,z:100},
  187. rotationAngle: 3.14,
  188. accelerationValue: {x: 0, y: 3, z: 0},
  189. accelerationSpread: {x: 2, y: 1, z: 2},
  190. velocityValue: {x: 0, y: 75, z: 0},
  191. velocitySpread: {x: 10, y: 50, z: 10},
  192. color: ['#FFFFFF'],
  193. size: 0.4,
  194. texture: 'https://cdn.rawgit.com/IdeaSpaceVR/aframe-particle-system-component/master/dist/images/raindrop.png'
  195. };
  196. },
  197. update: function (oldData) {
  198. // Remove old particle group.
  199. if (this.particleGroup) {
  200. this.el.removeObject3D('particle-system');
  201. }
  202. // Set the selected preset, if any, or use an empty object to keep schema defaults
  203. this.preset = this.presets[this.data.preset] || {};
  204. // Get custom, preset, or default data for each property defined in the schema
  205. for (var key in this.data) {
  206. this.data[key] = this.applyPreset(key);
  207. }
  208. this.initParticleSystem(this.data);
  209. if(this.data.enabled === true) {
  210. this.startParticles()
  211. } else {
  212. this.stopParticles()
  213. }
  214. },
  215. applyPreset: function (key) {
  216. // !this.attrValue[key] = the user did not set a custom value
  217. // this.preset[key] = there exists a value for this key in the selected preset
  218. if (!this.attrValue[key] && this.preset[key]) {
  219. return this.preset[key];
  220. } else {
  221. // Otherwise stick to the user or schema default value
  222. return this.data[key];
  223. }
  224. },
  225. tick: function(time, dt) {
  226. this.particleGroup.tick(dt / 1000);
  227. },
  228. remove: function() {
  229. // Remove particle system.
  230. if (!this.particleGroup) { return; }
  231. this.el.removeObject3D('particle-system');
  232. },
  233. startParticles: function() {
  234. this.particleGroup.emitters.forEach(function(em) { em.enable() });
  235. },
  236. stopParticles: function() {
  237. this.particleGroup.emitters.forEach(function(em) { em.disable() });
  238. },
  239. initParticleSystem: function(settings) {
  240. var loader = new THREE.TextureLoader();
  241. var particle_texture = loader.load(
  242. settings.texture,
  243. function (texture) {
  244. return texture;
  245. },
  246. function (xhr) {
  247. console.log((xhr.loaded / xhr.total * 100) + '% loaded');
  248. },
  249. function (xhr) {
  250. console.log('An error occurred');
  251. }
  252. );
  253. this.particleGroup = new SPE.Group({
  254. texture: {
  255. value: particle_texture
  256. },
  257. maxParticleCount: settings.maxParticleCount,
  258. blending: settings.blending
  259. });
  260. var emitter = new SPE.Emitter({
  261. maxAge: {
  262. value: settings.maxAge
  263. },
  264. type: {
  265. value: settings.type
  266. },
  267. position: {
  268. spread: new THREE.Vector3(settings.positionSpread.x, settings.positionSpread.y, settings.positionSpread.z),
  269. randomise: settings.randomise
  270. //spreadClamp: new THREE.Vector3( 2, 2, 2 ),
  271. //radius: 4
  272. },
  273. rotation: {
  274. axis: (settings.rotationAxis=='x'?new THREE.Vector3(1, 0, 0):(settings.rotationAxis=='y'?new THREE.Vector3(0, 1, 0):(settings.rotationAxis=='z'?new THREE.Vector3(0, 0, 1):new THREE.Vector3(0, 1, 0)))),
  275. angle: settings.rotationAngle,
  276. angleSpread: settings.rotationAngleSpread,
  277. static: true
  278. },
  279. acceleration: {
  280. value: new THREE.Vector3(settings.accelerationValue.x, settings.accelerationValue.y, settings.accelerationValue.z),
  281. spread: new THREE.Vector3(settings.accelerationSpread.x, settings.accelerationSpread.y, settings.accelerationSpread.z)
  282. },
  283. velocity: {
  284. value: new THREE.Vector3(settings.velocityValue.x, settings.velocityValue.y, settings.velocityValue.z),
  285. spread: new THREE.Vector3(settings.velocitySpread.x, settings.velocitySpread.y, settings.velocitySpread.z)
  286. },
  287. drag: {
  288. value: new THREE.Vector3(settings.dragValue.x, settings.dragValue.y, settings.dragValue.z),
  289. spread: new THREE.Vector3(settings.dragSpread.x, settings.dragSpread.y, settings.dragSpread.z),
  290. randomise: settings.dragRandomise
  291. },
  292. color: {
  293. value: settings.color.map(function(c) { return new THREE.Color(c); })
  294. },
  295. size: { value: settings.size.map(function (s) { return parseFloat(s); }),
  296. spread: settings.sizeSpread.map(function (s) { return parseFloat(s); }) },
  297. /*wiggle: { value: 4, spread: 2 }, //settings.wiggle,*/
  298. /*drag: {
  299. value: settings.drag
  300. },*/
  301. direction: {
  302. value: settings.direction
  303. },
  304. duration: settings.duration,
  305. opacity: { value: settings.opacity.map(function (o) { return parseFloat(o); }),
  306. spread: settings.opacitySpread.map(function (o) { return parseFloat(o); }) },
  307. particleCount: settings.particleCount
  308. });
  309. this.particleGroup.addEmitter(emitter);
  310. this.particleGroup.mesh.frustumCulled = false;
  311. this.el.setObject3D('particle-system', this.particleGroup.mesh);
  312. }
  313. });
  314. /***/ }),
  315. /* 1 */
  316. /***/ (function(module, exports, __webpack_require__) {
  317. var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;/* shader-particle-engine 1.0.6
  318. *
  319. * (c) 2015 Luke Moody (http://www.github.com/squarefeet)
  320. * Originally based on Lee Stemkoski's original work (https://github.com/stemkoski/stemkoski.github.com/blob/master/Three.js/js/ParticleEngine.js).
  321. *
  322. * shader-particle-engine may be freely distributed under the MIT license (See LICENSE at root of this repository.)
  323. */
  324. /**
  325. * @typedef {Number} distribution
  326. * @property {Number} SPE.distributions.BOX Values will be distributed within a box.
  327. * @property {Number} SPE.distributions.SPHERE Values will be distributed within a sphere.
  328. * @property {Number} SPE.distributions.DISC Values will be distributed within a 2D disc.
  329. */
  330. /**
  331. * Namespace for Shader Particle Engine.
  332. *
  333. * All SPE-related code sits under this namespace.
  334. *
  335. * @type {Object}
  336. * @namespace
  337. */
  338. var SPE = {
  339. /**
  340. * A map of supported distribution types used
  341. * by SPE.Emitter instances.
  342. *
  343. * These distribution types can be applied to
  344. * an emitter globally, which will affect the
  345. * `position`, `velocity`, and `acceleration`
  346. * value calculations for an emitter, or they
  347. * can be applied on a per-property basis.
  348. *
  349. * @enum {Number}
  350. */
  351. distributions: {
  352. /**
  353. * Values will be distributed within a box.
  354. * @type {Number}
  355. */
  356. BOX: 1,
  357. /**
  358. * Values will be distributed on a sphere.
  359. * @type {Number}
  360. */
  361. SPHERE: 2,
  362. /**
  363. * Values will be distributed on a 2d-disc shape.
  364. * @type {Number}
  365. */
  366. DISC: 3,
  367. /**
  368. * Values will be distributed along a line.
  369. * @type {Number}
  370. */
  371. LINE: 4
  372. },
  373. /**
  374. * Set this value to however many 'steps' you
  375. * want value-over-lifetime properties to have.
  376. *
  377. * It's adjustable to fix an interpolation problem:
  378. *
  379. * Assuming you specify an opacity value as [0, 1, 0]
  380. * and the `valueOverLifetimeLength` is 4, then the
  381. * opacity value array will be reinterpolated to
  382. * be [0, 0.66, 0.66, 0].
  383. * This isn't ideal, as particles would never reach
  384. * full opacity.
  385. *
  386. * NOTE:
  387. * This property affects the length of ALL
  388. * value-over-lifetime properties for ALL
  389. * emitters and ALL groups.
  390. *
  391. * Only values >= 3 && <= 4 are allowed.
  392. *
  393. * @type {Number}
  394. */
  395. valueOverLifetimeLength: 4
  396. };
  397. // Module loader support:
  398. if ( true ) {
  399. !(__WEBPACK_AMD_DEFINE_FACTORY__ = (SPE), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.call(exports, __webpack_require__, exports, module)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
  400. }
  401. else if ( typeof exports !== 'undefined' && typeof module !== 'undefined' ) {
  402. module.exports = SPE;
  403. }
  404. /**
  405. * A helper class for TypedArrays.
  406. *
  407. * Allows for easy resizing, assignment of various component-based
  408. * types (Vector2s, Vector3s, Vector4s, Mat3s, Mat4s),
  409. * as well as Colors (where components are `r`, `g`, `b`),
  410. * Numbers, and setting from other TypedArrays.
  411. *
  412. * @author Luke Moody
  413. * @constructor
  414. * @param {Function} TypedArrayConstructor The constructor to use (Float32Array, Uint8Array, etc.)
  415. * @param {Number} size The size of the array to create
  416. * @param {Number} componentSize The number of components per-value (ie. 3 for a vec3, 9 for a Mat3, etc.)
  417. * @param {Number} indexOffset The index in the array from which to start assigning values. Default `0` if none provided
  418. */
  419. SPE.TypedArrayHelper = function( TypedArrayConstructor, size, componentSize, indexOffset ) {
  420. 'use strict';
  421. this.componentSize = componentSize || 1;
  422. this.size = ( size || 1 );
  423. this.TypedArrayConstructor = TypedArrayConstructor || Float32Array;
  424. this.array = new TypedArrayConstructor( size * this.componentSize );
  425. this.indexOffset = indexOffset || 0;
  426. };
  427. SPE.TypedArrayHelper.constructor = SPE.TypedArrayHelper;
  428. /**
  429. * Sets the size of the internal array.
  430. *
  431. * Delegates to `this.shrink` or `this.grow` depending on size
  432. * argument's relation to the current size of the internal array.
  433. *
  434. * Note that if the array is to be shrunk, data will be lost.
  435. *
  436. * @param {Number} size The new size of the array.
  437. */
  438. SPE.TypedArrayHelper.prototype.setSize = function( size, noComponentMultiply ) {
  439. 'use strict';
  440. var currentArraySize = this.array.length;
  441. if ( !noComponentMultiply ) {
  442. size = size * this.componentSize;
  443. }
  444. if ( size < currentArraySize ) {
  445. return this.shrink( size );
  446. }
  447. else if ( size > currentArraySize ) {
  448. return this.grow( size );
  449. }
  450. else {
  451. console.info( 'TypedArray is already of size:', size + '.', 'Will not resize.' );
  452. }
  453. };
  454. /**
  455. * Shrinks the internal array.
  456. *
  457. * @param {Number} size The new size of the typed array. Must be smaller than `this.array.length`.
  458. * @return {SPE.TypedArrayHelper} Instance of this class.
  459. */
  460. SPE.TypedArrayHelper.prototype.shrink = function( size ) {
  461. 'use strict';
  462. this.array = this.array.subarray( 0, size );
  463. this.size = size;
  464. return this;
  465. };
  466. /**
  467. * Grows the internal array.
  468. * @param {Number} size The new size of the typed array. Must be larger than `this.array.length`.
  469. * @return {SPE.TypedArrayHelper} Instance of this class.
  470. */
  471. SPE.TypedArrayHelper.prototype.grow = function( size ) {
  472. 'use strict';
  473. var existingArray = this.array,
  474. newArray = new this.TypedArrayConstructor( size );
  475. newArray.set( existingArray );
  476. this.array = newArray;
  477. this.size = size;
  478. return this;
  479. };
  480. /**
  481. * Perform a splice operation on this array's buffer.
  482. * @param {Number} start The start index of the splice. Will be multiplied by the number of components for this attribute.
  483. * @param {Number} end The end index of the splice. Will be multiplied by the number of components for this attribute.
  484. * @returns {Object} The SPE.TypedArrayHelper instance.
  485. */
  486. SPE.TypedArrayHelper.prototype.splice = function( start, end ) {
  487. 'use strict';
  488. start *= this.componentSize;
  489. end *= this.componentSize;
  490. var data = [],
  491. array = this.array,
  492. size = array.length;
  493. for ( var i = 0; i < size; ++i ) {
  494. if ( i < start || i >= end ) {
  495. data.push( array[ i ] );
  496. }
  497. // array[ i ] = 0;
  498. }
  499. this.setFromArray( 0, data );
  500. return this;
  501. };
  502. /**
  503. * Copies from the given TypedArray into this one, using the index argument
  504. * as the start position. Alias for `TypedArray.set`. Will automatically resize
  505. * if the given source array is of a larger size than the internal array.
  506. *
  507. * @param {Number} index The start position from which to copy into this array.
  508. * @param {TypedArray} array The array from which to copy; the source array.
  509. * @return {SPE.TypedArrayHelper} Instance of this class.
  510. */
  511. SPE.TypedArrayHelper.prototype.setFromArray = function( index, array ) {
  512. 'use strict';
  513. var sourceArraySize = array.length,
  514. newSize = index + sourceArraySize;
  515. if ( newSize > this.array.length ) {
  516. this.grow( newSize );
  517. }
  518. else if ( newSize < this.array.length ) {
  519. this.shrink( newSize );
  520. }
  521. this.array.set( array, this.indexOffset + index );
  522. return this;
  523. };
  524. /**
  525. * Set a Vector2 value at `index`.
  526. *
  527. * @param {Number} index The index at which to set the vec2 values from.
  528. * @param {Vector2} vec2 Any object that has `x` and `y` properties.
  529. * @return {SPE.TypedArrayHelper} Instance of this class.
  530. */
  531. SPE.TypedArrayHelper.prototype.setVec2 = function( index, vec2 ) {
  532. 'use strict';
  533. return this.setVec2Components( index, vec2.x, vec2.y );
  534. };
  535. /**
  536. * Set a Vector2 value using raw components.
  537. *
  538. * @param {Number} index The index at which to set the vec2 values from.
  539. * @param {Number} x The Vec2's `x` component.
  540. * @param {Number} y The Vec2's `y` component.
  541. * @return {SPE.TypedArrayHelper} Instance of this class.
  542. */
  543. SPE.TypedArrayHelper.prototype.setVec2Components = function( index, x, y ) {
  544. 'use strict';
  545. var array = this.array,
  546. i = this.indexOffset + ( index * this.componentSize );
  547. array[ i ] = x;
  548. array[ i + 1 ] = y;
  549. return this;
  550. };
  551. /**
  552. * Set a Vector3 value at `index`.
  553. *
  554. * @param {Number} index The index at which to set the vec3 values from.
  555. * @param {Vector3} vec2 Any object that has `x`, `y`, and `z` properties.
  556. * @return {SPE.TypedArrayHelper} Instance of this class.
  557. */
  558. SPE.TypedArrayHelper.prototype.setVec3 = function( index, vec3 ) {
  559. 'use strict';
  560. return this.setVec3Components( index, vec3.x, vec3.y, vec3.z );
  561. };
  562. /**
  563. * Set a Vector3 value using raw components.
  564. *
  565. * @param {Number} index The index at which to set the vec3 values from.
  566. * @param {Number} x The Vec3's `x` component.
  567. * @param {Number} y The Vec3's `y` component.
  568. * @param {Number} z The Vec3's `z` component.
  569. * @return {SPE.TypedArrayHelper} Instance of this class.
  570. */
  571. SPE.TypedArrayHelper.prototype.setVec3Components = function( index, x, y, z ) {
  572. 'use strict';
  573. var array = this.array,
  574. i = this.indexOffset + ( index * this.componentSize );
  575. array[ i ] = x;
  576. array[ i + 1 ] = y;
  577. array[ i + 2 ] = z;
  578. return this;
  579. };
  580. /**
  581. * Set a Vector4 value at `index`.
  582. *
  583. * @param {Number} index The index at which to set the vec4 values from.
  584. * @param {Vector4} vec2 Any object that has `x`, `y`, `z`, and `w` properties.
  585. * @return {SPE.TypedArrayHelper} Instance of this class.
  586. */
  587. SPE.TypedArrayHelper.prototype.setVec4 = function( index, vec4 ) {
  588. 'use strict';
  589. return this.setVec4Components( index, vec4.x, vec4.y, vec4.z, vec4.w );
  590. };
  591. /**
  592. * Set a Vector4 value using raw components.
  593. *
  594. * @param {Number} index The index at which to set the vec4 values from.
  595. * @param {Number} x The Vec4's `x` component.
  596. * @param {Number} y The Vec4's `y` component.
  597. * @param {Number} z The Vec4's `z` component.
  598. * @param {Number} w The Vec4's `w` component.
  599. * @return {SPE.TypedArrayHelper} Instance of this class.
  600. */
  601. SPE.TypedArrayHelper.prototype.setVec4Components = function( index, x, y, z, w ) {
  602. 'use strict';
  603. var array = this.array,
  604. i = this.indexOffset + ( index * this.componentSize );
  605. array[ i ] = x;
  606. array[ i + 1 ] = y;
  607. array[ i + 2 ] = z;
  608. array[ i + 3 ] = w;
  609. return this;
  610. };
  611. /**
  612. * Set a Matrix3 value at `index`.
  613. *
  614. * @param {Number} index The index at which to set the matrix values from.
  615. * @param {Matrix3} mat3 The 3x3 matrix to set from. Must have a TypedArray property named `elements` to copy from.
  616. * @return {SPE.TypedArrayHelper} Instance of this class.
  617. */
  618. SPE.TypedArrayHelper.prototype.setMat3 = function( index, mat3 ) {
  619. 'use strict';
  620. return this.setFromArray( this.indexOffset + ( index * this.componentSize ), mat3.elements );
  621. };
  622. /**
  623. * Set a Matrix4 value at `index`.
  624. *
  625. * @param {Number} index The index at which to set the matrix values from.
  626. * @param {Matrix4} mat3 The 4x4 matrix to set from. Must have a TypedArray property named `elements` to copy from.
  627. * @return {SPE.TypedArrayHelper} Instance of this class.
  628. */
  629. SPE.TypedArrayHelper.prototype.setMat4 = function( index, mat4 ) {
  630. 'use strict';
  631. return this.setFromArray( this.indexOffset + ( index * this.componentSize ), mat4.elements );
  632. };
  633. /**
  634. * Set a Color value at `index`.
  635. *
  636. * @param {Number} index The index at which to set the vec3 values from.
  637. * @param {Color} color Any object that has `r`, `g`, and `b` properties.
  638. * @return {SPE.TypedArrayHelper} Instance of this class.
  639. */
  640. SPE.TypedArrayHelper.prototype.setColor = function( index, color ) {
  641. 'use strict';
  642. return this.setVec3Components( index, color.r, color.g, color.b );
  643. };
  644. /**
  645. * Set a Number value at `index`.
  646. *
  647. * @param {Number} index The index at which to set the vec3 values from.
  648. * @param {Number} numericValue The number to assign to this index in the array.
  649. * @return {SPE.TypedArrayHelper} Instance of this class.
  650. */
  651. SPE.TypedArrayHelper.prototype.setNumber = function( index, numericValue ) {
  652. 'use strict';
  653. this.array[ this.indexOffset + ( index * this.componentSize ) ] = numericValue;
  654. return this;
  655. };
  656. /**
  657. * Returns the value of the array at the given index, taking into account
  658. * the `indexOffset` property of this class.
  659. *
  660. * Note that this function ignores the component size and will just return a
  661. * single value.
  662. *
  663. * @param {Number} index The index in the array to fetch.
  664. * @return {Number} The value at the given index.
  665. */
  666. SPE.TypedArrayHelper.prototype.getValueAtIndex = function( index ) {
  667. 'use strict';
  668. return this.array[ this.indexOffset + index ];
  669. };
  670. /**
  671. * Returns the component value of the array at the given index, taking into account
  672. * the `indexOffset` property of this class.
  673. *
  674. * If the componentSize is set to 3, then it will return a new TypedArray
  675. * of length 3.
  676. *
  677. * @param {Number} index The index in the array to fetch.
  678. * @return {TypedArray} The component value at the given index.
  679. */
  680. SPE.TypedArrayHelper.prototype.getComponentValueAtIndex = function( index ) {
  681. 'use strict';
  682. return this.array.subarray( this.indexOffset + ( index * this.componentSize ) );
  683. };
  684. /**
  685. * A helper to handle creating and updating a THREE.BufferAttribute instance.
  686. *
  687. * @author Luke Moody
  688. * @constructor
  689. * @param {String} type The buffer attribute type. See SPE.ShaderAttribute.typeSizeMap for valid values.
  690. * @param {Boolean=} dynamicBuffer Whether this buffer attribute should be marked as dynamic or not.
  691. * @param {Function=} arrayType A reference to a TypedArray constructor. Defaults to Float32Array if none provided.
  692. */
  693. SPE.ShaderAttribute = function( type, dynamicBuffer, arrayType ) {
  694. 'use strict';
  695. var typeMap = SPE.ShaderAttribute.typeSizeMap;
  696. this.type = typeof type === 'string' && typeMap.hasOwnProperty( type ) ? type : 'f';
  697. this.componentSize = typeMap[ this.type ];
  698. this.arrayType = arrayType || Float32Array;
  699. this.typedArray = null;
  700. this.bufferAttribute = null;
  701. this.dynamicBuffer = !!dynamicBuffer;
  702. this.updateMin = 0;
  703. this.updateMax = 0;
  704. };
  705. SPE.ShaderAttribute.constructor = SPE.ShaderAttribute;
  706. /**
  707. * A map of uniform types to their component size.
  708. * @enum {Number}
  709. */
  710. SPE.ShaderAttribute.typeSizeMap = {
  711. /**
  712. * Float
  713. * @type {Number}
  714. */
  715. f: 1,
  716. /**
  717. * Vec2
  718. * @type {Number}
  719. */
  720. v2: 2,
  721. /**
  722. * Vec3
  723. * @type {Number}
  724. */
  725. v3: 3,
  726. /**
  727. * Vec4
  728. * @type {Number}
  729. */
  730. v4: 4,
  731. /**
  732. * Color
  733. * @type {Number}
  734. */
  735. c: 3,
  736. /**
  737. * Mat3
  738. * @type {Number}
  739. */
  740. m3: 9,
  741. /**
  742. * Mat4
  743. * @type {Number}
  744. */
  745. m4: 16
  746. };
  747. /**
  748. * Calculate the minimum and maximum update range for this buffer attribute using
  749. * component size independant min and max values.
  750. *
  751. * @param {Number} min The start of the range to mark as needing an update.
  752. * @param {Number} max The end of the range to mark as needing an update.
  753. */
  754. SPE.ShaderAttribute.prototype.setUpdateRange = function( min, max ) {
  755. 'use strict';
  756. this.updateMin = Math.min( min * this.componentSize, this.updateMin * this.componentSize );
  757. this.updateMax = Math.max( max * this.componentSize, this.updateMax * this.componentSize );
  758. };
  759. /**
  760. * Calculate the number of indices that this attribute should mark as needing
  761. * updating. Also marks the attribute as needing an update.
  762. */
  763. SPE.ShaderAttribute.prototype.flagUpdate = function() {
  764. 'use strict';
  765. var attr = this.bufferAttribute,
  766. range = attr.updateRange;
  767. range.offset = this.updateMin;
  768. range.count = Math.min( ( this.updateMax - this.updateMin ) + this.componentSize, this.typedArray.array.length );
  769. // console.log( range.offset, range.count, this.typedArray.array.length );
  770. // console.log( 'flagUpdate:', range.offset, range.count );
  771. attr.needsUpdate = true;
  772. };
  773. /**
  774. * Reset the index update counts for this attribute
  775. */
  776. SPE.ShaderAttribute.prototype.resetUpdateRange = function() {
  777. 'use strict';
  778. this.updateMin = 0;
  779. this.updateMax = 0;
  780. };
  781. SPE.ShaderAttribute.prototype.resetDynamic = function() {
  782. 'use strict';
  783. this.bufferAttribute.usage = this.dynamicBuffer ?
  784. THREE.DynamicDrawUsage :
  785. THREE.StaticDrawUsage;
  786. };
  787. /**
  788. * Perform a splice operation on this attribute's buffer.
  789. * @param {Number} start The start index of the splice. Will be multiplied by the number of components for this attribute.
  790. * @param {Number} end The end index of the splice. Will be multiplied by the number of components for this attribute.
  791. */
  792. SPE.ShaderAttribute.prototype.splice = function( start, end ) {
  793. 'use strict';
  794. this.typedArray.splice( start, end );
  795. // Reset the reference to the attribute's typed array
  796. // since it has probably changed.
  797. this.forceUpdateAll();
  798. };
  799. SPE.ShaderAttribute.prototype.forceUpdateAll = function() {
  800. 'use strict';
  801. this.bufferAttribute.array = this.typedArray.array;
  802. this.bufferAttribute.updateRange.offset = 0;
  803. this.bufferAttribute.updateRange.count = -1;
  804. // this.bufferAttribute.dynamic = false;
  805. // this.bufferAttribute.usage = this.dynamicBuffer ?
  806. // THREE.DynamicDrawUsage :
  807. // THREE.StaticDrawUsage;
  808. this.bufferAttribute.usage = THREE.StaticDrawUsage;
  809. this.bufferAttribute.needsUpdate = true;
  810. };
  811. /**
  812. * Make sure this attribute has a typed array associated with it.
  813. *
  814. * If it does, then it will ensure the typed array is of the correct size.
  815. *
  816. * If not, a new SPE.TypedArrayHelper instance will be created.
  817. *
  818. * @param {Number} size The size of the typed array to create or update to.
  819. */
  820. SPE.ShaderAttribute.prototype._ensureTypedArray = function( size ) {
  821. 'use strict';
  822. // Condition that's most likely to be true at the top: no change.
  823. if ( this.typedArray !== null && this.typedArray.size === size * this.componentSize ) {
  824. return;
  825. }
  826. // Resize the array if we need to, telling the TypedArrayHelper to
  827. // ignore it's component size when evaluating size.
  828. else if ( this.typedArray !== null && this.typedArray.size !== size ) {
  829. this.typedArray.setSize( size );
  830. }
  831. // This condition should only occur once in an attribute's lifecycle.
  832. else if ( this.typedArray === null ) {
  833. this.typedArray = new SPE.TypedArrayHelper( this.arrayType, size, this.componentSize );
  834. }
  835. };
  836. /**
  837. * Creates a THREE.BufferAttribute instance if one doesn't exist already.
  838. *
  839. * Ensures a typed array is present by calling _ensureTypedArray() first.
  840. *
  841. * If a buffer attribute exists already, then it will be marked as needing an update.
  842. *
  843. * @param {Number} size The size of the typed array to create if one doesn't exist, or resize existing array to.
  844. */
  845. SPE.ShaderAttribute.prototype._createBufferAttribute = function( size ) {
  846. 'use strict';
  847. // Make sure the typedArray is present and correct.
  848. this._ensureTypedArray( size );
  849. // Don't create it if it already exists, but do
  850. // flag that it needs updating on the next render
  851. // cycle.
  852. if ( this.bufferAttribute !== null ) {
  853. this.bufferAttribute.array = this.typedArray.array;
  854. // Since THREE.js version 81, dynamic count calculation was removed
  855. // so I need to do it manually here.
  856. //
  857. // In the next minor release, I may well remove this check and force
  858. // dependency on THREE r81+.
  859. if ( parseFloat( THREE.REVISION ) >= 81 ) {
  860. this.bufferAttribute.count = this.bufferAttribute.array.length / this.bufferAttribute.itemSize;
  861. }
  862. this.bufferAttribute.needsUpdate = true;
  863. return;
  864. }
  865. this.bufferAttribute = new THREE.BufferAttribute( this.typedArray.array, this.componentSize );
  866. // this.bufferAttribute.dynamic = this.dynamicBuffer;
  867. this.bufferAttribute.usage = this.dynamicBuffer ?
  868. THREE.DynamicDrawUsage :
  869. THREE.StaticDrawUsage;
  870. };
  871. /**
  872. * Returns the length of the typed array associated with this attribute.
  873. * @return {Number} The length of the typed array. Will be 0 if no typed array has been created yet.
  874. */
  875. SPE.ShaderAttribute.prototype.getLength = function() {
  876. 'use strict';
  877. if ( this.typedArray === null ) {
  878. return 0;
  879. }
  880. return this.typedArray.array.length;
  881. };
  882. SPE.shaderChunks = {
  883. // Register color-packing define statements.
  884. defines: [
  885. '#define PACKED_COLOR_SIZE 256.0',
  886. '#define PACKED_COLOR_DIVISOR 255.0'
  887. ].join( '\n' ),
  888. // All uniforms used by vertex / fragment shaders
  889. uniforms: [
  890. 'uniform float deltaTime;',
  891. 'uniform float runTime;',
  892. 'uniform sampler2D tex;',
  893. 'uniform vec4 textureAnimation;',
  894. 'uniform float scale;',
  895. ].join( '\n' ),
  896. // All attributes used by the vertex shader.
  897. //
  898. // Note that some attributes are squashed into other ones:
  899. //
  900. // * Drag is acceleration.w
  901. attributes: [
  902. 'attribute vec4 acceleration;',
  903. 'attribute vec3 velocity;',
  904. 'attribute vec4 rotation;',
  905. 'attribute vec3 rotationCenter;',
  906. 'attribute vec4 params;',
  907. 'attribute vec4 size;',
  908. 'attribute vec4 angle;',
  909. 'attribute vec4 color;',
  910. 'attribute vec4 opacity;'
  911. ].join( '\n' ),
  912. //
  913. varyings: [
  914. 'varying vec4 vColor;',
  915. '#ifdef SHOULD_ROTATE_TEXTURE',
  916. ' varying float vAngle;',
  917. '#endif',
  918. '#ifdef SHOULD_CALCULATE_SPRITE',
  919. ' varying vec4 vSpriteSheet;',
  920. '#endif'
  921. ].join( '\n' ),
  922. // Branch-avoiding comparison fns
  923. // - http://theorangeduck.com/page/avoiding-shader-conditionals
  924. branchAvoidanceFunctions: [
  925. 'float when_gt(float x, float y) {',
  926. ' return max(sign(x - y), 0.0);',
  927. '}',
  928. 'float when_lt(float x, float y) {',
  929. ' return min( max(1.0 - sign(x - y), 0.0), 1.0 );',
  930. '}',
  931. 'float when_eq( float x, float y ) {',
  932. ' return 1.0 - abs( sign( x - y ) );',
  933. '}',
  934. 'float when_ge(float x, float y) {',
  935. ' return 1.0 - when_lt(x, y);',
  936. '}',
  937. 'float when_le(float x, float y) {',
  938. ' return 1.0 - when_gt(x, y);',
  939. '}',
  940. // Branch-avoiding logical operators
  941. // (to be used with above comparison fns)
  942. 'float and(float a, float b) {',
  943. ' return a * b;',
  944. '}',
  945. 'float or(float a, float b) {',
  946. ' return min(a + b, 1.0);',
  947. '}',
  948. ].join( '\n' ),
  949. // From:
  950. // - http://stackoverflow.com/a/12553149
  951. // - https://stackoverflow.com/questions/22895237/hexadecimal-to-rgb-values-in-webgl-shader
  952. unpackColor: [
  953. 'vec3 unpackColor( in float hex ) {',
  954. ' vec3 c = vec3( 0.0 );',
  955. ' float r = mod( (hex / PACKED_COLOR_SIZE / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );',
  956. ' float g = mod( (hex / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );',
  957. ' float b = mod( hex, PACKED_COLOR_SIZE );',
  958. ' c.r = r / PACKED_COLOR_DIVISOR;',
  959. ' c.g = g / PACKED_COLOR_DIVISOR;',
  960. ' c.b = b / PACKED_COLOR_DIVISOR;',
  961. ' return c;',
  962. '}',
  963. ].join( '\n' ),
  964. unpackRotationAxis: [
  965. 'vec3 unpackRotationAxis( in float hex ) {',
  966. ' vec3 c = vec3( 0.0 );',
  967. ' float r = mod( (hex / PACKED_COLOR_SIZE / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );',
  968. ' float g = mod( (hex / PACKED_COLOR_SIZE), PACKED_COLOR_SIZE );',
  969. ' float b = mod( hex, PACKED_COLOR_SIZE );',
  970. ' c.r = r / PACKED_COLOR_DIVISOR;',
  971. ' c.g = g / PACKED_COLOR_DIVISOR;',
  972. ' c.b = b / PACKED_COLOR_DIVISOR;',
  973. ' c *= vec3( 2.0 );',
  974. ' c -= vec3( 1.0 );',
  975. ' return c;',
  976. '}',
  977. ].join( '\n' ),
  978. floatOverLifetime: [
  979. 'float getFloatOverLifetime( in float positionInTime, in vec4 attr ) {',
  980. ' highp float value = 0.0;',
  981. ' float deltaAge = positionInTime * float( VALUE_OVER_LIFETIME_LENGTH - 1 );',
  982. ' float fIndex = 0.0;',
  983. ' float shouldApplyValue = 0.0;',
  984. // This might look a little odd, but it's faster in the testing I've done than using branches.
  985. // Uses basic maths to avoid branching.
  986. //
  987. // Take a look at the branch-avoidance functions defined above,
  988. // and be sure to check out The Orange Duck site where I got this
  989. // from (link above).
  990. // Fix for static emitters (age is always zero).
  991. ' value += attr[ 0 ] * when_eq( deltaAge, 0.0 );',
  992. '',
  993. ' for( int i = 0; i < VALUE_OVER_LIFETIME_LENGTH - 1; ++i ) {',
  994. ' fIndex = float( i );',
  995. ' shouldApplyValue = and( when_gt( deltaAge, fIndex ), when_le( deltaAge, fIndex + 1.0 ) );',
  996. ' value += shouldApplyValue * mix( attr[ i ], attr[ i + 1 ], deltaAge - fIndex );',
  997. ' }',
  998. '',
  999. ' return value;',
  1000. '}',
  1001. ].join( '\n' ),
  1002. colorOverLifetime: [
  1003. 'vec3 getColorOverLifetime( in float positionInTime, in vec3 color1, in vec3 color2, in vec3 color3, in vec3 color4 ) {',
  1004. ' vec3 value = vec3( 0.0 );',
  1005. ' value.x = getFloatOverLifetime( positionInTime, vec4( color1.x, color2.x, color3.x, color4.x ) );',
  1006. ' value.y = getFloatOverLifetime( positionInTime, vec4( color1.y, color2.y, color3.y, color4.y ) );',
  1007. ' value.z = getFloatOverLifetime( positionInTime, vec4( color1.z, color2.z, color3.z, color4.z ) );',
  1008. ' return value;',
  1009. '}',
  1010. ].join( '\n' ),
  1011. paramFetchingFunctions: [
  1012. 'float getAlive() {',
  1013. ' return params.x;',
  1014. '}',
  1015. 'float getAge() {',
  1016. ' return params.y;',
  1017. '}',
  1018. 'float getMaxAge() {',
  1019. ' return params.z;',
  1020. '}',
  1021. 'float getWiggle() {',
  1022. ' return params.w;',
  1023. '}',
  1024. ].join( '\n' ),
  1025. forceFetchingFunctions: [
  1026. 'vec4 getPosition( in float age ) {',
  1027. ' return modelViewMatrix * vec4( position, 1.0 );',
  1028. '}',
  1029. 'vec3 getVelocity( in float age ) {',
  1030. ' return velocity * age;',
  1031. '}',
  1032. 'vec3 getAcceleration( in float age ) {',
  1033. ' return acceleration.xyz * age;',
  1034. '}',
  1035. ].join( '\n' ),
  1036. rotationFunctions: [
  1037. // Huge thanks to:
  1038. // - http://www.neilmendoza.com/glsl-rotation-about-an-arbitrary-axis/
  1039. '#ifdef SHOULD_ROTATE_PARTICLES',
  1040. ' mat4 getRotationMatrix( in vec3 axis, in float angle) {',
  1041. ' axis = normalize(axis);',
  1042. ' float s = sin(angle);',
  1043. ' float c = cos(angle);',
  1044. ' float oc = 1.0 - c;',
  1045. '',
  1046. ' 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,',
  1047. ' oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0,',
  1048. ' oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0,',
  1049. ' 0.0, 0.0, 0.0, 1.0);',
  1050. ' }',
  1051. '',
  1052. ' vec3 getRotation( in vec3 pos, in float positionInTime ) {',
  1053. ' if( rotation.y == 0.0 ) {',
  1054. ' return pos;',
  1055. ' }',
  1056. '',
  1057. ' vec3 axis = unpackRotationAxis( rotation.x );',
  1058. ' vec3 center = rotationCenter;',
  1059. ' vec3 translated;',
  1060. ' mat4 rotationMatrix;',
  1061. ' float angle = 0.0;',
  1062. ' angle += when_eq( rotation.z, 0.0 ) * rotation.y;',
  1063. ' angle += when_gt( rotation.z, 0.0 ) * mix( 0.0, rotation.y, positionInTime );',
  1064. ' translated = rotationCenter - pos;',
  1065. ' rotationMatrix = getRotationMatrix( axis, angle );',
  1066. ' return center - vec3( rotationMatrix * vec4( translated, 0.0 ) );',
  1067. ' }',
  1068. '#endif'
  1069. ].join( '\n' ),
  1070. // Fragment chunks
  1071. rotateTexture: [
  1072. ' vec2 vUv = vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y );',
  1073. '',
  1074. ' #ifdef SHOULD_ROTATE_TEXTURE',
  1075. ' float x = gl_PointCoord.x - 0.5;',
  1076. ' float y = 1.0 - gl_PointCoord.y - 0.5;',
  1077. ' float c = cos( -vAngle );',
  1078. ' float s = sin( -vAngle );',
  1079. ' vUv = vec2( c * x + s * y + 0.5, c * y - s * x + 0.5 );',
  1080. ' #endif',
  1081. '',
  1082. // Spritesheets overwrite angle calculations.
  1083. ' #ifdef SHOULD_CALCULATE_SPRITE',
  1084. ' float framesX = vSpriteSheet.x;',
  1085. ' float framesY = vSpriteSheet.y;',
  1086. ' float columnNorm = vSpriteSheet.z;',
  1087. ' float rowNorm = vSpriteSheet.w;',
  1088. ' vUv.x = gl_PointCoord.x * framesX + columnNorm;',
  1089. ' vUv.y = 1.0 - (gl_PointCoord.y * framesY + rowNorm);',
  1090. ' #endif',
  1091. '',
  1092. ' vec4 rotatedTexture = texture2D( tex, vUv );',
  1093. ].join( '\n' )
  1094. };
  1095. SPE.shaders = {
  1096. vertex: [
  1097. SPE.shaderChunks.defines,
  1098. SPE.shaderChunks.uniforms,
  1099. SPE.shaderChunks.attributes,
  1100. SPE.shaderChunks.varyings,
  1101. THREE.ShaderChunk.common,
  1102. THREE.ShaderChunk.logdepthbuf_pars_vertex,
  1103. THREE.ShaderChunk.fog_pars_vertex,
  1104. SPE.shaderChunks.branchAvoidanceFunctions,
  1105. SPE.shaderChunks.unpackColor,
  1106. SPE.shaderChunks.unpackRotationAxis,
  1107. SPE.shaderChunks.floatOverLifetime,
  1108. SPE.shaderChunks.colorOverLifetime,
  1109. SPE.shaderChunks.paramFetchingFunctions,
  1110. SPE.shaderChunks.forceFetchingFunctions,
  1111. SPE.shaderChunks.rotationFunctions,
  1112. 'void main() {',
  1113. //
  1114. // Setup...
  1115. //
  1116. ' highp float age = getAge();',
  1117. ' highp float alive = getAlive();',
  1118. ' highp float maxAge = getMaxAge();',
  1119. ' highp float positionInTime = (age / maxAge);',
  1120. ' highp float isAlive = when_gt( alive, 0.0 );',
  1121. ' #ifdef SHOULD_WIGGLE_PARTICLES',
  1122. ' float wiggleAmount = positionInTime * getWiggle();',
  1123. ' float wiggleSin = isAlive * sin( wiggleAmount );',
  1124. ' float wiggleCos = isAlive * cos( wiggleAmount );',
  1125. ' #endif',
  1126. //
  1127. // Forces
  1128. //
  1129. // Get forces & position
  1130. ' vec3 vel = getVelocity( age );',
  1131. ' vec3 accel = getAcceleration( age );',
  1132. ' vec3 force = vec3( 0.0 );',
  1133. ' vec3 pos = vec3( position );',
  1134. // Calculate the required drag to apply to the forces.
  1135. ' float drag = 1.0 - (positionInTime * 0.5) * acceleration.w;',
  1136. // Integrate forces...
  1137. ' force += vel;',
  1138. ' force *= drag;',
  1139. ' force += accel * age;',
  1140. ' pos += force;',
  1141. // Wiggly wiggly wiggle!
  1142. ' #ifdef SHOULD_WIGGLE_PARTICLES',
  1143. ' pos.x += wiggleSin;',
  1144. ' pos.y += wiggleCos;',
  1145. ' pos.z += wiggleSin;',
  1146. ' #endif',
  1147. // Rotate the emitter around it's central point
  1148. ' #ifdef SHOULD_ROTATE_PARTICLES',
  1149. ' pos = getRotation( pos, positionInTime );',
  1150. ' #endif',
  1151. // Convert pos to a world-space value
  1152. ' vec4 mvPosition = modelViewMatrix * vec4( pos, 1.0 );',
  1153. // Determine point size.
  1154. ' highp float pointSize = getFloatOverLifetime( positionInTime, size ) * isAlive;',
  1155. // Determine perspective
  1156. ' #ifdef HAS_PERSPECTIVE',
  1157. ' float perspective = scale / length( mvPosition.xyz );',
  1158. ' #else',
  1159. ' float perspective = 1.0;',
  1160. ' #endif',
  1161. // Apply perpective to pointSize value
  1162. ' float pointSizePerspective = pointSize * perspective;',
  1163. //
  1164. // Appearance
  1165. //
  1166. // Determine color and opacity for this particle
  1167. ' #ifdef COLORIZE',
  1168. ' vec3 c = isAlive * getColorOverLifetime(',
  1169. ' positionInTime,',
  1170. ' unpackColor( color.x ),',
  1171. ' unpackColor( color.y ),',
  1172. ' unpackColor( color.z ),',
  1173. ' unpackColor( color.w )',
  1174. ' );',
  1175. ' #else',
  1176. ' vec3 c = vec3(1.0);',
  1177. ' #endif',
  1178. ' float o = isAlive * getFloatOverLifetime( positionInTime, opacity );',
  1179. // Assign color to vColor varying.
  1180. ' vColor = vec4( c, o );',
  1181. // Determine angle
  1182. ' #ifdef SHOULD_ROTATE_TEXTURE',
  1183. ' vAngle = isAlive * getFloatOverLifetime( positionInTime, angle );',
  1184. ' #endif',
  1185. // If this particle is using a sprite-sheet as a texture, we'll have to figure out
  1186. // what frame of the texture the particle is using at it's current position in time.
  1187. ' #ifdef SHOULD_CALCULATE_SPRITE',
  1188. ' float framesX = textureAnimation.x;',
  1189. ' float framesY = textureAnimation.y;',
  1190. ' float loopCount = textureAnimation.w;',
  1191. ' float totalFrames = textureAnimation.z;',
  1192. ' float frameNumber = mod( (positionInTime * loopCount) * totalFrames, totalFrames );',
  1193. ' float column = floor(mod( frameNumber, framesX ));',
  1194. ' float row = floor( (frameNumber - column) / framesX );',
  1195. ' float columnNorm = column / framesX;',
  1196. ' float rowNorm = row / framesY;',
  1197. ' vSpriteSheet.x = 1.0 / framesX;',
  1198. ' vSpriteSheet.y = 1.0 / framesY;',
  1199. ' vSpriteSheet.z = columnNorm;',
  1200. ' vSpriteSheet.w = rowNorm;',
  1201. ' #endif',
  1202. //
  1203. // Write values
  1204. //
  1205. // Set PointSize according to size at current point in time.
  1206. ' gl_PointSize = pointSizePerspective;',
  1207. ' gl_Position = projectionMatrix * mvPosition;',
  1208. THREE.ShaderChunk.logdepthbuf_vertex,
  1209. THREE.ShaderChunk.fog_vertex,
  1210. '}'
  1211. ].join( '\n' ),
  1212. fragment: [
  1213. SPE.shaderChunks.uniforms,
  1214. THREE.ShaderChunk.common,
  1215. THREE.ShaderChunk.fog_pars_fragment,
  1216. THREE.ShaderChunk.logdepthbuf_pars_fragment,
  1217. SPE.shaderChunks.varyings,
  1218. SPE.shaderChunks.branchAvoidanceFunctions,
  1219. 'void main() {',
  1220. ' vec3 outgoingLight = vColor.xyz;',
  1221. ' ',
  1222. ' #ifdef ALPHATEST',
  1223. ' if ( vColor.w < float(ALPHATEST) ) discard;',
  1224. ' #endif',
  1225. SPE.shaderChunks.rotateTexture,
  1226. THREE.ShaderChunk.logdepthbuf_fragment,
  1227. ' outgoingLight = vColor.xyz * rotatedTexture.xyz;',
  1228. ' gl_FragColor = vec4( outgoingLight.xyz, rotatedTexture.w * vColor.w );',
  1229. THREE.ShaderChunk.fog_fragment,
  1230. '}'
  1231. ].join( '\n' )
  1232. };
  1233. /**
  1234. * A bunch of utility functions used throughout the library.
  1235. * @namespace
  1236. * @type {Object}
  1237. */
  1238. SPE.utils = {
  1239. /**
  1240. * A map of types used by `SPE.utils.ensureTypedArg` and
  1241. * `SPE.utils.ensureArrayTypedArg` to compare types against.
  1242. *
  1243. * @enum {String}
  1244. */
  1245. types: {
  1246. /**
  1247. * Boolean type.
  1248. * @type {String}
  1249. */
  1250. BOOLEAN: 'boolean',
  1251. /**
  1252. * String type.
  1253. * @type {String}
  1254. */
  1255. STRING: 'string',
  1256. /**
  1257. * Number type.
  1258. * @type {String}
  1259. */
  1260. NUMBER: 'number',
  1261. /**
  1262. * Object type.
  1263. * @type {String}
  1264. */
  1265. OBJECT: 'object'
  1266. },
  1267. /**
  1268. * Given a value, a type, and a default value to fallback to,
  1269. * ensure the given argument adheres to the type requesting,
  1270. * returning the default value if type check is false.
  1271. *
  1272. * @param {(boolean|string|number|object)} arg The value to perform a type-check on.
  1273. * @param {String} type The type the `arg` argument should adhere to.
  1274. * @param {(boolean|string|number|object)} defaultValue A default value to fallback on if the type check fails.
  1275. * @return {(boolean|string|number|object)} The given value if type check passes, or the default value if it fails.
  1276. */
  1277. ensureTypedArg: function( arg, type, defaultValue ) {
  1278. 'use strict';
  1279. if ( typeof arg === type ) {
  1280. return arg;
  1281. }
  1282. else {
  1283. return defaultValue;
  1284. }
  1285. },
  1286. /**
  1287. * Given an array of values, a type, and a default value,
  1288. * ensure the given array's contents ALL adhere to the provided type,
  1289. * returning the default value if type check fails.
  1290. *
  1291. * If the given value to check isn't an Array, delegates to SPE.utils.ensureTypedArg.
  1292. *
  1293. * @param {Array|boolean|string|number|object} arg The array of values to check type of.
  1294. * @param {String} type The type that should be adhered to.
  1295. * @param {(boolean|string|number|object)} defaultValue A default fallback value.
  1296. * @return {(boolean|string|number|object)} The given value if type check passes, or the default value if it fails.
  1297. */
  1298. ensureArrayTypedArg: function( arg, type, defaultValue ) {
  1299. 'use strict';
  1300. // If the argument being checked is an array, loop through
  1301. // it and ensure all the values are of the correct type,
  1302. // falling back to the defaultValue if any aren't.
  1303. if ( Array.isArray( arg ) ) {
  1304. for ( var i = arg.length - 1; i >= 0; --i ) {
  1305. if ( typeof arg[ i ] !== type ) {
  1306. return defaultValue;
  1307. }
  1308. }
  1309. return arg;
  1310. }
  1311. // If the arg isn't an array then just fallback to
  1312. // checking the type.
  1313. return this.ensureTypedArg( arg, type, defaultValue );
  1314. },
  1315. /**
  1316. * Ensures the given value is an instance of a constructor function.
  1317. *
  1318. * @param {Object} arg The value to check instance of.
  1319. * @param {Function} instance The constructor of the instance to check against.
  1320. * @param {Object} defaultValue A default fallback value if instance check fails
  1321. * @return {Object} The given value if type check passes, or the default value if it fails.
  1322. */
  1323. ensureInstanceOf: function( arg, instance, defaultValue ) {
  1324. 'use strict';
  1325. if ( instance !== undefined && arg instanceof instance ) {
  1326. return arg;
  1327. }
  1328. else {
  1329. return defaultValue;
  1330. }
  1331. },
  1332. /**
  1333. * Given an array of values, ensure the instances of all items in the array
  1334. * matches the given instance constructor falling back to a default value if
  1335. * the check fails.
  1336. *
  1337. * If given value isn't an Array, delegates to `SPE.utils.ensureInstanceOf`.
  1338. *
  1339. * @param {Array|Object} arg The value to perform the instanceof check on.
  1340. * @param {Function} instance The constructor of the instance to check against.
  1341. * @param {Object} defaultValue A default fallback value if instance check fails
  1342. * @return {Object} The given value if type check passes, or the default value if it fails.
  1343. */
  1344. ensureArrayInstanceOf: function( arg, instance, defaultValue ) {
  1345. 'use strict';
  1346. // If the argument being checked is an array, loop through
  1347. // it and ensure all the values are of the correct type,
  1348. // falling back to the defaultValue if any aren't.
  1349. if ( Array.isArray( arg ) ) {
  1350. for ( var i = arg.length - 1; i >= 0; --i ) {
  1351. if ( instance !== undefined && arg[ i ] instanceof instance === false ) {
  1352. return defaultValue;
  1353. }
  1354. }
  1355. return arg;
  1356. }
  1357. // If the arg isn't an array then just fallback to
  1358. // checking the type.
  1359. return this.ensureInstanceOf( arg, instance, defaultValue );
  1360. },
  1361. /**
  1362. * Ensures that any "value-over-lifetime" properties of an emitter are
  1363. * of the correct length (as dictated by `SPE.valueOverLifetimeLength`).
  1364. *
  1365. * Delegates to `SPE.utils.interpolateArray` for array resizing.
  1366. *
  1367. * If properties aren't arrays, then property values are put into one.
  1368. *
  1369. * @param {Object} property The property of an SPE.Emitter instance to check compliance of.
  1370. * @param {Number} minLength The minimum length of the array to create.
  1371. * @param {Number} maxLength The maximum length of the array to create.
  1372. */
  1373. ensureValueOverLifetimeCompliance: function( property, minLength, maxLength ) {
  1374. 'use strict';
  1375. minLength = minLength || 3;
  1376. maxLength = maxLength || 3;
  1377. // First, ensure both properties are arrays.
  1378. if ( Array.isArray( property._value ) === false ) {
  1379. property._value = [ property._value ];
  1380. }
  1381. if ( Array.isArray( property._spread ) === false ) {
  1382. property._spread = [ property._spread ];
  1383. }
  1384. var valueLength = this.clamp( property._value.length, minLength, maxLength ),
  1385. spreadLength = this.clamp( property._spread.length, minLength, maxLength ),
  1386. desiredLength = Math.max( valueLength, spreadLength );
  1387. if ( property._value.length !== desiredLength ) {
  1388. property._value = this.interpolateArray( property._value, desiredLength );
  1389. }
  1390. if ( property._spread.length !== desiredLength ) {
  1391. property._spread = this.interpolateArray( property._spread, desiredLength );
  1392. }
  1393. },
  1394. /**
  1395. * Performs linear interpolation (lerp) on an array.
  1396. *
  1397. * For example, lerping [1, 10], with a `newLength` of 10 will produce [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].
  1398. *
  1399. * Delegates to `SPE.utils.lerpTypeAgnostic` to perform the actual
  1400. * interpolation.
  1401. *
  1402. * @param {Array} srcArray The array to lerp.
  1403. * @param {Number} newLength The length the array should be interpolated to.
  1404. * @return {Array} The interpolated array.
  1405. */
  1406. interpolateArray: function( srcArray, newLength ) {
  1407. 'use strict';
  1408. var sourceLength = srcArray.length,
  1409. newArray = [ typeof srcArray[ 0 ].clone === 'function' ? srcArray[ 0 ].clone() : srcArray[ 0 ] ],
  1410. factor = ( sourceLength - 1 ) / ( newLength - 1 );
  1411. for ( var i = 1; i < newLength - 1; ++i ) {
  1412. var f = i * factor,
  1413. before = Math.floor( f ),
  1414. after = Math.ceil( f ),
  1415. delta = f - before;
  1416. newArray[ i ] = this.lerpTypeAgnostic( srcArray[ before ], srcArray[ after ], delta );
  1417. }
  1418. newArray.push(
  1419. typeof srcArray[ sourceLength - 1 ].clone === 'function' ?
  1420. srcArray[ sourceLength - 1 ].clone() :
  1421. srcArray[ sourceLength - 1 ]
  1422. );
  1423. return newArray;
  1424. },
  1425. /**
  1426. * Clamp a number to between the given min and max values.
  1427. * @param {Number} value The number to clamp.
  1428. * @param {Number} min The minimum value.
  1429. * @param {Number} max The maximum value.
  1430. * @return {Number} The clamped number.
  1431. */
  1432. clamp: function( value, min, max ) {
  1433. 'use strict';
  1434. return Math.max( min, Math.min( value, max ) );
  1435. },
  1436. /**
  1437. * If the given value is less than the epsilon value, then return
  1438. * a randomised epsilon value if specified, or just the epsilon value if not.
  1439. * Works for negative numbers as well as positive.
  1440. *
  1441. * @param {Number} value The value to perform the operation on.
  1442. * @param {Boolean} randomise Whether the value should be randomised.
  1443. * @return {Number} The result of the operation.
  1444. */
  1445. zeroToEpsilon: function( value, randomise ) {
  1446. 'use strict';
  1447. var epsilon = 0.00001,
  1448. result = value;
  1449. result = randomise ? Math.random() * epsilon * 10 : epsilon;
  1450. if ( value < 0 && value > -epsilon ) {
  1451. result = -result;
  1452. }
  1453. // if ( value === 0 ) {
  1454. // result = randomise ? Math.random() * epsilon * 10 : epsilon;
  1455. // }
  1456. // else if ( value > 0 && value < epsilon ) {
  1457. // result = randomise ? Math.random() * epsilon * 10 : epsilon;
  1458. // }
  1459. // else if ( value < 0 && value > -epsilon ) {
  1460. // result = -( randomise ? Math.random() * epsilon * 10 : epsilon );
  1461. // }
  1462. return result;
  1463. },
  1464. /**
  1465. * Linearly interpolates two values of various types. The given values
  1466. * must be of the same type for the interpolation to work.
  1467. * @param {(number|Object)} start The start value of the lerp.
  1468. * @param {(number|object)} end The end value of the lerp.
  1469. * @param {Number} delta The delta posiiton of the lerp operation. Ideally between 0 and 1 (inclusive).
  1470. * @return {(number|object|undefined)} The result of the operation. Result will be undefined if
  1471. * the start and end arguments aren't a supported type, or
  1472. * if their types do not match.
  1473. */
  1474. lerpTypeAgnostic: function( start, end, delta ) {
  1475. 'use strict';
  1476. var types = this.types,
  1477. out;
  1478. if ( typeof start === types.NUMBER && typeof end === types.NUMBER ) {
  1479. return start + ( ( end - start ) * delta );
  1480. }
  1481. else if ( start instanceof THREE.Vector2 && end instanceof THREE.Vector2 ) {
  1482. out = start.clone();
  1483. out.x = this.lerp( start.x, end.x, delta );
  1484. out.y = this.lerp( start.y, end.y, delta );
  1485. return out;
  1486. }
  1487. else if ( start instanceof THREE.Vector3 && end instanceof THREE.Vector3 ) {
  1488. out = start.clone();
  1489. out.x = this.lerp( start.x, end.x, delta );
  1490. out.y = this.lerp( start.y, end.y, delta );
  1491. out.z = this.lerp( start.z, end.z, delta );
  1492. return out;
  1493. }
  1494. else if ( start instanceof THREE.Vector4 && end instanceof THREE.Vector4 ) {
  1495. out = start.clone();
  1496. out.x = this.lerp( start.x, end.x, delta );
  1497. out.y = this.lerp( start.y, end.y, delta );
  1498. out.z = this.lerp( start.z, end.z, delta );
  1499. out.w = this.lerp( start.w, end.w, delta );
  1500. return out;
  1501. }
  1502. else if ( start instanceof THREE.Color && end instanceof THREE.Color ) {
  1503. out = start.clone();
  1504. out.r = this.lerp( start.r, end.r, delta );
  1505. out.g = this.lerp( start.g, end.g, delta );
  1506. out.b = this.lerp( start.b, end.b, delta );
  1507. return out;
  1508. }
  1509. else {
  1510. console.warn( 'Invalid argument types, or argument types do not match:', start, end );
  1511. }
  1512. },
  1513. /**
  1514. * Perform a linear interpolation operation on two numbers.
  1515. * @param {Number} start The start value.
  1516. * @param {Number} end The end value.
  1517. * @param {Number} delta The position to interpolate to.
  1518. * @return {Number} The result of the lerp operation.
  1519. */
  1520. lerp: function( start, end, delta ) {
  1521. 'use strict';
  1522. return start + ( ( end - start ) * delta );
  1523. },
  1524. /**
  1525. * Rounds a number to a nearest multiple.
  1526. *
  1527. * @param {Number} n The number to round.
  1528. * @param {Number} multiple The multiple to round to.
  1529. * @return {Number} The result of the round operation.
  1530. */
  1531. roundToNearestMultiple: function( n, multiple ) {
  1532. 'use strict';
  1533. var remainder = 0;
  1534. if ( multiple === 0 ) {
  1535. return n;
  1536. }
  1537. remainder = Math.abs( n ) % multiple;
  1538. if ( remainder === 0 ) {
  1539. return n;
  1540. }
  1541. if ( n < 0 ) {
  1542. return -( Math.abs( n ) - remainder );
  1543. }
  1544. return n + multiple - remainder;
  1545. },
  1546. /**
  1547. * Check if all items in an array are equal. Uses strict equality.
  1548. *
  1549. * @param {Array} array The array of values to check equality of.
  1550. * @return {Boolean} Whether the array's values are all equal or not.
  1551. */
  1552. arrayValuesAreEqual: function( array ) {
  1553. 'use strict';
  1554. for ( var i = 0; i < array.length - 1; ++i ) {
  1555. if ( array[ i ] !== array[ i + 1 ] ) {
  1556. return false;
  1557. }
  1558. }
  1559. return true;
  1560. },
  1561. // colorsAreEqual: function() {
  1562. // var colors = Array.prototype.slice.call( arguments ),
  1563. // numColors = colors.length;
  1564. // for ( var i = 0, color1, color2; i < numColors - 1; ++i ) {
  1565. // color1 = colors[ i ];
  1566. // color2 = colors[ i + 1 ];
  1567. // if (
  1568. // color1.r !== color2.r ||
  1569. // color1.g !== color2.g ||
  1570. // color1.b !== color2.b
  1571. // ) {
  1572. // return false
  1573. // }
  1574. // }
  1575. // return true;
  1576. // },
  1577. /**
  1578. * Given a start value and a spread value, create and return a random
  1579. * number.
  1580. * @param {Number} base The start value.
  1581. * @param {Number} spread The size of the random variance to apply.
  1582. * @return {Number} A randomised number.
  1583. */
  1584. randomFloat: function( base, spread ) {
  1585. 'use strict';
  1586. return base + spread * ( Math.random() - 0.5 );
  1587. },
  1588. /**
  1589. * Given an SPE.ShaderAttribute instance, and various other settings,
  1590. * assign values to the attribute's array in a `vec3` format.
  1591. *
  1592. * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to.
  1593. * @param {Number} index The offset in the attribute's TypedArray to save the result from.
  1594. * @param {Object} base THREE.Vector3 instance describing the start value.
  1595. * @param {Object} spread THREE.Vector3 instance describing the random variance to apply to the start value.
  1596. * @param {Object} spreadClamp THREE.Vector3 instance describing the multiples to clamp the randomness to.
  1597. */
  1598. randomVector3: function( attribute, index, base, spread, spreadClamp ) {
  1599. 'use strict';
  1600. var x = base.x + ( Math.random() * spread.x - ( spread.x * 0.5 ) ),
  1601. y = base.y + ( Math.random() * spread.y - ( spread.y * 0.5 ) ),
  1602. z = base.z + ( Math.random() * spread.z - ( spread.z * 0.5 ) );
  1603. // var x = this.randomFloat( base.x, spread.x ),
  1604. // y = this.randomFloat( base.y, spread.y ),
  1605. // z = this.randomFloat( base.z, spread.z );
  1606. if ( spreadClamp ) {
  1607. x = -spreadClamp.x * 0.5 + this.roundToNearestMultiple( x, spreadClamp.x );
  1608. y = -spreadClamp.y * 0.5 + this.roundToNearestMultiple( y, spreadClamp.y );
  1609. z = -spreadClamp.z * 0.5 + this.roundToNearestMultiple( z, spreadClamp.z );
  1610. }
  1611. attribute.typedArray.setVec3Components( index, x, y, z );
  1612. },
  1613. /**
  1614. * Given an SPE.Shader attribute instance, and various other settings,
  1615. * assign Color values to the attribute.
  1616. * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to.
  1617. * @param {Number} index The offset in the attribute's TypedArray to save the result from.
  1618. * @param {Object} base THREE.Color instance describing the start color.
  1619. * @param {Object} spread THREE.Vector3 instance describing the random variance to apply to the start color.
  1620. */
  1621. randomColor: function( attribute, index, base, spread ) {
  1622. 'use strict';
  1623. var r = base.r + ( Math.random() * spread.x ),
  1624. g = base.g + ( Math.random() * spread.y ),
  1625. b = base.b + ( Math.random() * spread.z );
  1626. r = this.clamp( r, 0, 1 );
  1627. g = this.clamp( g, 0, 1 );
  1628. b = this.clamp( b, 0, 1 );
  1629. attribute.typedArray.setVec3Components( index, r, g, b );
  1630. },
  1631. randomColorAsHex: ( function() {
  1632. 'use strict';
  1633. var workingColor = new THREE.Color();
  1634. /**
  1635. * Assigns a random color value, encoded as a hex value in decimal
  1636. * format, to a SPE.ShaderAttribute instance.
  1637. * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to.
  1638. * @param {Number} index The offset in the attribute's TypedArray to save the result from.
  1639. * @param {Object} base THREE.Color instance describing the start color.
  1640. * @param {Object} spread THREE.Vector3 instance describing the random variance to apply to the start color.
  1641. */
  1642. return function( attribute, index, base, spread ) {
  1643. var numItems = base.length,
  1644. colors = [];
  1645. for ( var i = 0; i < numItems; ++i ) {
  1646. var spreadVector = spread[ i ];
  1647. workingColor.copy( base[ i ] );
  1648. workingColor.r += ( Math.random() * spreadVector.x ) - ( spreadVector.x * 0.5 );
  1649. workingColor.g += ( Math.random() * spreadVector.y ) - ( spreadVector.y * 0.5 );
  1650. workingColor.b += ( Math.random() * spreadVector.z ) - ( spreadVector.z * 0.5 );
  1651. workingColor.r = this.clamp( workingColor.r, 0, 1 );
  1652. workingColor.g = this.clamp( workingColor.g, 0, 1 );
  1653. workingColor.b = this.clamp( workingColor.b, 0, 1 );
  1654. colors.push( workingColor.getHex() );
  1655. }
  1656. attribute.typedArray.setVec4Components( index, colors[ 0 ], colors[ 1 ], colors[ 2 ], colors[ 3 ] );
  1657. };
  1658. }() ),
  1659. /**
  1660. * Given an SPE.ShaderAttribute instance, and various other settings,
  1661. * assign values to the attribute's array in a `vec3` format.
  1662. *
  1663. * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to.
  1664. * @param {Number} index The offset in the attribute's TypedArray to save the result from.
  1665. * @param {Object} start THREE.Vector3 instance describing the start line position.
  1666. * @param {Object} end THREE.Vector3 instance describing the end line position.
  1667. */
  1668. randomVector3OnLine: function( attribute, index, start, end ) {
  1669. 'use strict';
  1670. var pos = start.clone();
  1671. pos.lerp( end, Math.random() );
  1672. attribute.typedArray.setVec3Components( index, pos.x, pos.y, pos.z );
  1673. },
  1674. /**
  1675. * Given an SPE.Shader attribute instance, and various other settings,
  1676. * assign Color values to the attribute.
  1677. * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to.
  1678. * @param {Number} index The offset in the attribute's TypedArray to save the result from.
  1679. * @param {Object} base THREE.Color instance describing the start color.
  1680. * @param {Object} spread THREE.Vector3 instance describing the random variance to apply to the start color.
  1681. */
  1682. /**
  1683. * Assigns a random vector 3 value to an SPE.ShaderAttribute instance, projecting the
  1684. * given values onto a sphere.
  1685. *
  1686. * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to.
  1687. * @param {Number} index The offset in the attribute's TypedArray to save the result from.
  1688. * @param {Object} base THREE.Vector3 instance describing the origin of the transform.
  1689. * @param {Number} radius The radius of the sphere to project onto.
  1690. * @param {Number} radiusSpread The amount of randomness to apply to the projection result
  1691. * @param {Object} radiusScale THREE.Vector3 instance describing the scale of each axis of the sphere.
  1692. * @param {Number} radiusSpreadClamp What numeric multiple the projected value should be clamped to.
  1693. */
  1694. randomVector3OnSphere: function(
  1695. attribute, index, base, radius, radiusSpread, radiusScale, radiusSpreadClamp, distributionClamp
  1696. ) {
  1697. 'use strict';
  1698. var depth = 2 * Math.random() - 1,
  1699. t = 6.2832 * Math.random(),
  1700. r = Math.sqrt( 1 - depth * depth ),
  1701. rand = this.randomFloat( radius, radiusSpread ),
  1702. x = 0,
  1703. y = 0,
  1704. z = 0;
  1705. if ( radiusSpreadClamp ) {
  1706. rand = Math.round( rand / radiusSpreadClamp ) * radiusSpreadClamp;
  1707. }
  1708. // Set position on sphere
  1709. x = r * Math.cos( t ) * rand;
  1710. y = r * Math.sin( t ) * rand;
  1711. z = depth * rand;
  1712. // Apply radius scale to this position
  1713. x *= radiusScale.x;
  1714. y *= radiusScale.y;
  1715. z *= radiusScale.z;
  1716. // Translate to the base position.
  1717. x += base.x;
  1718. y += base.y;
  1719. z += base.z;
  1720. // Set the values in the typed array.
  1721. attribute.typedArray.setVec3Components( index, x, y, z );
  1722. },
  1723. seededRandom: function( seed ) {
  1724. var x = Math.sin( seed ) * 10000;
  1725. return x - ( x | 0 );
  1726. },
  1727. /**
  1728. * Assigns a random vector 3 value to an SPE.ShaderAttribute instance, projecting the
  1729. * given values onto a 2d-disc.
  1730. *
  1731. * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to.
  1732. * @param {Number} index The offset in the attribute's TypedArray to save the result from.
  1733. * @param {Object} base THREE.Vector3 instance describing the origin of the transform.
  1734. * @param {Number} radius The radius of the sphere to project onto.
  1735. * @param {Number} radiusSpread The amount of randomness to apply to the projection result
  1736. * @param {Object} radiusScale THREE.Vector3 instance describing the scale of each axis of the disc. The z-component is ignored.
  1737. * @param {Number} radiusSpreadClamp What numeric multiple the projected value should be clamped to.
  1738. */
  1739. randomVector3OnDisc: function( attribute, index, base, radius, radiusSpread, radiusScale, radiusSpreadClamp ) {
  1740. 'use strict';
  1741. var t = 6.2832 * Math.random(),
  1742. rand = Math.abs( this.randomFloat( radius, radiusSpread ) ),
  1743. x = 0,
  1744. y = 0,
  1745. z = 0;
  1746. if ( radiusSpreadClamp ) {
  1747. rand = Math.round( rand / radiusSpreadClamp ) * radiusSpreadClamp;
  1748. }
  1749. // Set position on sphere
  1750. x = Math.cos( t ) * rand;
  1751. y = Math.sin( t ) * rand;
  1752. // Apply radius scale to this position
  1753. x *= radiusScale.x;
  1754. y *= radiusScale.y;
  1755. // Translate to the base position.
  1756. x += base.x;
  1757. y += base.y;
  1758. z += base.z;
  1759. // Set the values in the typed array.
  1760. attribute.typedArray.setVec3Components( index, x, y, z );
  1761. },
  1762. randomDirectionVector3OnSphere: ( function() {
  1763. 'use strict';
  1764. var v = new THREE.Vector3();
  1765. /**
  1766. * Given an SPE.ShaderAttribute instance, create a direction vector from the given
  1767. * position, using `speed` as the magnitude. Values are saved to the attribute.
  1768. *
  1769. * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to.
  1770. * @param {Number} index The offset in the attribute's TypedArray to save the result from.
  1771. * @param {Number} posX The particle's x coordinate.
  1772. * @param {Number} posY The particle's y coordinate.
  1773. * @param {Number} posZ The particle's z coordinate.
  1774. * @param {Object} emitterPosition THREE.Vector3 instance describing the emitter's base position.
  1775. * @param {Number} speed The magnitude to apply to the vector.
  1776. * @param {Number} speedSpread The amount of randomness to apply to the magnitude.
  1777. */
  1778. return function( attribute, index, posX, posY, posZ, emitterPosition, speed, speedSpread ) {
  1779. v.copy( emitterPosition );
  1780. v.x -= posX;
  1781. v.y -= posY;
  1782. v.z -= posZ;
  1783. v.normalize().multiplyScalar( -this.randomFloat( speed, speedSpread ) );
  1784. attribute.typedArray.setVec3Components( index, v.x, v.y, v.z );
  1785. };
  1786. }() ),
  1787. randomDirectionVector3OnDisc: ( function() {
  1788. 'use strict';
  1789. var v = new THREE.Vector3();
  1790. /**
  1791. * Given an SPE.ShaderAttribute instance, create a direction vector from the given
  1792. * position, using `speed` as the magnitude. Values are saved to the attribute.
  1793. *
  1794. * @param {Object} attribute The instance of SPE.ShaderAttribute to save the result to.
  1795. * @param {Number} index The offset in the attribute's TypedArray to save the result from.
  1796. * @param {Number} posX The particle's x coordinate.
  1797. * @param {Number} posY The particle's y coordinate.
  1798. * @param {Number} posZ The particle's z coordinate.
  1799. * @param {Object} emitterPosition THREE.Vector3 instance describing the emitter's base position.
  1800. * @param {Number} speed The magnitude to apply to the vector.
  1801. * @param {Number} speedSpread The amount of randomness to apply to the magnitude.
  1802. */
  1803. return function( attribute, index, posX, posY, posZ, emitterPosition, speed, speedSpread ) {
  1804. v.copy( emitterPosition );
  1805. v.x -= posX;
  1806. v.y -= posY;
  1807. v.z -= posZ;
  1808. v.normalize().multiplyScalar( -this.randomFloat( speed, speedSpread ) );
  1809. attribute.typedArray.setVec3Components( index, v.x, v.y, 0 );
  1810. };
  1811. }() ),
  1812. getPackedRotationAxis: ( function() {
  1813. 'use strict';
  1814. var v = new THREE.Vector3(),
  1815. vSpread = new THREE.Vector3(),
  1816. c = new THREE.Color(),
  1817. addOne = new THREE.Vector3( 1, 1, 1 );
  1818. /**
  1819. * Given a rotation axis, and a rotation axis spread vector,
  1820. * calculate a randomised rotation axis, and pack it into
  1821. * a hexadecimal value represented in decimal form.
  1822. * @param {Object} axis THREE.Vector3 instance describing the rotation axis.
  1823. * @param {Object} axisSpread THREE.Vector3 instance describing the amount of randomness to apply to the rotation axis.
  1824. * @return {Number} The packed rotation axis, with randomness.
  1825. */
  1826. return function( axis, axisSpread ) {
  1827. v.copy( axis ).normalize();
  1828. vSpread.copy( axisSpread ).normalize();
  1829. v.x += ( -axisSpread.x * 0.5 ) + ( Math.random() * axisSpread.x );
  1830. v.y += ( -axisSpread.y * 0.5 ) + ( Math.random() * axisSpread.y );
  1831. v.z += ( -axisSpread.z * 0.5 ) + ( Math.random() * axisSpread.z );
  1832. // v.x = Math.abs( v.x );
  1833. // v.y = Math.abs( v.y );
  1834. // v.z = Math.abs( v.z );
  1835. v.normalize().add( addOne ).multiplyScalar( 0.5 );
  1836. c.setRGB( v.x, v.y, v.z );
  1837. return c.getHex();
  1838. };
  1839. }() )
  1840. };
  1841. /**
  1842. * An SPE.Group instance.
  1843. * @typedef {Object} Group
  1844. * @see SPE.Group
  1845. */
  1846. /**
  1847. * A map of options to configure an SPE.Group instance.
  1848. * @typedef {Object} GroupOptions
  1849. *
  1850. * @property {Object} texture An object describing the texture used by the group.
  1851. *
  1852. * @property {Object} texture.value An instance of THREE.Texture.
  1853. *
  1854. * @property {Object=} texture.frames A THREE.Vector2 instance describing the number
  1855. * of frames on the x- and y-axis of the given texture.
  1856. * If not provided, the texture will NOT be treated as
  1857. * a sprite-sheet and as such will NOT be animated.
  1858. *
  1859. * @property {Number} [texture.frameCount=texture.frames.x * texture.frames.y] The total number of frames in the sprite-sheet.
  1860. * Allows for sprite-sheets that don't fill the entire
  1861. * texture.
  1862. *
  1863. * @property {Number} texture.loop The number of loops through the sprite-sheet that should
  1864. * be performed over the course of a single particle's lifetime.
  1865. *
  1866. * @property {Number} fixedTimeStep If no `dt` (or `deltaTime`) value is passed to this group's
  1867. * `tick()` function, this number will be used to move the particle
  1868. * simulation forward. Value in SECONDS.
  1869. *
  1870. * @property {Boolean} hasPerspective Whether the distance a particle is from the camera should affect
  1871. * the particle's size.
  1872. *
  1873. * @property {Boolean} colorize Whether the particles in this group should be rendered with color, or
  1874. * whether the only color of particles will come from the provided texture.
  1875. *
  1876. * @property {Number} blending One of Three.js's blending modes to apply to this group's `ShaderMaterial`.
  1877. *
  1878. * @property {Boolean} transparent Whether these particle's should be rendered with transparency.
  1879. *
  1880. * @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.
  1881. *
  1882. * @property {Boolean} depthWrite Whether rendering the group has any effect on the depth buffer.
  1883. *
  1884. * @property {Boolean} depthTest Whether to have depth test enabled when rendering this group.
  1885. *
  1886. * @property {Boolean} fog Whether this group's particles should be affected by their scene's fog.
  1887. *
  1888. * @property {Number} scale The scale factor to apply to this group's particle sizes. Useful for
  1889. * setting particle sizes to be relative to renderer size.
  1890. */
  1891. /**
  1892. * The SPE.Group class. Creates a new group, containing a material, geometry, and mesh.
  1893. *
  1894. * @constructor
  1895. * @param {GroupOptions} options A map of options to configure the group instance.
  1896. */
  1897. SPE.Group = function( options ) {
  1898. 'use strict';
  1899. var utils = SPE.utils,
  1900. types = utils.types;
  1901. // Ensure we have a map of options to play with
  1902. options = utils.ensureTypedArg( options, types.OBJECT, {} );
  1903. options.texture = utils.ensureTypedArg( options.texture, types.OBJECT, {} );
  1904. // Assign a UUID to this instance
  1905. this.uuid = THREE.MathUtils.generateUUID();
  1906. // If no `deltaTime` value is passed to the `SPE.Group.tick` function,
  1907. // the value of this property will be used to advance the simulation.
  1908. this.fixedTimeStep = utils.ensureTypedArg( options.fixedTimeStep, types.NUMBER, 0.016 );
  1909. // Set properties used in the uniforms map, starting with the
  1910. // texture stuff.
  1911. this.texture = utils.ensureInstanceOf( options.texture.value, THREE.Texture, null );
  1912. this.textureFrames = utils.ensureInstanceOf( options.texture.frames, THREE.Vector2, new THREE.Vector2( 1, 1 ) );
  1913. this.textureFrameCount = utils.ensureTypedArg( options.texture.frameCount, types.NUMBER, this.textureFrames.x * this.textureFrames.y );
  1914. this.textureLoop = utils.ensureTypedArg( options.texture.loop, types.NUMBER, 1 );
  1915. this.textureFrames.max( new THREE.Vector2( 1, 1 ) );
  1916. this.hasPerspective = utils.ensureTypedArg( options.hasPerspective, types.BOOLEAN, true );
  1917. this.colorize = utils.ensureTypedArg( options.colorize, types.BOOLEAN, true );
  1918. this.maxParticleCount = utils.ensureTypedArg( options.maxParticleCount, types.NUMBER, null );
  1919. // Set properties used to define the ShaderMaterial's appearance.
  1920. this.blending = utils.ensureTypedArg( options.blending, types.NUMBER, THREE.AdditiveBlending );
  1921. this.transparent = utils.ensureTypedArg( options.transparent, types.BOOLEAN, true );
  1922. this.alphaTest = parseFloat( utils.ensureTypedArg( options.alphaTest, types.NUMBER, 0.0 ) );
  1923. this.depthWrite = utils.ensureTypedArg( options.depthWrite, types.BOOLEAN, false );
  1924. this.depthTest = utils.ensureTypedArg( options.depthTest, types.BOOLEAN, true );
  1925. this.fog = utils.ensureTypedArg( options.fog, types.BOOLEAN, true );
  1926. this.scale = utils.ensureTypedArg( options.scale, types.NUMBER, 300 );
  1927. // Where emitter's go to curl up in a warm blanket and live
  1928. // out their days.
  1929. this.emitters = [];
  1930. this.emitterIDs = [];
  1931. // Create properties for use by the emitter pooling functions.
  1932. this._pool = [];
  1933. this._poolCreationSettings = null;
  1934. this._createNewWhenPoolEmpty = 0;
  1935. // Whether all attributes should be forced to updated
  1936. // their entire buffer contents on the next tick.
  1937. //
  1938. // Used when an emitter is removed.
  1939. this._attributesNeedRefresh = false;
  1940. this._attributesNeedDynamicReset = false;
  1941. this.particleCount = 0;
  1942. // Map of uniforms to be applied to the ShaderMaterial instance.
  1943. this.uniforms = {
  1944. tex: {
  1945. type: 't',
  1946. value: this.texture
  1947. },
  1948. textureAnimation: {
  1949. type: 'v4',
  1950. value: new THREE.Vector4(
  1951. this.textureFrames.x,
  1952. this.textureFrames.y,
  1953. this.textureFrameCount,
  1954. Math.max( Math.abs( this.textureLoop ), 1.0 )
  1955. )
  1956. },
  1957. fogColor: {
  1958. type: 'c',
  1959. value: this.fog ? new THREE.Color() : null
  1960. },
  1961. fogNear: {
  1962. type: 'f',
  1963. value: 10
  1964. },
  1965. fogFar: {
  1966. type: 'f',
  1967. value: 200
  1968. },
  1969. fogDensity: {
  1970. type: 'f',
  1971. value: 0.5
  1972. },
  1973. deltaTime: {
  1974. type: 'f',
  1975. value: 0
  1976. },
  1977. runTime: {
  1978. type: 'f',
  1979. value: 0
  1980. },
  1981. scale: {
  1982. type: 'f',
  1983. value: this.scale
  1984. }
  1985. };
  1986. // Add some defines into the mix...
  1987. this.defines = {
  1988. HAS_PERSPECTIVE: this.hasPerspective,
  1989. COLORIZE: this.colorize,
  1990. VALUE_OVER_LIFETIME_LENGTH: SPE.valueOverLifetimeLength,
  1991. SHOULD_ROTATE_TEXTURE: false,
  1992. SHOULD_ROTATE_PARTICLES: false,
  1993. SHOULD_WIGGLE_PARTICLES: false,
  1994. SHOULD_CALCULATE_SPRITE: this.textureFrames.x > 1 || this.textureFrames.y > 1
  1995. };
  1996. // Map of all attributes to be applied to the particles.
  1997. //
  1998. // See SPE.ShaderAttribute for a bit more info on this bit.
  1999. this.attributes = {
  2000. position: new SPE.ShaderAttribute( 'v3', true ),
  2001. acceleration: new SPE.ShaderAttribute( 'v4', true ), // w component is drag
  2002. velocity: new SPE.ShaderAttribute( 'v3', true ),
  2003. rotation: new SPE.ShaderAttribute( 'v4', true ),
  2004. rotationCenter: new SPE.ShaderAttribute( 'v3', true ),
  2005. params: new SPE.ShaderAttribute( 'v4', true ), // Holds (alive, age, delay, wiggle)
  2006. size: new SPE.ShaderAttribute( 'v4', true ),
  2007. angle: new SPE.ShaderAttribute( 'v4', true ),
  2008. color: new SPE.ShaderAttribute( 'v4', true ),
  2009. opacity: new SPE.ShaderAttribute( 'v4', true )
  2010. };
  2011. this.attributeKeys = Object.keys( this.attributes );
  2012. this.attributeCount = this.attributeKeys.length;
  2013. // Create the ShaderMaterial instance that'll help render the
  2014. // particles.
  2015. this.material = new THREE.ShaderMaterial( {
  2016. uniforms: this.uniforms,
  2017. vertexShader: SPE.shaders.vertex,
  2018. fragmentShader: SPE.shaders.fragment,
  2019. blending: this.blending,
  2020. transparent: this.transparent,
  2021. alphaTest: this.alphaTest,
  2022. depthWrite: this.depthWrite,
  2023. depthTest: this.depthTest,
  2024. defines: this.defines,
  2025. fog: this.fog
  2026. } );
  2027. // Create the BufferGeometry and Points instances, ensuring
  2028. // the geometry and material are given to the latter.
  2029. this.geometry = new THREE.BufferGeometry();
  2030. this.mesh = new THREE.Points( this.geometry, this.material );
  2031. if ( this.maxParticleCount === null ) {
  2032. console.warn( 'SPE.Group: No maxParticleCount specified. Adding emitters after rendering will probably cause errors.' );
  2033. }
  2034. };
  2035. SPE.Group.constructor = SPE.Group;
  2036. SPE.Group.prototype._updateDefines = function() {
  2037. 'use strict';
  2038. var emitters = this.emitters,
  2039. i = emitters.length - 1,
  2040. emitter,
  2041. defines = this.defines;
  2042. for ( i; i >= 0; --i ) {
  2043. emitter = emitters[ i ];
  2044. // Only do angle calculation if there's no spritesheet defined.
  2045. //
  2046. // Saves calculations being done and then overwritten in the shaders.
  2047. if ( !defines.SHOULD_CALCULATE_SPRITE ) {
  2048. defines.SHOULD_ROTATE_TEXTURE = defines.SHOULD_ROTATE_TEXTURE || !!Math.max(
  2049. Math.max.apply( null, emitter.angle.value ),
  2050. Math.max.apply( null, emitter.angle.spread )
  2051. );
  2052. }
  2053. defines.SHOULD_ROTATE_PARTICLES = defines.SHOULD_ROTATE_PARTICLES || !!Math.max(
  2054. emitter.rotation.angle,
  2055. emitter.rotation.angleSpread
  2056. );
  2057. defines.SHOULD_WIGGLE_PARTICLES = defines.SHOULD_WIGGLE_PARTICLES || !!Math.max(
  2058. emitter.wiggle.value,
  2059. emitter.wiggle.spread
  2060. );
  2061. }
  2062. this.material.needsUpdate = true;
  2063. };
  2064. SPE.Group.prototype._applyAttributesToGeometry = function() {
  2065. 'use strict';
  2066. var attributes = this.attributes,
  2067. geometry = this.geometry,
  2068. geometryAttributes = geometry.attributes,
  2069. attribute,
  2070. geometryAttribute;
  2071. // Loop through all the shader attributes and assign (or re-assign)
  2072. // typed array buffers to each one.
  2073. for ( var attr in attributes ) {
  2074. if ( attributes.hasOwnProperty( attr ) ) {
  2075. attribute = attributes[ attr ];
  2076. geometryAttribute = geometryAttributes[ attr ];
  2077. // Update the array if this attribute exists on the geometry.
  2078. //
  2079. // This needs to be done because the attribute's typed array might have
  2080. // been resized and reinstantiated, and might now be looking at a
  2081. // different ArrayBuffer, so reference needs updating.
  2082. if ( geometryAttribute ) {
  2083. geometryAttribute.array = attribute.typedArray.array;
  2084. }
  2085. // // Add the attribute to the geometry if it doesn't already exist.
  2086. else {
  2087. geometry.setAttribute( attr, attribute.bufferAttribute );
  2088. }
  2089. // Mark the attribute as needing an update the next time a frame is rendered.
  2090. attribute.bufferAttribute.needsUpdate = true;
  2091. }
  2092. }
  2093. // Mark the draw range on the geometry. This will ensure
  2094. // only the values in the attribute buffers that are
  2095. // associated with a particle will be used in THREE's
  2096. // render cycle.
  2097. this.geometry.setDrawRange( 0, this.particleCount );
  2098. };
  2099. /**
  2100. * Adds an SPE.Emitter instance to this group, creating particle values and
  2101. * assigning them to this group's shader attributes.
  2102. *
  2103. * @param {Emitter} emitter The emitter to add to this group.
  2104. */
  2105. SPE.Group.prototype.addEmitter = function( emitter ) {
  2106. 'use strict';
  2107. // Ensure an actual emitter instance is passed here.
  2108. //
  2109. // Decided not to throw here, just in case a scene's
  2110. // rendering would be paused. Logging an error instead
  2111. // of stopping execution if exceptions aren't caught.
  2112. if ( emitter instanceof SPE.Emitter === false ) {
  2113. console.error( '`emitter` argument must be instance of SPE.Emitter. Was provided with:', emitter );
  2114. return;
  2115. }
  2116. // If the emitter already exists as a member of this group, then
  2117. // stop here, we don't want to add it again.
  2118. else if ( this.emitterIDs.indexOf( emitter.uuid ) > -1 ) {
  2119. console.error( 'Emitter already exists in this group. Will not add again.' );
  2120. return;
  2121. }
  2122. // And finally, if the emitter is a member of another group,
  2123. // don't add it to this group.
  2124. else if ( emitter.group !== null ) {
  2125. console.error( 'Emitter already belongs to another group. Will not add to requested group.' );
  2126. return;
  2127. }
  2128. var attributes = this.attributes,
  2129. start = this.particleCount,
  2130. end = start + emitter.particleCount;
  2131. // Update this group's particle count.
  2132. this.particleCount = end;
  2133. // Emit a warning if the emitter being added will exceed the buffer sizes specified.
  2134. if ( this.maxParticleCount !== null && this.particleCount > this.maxParticleCount ) {
  2135. console.warn( 'SPE.Group: maxParticleCount exceeded. Requesting', this.particleCount, 'particles, can support only', this.maxParticleCount );
  2136. }
  2137. // Set the `particlesPerSecond` value (PPS) on the emitter.
  2138. // It's used to determine how many particles to release
  2139. // on a per-frame basis.
  2140. emitter._calculatePPSValue( emitter.maxAge._value + emitter.maxAge._spread );
  2141. emitter._setBufferUpdateRanges( this.attributeKeys );
  2142. // Store the offset value in the TypedArray attributes for this emitter.
  2143. emitter._setAttributeOffset( start );
  2144. // Save a reference to this group on the emitter so it knows
  2145. // where it belongs.
  2146. emitter.group = this;
  2147. // Store reference to the attributes on the emitter for
  2148. // easier access during the emitter's tick function.
  2149. emitter.attributes = this.attributes;
  2150. // Ensure the attributes and their BufferAttributes exist, and their
  2151. // TypedArrays are of the correct size.
  2152. for ( var attr in attributes ) {
  2153. if ( attributes.hasOwnProperty( attr ) ) {
  2154. // When creating a buffer, pass through the maxParticle count
  2155. // if one is specified.
  2156. attributes[ attr ]._createBufferAttribute(
  2157. this.maxParticleCount !== null ?
  2158. this.maxParticleCount :
  2159. this.particleCount
  2160. );
  2161. }
  2162. }
  2163. // Loop through each particle this emitter wants to have, and create the attributes values,
  2164. // storing them in the TypedArrays that each attribute holds.
  2165. for ( var i = start; i < end; ++i ) {
  2166. emitter._assignPositionValue( i );
  2167. emitter._assignForceValue( i, 'velocity' );
  2168. emitter._assignForceValue( i, 'acceleration' );
  2169. emitter._assignAbsLifetimeValue( i, 'opacity' );
  2170. emitter._assignAbsLifetimeValue( i, 'size' );
  2171. emitter._assignAngleValue( i );
  2172. emitter._assignRotationValue( i );
  2173. emitter._assignParamsValue( i );
  2174. emitter._assignColorValue( i );
  2175. }
  2176. // Update the geometry and make sure the attributes are referencing
  2177. // the typed arrays properly.
  2178. this._applyAttributesToGeometry();
  2179. // Store this emitter in this group's emitter's store.
  2180. this.emitters.push( emitter );
  2181. this.emitterIDs.push( emitter.uuid );
  2182. // Update certain flags to enable shader calculations only if they're necessary.
  2183. this._updateDefines( emitter );
  2184. // Update the material since defines might have changed
  2185. this.material.needsUpdate = true;
  2186. this.geometry.needsUpdate = true;
  2187. this._attributesNeedRefresh = true;
  2188. // Return the group to enable chaining.
  2189. return this;
  2190. };
  2191. /**
  2192. * Removes an SPE.Emitter instance from this group. When called,
  2193. * all particle's belonging to the given emitter will be instantly
  2194. * removed from the scene.
  2195. *
  2196. * @param {Emitter} emitter The emitter to add to this group.
  2197. */
  2198. SPE.Group.prototype.removeEmitter = function( emitter ) {
  2199. 'use strict';
  2200. var emitterIndex = this.emitterIDs.indexOf( emitter.uuid );
  2201. // Ensure an actual emitter instance is passed here.
  2202. //
  2203. // Decided not to throw here, just in case a scene's
  2204. // rendering would be paused. Logging an error instead
  2205. // of stopping execution if exceptions aren't caught.
  2206. if ( emitter instanceof SPE.Emitter === false ) {
  2207. console.error( '`emitter` argument must be instance of SPE.Emitter. Was provided with:', emitter );
  2208. return;
  2209. }
  2210. // Issue an error if the emitter isn't a member of this group.
  2211. else if ( emitterIndex === -1 ) {
  2212. console.error( 'Emitter does not exist in this group. Will not remove.' );
  2213. return;
  2214. }
  2215. // Kill all particles by marking them as dead
  2216. // and their age as 0.
  2217. var start = emitter.attributeOffset,
  2218. end = start + emitter.particleCount,
  2219. params = this.attributes.params.typedArray;
  2220. // Set alive and age to zero.
  2221. for ( var i = start; i < end; ++i ) {
  2222. params.array[ i * 4 ] = 0.0;
  2223. params.array[ i * 4 + 1 ] = 0.0;
  2224. }
  2225. // Remove the emitter from this group's "store".
  2226. this.emitters.splice( emitterIndex, 1 );
  2227. this.emitterIDs.splice( emitterIndex, 1 );
  2228. // Remove this emitter's attribute values from all shader attributes.
  2229. // The `.splice()` call here also marks each attribute's buffer
  2230. // as needing to update it's entire contents.
  2231. for ( var attr in this.attributes ) {
  2232. if ( this.attributes.hasOwnProperty( attr ) ) {
  2233. this.attributes[ attr ].splice( start, end );
  2234. }
  2235. }
  2236. // Ensure this group's particle count is correct.
  2237. this.particleCount -= emitter.particleCount;
  2238. // Call the emitter's remove method.
  2239. emitter._onRemove();
  2240. // Set a flag to indicate that the attribute buffers should
  2241. // be updated in their entirety on the next frame.
  2242. this._attributesNeedRefresh = true;
  2243. };
  2244. /**
  2245. * Fetch a single emitter instance from the pool.
  2246. * If there are no objects in the pool, a new emitter will be
  2247. * created if specified.
  2248. *
  2249. * @return {Emitter|null}
  2250. */
  2251. SPE.Group.prototype.getFromPool = function() {
  2252. 'use strict';
  2253. var pool = this._pool,
  2254. createNew = this._createNewWhenPoolEmpty;
  2255. if ( pool.length ) {
  2256. return pool.pop();
  2257. }
  2258. else if ( createNew ) {
  2259. var emitter = new SPE.Emitter( this._poolCreationSettings );
  2260. this.addEmitter( emitter );
  2261. return emitter;
  2262. }
  2263. return null;
  2264. };
  2265. /**
  2266. * Release an emitter into the pool.
  2267. *
  2268. * @param {ShaderParticleEmitter} emitter
  2269. * @return {Group} This group instance.
  2270. */
  2271. SPE.Group.prototype.releaseIntoPool = function( emitter ) {
  2272. 'use strict';
  2273. if ( emitter instanceof SPE.Emitter === false ) {
  2274. console.error( 'Argument is not instanceof SPE.Emitter:', emitter );
  2275. return;
  2276. }
  2277. emitter.reset();
  2278. this._pool.unshift( emitter );
  2279. return this;
  2280. };
  2281. /**
  2282. * Get the pool array
  2283. *
  2284. * @return {Array}
  2285. */
  2286. SPE.Group.prototype.getPool = function() {
  2287. 'use strict';
  2288. return this._pool;
  2289. };
  2290. /**
  2291. * Add a pool of emitters to this particle group
  2292. *
  2293. * @param {Number} numEmitters The number of emitters to add to the pool.
  2294. * @param {EmitterOptions|Array} emitterOptions An object, or array of objects, describing the options to pass to each emitter.
  2295. * @param {Boolean} createNew Should a new emitter be created if the pool runs out?
  2296. * @return {Group} This group instance.
  2297. */
  2298. SPE.Group.prototype.addPool = function( numEmitters, emitterOptions, createNew ) {
  2299. 'use strict';
  2300. var emitter;
  2301. // Save relevant settings and flags.
  2302. this._poolCreationSettings = emitterOptions;
  2303. this._createNewWhenPoolEmpty = !!createNew;
  2304. // Create the emitters, add them to this group and the pool.
  2305. for ( var i = 0; i < numEmitters; ++i ) {
  2306. if ( Array.isArray( emitterOptions ) ) {
  2307. emitter = new SPE.Emitter( emitterOptions[ i ] );
  2308. }
  2309. else {
  2310. emitter = new SPE.Emitter( emitterOptions );
  2311. }
  2312. this.addEmitter( emitter );
  2313. this.releaseIntoPool( emitter );
  2314. }
  2315. return this;
  2316. };
  2317. SPE.Group.prototype._triggerSingleEmitter = function( pos ) {
  2318. 'use strict';
  2319. var emitter = this.getFromPool(),
  2320. self = this;
  2321. if ( emitter === null ) {
  2322. console.log( 'SPE.Group pool ran out.' );
  2323. return;
  2324. }
  2325. // TODO:
  2326. // - Make sure buffers are update with thus new position.
  2327. if ( pos instanceof THREE.Vector3 ) {
  2328. emitter.position.value.copy( pos );
  2329. // Trigger the setter for this property to force an
  2330. // update to the emitter's position attribute.
  2331. emitter.position.value = emitter.position.value;
  2332. }
  2333. emitter.enable();
  2334. setTimeout( function() {
  2335. emitter.disable();
  2336. self.releaseIntoPool( emitter );
  2337. }, ( Math.max( emitter.duration, ( emitter.maxAge.value + emitter.maxAge.spread ) ) ) * 1000 );
  2338. return this;
  2339. };
  2340. /**
  2341. * Set a given number of emitters as alive, with an optional position
  2342. * vector3 to move them to.
  2343. *
  2344. * @param {Number} numEmitters The number of emitters to activate
  2345. * @param {Object} [position=undefined] A THREE.Vector3 instance describing the position to activate the emitter(s) at.
  2346. * @return {Group} This group instance.
  2347. */
  2348. SPE.Group.prototype.triggerPoolEmitter = function( numEmitters, position ) {
  2349. 'use strict';
  2350. if ( typeof numEmitters === 'number' && numEmitters > 1 ) {
  2351. for ( var i = 0; i < numEmitters; ++i ) {
  2352. this._triggerSingleEmitter( position );
  2353. }
  2354. }
  2355. else {
  2356. this._triggerSingleEmitter( position );
  2357. }
  2358. return this;
  2359. };
  2360. SPE.Group.prototype._updateUniforms = function( dt ) {
  2361. 'use strict';
  2362. this.uniforms.runTime.value += dt;
  2363. this.uniforms.deltaTime.value = dt;
  2364. };
  2365. SPE.Group.prototype._resetBufferRanges = function() {
  2366. 'use strict';
  2367. var keys = this.attributeKeys,
  2368. i = this.attributeCount - 1,
  2369. attrs = this.attributes;
  2370. for ( i; i >= 0; --i ) {
  2371. attrs[ keys[ i ] ].resetUpdateRange();
  2372. }
  2373. };
  2374. SPE.Group.prototype._updateBuffers = function( emitter ) {
  2375. 'use strict';
  2376. var keys = this.attributeKeys,
  2377. i = this.attributeCount - 1,
  2378. attrs = this.attributes,
  2379. emitterRanges = emitter.bufferUpdateRanges,
  2380. key,
  2381. emitterAttr,
  2382. attr;
  2383. for ( i; i >= 0; --i ) {
  2384. key = keys[ i ];
  2385. emitterAttr = emitterRanges[ key ];
  2386. attr = attrs[ key ];
  2387. attr.setUpdateRange( emitterAttr.min, emitterAttr.max );
  2388. attr.flagUpdate();
  2389. }
  2390. };
  2391. /**
  2392. * Simulate all the emitter's belonging to this group, updating
  2393. * attribute values along the way.
  2394. * @param {Number} [dt=Group's `fixedTimeStep` value] The number of seconds to simulate the group's emitters for (deltaTime)
  2395. */
  2396. SPE.Group.prototype.tick = function( dt ) {
  2397. 'use strict';
  2398. var emitters = this.emitters,
  2399. numEmitters = emitters.length,
  2400. deltaTime = dt || this.fixedTimeStep,
  2401. keys = this.attributeKeys,
  2402. i,
  2403. attrs = this.attributes;
  2404. // Update uniform values.
  2405. this._updateUniforms( deltaTime );
  2406. // Reset buffer update ranges on the shader attributes.
  2407. this._resetBufferRanges();
  2408. // If nothing needs updating, then stop here.
  2409. if (
  2410. numEmitters === 0 &&
  2411. this._attributesNeedRefresh === false &&
  2412. this._attributesNeedDynamicReset === false
  2413. ) {
  2414. return;
  2415. }
  2416. // Loop through each emitter in this group and
  2417. // simulate it, then update the shader attribute
  2418. // buffers.
  2419. for ( var i = 0, emitter; i < numEmitters; ++i ) {
  2420. emitter = emitters[ i ];
  2421. emitter.tick( deltaTime );
  2422. this._updateBuffers( emitter );
  2423. }
  2424. // If the shader attributes have been refreshed,
  2425. // then the dynamic properties of each buffer
  2426. // attribute will need to be reset back to
  2427. // what they should be.
  2428. if ( this._attributesNeedDynamicReset === true ) {
  2429. i = this.attributeCount - 1;
  2430. for ( i; i >= 0; --i ) {
  2431. attrs[ keys[ i ] ].resetDynamic();
  2432. }
  2433. this._attributesNeedDynamicReset = false;
  2434. }
  2435. // If this group's shader attributes need a full refresh
  2436. // then mark each attribute's buffer attribute as
  2437. // needing so.
  2438. if ( this._attributesNeedRefresh === true ) {
  2439. i = this.attributeCount - 1;
  2440. for ( i; i >= 0; --i ) {
  2441. attrs[ keys[ i ] ].forceUpdateAll();
  2442. }
  2443. this._attributesNeedRefresh = false;
  2444. this._attributesNeedDynamicReset = true;
  2445. }
  2446. };
  2447. /**
  2448. * Dipose the geometry and material for the group.
  2449. *
  2450. * @return {Group} Group instance.
  2451. */
  2452. SPE.Group.prototype.dispose = function() {
  2453. 'use strict';
  2454. this.geometry.dispose();
  2455. this.material.dispose();
  2456. return this;
  2457. };
  2458. /**
  2459. * An SPE.Emitter instance.
  2460. * @typedef {Object} Emitter
  2461. * @see SPE.Emitter
  2462. */
  2463. /**
  2464. * A map of options to configure an SPE.Emitter instance.
  2465. *
  2466. * @typedef {Object} EmitterOptions
  2467. *
  2468. * @property {distribution} [type=BOX] The default distribution this emitter should use to control
  2469. * its particle's spawn position and force behaviour.
  2470. * Must be an SPE.distributions.* value.
  2471. *
  2472. *
  2473. * @property {Number} [particleCount=100] The total number of particles this emitter will hold. NOTE: this is not the number
  2474. * of particles emitted in a second, or anything like that. The number of particles
  2475. * emitted per-second is calculated by particleCount / maxAge (approximately!)
  2476. *
  2477. * @property {Number|null} [duration=null] The duration in seconds that this emitter should live for. If not specified, the emitter
  2478. * will emit particles indefinitely.
  2479. * NOTE: When an emitter is older than a specified duration, the emitter is NOT removed from
  2480. * it's group, but rather is just marked as dead, allowing it to be reanimated at a later time
  2481. * using `SPE.Emitter.prototype.enable()`.
  2482. *
  2483. * @property {Boolean} [isStatic=false] Whether this emitter should be not be simulated (true).
  2484. * @property {Boolean} [activeMultiplier=1] A value between 0 and 1 describing what percentage of this emitter's particlesPerSecond should be
  2485. * emitted, where 0 is 0%, and 1 is 100%.
  2486. * For example, having an emitter with 100 particles, a maxAge of 2, yields a particlesPerSecond
  2487. * value of 50. Setting `activeMultiplier` to 0.5, then, will only emit 25 particles per second (0.5 = 50%).
  2488. * Values greater than 1 will emulate a burst of particles, causing the emitter to run out of particles
  2489. * before it's next activation cycle.
  2490. *
  2491. * @property {Boolean} [direction=1] The direction of the emitter. If value is `1`, emitter will start at beginning of particle's lifecycle.
  2492. * If value is `-1`, emitter will start at end of particle's lifecycle and work it's way backwards.
  2493. *
  2494. * @property {Object} [maxAge={}] An object describing the particle's maximum age in seconds.
  2495. * @property {Number} [maxAge.value=2] A number between 0 and 1 describing the amount of maxAge to apply to all particles.
  2496. * @property {Number} [maxAge.spread=0] A number describing the maxAge variance on a per-particle basis.
  2497. *
  2498. *
  2499. * @property {Object} [position={}] An object describing this emitter's position.
  2500. * @property {Object} [position.value=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's base position.
  2501. * @property {Object} [position.spread=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's position variance on a per-particle basis.
  2502. * Note that when using a SPHERE or DISC distribution, only the x-component
  2503. * of this vector is used.
  2504. * When using a LINE distribution, this value is the endpoint of the LINE.
  2505. * @property {Object} [position.spreadClamp=new THREE.Vector3()] A THREE.Vector3 instance describing the numeric multiples the particle's should
  2506. * be spread out over.
  2507. * Note that when using a SPHERE or DISC distribution, only the x-component
  2508. * of this vector is used.
  2509. * When using a LINE distribution, this property is ignored.
  2510. * @property {Number} [position.radius=10] This emitter's base radius.
  2511. * @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.
  2512. * @property {distribution} [position.distribution=value of the `type` option.] A specific distribution to use when radiusing particles. Overrides the `type` option.
  2513. * @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.
  2514. *
  2515. *
  2516. * @property {Object} [velocity={}] An object describing this particle velocity.
  2517. * @property {Object} [velocity.value=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's base velocity.
  2518. * @property {Object} [velocity.spread=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's velocity variance on a per-particle basis.
  2519. * Note that when using a SPHERE or DISC distribution, only the x-component
  2520. * of this vector is used.
  2521. * @property {distribution} [velocity.distribution=value of the `type` option.] A specific distribution to use when calculating a particle's velocity. Overrides the `type` option.
  2522. * @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.
  2523. *
  2524. *
  2525. * @property {Object} [acceleration={}] An object describing this particle's acceleration.
  2526. * @property {Object} [acceleration.value=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's base acceleration.
  2527. * @property {Object} [acceleration.spread=new THREE.Vector3()] A THREE.Vector3 instance describing this emitter's acceleration variance on a per-particle basis.
  2528. * Note that when using a SPHERE or DISC distribution, only the x-component
  2529. * of this vector is used.
  2530. * @property {distribution} [acceleration.distribution=value of the `type` option.] A specific distribution to use when calculating a particle's acceleration. Overrides the `type` option.
  2531. * @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.
  2532. *
  2533. *
  2534. * @property {Object} [drag={}] An object describing this particle drag. Drag is applied to both velocity and acceleration values.
  2535. * @property {Number} [drag.value=0] A number between 0 and 1 describing the amount of drag to apply to all particles.
  2536. * @property {Number} [drag.spread=0] A number describing the drag variance on a per-particle basis.
  2537. * @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.
  2538. *
  2539. *
  2540. * @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,
  2541. * or shimmy, or waggle, or... Well you get the idea. The wiggle is calculated over-time, meaning that a particle will
  2542. * start off with no wiggle, and end up wiggling about with the distance of the `value` specified by the time it dies.
  2543. * It's quite handy to simulate fire embers, or similar effects where the particle's position should slightly change over
  2544. * 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.
  2545. * @property {Number} [wiggle.value=0] A number describing the amount of wiggle to apply to all particles. It's measured in distance.
  2546. * @property {Number} [wiggle.spread=0] A number describing the wiggle variance on a per-particle basis.
  2547. *
  2548. *
  2549. * @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`
  2550. * over a particle's lifetime. Rotation values affect both a particle's position and the forces applied to it.
  2551. * @property {Object} [rotation.axis=new THREE.Vector3(0, 1, 0)] A THREE.Vector3 instance describing this emitter's axis of rotation.
  2552. * @property {Object} [rotation.axisSpread=new THREE.Vector3()] A THREE.Vector3 instance describing the amount of variance to apply to the axis of rotation on
  2553. * a per-particle basis.
  2554. * @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.
  2555. * Otherwise, the particles will rotate from 0radians to this value over their lifetimes.
  2556. * @property {Number} [rotation.angleSpread=0] The amount of variance in each particle's rotation angle.
  2557. * @property {Boolean} [rotation.static=false] Whether the rotation should be static or not.
  2558. * @property {Object} [rotation.center=The value of `position.value`] A THREE.Vector3 instance describing the center point of rotation.
  2559. * @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.
  2560. *
  2561. *
  2562. * @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
  2563. * given to describe specific value changes over a particle's lifetime.
  2564. * Depending on the value of SPE.valueOverLifetimeLength, if arrays of THREE.Color instances are given, then the array will be interpolated to
  2565. * have a length matching the value of SPE.valueOverLifetimeLength.
  2566. * @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.
  2567. * @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.
  2568. * @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.
  2569. *
  2570. *
  2571. * @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
  2572. * given to describe specific value changes over a particle's lifetime.
  2573. * Depending on the value of SPE.valueOverLifetimeLength, if arrays of numbers are given, then the array will be interpolated to
  2574. * have a length matching the value of SPE.valueOverLifetimeLength.
  2575. * @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.
  2576. * @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.
  2577. * @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.
  2578. *
  2579. *
  2580. * @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
  2581. * given to describe specific value changes over a particle's lifetime.
  2582. * Depending on the value of SPE.valueOverLifetimeLength, if arrays of numbers are given, then the array will be interpolated to
  2583. * have a length matching the value of SPE.valueOverLifetimeLength.
  2584. * @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.
  2585. * @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.
  2586. * @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.
  2587. *
  2588. *
  2589. * @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.
  2590. * NOTE: if a particle's texture is a sprite-sheet, this value IS IGNORED.
  2591. * This property is a "value-over-lifetime" property, meaning an array of values and spreads can be
  2592. * given to describe specific value changes over a particle's lifetime.
  2593. * Depending on the value of SPE.valueOverLifetimeLength, if arrays of numbers are given, then the array will be interpolated to
  2594. * have a length matching the value of SPE.valueOverLifetimeLength.
  2595. * @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.
  2596. * @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.
  2597. * @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.
  2598. *
  2599. */
  2600. /**
  2601. * The SPE.Emitter class.
  2602. *
  2603. * @constructor
  2604. *
  2605. * @param {EmitterOptions} options A map of options to configure the emitter.
  2606. */
  2607. SPE.Emitter = function( options ) {
  2608. 'use strict';
  2609. var utils = SPE.utils,
  2610. types = utils.types,
  2611. lifetimeLength = SPE.valueOverLifetimeLength;
  2612. // Ensure we have a map of options to play with,
  2613. // and that each option is in the correct format.
  2614. options = utils.ensureTypedArg( options, types.OBJECT, {} );
  2615. options.position = utils.ensureTypedArg( options.position, types.OBJECT, {} );
  2616. options.velocity = utils.ensureTypedArg( options.velocity, types.OBJECT, {} );
  2617. options.acceleration = utils.ensureTypedArg( options.acceleration, types.OBJECT, {} );
  2618. options.radius = utils.ensureTypedArg( options.radius, types.OBJECT, {} );
  2619. options.drag = utils.ensureTypedArg( options.drag, types.OBJECT, {} );
  2620. options.rotation = utils.ensureTypedArg( options.rotation, types.OBJECT, {} );
  2621. options.color = utils.ensureTypedArg( options.color, types.OBJECT, {} );
  2622. options.opacity = utils.ensureTypedArg( options.opacity, types.OBJECT, {} );
  2623. options.size = utils.ensureTypedArg( options.size, types.OBJECT, {} );
  2624. options.angle = utils.ensureTypedArg( options.angle, types.OBJECT, {} );
  2625. options.wiggle = utils.ensureTypedArg( options.wiggle, types.OBJECT, {} );
  2626. options.maxAge = utils.ensureTypedArg( options.maxAge, types.OBJECT, {} );
  2627. if ( options.onParticleSpawn ) {
  2628. console.warn( 'onParticleSpawn has been removed. Please set properties directly to alter values at runtime.' );
  2629. }
  2630. this.uuid = THREE.MathUtils.generateUUID();
  2631. this.type = utils.ensureTypedArg( options.type, types.NUMBER, SPE.distributions.BOX );
  2632. // Start assigning properties...kicking it off with props that DON'T support values over
  2633. // lifetimes.
  2634. //
  2635. // Btw, values over lifetimes are just the new way of referring to *Start, *Middle, and *End.
  2636. this.position = {
  2637. _value: utils.ensureInstanceOf( options.position.value, THREE.Vector3, new THREE.Vector3() ),
  2638. _spread: utils.ensureInstanceOf( options.position.spread, THREE.Vector3, new THREE.Vector3() ),
  2639. _spreadClamp: utils.ensureInstanceOf( options.position.spreadClamp, THREE.Vector3, new THREE.Vector3() ),
  2640. _distribution: utils.ensureTypedArg( options.position.distribution, types.NUMBER, this.type ),
  2641. _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false ),
  2642. _radius: utils.ensureTypedArg( options.position.radius, types.NUMBER, 10 ),
  2643. _radiusScale: utils.ensureInstanceOf( options.position.radiusScale, THREE.Vector3, new THREE.Vector3( 1, 1, 1 ) ),
  2644. _distributionClamp: utils.ensureTypedArg( options.position.distributionClamp, types.NUMBER, 0 ),
  2645. };
  2646. this.velocity = {
  2647. _value: utils.ensureInstanceOf( options.velocity.value, THREE.Vector3, new THREE.Vector3() ),
  2648. _spread: utils.ensureInstanceOf( options.velocity.spread, THREE.Vector3, new THREE.Vector3() ),
  2649. _distribution: utils.ensureTypedArg( options.velocity.distribution, types.NUMBER, this.type ),
  2650. _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )
  2651. };
  2652. this.acceleration = {
  2653. _value: utils.ensureInstanceOf( options.acceleration.value, THREE.Vector3, new THREE.Vector3() ),
  2654. _spread: utils.ensureInstanceOf( options.acceleration.spread, THREE.Vector3, new THREE.Vector3() ),
  2655. _distribution: utils.ensureTypedArg( options.acceleration.distribution, types.NUMBER, this.type ),
  2656. _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )
  2657. };
  2658. this.drag = {
  2659. _value: utils.ensureTypedArg( options.drag.value, types.NUMBER, 0 ),
  2660. _spread: utils.ensureTypedArg( options.drag.spread, types.NUMBER, 0 ),
  2661. _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )
  2662. };
  2663. this.wiggle = {
  2664. _value: utils.ensureTypedArg( options.wiggle.value, types.NUMBER, 0 ),
  2665. _spread: utils.ensureTypedArg( options.wiggle.spread, types.NUMBER, 0 )
  2666. };
  2667. this.rotation = {
  2668. _axis: utils.ensureInstanceOf( options.rotation.axis, THREE.Vector3, new THREE.Vector3( 0.0, 1.0, 0.0 ) ),
  2669. _axisSpread: utils.ensureInstanceOf( options.rotation.axisSpread, THREE.Vector3, new THREE.Vector3() ),
  2670. _angle: utils.ensureTypedArg( options.rotation.angle, types.NUMBER, 0 ),
  2671. _angleSpread: utils.ensureTypedArg( options.rotation.angleSpread, types.NUMBER, 0 ),
  2672. _static: utils.ensureTypedArg( options.rotation.static, types.BOOLEAN, false ),
  2673. _center: utils.ensureInstanceOf( options.rotation.center, THREE.Vector3, this.position._value.clone() ),
  2674. _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )
  2675. };
  2676. this.maxAge = {
  2677. _value: utils.ensureTypedArg( options.maxAge.value, types.NUMBER, 2 ),
  2678. _spread: utils.ensureTypedArg( options.maxAge.spread, types.NUMBER, 0 )
  2679. };
  2680. // The following properties can support either single values, or an array of values that change
  2681. // the property over a particle's lifetime (value over lifetime).
  2682. this.color = {
  2683. _value: utils.ensureArrayInstanceOf( options.color.value, THREE.Color, new THREE.Color() ),
  2684. _spread: utils.ensureArrayInstanceOf( options.color.spread, THREE.Vector3, new THREE.Vector3() ),
  2685. _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )
  2686. };
  2687. this.opacity = {
  2688. _value: utils.ensureArrayTypedArg( options.opacity.value, types.NUMBER, 1 ),
  2689. _spread: utils.ensureArrayTypedArg( options.opacity.spread, types.NUMBER, 0 ),
  2690. _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )
  2691. };
  2692. this.size = {
  2693. _value: utils.ensureArrayTypedArg( options.size.value, types.NUMBER, 1 ),
  2694. _spread: utils.ensureArrayTypedArg( options.size.spread, types.NUMBER, 0 ),
  2695. _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )
  2696. };
  2697. this.angle = {
  2698. _value: utils.ensureArrayTypedArg( options.angle.value, types.NUMBER, 0 ),
  2699. _spread: utils.ensureArrayTypedArg( options.angle.spread, types.NUMBER, 0 ),
  2700. _randomise: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false )
  2701. };
  2702. // Assign renaining option values.
  2703. this.particleCount = utils.ensureTypedArg( options.particleCount, types.NUMBER, 100 );
  2704. this.duration = utils.ensureTypedArg( options.duration, types.NUMBER, null );
  2705. this.isStatic = utils.ensureTypedArg( options.isStatic, types.BOOLEAN, false );
  2706. this.activeMultiplier = utils.ensureTypedArg( options.activeMultiplier, types.NUMBER, 1 );
  2707. this.direction = utils.ensureTypedArg( options.direction, types.NUMBER, 1 );
  2708. // Whether this emitter is alive or not.
  2709. this.alive = utils.ensureTypedArg( options.alive, types.BOOLEAN, true );
  2710. // The following properties are set internally and are not
  2711. // user-controllable.
  2712. this.particlesPerSecond = 0;
  2713. // The current particle index for which particles should
  2714. // be marked as active on the next update cycle.
  2715. this.activationIndex = 0;
  2716. // The offset in the typed arrays this emitter's
  2717. // particle's values will start at
  2718. this.attributeOffset = 0;
  2719. // The end of the range in the attribute buffers
  2720. this.attributeEnd = 0;
  2721. // Holds the time the emitter has been alive for.
  2722. this.age = 0.0;
  2723. // Holds the number of currently-alive particles
  2724. this.activeParticleCount = 0.0;
  2725. // Holds a reference to this emitter's group once
  2726. // it's added to one.
  2727. this.group = null;
  2728. // Holds a reference to this emitter's group's attributes object
  2729. // for easier access.
  2730. this.attributes = null;
  2731. // Holds a reference to the params attribute's typed array
  2732. // for quicker access.
  2733. this.paramsArray = null;
  2734. // A set of flags to determine whether particular properties
  2735. // should be re-randomised when a particle is reset.
  2736. //
  2737. // If a `randomise` property is given, this is preferred.
  2738. // Otherwise, it looks at whether a spread value has been
  2739. // given.
  2740. //
  2741. // It allows randomization to be turned off as desired. If
  2742. // all randomization is turned off, then I'd expect a performance
  2743. // boost as no attribute buffers (excluding the `params`)
  2744. // would have to be re-passed to the GPU each frame (since nothing
  2745. // except the `params` attribute would have changed).
  2746. this.resetFlags = {
  2747. // params: utils.ensureTypedArg( options.maxAge.randomise, types.BOOLEAN, !!options.maxAge.spread ) ||
  2748. // utils.ensureTypedArg( options.wiggle.randomise, types.BOOLEAN, !!options.wiggle.spread ),
  2749. position: utils.ensureTypedArg( options.position.randomise, types.BOOLEAN, false ) ||
  2750. utils.ensureTypedArg( options.radius.randomise, types.BOOLEAN, false ),
  2751. velocity: utils.ensureTypedArg( options.velocity.randomise, types.BOOLEAN, false ),
  2752. acceleration: utils.ensureTypedArg( options.acceleration.randomise, types.BOOLEAN, false ) ||
  2753. utils.ensureTypedArg( options.drag.randomise, types.BOOLEAN, false ),
  2754. rotation: utils.ensureTypedArg( options.rotation.randomise, types.BOOLEAN, false ),
  2755. rotationCenter: utils.ensureTypedArg( options.rotation.randomise, types.BOOLEAN, false ),
  2756. size: utils.ensureTypedArg( options.size.randomise, types.BOOLEAN, false ),
  2757. color: utils.ensureTypedArg( options.color.randomise, types.BOOLEAN, false ),
  2758. opacity: utils.ensureTypedArg( options.opacity.randomise, types.BOOLEAN, false ),
  2759. angle: utils.ensureTypedArg( options.angle.randomise, types.BOOLEAN, false )
  2760. };
  2761. this.updateFlags = {};
  2762. this.updateCounts = {};
  2763. // A map to indicate which emitter parameters should update
  2764. // which attribute.
  2765. this.updateMap = {
  2766. maxAge: 'params',
  2767. position: 'position',
  2768. velocity: 'velocity',
  2769. acceleration: 'acceleration',
  2770. drag: 'acceleration',
  2771. wiggle: 'params',
  2772. rotation: 'rotation',
  2773. size: 'size',
  2774. color: 'color',
  2775. opacity: 'opacity',
  2776. angle: 'angle'
  2777. };
  2778. for ( var i in this.updateMap ) {
  2779. if ( this.updateMap.hasOwnProperty( i ) ) {
  2780. this.updateCounts[ this.updateMap[ i ] ] = 0.0;
  2781. this.updateFlags[ this.updateMap[ i ] ] = false;
  2782. this._createGetterSetters( this[ i ], i );
  2783. }
  2784. }
  2785. this.bufferUpdateRanges = {};
  2786. this.attributeKeys = null;
  2787. this.attributeCount = 0;
  2788. // Ensure that the value-over-lifetime property objects above
  2789. // have value and spread properties that are of the same length.
  2790. //
  2791. // Also, for now, make sure they have a length of 3 (min/max arguments here).
  2792. utils.ensureValueOverLifetimeCompliance( this.color, lifetimeLength, lifetimeLength );
  2793. utils.ensureValueOverLifetimeCompliance( this.opacity, lifetimeLength, lifetimeLength );
  2794. utils.ensureValueOverLifetimeCompliance( this.size, lifetimeLength, lifetimeLength );
  2795. utils.ensureValueOverLifetimeCompliance( this.angle, lifetimeLength, lifetimeLength );
  2796. };
  2797. SPE.Emitter.constructor = SPE.Emitter;
  2798. SPE.Emitter.prototype._createGetterSetters = function( propObj, propName ) {
  2799. 'use strict';
  2800. var self = this;
  2801. for ( var i in propObj ) {
  2802. if ( propObj.hasOwnProperty( i ) ) {
  2803. var name = i.replace( '_', '' );
  2804. Object.defineProperty( propObj, name, {
  2805. get: ( function( prop ) {
  2806. return function() {
  2807. return this[ prop ];
  2808. };
  2809. }( i ) ),
  2810. set: ( function( prop ) {
  2811. return function( value ) {
  2812. var mapName = self.updateMap[ propName ],
  2813. prevValue = this[ prop ],
  2814. length = SPE.valueOverLifetimeLength;
  2815. if ( prop === '_rotationCenter' ) {
  2816. self.updateFlags.rotationCenter = true;
  2817. self.updateCounts.rotationCenter = 0.0;
  2818. }
  2819. else if ( prop === '_randomise' ) {
  2820. self.resetFlags[ mapName ] = value;
  2821. }
  2822. else {
  2823. self.updateFlags[ mapName ] = true;
  2824. self.updateCounts[ mapName ] = 0.0;
  2825. }
  2826. self.group._updateDefines();
  2827. this[ prop ] = value;
  2828. // If the previous value was an array, then make
  2829. // sure the provided value is interpolated correctly.
  2830. if ( Array.isArray( prevValue ) ) {
  2831. SPE.utils.ensureValueOverLifetimeCompliance( self[ propName ], length, length );
  2832. }
  2833. };
  2834. }( i ) )
  2835. } );
  2836. }
  2837. }
  2838. };
  2839. SPE.Emitter.prototype._setBufferUpdateRanges = function( keys ) {
  2840. 'use strict';
  2841. this.attributeKeys = keys;
  2842. this.attributeCount = keys.length;
  2843. for ( var i = this.attributeCount - 1; i >= 0; --i ) {
  2844. this.bufferUpdateRanges[ keys[ i ] ] = {
  2845. min: Number.POSITIVE_INFINITY,
  2846. max: Number.NEGATIVE_INFINITY
  2847. };
  2848. }
  2849. };
  2850. SPE.Emitter.prototype._calculatePPSValue = function( groupMaxAge ) {
  2851. 'use strict';
  2852. var particleCount = this.particleCount;
  2853. // Calculate the `particlesPerSecond` value for this emitter. It's used
  2854. // when determining which particles should die and which should live to
  2855. // see another day. Or be born, for that matter. The "God" property.
  2856. if ( this.duration ) {
  2857. this.particlesPerSecond = particleCount / ( groupMaxAge < this.duration ? groupMaxAge : this.duration );
  2858. }
  2859. else {
  2860. this.particlesPerSecond = particleCount / groupMaxAge;
  2861. }
  2862. };
  2863. SPE.Emitter.prototype._setAttributeOffset = function( startIndex ) {
  2864. this.attributeOffset = startIndex;
  2865. this.activationIndex = startIndex;
  2866. this.activationEnd = startIndex + this.particleCount;
  2867. };
  2868. SPE.Emitter.prototype._assignValue = function( prop, index ) {
  2869. 'use strict';
  2870. switch ( prop ) {
  2871. case 'position':
  2872. this._assignPositionValue( index );
  2873. break;
  2874. case 'velocity':
  2875. case 'acceleration':
  2876. this._assignForceValue( index, prop );
  2877. break;
  2878. case 'size':
  2879. case 'opacity':
  2880. this._assignAbsLifetimeValue( index, prop );
  2881. break;
  2882. case 'angle':
  2883. this._assignAngleValue( index );
  2884. break;
  2885. case 'params':
  2886. this._assignParamsValue( index );
  2887. break;
  2888. case 'rotation':
  2889. this._assignRotationValue( index );
  2890. break;
  2891. case 'color':
  2892. this._assignColorValue( index );
  2893. break;
  2894. }
  2895. };
  2896. SPE.Emitter.prototype._assignPositionValue = function( index ) {
  2897. 'use strict';
  2898. var distributions = SPE.distributions,
  2899. utils = SPE.utils,
  2900. prop = this.position,
  2901. attr = this.attributes.position,
  2902. value = prop._value,
  2903. spread = prop._spread,
  2904. distribution = prop._distribution;
  2905. switch ( distribution ) {
  2906. case distributions.BOX:
  2907. utils.randomVector3( attr, index, value, spread, prop._spreadClamp );
  2908. break;
  2909. case distributions.SPHERE:
  2910. utils.randomVector3OnSphere( attr, index, value, prop._radius, prop._spread.x, prop._radiusScale, prop._spreadClamp.x, prop._distributionClamp || this.particleCount );
  2911. break;
  2912. case distributions.DISC:
  2913. utils.randomVector3OnDisc( attr, index, value, prop._radius, prop._spread.x, prop._radiusScale, prop._spreadClamp.x );
  2914. break;
  2915. case distributions.LINE:
  2916. utils.randomVector3OnLine( attr, index, value, spread );
  2917. break;
  2918. }
  2919. };
  2920. SPE.Emitter.prototype._assignForceValue = function( index, attrName ) {
  2921. 'use strict';
  2922. var distributions = SPE.distributions,
  2923. utils = SPE.utils,
  2924. prop = this[ attrName ],
  2925. value = prop._value,
  2926. spread = prop._spread,
  2927. distribution = prop._distribution,
  2928. pos,
  2929. positionX,
  2930. positionY,
  2931. positionZ,
  2932. i;
  2933. switch ( distribution ) {
  2934. case distributions.BOX:
  2935. utils.randomVector3( this.attributes[ attrName ], index, value, spread );
  2936. break;
  2937. case distributions.SPHERE:
  2938. pos = this.attributes.position.typedArray.array;
  2939. i = index * 3;
  2940. // Ensure position values aren't zero, otherwise no force will be
  2941. // applied.
  2942. // positionX = utils.zeroToEpsilon( pos[ i ], true );
  2943. // positionY = utils.zeroToEpsilon( pos[ i + 1 ], true );
  2944. // positionZ = utils.zeroToEpsilon( pos[ i + 2 ], true );
  2945. positionX = pos[ i ];
  2946. positionY = pos[ i + 1 ];
  2947. positionZ = pos[ i + 2 ];
  2948. utils.randomDirectionVector3OnSphere(
  2949. this.attributes[ attrName ], index,
  2950. positionX, positionY, positionZ,
  2951. this.position._value,
  2952. prop._value.x,
  2953. prop._spread.x
  2954. );
  2955. break;
  2956. case distributions.DISC:
  2957. pos = this.attributes.position.typedArray.array;
  2958. i = index * 3;
  2959. // Ensure position values aren't zero, otherwise no force will be
  2960. // applied.
  2961. // positionX = utils.zeroToEpsilon( pos[ i ], true );
  2962. // positionY = utils.zeroToEpsilon( pos[ i + 1 ], true );
  2963. // positionZ = utils.zeroToEpsilon( pos[ i + 2 ], true );
  2964. positionX = pos[ i ];
  2965. positionY = pos[ i + 1 ];
  2966. positionZ = pos[ i + 2 ];
  2967. utils.randomDirectionVector3OnDisc(
  2968. this.attributes[ attrName ], index,
  2969. positionX, positionY, positionZ,
  2970. this.position._value,
  2971. prop._value.x,
  2972. prop._spread.x
  2973. );
  2974. break;
  2975. case distributions.LINE:
  2976. utils.randomVector3OnLine( this.attributes[ attrName ], index, value, spread );
  2977. break;
  2978. }
  2979. if ( attrName === 'acceleration' ) {
  2980. var drag = utils.clamp( utils.randomFloat( this.drag._value, this.drag._spread ), 0, 1 );
  2981. this.attributes.acceleration.typedArray.array[ index * 4 + 3 ] = drag;
  2982. }
  2983. };
  2984. SPE.Emitter.prototype._assignAbsLifetimeValue = function( index, propName ) {
  2985. 'use strict';
  2986. var array = this.attributes[ propName ].typedArray,
  2987. prop = this[ propName ],
  2988. utils = SPE.utils,
  2989. value;
  2990. if ( utils.arrayValuesAreEqual( prop._value ) && utils.arrayValuesAreEqual( prop._spread ) ) {
  2991. value = Math.abs( utils.randomFloat( prop._value[ 0 ], prop._spread[ 0 ] ) );
  2992. array.setVec4Components( index, value, value, value, value );
  2993. }
  2994. else {
  2995. array.setVec4Components( index,
  2996. Math.abs( utils.randomFloat( prop._value[ 0 ], prop._spread[ 0 ] ) ),
  2997. Math.abs( utils.randomFloat( prop._value[ 1 ], prop._spread[ 1 ] ) ),
  2998. Math.abs( utils.randomFloat( prop._value[ 2 ], prop._spread[ 2 ] ) ),
  2999. Math.abs( utils.randomFloat( prop._value[ 3 ], prop._spread[ 3 ] ) )
  3000. );
  3001. }
  3002. };
  3003. SPE.Emitter.prototype._assignAngleValue = function( index ) {
  3004. 'use strict';
  3005. var array = this.attributes.angle.typedArray,
  3006. prop = this.angle,
  3007. utils = SPE.utils,
  3008. value;
  3009. if ( utils.arrayValuesAreEqual( prop._value ) && utils.arrayValuesAreEqual( prop._spread ) ) {
  3010. value = utils.randomFloat( prop._value[ 0 ], prop._spread[ 0 ] );
  3011. array.setVec4Components( index, value, value, value, value );
  3012. }
  3013. else {
  3014. array.setVec4Components( index,
  3015. utils.randomFloat( prop._value[ 0 ], prop._spread[ 0 ] ),
  3016. utils.randomFloat( prop._value[ 1 ], prop._spread[ 1 ] ),
  3017. utils.randomFloat( prop._value[ 2 ], prop._spread[ 2 ] ),
  3018. utils.randomFloat( prop._value[ 3 ], prop._spread[ 3 ] )
  3019. );
  3020. }
  3021. };
  3022. SPE.Emitter.prototype._assignParamsValue = function( index ) {
  3023. 'use strict';
  3024. this.attributes.params.typedArray.setVec4Components( index,
  3025. this.isStatic ? 1 : 0,
  3026. 0.0,
  3027. Math.abs( SPE.utils.randomFloat( this.maxAge._value, this.maxAge._spread ) ),
  3028. SPE.utils.randomFloat( this.wiggle._value, this.wiggle._spread )
  3029. );
  3030. };
  3031. SPE.Emitter.prototype._assignRotationValue = function( index ) {
  3032. 'use strict';
  3033. this.attributes.rotation.typedArray.setVec3Components( index,
  3034. SPE.utils.getPackedRotationAxis( this.rotation._axis, this.rotation._axisSpread ),
  3035. SPE.utils.randomFloat( this.rotation._angle, this.rotation._angleSpread ),
  3036. this.rotation._static ? 0 : 1
  3037. );
  3038. this.attributes.rotationCenter.typedArray.setVec3( index, this.rotation._center );
  3039. };
  3040. SPE.Emitter.prototype._assignColorValue = function( index ) {
  3041. 'use strict';
  3042. SPE.utils.randomColorAsHex( this.attributes.color, index, this.color._value, this.color._spread );
  3043. };
  3044. SPE.Emitter.prototype._resetParticle = function( index ) {
  3045. 'use strict';
  3046. var resetFlags = this.resetFlags,
  3047. updateFlags = this.updateFlags,
  3048. updateCounts = this.updateCounts,
  3049. keys = this.attributeKeys,
  3050. key,
  3051. updateFlag;
  3052. for ( var i = this.attributeCount - 1; i >= 0; --i ) {
  3053. key = keys[ i ];
  3054. updateFlag = updateFlags[ key ];
  3055. if ( resetFlags[ key ] === true || updateFlag === true ) {
  3056. this._assignValue( key, index );
  3057. this._updateAttributeUpdateRange( key, index );
  3058. if ( updateFlag === true && updateCounts[ key ] === this.particleCount ) {
  3059. updateFlags[ key ] = false;
  3060. updateCounts[ key ] = 0.0;
  3061. }
  3062. else if ( updateFlag == true ) {
  3063. ++updateCounts[ key ];
  3064. }
  3065. }
  3066. }
  3067. };
  3068. SPE.Emitter.prototype._updateAttributeUpdateRange = function( attr, i ) {
  3069. 'use strict';
  3070. var ranges = this.bufferUpdateRanges[ attr ];
  3071. ranges.min = Math.min( i, ranges.min );
  3072. ranges.max = Math.max( i, ranges.max );
  3073. };
  3074. SPE.Emitter.prototype._resetBufferRanges = function() {
  3075. 'use strict';
  3076. var ranges = this.bufferUpdateRanges,
  3077. keys = this.bufferUpdateKeys,
  3078. i = this.bufferUpdateCount - 1,
  3079. key;
  3080. for ( i; i >= 0; --i ) {
  3081. key = keys[ i ];
  3082. ranges[ key ].min = Number.POSITIVE_INFINITY;
  3083. ranges[ key ].max = Number.NEGATIVE_INFINITY;
  3084. }
  3085. };
  3086. SPE.Emitter.prototype._onRemove = function() {
  3087. 'use strict';
  3088. // Reset any properties of the emitter that were set by
  3089. // a group when it was added.
  3090. this.particlesPerSecond = 0;
  3091. this.attributeOffset = 0;
  3092. this.activationIndex = 0;
  3093. this.activeParticleCount = 0;
  3094. this.group = null;
  3095. this.attributes = null;
  3096. this.paramsArray = null;
  3097. this.age = 0.0;
  3098. };
  3099. SPE.Emitter.prototype._decrementParticleCount = function() {
  3100. 'use strict';
  3101. --this.activeParticleCount;
  3102. // TODO:
  3103. // - Trigger event if count === 0.
  3104. };
  3105. SPE.Emitter.prototype._incrementParticleCount = function() {
  3106. 'use strict';
  3107. ++this.activeParticleCount;
  3108. // TODO:
  3109. // - Trigger event if count === this.particleCount.
  3110. };
  3111. SPE.Emitter.prototype._checkParticleAges = function( start, end, params, dt ) {
  3112. 'use strict';
  3113. for ( var i = end - 1, index, maxAge, age, alive; i >= start; --i ) {
  3114. index = i * 4;
  3115. alive = params[ index ];
  3116. if ( alive === 0.0 ) {
  3117. continue;
  3118. }
  3119. // Increment age
  3120. age = params[ index + 1 ];
  3121. maxAge = params[ index + 2 ];
  3122. if ( this.direction === 1 ) {
  3123. age += dt;
  3124. if ( age >= maxAge ) {
  3125. age = 0.0;
  3126. alive = 0.0;
  3127. this._decrementParticleCount();
  3128. }
  3129. }
  3130. else {
  3131. age -= dt;
  3132. if ( age <= 0.0 ) {
  3133. age = maxAge;
  3134. alive = 0.0;
  3135. this._decrementParticleCount();
  3136. }
  3137. }
  3138. params[ index ] = alive;
  3139. params[ index + 1 ] = age;
  3140. this._updateAttributeUpdateRange( 'params', i );
  3141. }
  3142. };
  3143. SPE.Emitter.prototype._activateParticles = function( activationStart, activationEnd, params, dtPerParticle ) {
  3144. 'use strict';
  3145. var direction = this.direction;
  3146. for ( var i = activationStart, index, dtValue; i < activationEnd; ++i ) {
  3147. index = i * 4;
  3148. // Don't re-activate particles that aren't dead yet.
  3149. // if ( params[ index ] !== 0.0 && ( this.particleCount !== 1 || this.activeMultiplier !== 1 ) ) {
  3150. // continue;
  3151. // }
  3152. if ( params[ index ] != 0.0 && this.particleCount !== 1 ) {
  3153. continue;
  3154. }
  3155. // Increment the active particle count.
  3156. this._incrementParticleCount();
  3157. // Mark the particle as alive.
  3158. params[ index ] = 1.0;
  3159. // Reset the particle
  3160. this._resetParticle( i );
  3161. // Move each particle being activated to
  3162. // it's actual position in time.
  3163. //
  3164. // This stops particles being 'clumped' together
  3165. // when frame rates are on the lower side of 60fps
  3166. // or not constant (a very real possibility!)
  3167. dtValue = dtPerParticle * ( i - activationStart )
  3168. params[ index + 1 ] = direction === -1 ? params[ index + 2 ] - dtValue : dtValue;
  3169. this._updateAttributeUpdateRange( 'params', i );
  3170. }
  3171. };
  3172. /**
  3173. * Simulates one frame's worth of particles, updating particles
  3174. * that are already alive, and marking ones that are currently dead
  3175. * but should be alive as alive.
  3176. *
  3177. * If the emitter is marked as static, then this function will do nothing.
  3178. *
  3179. * @param {Number} dt The number of seconds to simulate (deltaTime)
  3180. */
  3181. SPE.Emitter.prototype.tick = function( dt ) {
  3182. 'use strict';
  3183. if ( this.isStatic ) {
  3184. return;
  3185. }
  3186. if ( this.paramsArray === null ) {
  3187. this.paramsArray = this.attributes.params.typedArray.array;
  3188. }
  3189. var start = this.attributeOffset,
  3190. end = start + this.particleCount,
  3191. params = this.paramsArray, // vec3( alive, age, maxAge, wiggle )
  3192. ppsDt = this.particlesPerSecond * this.activeMultiplier * dt,
  3193. activationIndex = this.activationIndex;
  3194. // Reset the buffer update indices.
  3195. this._resetBufferRanges();
  3196. // Increment age for those particles that are alive,
  3197. // and kill off any particles whose age is over the limit.
  3198. this._checkParticleAges( start, end, params, dt );
  3199. // If the emitter is dead, reset the age of the emitter to zero,
  3200. // ready to go again if required
  3201. if ( this.alive === false ) {
  3202. this.age = 0.0;
  3203. return;
  3204. }
  3205. // If the emitter has a specified lifetime and we've exceeded it,
  3206. // mark the emitter as dead.
  3207. if ( this.duration !== null && this.age > this.duration ) {
  3208. this.alive = false;
  3209. this.age = 0.0;
  3210. return;
  3211. }
  3212. var activationStart = this.particleCount === 1 ? activationIndex : ( activationIndex | 0 ),
  3213. activationEnd = Math.min( activationStart + ppsDt, this.activationEnd ),
  3214. activationCount = activationEnd - this.activationIndex | 0,
  3215. dtPerParticle = activationCount > 0 ? dt / activationCount : 0;
  3216. this._activateParticles( activationStart, activationEnd, params, dtPerParticle );
  3217. // Move the activation window forward, soldier.
  3218. this.activationIndex += ppsDt;
  3219. if ( this.activationIndex > end ) {
  3220. this.activationIndex = start;
  3221. }
  3222. // Increment the age of the emitter.
  3223. this.age += dt;
  3224. };
  3225. /**
  3226. * Resets all the emitter's particles to their start positions
  3227. * and marks the particles as dead if the `force` argument is
  3228. * true.
  3229. *
  3230. * @param {Boolean} [force=undefined] If true, all particles will be marked as dead instantly.
  3231. * @return {Emitter} This emitter instance.
  3232. */
  3233. SPE.Emitter.prototype.reset = function( force ) {
  3234. 'use strict';
  3235. this.age = 0.0;
  3236. this.alive = false;
  3237. if ( force === true ) {
  3238. var start = this.attributeOffset,
  3239. end = start + this.particleCount,
  3240. array = this.paramsArray,
  3241. attr = this.attributes.params.bufferAttribute;
  3242. for ( var i = end - 1, index; i >= start; --i ) {
  3243. index = i * 4;
  3244. array[ index ] = 0.0;
  3245. array[ index + 1 ] = 0.0;
  3246. }
  3247. attr.updateRange.offset = 0;
  3248. attr.updateRange.count = -1;
  3249. attr.needsUpdate = true;
  3250. }
  3251. return this;
  3252. };
  3253. /**
  3254. * Enables the emitter. If not already enabled, the emitter
  3255. * will start emitting particles.
  3256. *
  3257. * @return {Emitter} This emitter instance.
  3258. */
  3259. SPE.Emitter.prototype.enable = function() {
  3260. 'use strict';
  3261. this.alive = true;
  3262. return this;
  3263. };
  3264. /**
  3265. * Disables th emitter, but does not instantly remove it's
  3266. * particles fromt the scene. When called, the emitter will be
  3267. * 'switched off' and just stop emitting. Any particle's alive will
  3268. * be allowed to finish their lifecycle.
  3269. *
  3270. * @return {Emitter} This emitter instance.
  3271. */
  3272. SPE.Emitter.prototype.disable = function() {
  3273. 'use strict';
  3274. this.alive = false;
  3275. return this;
  3276. };
  3277. /**
  3278. * Remove this emitter from it's parent group (if it has been added to one).
  3279. * Delgates to SPE.group.prototype.removeEmitter().
  3280. *
  3281. * When called, all particle's belonging to this emitter will be instantly
  3282. * removed from the scene.
  3283. *
  3284. * @return {Emitter} This emitter instance.
  3285. *
  3286. * @see SPE.Group.prototype.removeEmitter
  3287. */
  3288. SPE.Emitter.prototype.remove = function() {
  3289. 'use strict';
  3290. if ( this.group !== null ) {
  3291. this.group.removeEmitter( this );
  3292. }
  3293. else {
  3294. console.error( 'Emitter does not belong to a group, cannot remove.' );
  3295. }
  3296. return this;
  3297. };
  3298. /***/ })
  3299. /******/ ]);