ParticleSystem.js 29 KB

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