ParticleSystem.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910
  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. });
  539. particlePool.push(particle);
  540. }
  541. system._particleEstimate = particleEstimate;
  542. }
  543. function getOrCreateParticle(system) {
  544. // Try to reuse an existing particle from the pool.
  545. let particle = system._particlePool.pop();
  546. if (!defined(particle)) {
  547. // Create a new one
  548. particle = new Particle();
  549. }
  550. return particle;
  551. }
  552. function addParticleToPool(system, particle) {
  553. system._particlePool.push(particle);
  554. }
  555. function freeParticlePool(system) {
  556. const particles = system._particles;
  557. const particlePool = system._particlePool;
  558. const billboardCollection = system._billboardCollection;
  559. const numParticles = particles.length;
  560. const numInPool = particlePool.length;
  561. const estimate = system._particleEstimate;
  562. const start = numInPool - Math.max(estimate - numParticles - numInPool, 0);
  563. for (let i = start; i < numInPool; ++i) {
  564. const p = particlePool[i];
  565. billboardCollection.remove(p._billboard);
  566. }
  567. particlePool.length = start;
  568. }
  569. function removeBillboard(particle) {
  570. if (defined(particle._billboard)) {
  571. particle._billboard.show = false;
  572. }
  573. }
  574. function updateBillboard(system, particle) {
  575. let billboard = particle._billboard;
  576. if (!defined(billboard)) {
  577. billboard = particle._billboard = system._billboardCollection.add({
  578. image: particle.image,
  579. });
  580. }
  581. billboard.width = particle.imageSize.x;
  582. billboard.height = particle.imageSize.y;
  583. billboard.position = particle.position;
  584. billboard.sizeInMeters = system.sizeInMeters;
  585. billboard.show = true;
  586. // Update the color
  587. const r = CesiumMath.lerp(
  588. particle.startColor.red,
  589. particle.endColor.red,
  590. particle.normalizedAge
  591. );
  592. const g = CesiumMath.lerp(
  593. particle.startColor.green,
  594. particle.endColor.green,
  595. particle.normalizedAge
  596. );
  597. const b = CesiumMath.lerp(
  598. particle.startColor.blue,
  599. particle.endColor.blue,
  600. particle.normalizedAge
  601. );
  602. const a = CesiumMath.lerp(
  603. particle.startColor.alpha,
  604. particle.endColor.alpha,
  605. particle.normalizedAge
  606. );
  607. billboard.color = new Color(r, g, b, a);
  608. // Update the scale
  609. billboard.scale = CesiumMath.lerp(
  610. particle.startScale,
  611. particle.endScale,
  612. particle.normalizedAge
  613. );
  614. }
  615. function addParticle(system, particle) {
  616. particle.startColor = Color.clone(system._startColor, particle.startColor);
  617. particle.endColor = Color.clone(system._endColor, particle.endColor);
  618. particle.startScale = system._startScale;
  619. particle.endScale = system._endScale;
  620. particle.image = system.image;
  621. particle.life = CesiumMath.randomBetween(
  622. system._minimumParticleLife,
  623. system._maximumParticleLife
  624. );
  625. particle.mass = CesiumMath.randomBetween(
  626. system._minimumMass,
  627. system._maximumMass
  628. );
  629. particle.imageSize.x = CesiumMath.randomBetween(
  630. system._minimumImageSize.x,
  631. system._maximumImageSize.x
  632. );
  633. particle.imageSize.y = CesiumMath.randomBetween(
  634. system._minimumImageSize.y,
  635. system._maximumImageSize.y
  636. );
  637. // Reset the normalizedAge and age in case the particle was reused.
  638. particle._normalizedAge = 0.0;
  639. particle._age = 0.0;
  640. const speed = CesiumMath.randomBetween(
  641. system._minimumSpeed,
  642. system._maximumSpeed
  643. );
  644. Cartesian3.multiplyByScalar(particle.velocity, speed, particle.velocity);
  645. system._particles.push(particle);
  646. }
  647. function calculateNumberToEmit(system, dt) {
  648. // This emitter is finished if it exceeds it's lifetime.
  649. if (system._isComplete) {
  650. return 0;
  651. }
  652. dt = CesiumMath.mod(dt, system._lifetime);
  653. // Compute the number of particles to emit based on the emissionRate.
  654. const v = dt * system._emissionRate;
  655. let numToEmit = Math.floor(v);
  656. system._carryOver += v - numToEmit;
  657. if (system._carryOver > 1.0) {
  658. numToEmit++;
  659. system._carryOver -= 1.0;
  660. }
  661. // Apply any bursts
  662. if (defined(system.bursts)) {
  663. const length = system.bursts.length;
  664. for (let i = 0; i < length; i++) {
  665. const burst = system.bursts[i];
  666. const currentTime = system._currentTime;
  667. if (defined(burst) && !burst._complete && currentTime > burst.time) {
  668. numToEmit += CesiumMath.randomBetween(burst.minimum, burst.maximum);
  669. burst._complete = true;
  670. }
  671. }
  672. }
  673. return numToEmit;
  674. }
  675. const rotatedVelocityScratch = new Cartesian3();
  676. /**
  677. * @private
  678. */
  679. ParticleSystem.prototype.update = function (frameState) {
  680. if (!this.show) {
  681. return;
  682. }
  683. if (!defined(this._billboardCollection)) {
  684. this._billboardCollection = new BillboardCollection();
  685. }
  686. if (this._updateParticlePool) {
  687. updateParticlePool(this);
  688. this._updateParticlePool = false;
  689. }
  690. // Compute the frame time
  691. let dt = 0.0;
  692. if (this._previousTime) {
  693. dt = JulianDate.secondsDifference(frameState.time, this._previousTime);
  694. }
  695. if (dt < 0.0) {
  696. dt = 0.0;
  697. }
  698. const particles = this._particles;
  699. const emitter = this._emitter;
  700. const updateCallback = this.updateCallback;
  701. let i;
  702. let particle;
  703. // update particles and remove dead particles
  704. let length = particles.length;
  705. for (i = 0; i < length; ++i) {
  706. particle = particles[i];
  707. if (!particle.update(dt, updateCallback)) {
  708. removeBillboard(particle);
  709. // Add the particle back to the pool so it can be reused.
  710. addParticleToPool(this, particle);
  711. particles[i] = particles[length - 1];
  712. --i;
  713. --length;
  714. } else {
  715. updateBillboard(this, particle);
  716. }
  717. }
  718. particles.length = length;
  719. const numToEmit = calculateNumberToEmit(this, dt);
  720. if (numToEmit > 0 && defined(emitter)) {
  721. // Compute the final model matrix by combining the particle systems model matrix and the emitter matrix.
  722. if (this._matrixDirty) {
  723. this._combinedMatrix = Matrix4.multiply(
  724. this.modelMatrix,
  725. this.emitterModelMatrix,
  726. this._combinedMatrix
  727. );
  728. this._matrixDirty = false;
  729. }
  730. const combinedMatrix = this._combinedMatrix;
  731. for (i = 0; i < numToEmit; i++) {
  732. // Create a new particle.
  733. particle = getOrCreateParticle(this);
  734. // Let the emitter initialize the particle.
  735. this._emitter.emit(particle);
  736. //For the velocity we need to add it to the original position and then multiply by point.
  737. Cartesian3.add(
  738. particle.position,
  739. particle.velocity,
  740. rotatedVelocityScratch
  741. );
  742. Matrix4.multiplyByPoint(
  743. combinedMatrix,
  744. rotatedVelocityScratch,
  745. rotatedVelocityScratch
  746. );
  747. // Change the position to be in world coordinates
  748. particle.position = Matrix4.multiplyByPoint(
  749. combinedMatrix,
  750. particle.position,
  751. particle.position
  752. );
  753. // Orient the velocity in world space as well.
  754. Cartesian3.subtract(
  755. rotatedVelocityScratch,
  756. particle.position,
  757. particle.velocity
  758. );
  759. Cartesian3.normalize(particle.velocity, particle.velocity);
  760. // Add the particle to the system.
  761. addParticle(this, particle);
  762. updateBillboard(this, particle);
  763. }
  764. }
  765. this._billboardCollection.update(frameState);
  766. this._previousTime = JulianDate.clone(frameState.time, this._previousTime);
  767. this._currentTime += dt;
  768. if (
  769. this._lifetime !== Number.MAX_VALUE &&
  770. this._currentTime > this._lifetime
  771. ) {
  772. if (this.loop) {
  773. this._currentTime = CesiumMath.mod(this._currentTime, this._lifetime);
  774. if (this.bursts) {
  775. const burstLength = this.bursts.length;
  776. // Reset any bursts
  777. for (i = 0; i < burstLength; i++) {
  778. this.bursts[i]._complete = false;
  779. }
  780. }
  781. } else {
  782. this._isComplete = true;
  783. this._complete.raiseEvent(this);
  784. }
  785. }
  786. // free particles in the pool and release billboard GPU memory
  787. if (frameState.frameNumber % 120 === 0) {
  788. freeParticlePool(this);
  789. }
  790. };
  791. /**
  792. * Returns true if this object was destroyed; otherwise, false.
  793. * <br /><br />
  794. * If this object was destroyed, it should not be used; calling any function other than
  795. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
  796. *
  797. * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
  798. *
  799. * @see ParticleSystem#destroy
  800. */
  801. ParticleSystem.prototype.isDestroyed = function () {
  802. return false;
  803. };
  804. /**
  805. * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
  806. * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
  807. * <br /><br />
  808. * Once an object is destroyed, it should not be used; calling any function other than
  809. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore,
  810. * assign the return value (<code>undefined</code>) to the object as done in the example.
  811. *
  812. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  813. *
  814. * @see ParticleSystem#isDestroyed
  815. */
  816. ParticleSystem.prototype.destroy = function () {
  817. this._billboardCollection =
  818. this._billboardCollection && this._billboardCollection.destroy();
  819. return destroyObject(this);
  820. };
  821. /**
  822. * A function used to modify attributes of the particle at each time step. This can include force modifications,
  823. * color, sizing, etc.
  824. *
  825. * @callback ParticleSystem.updateCallback
  826. *
  827. * @param {Particle} particle The particle being updated.
  828. * @param {Number} dt The time in seconds since the last update.
  829. *
  830. * @example
  831. * function applyGravity(particle, dt) {
  832. * const position = particle.position;
  833. * const gravityVector = Cesium.Cartesian3.normalize(position, new Cesium.Cartesian3());
  834. * Cesium.Cartesian3.multiplyByScalar(gravityVector, GRAVITATIONAL_CONSTANT * dt, gravityVector);
  835. * particle.velocity = Cesium.Cartesian3.add(particle.velocity, gravityVector, particle.velocity);
  836. * }
  837. */
  838. export default ParticleSystem;