TimeDynamicPointCloud.js 25 KB

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