智慧水务管理系统 - 精河县供水工程综合管理平台

ParticleSystem.js 28KB

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