ParticleSystem.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785
  1. import Cartesian2 from '../Core/Cartesian2.js';
  2. import Cartesian3 from '../Core/Cartesian3.js';
  3. import Check from '../Core/Check.js';
  4. import Color from '../Core/Color.js';
  5. import defaultValue from '../Core/defaultValue.js';
  6. import defined from '../Core/defined.js';
  7. import defineProperties from '../Core/defineProperties.js';
  8. import destroyObject from '../Core/destroyObject.js';
  9. import Event from '../Core/Event.js';
  10. import JulianDate from '../Core/JulianDate.js';
  11. import CesiumMath from '../Core/Math.js';
  12. import Matrix4 from '../Core/Matrix4.js';
  13. import BillboardCollection from './BillboardCollection.js';
  14. import CircleEmitter from './CircleEmitter.js';
  15. import Particle from './Particle.js';
  16. var defaultImageSize = new Cartesian2(1.0, 1.0);
  17. /**
  18. * A ParticleSystem manages the updating and display of a collection of particles.
  19. *
  20. * @alias ParticleSystem
  21. * @constructor
  22. *
  23. * @param {Object} [options] Object with the following properties:
  24. * @param {Boolean} [options.show=true] Whether to display the particle system.
  25. * @param {ParticleSystem~updateCallback} [options.updateCallback] The callback function to be called each frame to update a particle.
  26. * @param {ParticleEmitter} [options.emitter=new CircleEmitter(0.5)] The particle emitter for this system.
  27. * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms the particle system from model to world coordinates.
  28. * @param {Matrix4} [options.emitterModelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms the particle system emitter within the particle systems local coordinate system.
  29. * @param {Number} [options.emissionRate=5] The number of particles to emit per second.
  30. * @param {ParticleBurst[]} [options.bursts] An array of {@link ParticleBurst}, emitting bursts of particles at periodic times.
  31. * @param {Boolean} [options.loop=true] Whether the particle system should loop its bursts when it is complete.
  32. * @param {Number} [options.scale=1.0] Sets the scale to apply to the image of the particle for the duration of its particleLife.
  33. * @param {Number} [options.startScale] The initial scale to apply to the image of the particle at the beginning of its life.
  34. * @param {Number} [options.endScale] The final scale to apply to the image of the particle at the end of its life.
  35. * @param {Color} [options.color=Color.WHITE] Sets the color of a particle for the duration of its particleLife.
  36. * @param {Color} [options.startColor] The color of the particle at the beginning of its life.
  37. * @param {Color} [options.endColor] The color of the particle at the end of its life.
  38. * @param {Object} [options.image] The URI, HTMLImageElement, or HTMLCanvasElement to use for the billboard.
  39. * @param {Cartesian2} [options.imageSize=new Cartesian2(1.0, 1.0)] If set, overrides the minimumImageSize and maximumImageSize inputs that scale the particle image's dimensions in pixels.
  40. * @param {Cartesian2} [options.minimumImageSize] Sets the minimum bound, width by height, above which to randomly scale the particle image's dimensions in pixels.
  41. * @param {Cartesian2} [options.maximumImageSize] Sets the maximum bound, width by height, below which to randomly scale the particle image's dimensions in pixels.
  42. * @param {Number} [options.speed=1.0] If set, overrides the minimumSpeed and maximumSpeed inputs with this value.
  43. * @param {Number} [options.minimumSpeed] Sets the minimum bound in meters per second above which a particle's actual speed will be randomly chosen.
  44. * @param {Number} [options.maximumSpeed] Sets the maximum bound in meters per second below which a particle's actual speed will be randomly chosen.
  45. * @param {Number} [options.lifetime=Number.MAX_VALUE] How long the particle system will emit particles, in seconds.
  46. * @param {Number} [options.particleLife=5.0] If set, overrides the minimumParticleLife and maximumParticleLife inputs with this value.
  47. * @param {Number} [options.minimumParticleLife] Sets the minimum bound in seconds for the possible duration of a particle's life above which a particle's actual life will be randomly chosen.
  48. * @param {Number} [options.maximumParticleLife] Sets the maximum bound in seconds for the possible duration of a particle's life below which a particle's actual life will be randomly chosen.
  49. * @param {Number} [options.mass=1.0] Sets the minimum and maximum mass of particles in kilograms.
  50. * @param {Number} [options.minimumMass] Sets the minimum bound for the mass of a particle in kilograms. A particle's actual mass will be chosen as a random amount above this value.
  51. * @param {Number} [options.maximumMass] Sets the maximum mass of particles in kilograms. A particle's actual mass will be chosen as a random amount below this value.
  52. * @tutorial {@link https://cesium.com/docs/tutorials/particle-systems/|Particle Systems Tutorial}
  53. * @demo {@link https://sandcastle.cesium.com/?src=Particle%20System.html&label=Showcases|Particle Systems Tutorial Demo}
  54. * @demo {@link https://sandcastle.cesium.com/?src=Particle%20System%20Fireworks.html&label=Showcases|Particle Systems Fireworks Demo}
  55. */
  56. function ParticleSystem(options) {
  57. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  58. /**
  59. * Whether to display the particle system.
  60. * @type {Boolean}
  61. * @default true
  62. */
  63. this.show = defaultValue(options.show, true);
  64. /**
  65. * An array of force callbacks. The callback is passed a {@link Particle} and the difference from the last time
  66. * @type {ParticleSystem~updateCallback}
  67. * @default undefined
  68. */
  69. this.updateCallback = options.updateCallback;
  70. /**
  71. * Whether the particle system should loop it's bursts when it is complete.
  72. * @type {Boolean}
  73. * @default true
  74. */
  75. this.loop = defaultValue(options.loop, true);
  76. /**
  77. * The URI, HTMLImageElement, or HTMLCanvasElement to use for the billboard.
  78. * @type {Object}
  79. * @default undefined
  80. */
  81. this.image = defaultValue(options.image, undefined);
  82. var emitter = options.emitter;
  83. if (!defined(emitter)) {
  84. emitter = new CircleEmitter(0.5);
  85. }
  86. this._emitter = emitter;
  87. this._bursts = options.bursts;
  88. this._modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY));
  89. this._emitterModelMatrix = Matrix4.clone(defaultValue(options.emitterModelMatrix, Matrix4.IDENTITY));
  90. this._matrixDirty = true;
  91. this._combinedMatrix = new Matrix4();
  92. this._startColor = Color.clone(defaultValue(options.color, defaultValue(options.startColor, Color.WHITE)));
  93. this._endColor = Color.clone(defaultValue(options.color, defaultValue(options.endColor, Color.WHITE)));
  94. this._startScale = defaultValue(options.scale, defaultValue(options.startScale, 1.0));
  95. this._endScale = defaultValue(options.scale, defaultValue(options.endScale, 1.0));
  96. this._emissionRate = defaultValue(options.emissionRate, 5.0);
  97. this._minimumSpeed = defaultValue(options.speed, defaultValue(options.minimumSpeed, 1.0));
  98. this._maximumSpeed = defaultValue(options.speed, defaultValue(options.maximumSpeed, 1.0));
  99. this._minimumParticleLife = defaultValue(options.particleLife, defaultValue(options.minimumParticleLife, 5.0));
  100. this._maximumParticleLife = defaultValue(options.particleLife, defaultValue(options.maximumParticleLife, 5.0));
  101. this._minimumMass = defaultValue(options.mass, defaultValue(options.minimumMass, 1.0));
  102. this._maximumMass = defaultValue(options.mass, defaultValue(options.maximumMass, 1.0));
  103. this._minimumImageSize = Cartesian2.clone(defaultValue(options.imageSize, defaultValue(options.minimumImageSize, defaultImageSize)));
  104. this._maximumImageSize = Cartesian2.clone(defaultValue(options.imageSize, defaultValue(options.maximumImageSize, defaultImageSize)));
  105. this._lifetime = defaultValue(options.lifetime, Number.MAX_VALUE);
  106. this._billboardCollection = undefined;
  107. this._particles = [];
  108. // An array of available particles that we can reuse instead of allocating new.
  109. this._particlePool = [];
  110. this._previousTime = undefined;
  111. this._currentTime = 0.0;
  112. this._carryOver = 0.0;
  113. this._complete = new Event();
  114. this._isComplete = false;
  115. this._updateParticlePool = true;
  116. this._particleEstimate = 0;
  117. }
  118. defineProperties(ParticleSystem.prototype, {
  119. /**
  120. * The particle emitter for this
  121. * @memberof ParticleSystem.prototype
  122. * @type {ParticleEmitter}
  123. * @default CircleEmitter
  124. */
  125. emitter : {
  126. get : function() {
  127. return this._emitter;
  128. },
  129. set : function(value) {
  130. //>>includeStart('debug', pragmas.debug);
  131. Check.defined('value', value);
  132. //>>includeEnd('debug');
  133. this._emitter = value;
  134. }
  135. },
  136. /**
  137. * An array of {@link ParticleBurst}, emitting bursts of particles at periodic times.
  138. * @memberof ParticleSystem.prototype
  139. * @type {ParticleBurst[]}
  140. * @default undefined
  141. */
  142. bursts : {
  143. get : function() {
  144. return this._bursts;
  145. },
  146. set : function(value) {
  147. this._bursts = value;
  148. this._updateParticlePool = true;
  149. }
  150. },
  151. /**
  152. * The 4x4 transformation matrix that transforms the particle system from model to world coordinates.
  153. * @memberof ParticleSystem.prototype
  154. * @type {Matrix4}
  155. * @default Matrix4.IDENTITY
  156. */
  157. modelMatrix : {
  158. get : function() {
  159. return this._modelMatrix;
  160. },
  161. set : function(value) {
  162. //>>includeStart('debug', pragmas.debug);
  163. Check.defined('value', value);
  164. //>>includeEnd('debug');
  165. this._matrixDirty = this._matrixDirty || !Matrix4.equals(this._modelMatrix, value);
  166. Matrix4.clone(value, this._modelMatrix);
  167. }
  168. },
  169. /**
  170. * The 4x4 transformation matrix that transforms the particle system emitter within the particle systems local coordinate system.
  171. * @memberof ParticleSystem.prototype
  172. * @type {Matrix4}
  173. * @default Matrix4.IDENTITY
  174. */
  175. emitterModelMatrix : {
  176. get : function() {
  177. return this._emitterModelMatrix;
  178. },
  179. set : function(value) {
  180. //>>includeStart('debug', pragmas.debug);
  181. Check.defined('value', value);
  182. //>>includeEnd('debug');
  183. this._matrixDirty = this._matrixDirty || !Matrix4.equals(this._emitterModelMatrix, value);
  184. Matrix4.clone(value, this._emitterModelMatrix);
  185. }
  186. },
  187. /**
  188. * The color of the particle at the beginning of its life.
  189. * @memberof ParticleSystem.prototype
  190. * @type {Color}
  191. * @default Color.WHITE
  192. */
  193. startColor : {
  194. get : function() {
  195. return this._startColor;
  196. },
  197. set : function(value) {
  198. //>>includeStart('debug', pragmas.debug);
  199. Check.defined('value', value);
  200. //>>includeEnd('debug');
  201. Color.clone(value, this._startColor);
  202. }
  203. },
  204. /**
  205. * The color of the particle at the end of its life.
  206. * @memberof ParticleSystem.prototype
  207. * @type {Color}
  208. * @default Color.WHITE
  209. */
  210. endColor : {
  211. get : function() {
  212. return this._endColor;
  213. },
  214. set : function(value) {
  215. //>>includeStart('debug', pragmas.debug);
  216. Check.defined('value', value);
  217. //>>includeEnd('debug');
  218. Color.clone(value, this._endColor);
  219. }
  220. },
  221. /**
  222. * The initial scale to apply to the image of the particle at the beginning of its life.
  223. * @memberof ParticleSystem.prototype
  224. * @type {Number}
  225. * @default 1.0
  226. */
  227. startScale : {
  228. get : function() {
  229. return this._startScale;
  230. },
  231. set : function(value) {
  232. //>>includeStart('debug', pragmas.debug);
  233. Check.typeOf.number.greaterThanOrEquals('value', value, 0.0);
  234. //>>includeEnd('debug');
  235. this._startScale = value;
  236. }
  237. },
  238. /**
  239. * The final scale to apply to the image of the particle at the end of its life.
  240. * @memberof ParticleSystem.prototype
  241. * @type {Number}
  242. * @default 1.0
  243. */
  244. endScale : {
  245. get : function() {
  246. return this._endScale;
  247. },
  248. set : function(value) {
  249. //>>includeStart('debug', pragmas.debug);
  250. Check.typeOf.number.greaterThanOrEquals('value', value, 0.0);
  251. //>>includeEnd('debug');
  252. this._endScale = value;
  253. }
  254. },
  255. /**
  256. * The number of particles to emit per second.
  257. * @memberof ParticleSystem.prototype
  258. * @type {Number}
  259. * @default 5
  260. */
  261. emissionRate : {
  262. get : function() {
  263. return this._emissionRate;
  264. },
  265. set : function(value) {
  266. //>>includeStart('debug', pragmas.debug);
  267. Check.typeOf.number.greaterThanOrEquals('value', value, 0.0);
  268. //>>includeEnd('debug');
  269. this._emissionRate = value;
  270. this._updateParticlePool = true;
  271. }
  272. },
  273. /**
  274. * Sets the minimum bound in meters per second above which a particle's actual speed will be randomly chosen.
  275. * @memberof ParticleSystem.prototype
  276. * @type {Number}
  277. * @default 1.0
  278. */
  279. minimumSpeed : {
  280. get : function() {
  281. return this._minimumSpeed;
  282. },
  283. set : function(value) {
  284. //>>includeStart('debug', pragmas.debug);
  285. Check.typeOf.number.greaterThanOrEquals('value', value, 0.0);
  286. //>>includeEnd('debug');
  287. this._minimumSpeed = value;
  288. }
  289. },
  290. /**
  291. * Sets the maximum bound in meters per second below which a particle's actual speed will be randomly chosen.
  292. * @memberof ParticleSystem.prototype
  293. * @type {Number}
  294. * @default 1.0
  295. */
  296. maximumSpeed : {
  297. get : function() {
  298. return this._maximumSpeed;
  299. },
  300. set : function(value) {
  301. //>>includeStart('debug', pragmas.debug);
  302. Check.typeOf.number.greaterThanOrEquals('value', value, 0.0);
  303. //>>includeEnd('debug');
  304. this._maximumSpeed = value;
  305. }
  306. },
  307. /**
  308. * Sets the minimum bound in seconds for the possible duration of a particle's life above which a particle's actual life will be randomly chosen.
  309. * @memberof ParticleSystem.prototype
  310. * @type {Number}
  311. * @default 5.0
  312. */
  313. minimumParticleLife : {
  314. get : function() {
  315. return this._minimumParticleLife;
  316. },
  317. set : function(value) {
  318. //>>includeStart('debug', pragmas.debug);
  319. Check.typeOf.number.greaterThanOrEquals('value', value, 0.0);
  320. //>>includeEnd('debug');
  321. this._minimumParticleLife = value;
  322. }
  323. },
  324. /**
  325. * Sets the maximum bound in seconds for the possible duration of a particle's life below which a particle's actual life will be randomly chosen.
  326. * @memberof ParticleSystem.prototype
  327. * @type {Number}
  328. * @default 5.0
  329. */
  330. maximumParticleLife : {
  331. get : function() {
  332. return this._maximumParticleLife;
  333. },
  334. set : function(value) {
  335. //>>includeStart('debug', pragmas.debug);
  336. Check.typeOf.number.greaterThanOrEquals('value', value, 0.0);
  337. //>>includeEnd('debug');
  338. this._maximumParticleLife = value;
  339. this._updateParticlePool = true;
  340. }
  341. },
  342. /**
  343. * Sets the minimum mass of particles in kilograms.
  344. * @memberof ParticleSystem.prototype
  345. * @type {Number}
  346. * @default 1.0
  347. */
  348. minimumMass : {
  349. get : function() {
  350. return this._minimumMass;
  351. },
  352. set : function(value) {
  353. //>>includeStart('debug', pragmas.debug);
  354. Check.typeOf.number.greaterThanOrEquals('value', value, 0.0);
  355. //>>includeEnd('debug');
  356. this._minimumMass = value;
  357. }
  358. },
  359. /**
  360. * Sets the maximum mass of particles in kilograms.
  361. * @memberof ParticleSystem.prototype
  362. * @type {Number}
  363. * @default 1.0
  364. */
  365. maximumMass : {
  366. get : function() {
  367. return this._maximumMass;
  368. },
  369. set : function(value) {
  370. //>>includeStart('debug', pragmas.debug);
  371. Check.typeOf.number.greaterThanOrEquals('value', value, 0.0);
  372. //>>includeEnd('debug');
  373. this._maximumMass = value;
  374. }
  375. },
  376. /**
  377. * Sets the minimum bound, width by height, above which to randomly scale the particle image's dimensions in pixels.
  378. * @memberof ParticleSystem.prototype
  379. * @type {Cartesian2}
  380. * @default new Cartesian2(1.0, 1.0)
  381. */
  382. minimumImageSize : {
  383. get : function() {
  384. return this._minimumImageSize;
  385. },
  386. set : function(value) {
  387. //>>includeStart('debug', pragmas.debug);
  388. Check.typeOf.object('value', value);
  389. Check.typeOf.number.greaterThanOrEquals('value.x', value.x, 0.0);
  390. Check.typeOf.number.greaterThanOrEquals('value.y', value.y, 0.0);
  391. //>>includeEnd('debug');
  392. this._minimumImageSize = value;
  393. }
  394. },
  395. /**
  396. * Sets the maximum bound, width by height, below which to randomly scale the particle image's dimensions in pixels.
  397. * @memberof ParticleSystem.prototype
  398. * @type {Cartesian2}
  399. * @default new Cartesian2(1.0, 1.0)
  400. */
  401. maximumImageSize : {
  402. get : function() {
  403. return this._maximumImageSize;
  404. },
  405. set : function(value) {
  406. //>>includeStart('debug', pragmas.debug);
  407. Check.typeOf.object('value', value);
  408. Check.typeOf.number.greaterThanOrEquals('value.x', value.x, 0.0);
  409. Check.typeOf.number.greaterThanOrEquals('value.y', value.y, 0.0);
  410. //>>includeEnd('debug');
  411. this._maximumImageSize = value;
  412. }
  413. },
  414. /**
  415. * How long the particle system will emit particles, in seconds.
  416. * @memberof ParticleSystem.prototype
  417. * @type {Number}
  418. * @default Number.MAX_VALUE
  419. */
  420. lifetime : {
  421. get : function() {
  422. return this._lifetime;
  423. },
  424. set : function(value) {
  425. //>>includeStart('debug', pragmas.debug);
  426. Check.typeOf.number.greaterThanOrEquals('value', value, 0.0);
  427. //>>includeEnd('debug');
  428. this._lifetime = value;
  429. }
  430. },
  431. /**
  432. * Fires an event when the particle system has reached the end of its lifetime.
  433. * @memberof ParticleSystem.prototype
  434. * @type {Event}
  435. */
  436. complete : {
  437. get : function() {
  438. return this._complete;
  439. }
  440. },
  441. /**
  442. * When <code>true</code>, the particle system has reached the end of its lifetime; <code>false</code> otherwise.
  443. * @memberof ParticleSystem.prototype
  444. * @type {Boolean}
  445. */
  446. isComplete : {
  447. get : function() {
  448. return this._isComplete;
  449. }
  450. }
  451. });
  452. function updateParticlePool(system) {
  453. var emissionRate = system._emissionRate;
  454. var life = system._maximumParticleLife;
  455. var burstAmount = 0;
  456. var bursts = system._bursts;
  457. if (defined(bursts)) {
  458. var length = bursts.length;
  459. for (var i = 0; i < length; ++i) {
  460. burstAmount += bursts[i].maximum;
  461. }
  462. }
  463. var billboardCollection = system._billboardCollection;
  464. var image = system.image;
  465. var particleEstimate = Math.ceil(emissionRate * life + burstAmount);
  466. var particles = system._particles;
  467. var particlePool = system._particlePool;
  468. var numToAdd = Math.max(particleEstimate - particles.length - particlePool.length, 0);
  469. for (var j = 0; j < numToAdd; ++j) {
  470. var particle = new Particle();
  471. particle._billboard = billboardCollection.add({
  472. image : image
  473. });
  474. particlePool.push(particle);
  475. }
  476. system._particleEstimate = particleEstimate;
  477. }
  478. function getOrCreateParticle(system) {
  479. // Try to reuse an existing particle from the pool.
  480. var particle = system._particlePool.pop();
  481. if (!defined(particle)) {
  482. // Create a new one
  483. particle = new Particle();
  484. }
  485. return particle;
  486. }
  487. function addParticleToPool(system, particle) {
  488. system._particlePool.push(particle);
  489. }
  490. function freeParticlePool(system) {
  491. var particles = system._particles;
  492. var particlePool = system._particlePool;
  493. var billboardCollection = system._billboardCollection;
  494. var numParticles = particles.length;
  495. var numInPool = particlePool.length;
  496. var estimate = system._particleEstimate;
  497. var start = numInPool - Math.max(estimate - numParticles - numInPool, 0);
  498. for (var i = start; i < numInPool; ++i) {
  499. var p = particlePool[i];
  500. billboardCollection.remove(p._billboard);
  501. }
  502. particlePool.length = start;
  503. }
  504. function removeBillboard(particle) {
  505. if (defined(particle._billboard)) {
  506. particle._billboard.show = false;
  507. }
  508. }
  509. function updateBillboard(system, particle) {
  510. var billboard = particle._billboard;
  511. if (!defined(billboard)) {
  512. billboard = particle._billboard = system._billboardCollection.add({
  513. image : particle.image
  514. });
  515. }
  516. billboard.width = particle.imageSize.x;
  517. billboard.height = particle.imageSize.y;
  518. billboard.position = particle.position;
  519. billboard.show = true;
  520. // Update the color
  521. var r = CesiumMath.lerp(particle.startColor.red, particle.endColor.red, particle.normalizedAge);
  522. var g = CesiumMath.lerp(particle.startColor.green, particle.endColor.green, particle.normalizedAge);
  523. var b = CesiumMath.lerp(particle.startColor.blue, particle.endColor.blue, particle.normalizedAge);
  524. var a = CesiumMath.lerp(particle.startColor.alpha, particle.endColor.alpha, particle.normalizedAge);
  525. billboard.color = new Color(r,g,b,a);
  526. // Update the scale
  527. billboard.scale = CesiumMath.lerp(particle.startScale, particle.endScale, particle.normalizedAge);
  528. }
  529. function addParticle(system, particle) {
  530. particle.startColor = Color.clone(system._startColor, particle.startColor);
  531. particle.endColor = Color.clone(system._endColor, particle.endColor);
  532. particle.startScale = system._startScale;
  533. particle.endScale = system._endScale;
  534. particle.image = system.image;
  535. particle.life = CesiumMath.randomBetween(system._minimumParticleLife, system._maximumParticleLife);
  536. particle.mass = CesiumMath.randomBetween(system._minimumMass, system._maximumMass);
  537. particle.imageSize.x = CesiumMath.randomBetween(system._minimumImageSize.x, system._maximumImageSize.x);
  538. particle.imageSize.y = CesiumMath.randomBetween(system._minimumImageSize.y, system._maximumImageSize.y);
  539. // Reset the normalizedAge and age in case the particle was reused.
  540. particle._normalizedAge = 0.0;
  541. particle._age = 0.0;
  542. var speed = CesiumMath.randomBetween(system._minimumSpeed, system._maximumSpeed);
  543. Cartesian3.multiplyByScalar(particle.velocity, speed, particle.velocity);
  544. system._particles.push(particle);
  545. }
  546. function calculateNumberToEmit(system, dt) {
  547. // This emitter is finished if it exceeds it's lifetime.
  548. if (system._isComplete) {
  549. return 0;
  550. }
  551. dt = CesiumMath.mod(dt, system._lifetime);
  552. // Compute the number of particles to emit based on the emissionRate.
  553. var v = dt * system._emissionRate;
  554. var numToEmit = Math.floor(v);
  555. system._carryOver += (v - numToEmit);
  556. if (system._carryOver > 1.0)
  557. {
  558. numToEmit++;
  559. system._carryOver -= 1.0;
  560. }
  561. // Apply any bursts
  562. if (defined(system.bursts)) {
  563. var length = system.bursts.length;
  564. for (var i = 0; i < length; i++) {
  565. var burst = system.bursts[i];
  566. var currentTime = system._currentTime;
  567. if (defined(burst) && !burst._complete && currentTime > burst.time) {
  568. numToEmit += CesiumMath.randomBetween(burst.minimum, burst.maximum);
  569. burst._complete = true;
  570. }
  571. }
  572. }
  573. return numToEmit;
  574. }
  575. var rotatedVelocityScratch = new Cartesian3();
  576. /**
  577. * @private
  578. */
  579. ParticleSystem.prototype.update = function(frameState) {
  580. if (!this.show) {
  581. return;
  582. }
  583. if (!defined(this._billboardCollection)) {
  584. this._billboardCollection = new BillboardCollection();
  585. }
  586. if (this._updateParticlePool) {
  587. updateParticlePool(this);
  588. this._updateParticlePool = false;
  589. }
  590. // Compute the frame time
  591. var dt = 0.0;
  592. if (this._previousTime) {
  593. dt = JulianDate.secondsDifference(frameState.time, this._previousTime);
  594. }
  595. if (dt < 0.0) {
  596. dt = 0.0;
  597. }
  598. var particles = this._particles;
  599. var emitter = this._emitter;
  600. var updateCallback = this.updateCallback;
  601. var i;
  602. var particle;
  603. // update particles and remove dead particles
  604. var length = particles.length;
  605. for (i = 0; i < length; ++i) {
  606. particle = particles[i];
  607. if (!particle.update(dt, updateCallback)) {
  608. removeBillboard(particle);
  609. // Add the particle back to the pool so it can be reused.
  610. addParticleToPool(this, particle);
  611. particles[i] = particles[length - 1];
  612. --i;
  613. --length;
  614. } else {
  615. updateBillboard(this, particle);
  616. }
  617. }
  618. particles.length = length;
  619. var numToEmit = calculateNumberToEmit(this, dt);
  620. if (numToEmit > 0 && defined(emitter)) {
  621. // Compute the final model matrix by combining the particle systems model matrix and the emitter matrix.
  622. if (this._matrixDirty) {
  623. this._combinedMatrix = Matrix4.multiply(this.modelMatrix, this.emitterModelMatrix, this._combinedMatrix);
  624. this._matrixDirty = false;
  625. }
  626. var combinedMatrix = this._combinedMatrix;
  627. for (i = 0; i < numToEmit; i++) {
  628. // Create a new particle.
  629. particle = getOrCreateParticle(this);
  630. // Let the emitter initialize the particle.
  631. this._emitter.emit(particle);
  632. //For the velocity we need to add it to the original position and then multiply by point.
  633. Cartesian3.add(particle.position, particle.velocity, rotatedVelocityScratch);
  634. Matrix4.multiplyByPoint(combinedMatrix, rotatedVelocityScratch, rotatedVelocityScratch);
  635. // Change the position to be in world coordinates
  636. particle.position = Matrix4.multiplyByPoint(combinedMatrix, particle.position, particle.position);
  637. // Orient the velocity in world space as well.
  638. Cartesian3.subtract(rotatedVelocityScratch, particle.position, particle.velocity);
  639. Cartesian3.normalize(particle.velocity, particle.velocity);
  640. // Add the particle to the system.
  641. addParticle(this, particle);
  642. updateBillboard(this, particle);
  643. }
  644. }
  645. this._billboardCollection.update(frameState);
  646. this._previousTime = JulianDate.clone(frameState.time, this._previousTime);
  647. this._currentTime += dt;
  648. if (this._lifetime !== Number.MAX_VALUE && this._currentTime > this._lifetime) {
  649. if (this.loop) {
  650. this._currentTime = CesiumMath.mod(this._currentTime, this._lifetime);
  651. if (this.bursts) {
  652. var burstLength = this.bursts.length;
  653. // Reset any bursts
  654. for (i = 0; i < burstLength; i++) {
  655. this.bursts[i]._complete = false;
  656. }
  657. }
  658. } else {
  659. this._isComplete = true;
  660. this._complete.raiseEvent(this);
  661. }
  662. }
  663. // free particles in the pool and release billboard GPU memory
  664. if (frameState.frameNumber % 120 === 0) {
  665. freeParticlePool(this);
  666. }
  667. };
  668. /**
  669. * Returns true if this object was destroyed; otherwise, false.
  670. * <br /><br />
  671. * If this object was destroyed, it should not be used; calling any function other than
  672. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
  673. *
  674. * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
  675. *
  676. * @see ParticleSystem#destroy
  677. */
  678. ParticleSystem.prototype.isDestroyed = function() {
  679. return false;
  680. };
  681. /**
  682. * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
  683. * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
  684. * <br /><br />
  685. * Once an object is destroyed, it should not be used; calling any function other than
  686. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore,
  687. * assign the return value (<code>undefined</code>) to the object as done in the example.
  688. *
  689. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  690. *
  691. * @see ParticleSystem#isDestroyed
  692. */
  693. ParticleSystem.prototype.destroy = function() {
  694. this._billboardCollection = this._billboardCollection && this._billboardCollection.destroy();
  695. return destroyObject(this);
  696. };
  697. /**
  698. * A function used to modify attributes of the particle at each time step. This can include force modifications,
  699. * color, sizing, etc.
  700. *
  701. * @callback ParticleSystem~updateCallback
  702. *
  703. * @param {Particle} particle The particle being updated.
  704. * @param {Number} dt The time in seconds since the last update.
  705. *
  706. * @example
  707. * function applyGravity(particle, dt) {
  708. * var position = particle.position;
  709. * var gravityVector = Cesium.Cartesian3.normalize(position, new Cesium.Cartesian3());
  710. * Cesium.Cartesian3.multiplyByScalar(gravityVector, GRAVITATIONAL_CONSTANT * dt, gravityVector);
  711. * particle.velocity = Cesium.Cartesian3.add(particle.velocity, gravityVector, particle.velocity);
  712. * }
  713. */
  714. export default ParticleSystem;