TimeDynamicPointCloud.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832
  1. import Check from "../Core/Check.js";
  2. import combine from "../Core/combine.js";
  3. import defaultValue from "../Core/defaultValue.js";
  4. import defined from "../Core/defined.js";
  5. import destroyObject from "../Core/destroyObject.js";
  6. import deprecationWarning from "../Core/deprecationWarning.js";
  7. import Event from "../Core/Event.js";
  8. import getTimestamp from "../Core/getTimestamp.js";
  9. import JulianDate from "../Core/JulianDate.js";
  10. import CesiumMath from "../Core/Math.js";
  11. import Matrix4 from "../Core/Matrix4.js";
  12. import Resource from "../Core/Resource.js";
  13. import ClippingPlaneCollection from "./ClippingPlaneCollection.js";
  14. import PointCloud from "./PointCloud.js";
  15. import PointCloudEyeDomeLighting from "./PointCloudEyeDomeLighting.js";
  16. import PointCloudShading from "./PointCloudShading.js";
  17. import SceneMode from "./SceneMode.js";
  18. import ShadowMode from "./ShadowMode.js";
  19. /**
  20. * Provides playback of time-dynamic point cloud data.
  21. * <p>
  22. * Point cloud frames are prefetched in intervals determined by the average frame load time and the current clock speed.
  23. * If intermediate frames cannot be loaded in time to meet playback speed, they will be skipped. If frames are sufficiently
  24. * small or the clock is sufficiently slow then no frames will be skipped.
  25. * </p>
  26. *
  27. * @alias TimeDynamicPointCloud
  28. * @constructor
  29. *
  30. * @param {object} options Object with the following properties:
  31. * @param {Clock} options.clock A {@link Clock} instance that is used when determining the value for the time dimension.
  32. * @param {TimeIntervalCollection} options.intervals A {@link TimeIntervalCollection} with its data property being an object containing a <code>uri</code> to a 3D Tiles Point Cloud tile and an optional <code>transform</code>.
  33. * @param {boolean} [options.show=true] Determines if the point cloud will be shown.
  34. * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] A 4x4 transformation matrix that transforms the point cloud.
  35. * @param {ShadowMode} [options.shadows=ShadowMode.ENABLED] Determines whether the point cloud casts or receives shadows from light sources.
  36. * @param {number} [options.maximumMemoryUsage=256] The maximum amount of memory in MB that can be used by the point cloud.
  37. * @param {object} [options.shading] Options for constructing a {@link PointCloudShading} object to control point attenuation and eye dome lighting.
  38. * @param {Cesium3DTileStyle} [options.style] The style, defined using the {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification/Styling|3D Tiles Styling language}, applied to each point in the point cloud.
  39. * @param {ClippingPlaneCollection} [options.clippingPlanes] The {@link ClippingPlaneCollection} used to selectively disable rendering the point cloud.
  40. */
  41. function TimeDynamicPointCloud(options) {
  42. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  43. //>>includeStart('debug', pragmas.debug);
  44. Check.typeOf.object("options.clock", options.clock);
  45. Check.typeOf.object("options.intervals", options.intervals);
  46. //>>includeEnd('debug');
  47. /**
  48. * Determines if the point cloud will be shown.
  49. *
  50. * @type {boolean}
  51. * @default true
  52. */
  53. this.show = defaultValue(options.show, true);
  54. /**
  55. * A 4x4 transformation matrix that transforms the point cloud.
  56. *
  57. * @type {Matrix4}
  58. * @default Matrix4.IDENTITY
  59. */
  60. this.modelMatrix = Matrix4.clone(
  61. defaultValue(options.modelMatrix, Matrix4.IDENTITY)
  62. );
  63. /**
  64. * Determines whether the point cloud casts or receives shadows from light sources.
  65. * <p>
  66. * Enabling shadows has a performance impact. A point cloud that casts shadows must be rendered twice, once from the camera and again from the light's point of view.
  67. * </p>
  68. * <p>
  69. * Shadows are rendered only when {@link Viewer#shadows} is <code>true</code>.
  70. * </p>
  71. *
  72. * @type {ShadowMode}
  73. * @default ShadowMode.ENABLED
  74. */
  75. this.shadows = defaultValue(options.shadows, ShadowMode.ENABLED);
  76. /**
  77. * The maximum amount of GPU memory (in MB) that may be used to cache point cloud frames.
  78. * <p>
  79. * Frames that are not being loaded or rendered are unloaded to enforce this.
  80. * </p>
  81. * <p>
  82. * If decreasing this value results in unloading tiles, the tiles are unloaded the next frame.
  83. * </p>
  84. *
  85. * @type {number}
  86. * @default 256
  87. *
  88. * @see TimeDynamicPointCloud#totalMemoryUsageInBytes
  89. */
  90. this.maximumMemoryUsage = defaultValue(options.maximumMemoryUsage, 256);
  91. /**
  92. * Options for controlling point size based on geometric error and eye dome lighting.
  93. * @type {PointCloudShading}
  94. */
  95. this.shading = new PointCloudShading(options.shading);
  96. /**
  97. * The style, defined using the
  98. * {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification/Styling|3D Tiles Styling language},
  99. * applied to each point in the point cloud.
  100. * <p>
  101. * Assign <code>undefined</code> to remove the style, which will restore the visual
  102. * appearance of the point cloud to its default when no style was applied.
  103. * </p>
  104. *
  105. * @type {Cesium3DTileStyle}
  106. *
  107. * @example
  108. * pointCloud.style = new Cesium.Cesium3DTileStyle({
  109. * color : {
  110. * conditions : [
  111. * ['${Classification} === 0', 'color("purple", 0.5)'],
  112. * ['${Classification} === 1', 'color("red")'],
  113. * ['true', '${COLOR}']
  114. * ]
  115. * },
  116. * show : '${Classification} !== 2'
  117. * });
  118. *
  119. * @see {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification/Styling|3D Tiles Styling language}
  120. */
  121. this.style = options.style;
  122. /**
  123. * The event fired to indicate that a frame failed to load. A frame may fail to load if the
  124. * request for its uri fails or processing fails due to invalid content.
  125. * <p>
  126. * If there are no event listeners, error messages will be logged to the console.
  127. * </p>
  128. * <p>
  129. * The error object passed to the listener contains two properties:
  130. * <ul>
  131. * <li><code>uri</code>: the uri of the failed frame.</li>
  132. * <li><code>message</code>: the error message.</li>
  133. * </ul>
  134. *
  135. * @type {Event}
  136. * @default new Event()
  137. *
  138. * @example
  139. * pointCloud.frameFailed.addEventListener(function(error) {
  140. * console.log(`An error occurred loading frame: ${error.uri}`);
  141. * console.log(`Error: ${error.message}`);
  142. * });
  143. */
  144. this.frameFailed = new Event();
  145. /**
  146. * The event fired to indicate that a new frame was rendered.
  147. * <p>
  148. * The time dynamic point cloud {@link TimeDynamicPointCloud} is passed to the event listener.
  149. * </p>
  150. * @type {Event}
  151. * @default new Event()
  152. *
  153. * @example
  154. * pointCloud.frameChanged.addEventListener(function(timeDynamicPointCloud) {
  155. * viewer.camera.viewBoundingSphere(timeDynamicPointCloud.boundingSphere);
  156. * });
  157. */
  158. this.frameChanged = new Event();
  159. this._clock = options.clock;
  160. this._intervals = options.intervals;
  161. this._clippingPlanes = undefined;
  162. this.clippingPlanes = options.clippingPlanes; // Call setter
  163. this._pointCloudEyeDomeLighting = new PointCloudEyeDomeLighting();
  164. this._loadTimestamp = undefined;
  165. this._clippingPlanesState = 0;
  166. this._styleDirty = false;
  167. this._pickId = undefined;
  168. this._totalMemoryUsageInBytes = 0;
  169. this._frames = [];
  170. this._previousInterval = undefined;
  171. this._nextInterval = undefined;
  172. this._lastRenderedFrame = undefined;
  173. this._clockMultiplier = 0.0;
  174. this._resolveReadyPromise = undefined;
  175. const that = this;
  176. // This is here for backwards compatibility and can be removed when readyPromise is removed.
  177. this._readyPromise = new Promise(function (resolve) {
  178. that._resolveReadyPromise = resolve;
  179. });
  180. // For calculating average load time of the last N frames
  181. this._runningSum = 0.0;
  182. this._runningLength = 0;
  183. this._runningIndex = 0;
  184. this._runningSamples = new Array(5).fill(0.0);
  185. this._runningAverage = 0.0;
  186. }
  187. Object.defineProperties(TimeDynamicPointCloud.prototype, {
  188. /**
  189. * The {@link ClippingPlaneCollection} used to selectively disable rendering the point cloud.
  190. *
  191. * @memberof TimeDynamicPointCloud.prototype
  192. *
  193. * @type {ClippingPlaneCollection}
  194. */
  195. clippingPlanes: {
  196. get: function () {
  197. return this._clippingPlanes;
  198. },
  199. set: function (value) {
  200. ClippingPlaneCollection.setOwner(value, this, "_clippingPlanes");
  201. },
  202. },
  203. /**
  204. * The total amount of GPU memory in bytes used by the point cloud.
  205. *
  206. * @memberof TimeDynamicPointCloud.prototype
  207. *
  208. * @type {number}
  209. * @readonly
  210. *
  211. * @see TimeDynamicPointCloud#maximumMemoryUsage
  212. */
  213. totalMemoryUsageInBytes: {
  214. get: function () {
  215. return this._totalMemoryUsageInBytes;
  216. },
  217. },
  218. /**
  219. * The bounding sphere of the frame being rendered. Returns <code>undefined</code> if no frame is being rendered.
  220. *
  221. * @memberof TimeDynamicPointCloud.prototype
  222. *
  223. * @type {BoundingSphere}
  224. * @readonly
  225. */
  226. boundingSphere: {
  227. get: function () {
  228. if (defined(this._lastRenderedFrame)) {
  229. return this._lastRenderedFrame.pointCloud.boundingSphere;
  230. }
  231. return undefined;
  232. },
  233. },
  234. /**
  235. * Gets the promise that will be resolved when the point cloud renders a frame for the first time.
  236. *
  237. * @memberof TimeDynamicPointCloud.prototype
  238. *
  239. * @type {Promise<TimeDynamicPointCloud>}
  240. * @readonly
  241. * @deprecated
  242. */
  243. readyPromise: {
  244. get: function () {
  245. deprecationWarning(
  246. "TimeDynamicPointCloud.readyPromise",
  247. "TimeDynamicPointCloud.readyPromise was deprecated in CesiumJS 1.104. It will be removed in 1.107. Use TimeDynamicPointCloud.frameFailed instead."
  248. );
  249. return this._readyPromise;
  250. },
  251. },
  252. });
  253. function getFragmentShaderLoaded(fs) {
  254. return `uniform vec4 czm_pickColor;\n${fs}`;
  255. }
  256. function getUniformMapLoaded(stream) {
  257. return function (uniformMap) {
  258. return combine(uniformMap, {
  259. czm_pickColor: function () {
  260. return stream._pickId.color;
  261. },
  262. });
  263. };
  264. }
  265. function getPickIdLoaded() {
  266. return "czm_pickColor";
  267. }
  268. /**
  269. * Marks the point cloud's {@link TimeDynamicPointCloud#style} as dirty, which forces all
  270. * points to re-evaluate the style in the next frame.
  271. */
  272. TimeDynamicPointCloud.prototype.makeStyleDirty = function () {
  273. this._styleDirty = true;
  274. };
  275. /**
  276. * Exposed for testing.
  277. *
  278. * @private
  279. */
  280. TimeDynamicPointCloud.prototype._getAverageLoadTime = function () {
  281. if (this._runningLength === 0) {
  282. // Before any frames have loaded make a best guess about the average load time
  283. return 0.05;
  284. }
  285. return this._runningAverage;
  286. };
  287. const scratchDate = new JulianDate();
  288. function getClockMultiplier(that) {
  289. const clock = that._clock;
  290. const isAnimating = clock.canAnimate && clock.shouldAnimate;
  291. const multiplier = clock.multiplier;
  292. return isAnimating ? multiplier : 0.0;
  293. }
  294. function getIntervalIndex(that, interval) {
  295. return that._intervals.indexOf(interval.start);
  296. }
  297. function getNextInterval(that, currentInterval) {
  298. const intervals = that._intervals;
  299. const clock = that._clock;
  300. const multiplier = getClockMultiplier(that);
  301. if (multiplier === 0.0) {
  302. return undefined;
  303. }
  304. const averageLoadTime = that._getAverageLoadTime();
  305. const time = JulianDate.addSeconds(
  306. clock.currentTime,
  307. averageLoadTime * multiplier,
  308. scratchDate
  309. );
  310. let index = intervals.indexOf(time);
  311. const currentIndex = getIntervalIndex(that, currentInterval);
  312. if (index === currentIndex) {
  313. if (multiplier >= 0) {
  314. ++index;
  315. } else {
  316. --index;
  317. }
  318. }
  319. // Returns undefined if not in range
  320. return intervals.get(index);
  321. }
  322. function getCurrentInterval(that) {
  323. const intervals = that._intervals;
  324. const clock = that._clock;
  325. const time = clock.currentTime;
  326. const index = intervals.indexOf(time);
  327. // Returns undefined if not in range
  328. return intervals.get(index);
  329. }
  330. function reachedInterval(that, currentInterval, nextInterval) {
  331. const multiplier = getClockMultiplier(that);
  332. const currentIndex = getIntervalIndex(that, currentInterval);
  333. const nextIndex = getIntervalIndex(that, nextInterval);
  334. if (multiplier >= 0) {
  335. return currentIndex >= nextIndex;
  336. }
  337. return currentIndex <= nextIndex;
  338. }
  339. function handleFrameFailure(that, uri) {
  340. return function (error) {
  341. const message = defined(error.message) ? error.message : error.toString();
  342. if (that.frameFailed.numberOfListeners > 0) {
  343. that.frameFailed.raiseEvent({
  344. uri: uri,
  345. message: message,
  346. });
  347. } else {
  348. console.log(`A frame failed to load: ${uri}`);
  349. console.log(`Error: ${message}`);
  350. }
  351. };
  352. }
  353. function requestFrame(that, interval, frameState) {
  354. const index = getIntervalIndex(that, interval);
  355. const frames = that._frames;
  356. let frame = frames[index];
  357. if (!defined(frame)) {
  358. const transformArray = interval.data.transform;
  359. const transform = defined(transformArray)
  360. ? Matrix4.fromArray(transformArray)
  361. : undefined;
  362. const uri = interval.data.uri;
  363. frame = {
  364. pointCloud: undefined,
  365. transform: transform,
  366. timestamp: getTimestamp(),
  367. sequential: true,
  368. ready: false,
  369. touchedFrameNumber: frameState.frameNumber,
  370. uri: uri,
  371. };
  372. frames[index] = frame;
  373. Resource.fetchArrayBuffer({
  374. url: uri,
  375. })
  376. .then(function (arrayBuffer) {
  377. // PERFORMANCE_IDEA: share a memory pool, render states, shaders, and other resources among all
  378. // frames. Each frame just needs an index/offset into the pool.
  379. frame.pointCloud = new PointCloud({
  380. arrayBuffer: arrayBuffer,
  381. cull: true,
  382. fragmentShaderLoaded: getFragmentShaderLoaded,
  383. uniformMapLoaded: getUniformMapLoaded(that),
  384. pickIdLoaded: getPickIdLoaded,
  385. });
  386. })
  387. .catch(handleFrameFailure(that, uri));
  388. }
  389. return frame;
  390. }
  391. function updateAverageLoadTime(that, loadTime) {
  392. that._runningSum += loadTime;
  393. that._runningSum -= that._runningSamples[that._runningIndex];
  394. that._runningSamples[that._runningIndex] = loadTime;
  395. that._runningLength = Math.min(
  396. that._runningLength + 1,
  397. that._runningSamples.length
  398. );
  399. that._runningIndex = (that._runningIndex + 1) % that._runningSamples.length;
  400. that._runningAverage = that._runningSum / that._runningLength;
  401. }
  402. function prepareFrame(that, frame, updateState, frameState) {
  403. if (frame.touchedFrameNumber < frameState.frameNumber - 1) {
  404. // If this frame was not loaded in sequential updates then it can't be used it for calculating the average load time.
  405. // For example: selecting a frame on the timeline, selecting another frame before the request finishes, then selecting this frame later.
  406. frame.sequential = false;
  407. }
  408. const pointCloud = frame.pointCloud;
  409. if (defined(pointCloud) && !frame.ready) {
  410. // Call update to prepare renderer resources. Don't render anything yet.
  411. const commandList = frameState.commandList;
  412. const lengthBeforeUpdate = commandList.length;
  413. renderFrame(that, frame, updateState, frameState);
  414. if (pointCloud.ready) {
  415. // Point cloud became ready this update
  416. frame.ready = true;
  417. that._totalMemoryUsageInBytes += pointCloud.geometryByteLength;
  418. commandList.length = lengthBeforeUpdate; // Don't allow preparing frame to insert commands.
  419. if (frame.sequential) {
  420. // Update the values used to calculate average load time
  421. const loadTime = (getTimestamp() - frame.timestamp) / 1000.0;
  422. updateAverageLoadTime(that, loadTime);
  423. }
  424. }
  425. }
  426. frame.touchedFrameNumber = frameState.frameNumber;
  427. }
  428. const scratchModelMatrix = new Matrix4();
  429. function getGeometricError(that, pointCloud) {
  430. const shading = that.shading;
  431. if (defined(shading) && defined(shading.baseResolution)) {
  432. return shading.baseResolution;
  433. } else if (defined(pointCloud.boundingSphere)) {
  434. return CesiumMath.cbrt(
  435. pointCloud.boundingSphere.volume() / pointCloud.pointsLength
  436. );
  437. }
  438. return 0.0;
  439. }
  440. function getMaximumAttenuation(that) {
  441. const shading = that.shading;
  442. if (defined(shading) && defined(shading.maximumAttenuation)) {
  443. return shading.maximumAttenuation;
  444. }
  445. // Return a hardcoded maximum attenuation. For a tileset this would instead be the maximum screen space error.
  446. return 10.0;
  447. }
  448. const defaultShading = new PointCloudShading();
  449. function renderFrame(that, frame, updateState, frameState) {
  450. const shading = defaultValue(that.shading, defaultShading);
  451. const pointCloud = frame.pointCloud;
  452. const transform = defaultValue(frame.transform, Matrix4.IDENTITY);
  453. pointCloud.modelMatrix = Matrix4.multiplyTransformation(
  454. that.modelMatrix,
  455. transform,
  456. scratchModelMatrix
  457. );
  458. pointCloud.style = that.style;
  459. pointCloud.time = updateState.timeSinceLoad;
  460. pointCloud.shadows = that.shadows;
  461. pointCloud.clippingPlanes = that._clippingPlanes;
  462. pointCloud.isClipped = updateState.isClipped;
  463. pointCloud.attenuation = shading.attenuation;
  464. pointCloud.backFaceCulling = shading.backFaceCulling;
  465. pointCloud.normalShading = shading.normalShading;
  466. pointCloud.geometricError = getGeometricError(that, pointCloud);
  467. pointCloud.geometricErrorScale = shading.geometricErrorScale;
  468. pointCloud.maximumAttenuation = getMaximumAttenuation(that);
  469. try {
  470. pointCloud.update(frameState);
  471. } catch (error) {
  472. handleFrameFailure(that, frame.uri)(error);
  473. }
  474. frame.touchedFrameNumber = frameState.frameNumber;
  475. }
  476. function loadFrame(that, interval, updateState, frameState) {
  477. const frame = requestFrame(that, interval, frameState);
  478. prepareFrame(that, frame, updateState, frameState);
  479. }
  480. function getUnloadCondition(frameState) {
  481. return function (frame) {
  482. // Unload all frames that aren't currently being loaded or rendered
  483. return frame.touchedFrameNumber < frameState.frameNumber;
  484. };
  485. }
  486. function unloadFrames(that, unloadCondition) {
  487. const frames = that._frames;
  488. const length = frames.length;
  489. for (let i = 0; i < length; ++i) {
  490. const frame = frames[i];
  491. if (defined(frame)) {
  492. if (!defined(unloadCondition) || unloadCondition(frame)) {
  493. const pointCloud = frame.pointCloud;
  494. if (frame.ready) {
  495. that._totalMemoryUsageInBytes -= pointCloud.geometryByteLength;
  496. }
  497. if (defined(pointCloud)) {
  498. pointCloud.destroy();
  499. }
  500. if (frame === that._lastRenderedFrame) {
  501. that._lastRenderedFrame = undefined;
  502. }
  503. frames[i] = undefined;
  504. }
  505. }
  506. }
  507. }
  508. function getFrame(that, interval) {
  509. const index = getIntervalIndex(that, interval);
  510. const frame = that._frames[index];
  511. if (defined(frame) && frame.ready) {
  512. return frame;
  513. }
  514. }
  515. function updateInterval(that, interval, frame, updateState, frameState) {
  516. if (defined(frame)) {
  517. if (frame.ready) {
  518. return true;
  519. }
  520. loadFrame(that, interval, updateState, frameState);
  521. return frame.ready;
  522. }
  523. return false;
  524. }
  525. function getNearestReadyInterval(
  526. that,
  527. previousInterval,
  528. currentInterval,
  529. updateState,
  530. frameState
  531. ) {
  532. let i;
  533. let interval;
  534. let frame;
  535. const intervals = that._intervals;
  536. const frames = that._frames;
  537. const currentIndex = getIntervalIndex(that, currentInterval);
  538. const previousIndex = getIntervalIndex(that, previousInterval);
  539. if (currentIndex >= previousIndex) {
  540. // look backwards
  541. for (i = currentIndex; i >= previousIndex; --i) {
  542. interval = intervals.get(i);
  543. frame = frames[i];
  544. if (updateInterval(that, interval, frame, updateState, frameState)) {
  545. return interval;
  546. }
  547. }
  548. } else {
  549. // look forwards
  550. for (i = currentIndex; i <= previousIndex; ++i) {
  551. interval = intervals.get(i);
  552. frame = frames[i];
  553. if (updateInterval(that, interval, frame, updateState, frameState)) {
  554. return interval;
  555. }
  556. }
  557. }
  558. // If no intervals are ready return the previous interval
  559. return previousInterval;
  560. }
  561. function setFramesDirty(that, clippingPlanesDirty, styleDirty) {
  562. const frames = that._frames;
  563. const framesLength = frames.length;
  564. for (let i = 0; i < framesLength; ++i) {
  565. const frame = frames[i];
  566. if (defined(frame) && defined(frame.pointCloud)) {
  567. frame.pointCloud.clippingPlanesDirty = clippingPlanesDirty;
  568. frame.pointCloud.styleDirty = styleDirty;
  569. }
  570. }
  571. }
  572. const updateState = {
  573. timeSinceLoad: 0,
  574. isClipped: false,
  575. clippingPlanesDirty: false,
  576. };
  577. /**
  578. * @private
  579. */
  580. TimeDynamicPointCloud.prototype.update = function (frameState) {
  581. if (frameState.mode === SceneMode.MORPHING) {
  582. return;
  583. }
  584. if (!this.show) {
  585. return;
  586. }
  587. if (!defined(this._pickId)) {
  588. this._pickId = frameState.context.createPickId({
  589. primitive: this,
  590. });
  591. }
  592. if (!defined(this._loadTimestamp)) {
  593. this._loadTimestamp = JulianDate.clone(frameState.time);
  594. }
  595. // For styling
  596. const timeSinceLoad = Math.max(
  597. JulianDate.secondsDifference(frameState.time, this._loadTimestamp) * 1000,
  598. 0.0
  599. );
  600. // Update clipping planes
  601. const clippingPlanes = this._clippingPlanes;
  602. let clippingPlanesState = 0;
  603. let clippingPlanesDirty = false;
  604. const isClipped = defined(clippingPlanes) && clippingPlanes.enabled;
  605. if (isClipped) {
  606. clippingPlanes.update(frameState);
  607. clippingPlanesState = clippingPlanes.clippingPlanesState;
  608. }
  609. if (this._clippingPlanesState !== clippingPlanesState) {
  610. this._clippingPlanesState = clippingPlanesState;
  611. clippingPlanesDirty = true;
  612. }
  613. const styleDirty = this._styleDirty;
  614. this._styleDirty = false;
  615. if (clippingPlanesDirty || styleDirty) {
  616. setFramesDirty(this, clippingPlanesDirty, styleDirty);
  617. }
  618. updateState.timeSinceLoad = timeSinceLoad;
  619. updateState.isClipped = isClipped;
  620. const shading = this.shading;
  621. const eyeDomeLighting = this._pointCloudEyeDomeLighting;
  622. const commandList = frameState.commandList;
  623. const lengthBeforeUpdate = commandList.length;
  624. let previousInterval = this._previousInterval;
  625. let nextInterval = this._nextInterval;
  626. const currentInterval = getCurrentInterval(this);
  627. if (!defined(currentInterval)) {
  628. return;
  629. }
  630. let clockMultiplierChanged = false;
  631. const clockMultiplier = getClockMultiplier(this);
  632. const clockPaused = clockMultiplier === 0;
  633. if (clockMultiplier !== this._clockMultiplier) {
  634. clockMultiplierChanged = true;
  635. this._clockMultiplier = clockMultiplier;
  636. }
  637. if (!defined(previousInterval) || clockPaused) {
  638. previousInterval = currentInterval;
  639. }
  640. if (
  641. !defined(nextInterval) ||
  642. clockMultiplierChanged ||
  643. reachedInterval(this, currentInterval, nextInterval)
  644. ) {
  645. nextInterval = getNextInterval(this, currentInterval);
  646. }
  647. previousInterval = getNearestReadyInterval(
  648. this,
  649. previousInterval,
  650. currentInterval,
  651. updateState,
  652. frameState
  653. );
  654. let frame = getFrame(this, previousInterval);
  655. if (!defined(frame)) {
  656. // The frame is not ready to render. This can happen when the simulation starts or when scrubbing the timeline
  657. // to a frame that hasn't loaded yet. Just render the last rendered frame in its place until it finishes loading.
  658. loadFrame(this, previousInterval, updateState, frameState);
  659. frame = this._lastRenderedFrame;
  660. }
  661. if (defined(frame)) {
  662. renderFrame(this, frame, updateState, frameState);
  663. }
  664. if (defined(nextInterval)) {
  665. // Start loading the next frame
  666. loadFrame(this, nextInterval, updateState, frameState);
  667. }
  668. const that = this;
  669. if (defined(frame) && !defined(this._lastRenderedFrame)) {
  670. frameState.afterRender.push(function () {
  671. // This is here for backwards compatibility and can be removed when readyPromise is removed.
  672. that._resolveReadyPromise(that);
  673. return true;
  674. });
  675. }
  676. if (defined(frame) && frame !== this._lastRenderedFrame) {
  677. if (that.frameChanged.numberOfListeners > 0) {
  678. frameState.afterRender.push(function () {
  679. that.frameChanged.raiseEvent(that);
  680. return true;
  681. });
  682. }
  683. }
  684. this._previousInterval = previousInterval;
  685. this._nextInterval = nextInterval;
  686. this._lastRenderedFrame = frame;
  687. const totalMemoryUsageInBytes = this._totalMemoryUsageInBytes;
  688. const maximumMemoryUsageInBytes = this.maximumMemoryUsage * 1024 * 1024;
  689. if (totalMemoryUsageInBytes > maximumMemoryUsageInBytes) {
  690. unloadFrames(this, getUnloadCondition(frameState));
  691. }
  692. const lengthAfterUpdate = commandList.length;
  693. const addedCommandsLength = lengthAfterUpdate - lengthBeforeUpdate;
  694. if (
  695. defined(shading) &&
  696. shading.attenuation &&
  697. shading.eyeDomeLighting &&
  698. addedCommandsLength > 0
  699. ) {
  700. eyeDomeLighting.update(
  701. frameState,
  702. lengthBeforeUpdate,
  703. shading,
  704. this.boundingSphere
  705. );
  706. }
  707. };
  708. /**
  709. * Returns true if this object was destroyed; otherwise, false.
  710. * <br /><br />
  711. * If this object was destroyed, it should not be used; calling any function other than
  712. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
  713. *
  714. * @returns {boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
  715. *
  716. * @see TimeDynamicPointCloud#destroy
  717. */
  718. TimeDynamicPointCloud.prototype.isDestroyed = function () {
  719. return false;
  720. };
  721. /**
  722. * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
  723. * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
  724. * <br /><br />
  725. * Once an object is destroyed, it should not be used; calling any function other than
  726. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore,
  727. * assign the return value (<code>undefined</code>) to the object as done in the example.
  728. *
  729. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  730. *
  731. * @example
  732. * pointCloud = pointCloud && pointCloud.destroy();
  733. *
  734. * @see TimeDynamicPointCloud#isDestroyed
  735. */
  736. TimeDynamicPointCloud.prototype.destroy = function () {
  737. unloadFrames(this);
  738. this._clippingPlanes = this._clippingPlanes && this._clippingPlanes.destroy();
  739. this._pickId = this._pickId && this._pickId.destroy();
  740. return destroyObject(this);
  741. };
  742. export default TimeDynamicPointCloud;