QuadtreePrimitive.js 50 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544
  1. import Cartesian3 from "../Core/Cartesian3.js";
  2. import Cartographic from "../Core/Cartographic.js";
  3. import defaultValue from "../Core/defaultValue.js";
  4. import defined from "../Core/defined.js";
  5. import DeveloperError from "../Core/DeveloperError.js";
  6. import Event from "../Core/Event.js";
  7. import getTimestamp from "../Core/getTimestamp.js";
  8. import CesiumMath from "../Core/Math.js";
  9. import Matrix4 from "../Core/Matrix4.js";
  10. import OrthographicFrustum from "../Core/OrthographicFrustum.js";
  11. import OrthographicOffCenterFrustum from "../Core/OrthographicOffCenterFrustum.js";
  12. import Ray from "../Core/Ray.js";
  13. import Rectangle from "../Core/Rectangle.js";
  14. import Visibility from "../Core/Visibility.js";
  15. import QuadtreeOccluders from "./QuadtreeOccluders.js";
  16. import QuadtreeTile from "./QuadtreeTile.js";
  17. import QuadtreeTileLoadState from "./QuadtreeTileLoadState.js";
  18. import SceneMode from "./SceneMode.js";
  19. import TileReplacementQueue from "./TileReplacementQueue.js";
  20. import TileSelectionResult from "./TileSelectionResult.js";
  21. /**
  22. * Renders massive sets of data by utilizing level-of-detail and culling. The globe surface is divided into
  23. * a quadtree of tiles with large, low-detail tiles at the root and small, high-detail tiles at the leaves.
  24. * The set of tiles to render is selected by projecting an estimate of the geometric error in a tile onto
  25. * the screen to estimate screen-space error, in pixels, which must be below a user-specified threshold.
  26. * The actual content of the tiles is arbitrary and is specified using a {@link QuadtreeTileProvider}.
  27. *
  28. * @alias QuadtreePrimitive
  29. * @constructor
  30. * @private
  31. *
  32. * @param {QuadtreeTileProvider} options.tileProvider The tile provider that loads, renders, and estimates
  33. * the distance to individual tiles.
  34. * @param {Number} [options.maximumScreenSpaceError=2] The maximum screen-space error, in pixels, that is allowed.
  35. * A higher maximum error will render fewer tiles and improve performance, while a lower
  36. * value will improve visual quality.
  37. * @param {Number} [options.tileCacheSize=100] The maximum number of tiles that will be retained in the tile cache.
  38. * Note that tiles will never be unloaded if they were used for rendering the last
  39. * frame, so the actual number of resident tiles may be higher. The value of
  40. * this property will not affect visual quality.
  41. */
  42. function QuadtreePrimitive(options) {
  43. //>>includeStart('debug', pragmas.debug);
  44. if (!defined(options) || !defined(options.tileProvider)) {
  45. throw new DeveloperError("options.tileProvider is required.");
  46. }
  47. if (defined(options.tileProvider.quadtree)) {
  48. throw new DeveloperError(
  49. "A QuadtreeTileProvider can only be used with a single QuadtreePrimitive"
  50. );
  51. }
  52. //>>includeEnd('debug');
  53. this._tileProvider = options.tileProvider;
  54. this._tileProvider.quadtree = this;
  55. this._debug = {
  56. enableDebugOutput: false,
  57. maxDepth: 0,
  58. maxDepthVisited: 0,
  59. tilesVisited: 0,
  60. tilesCulled: 0,
  61. tilesRendered: 0,
  62. tilesWaitingForChildren: 0,
  63. lastMaxDepth: -1,
  64. lastMaxDepthVisited: -1,
  65. lastTilesVisited: -1,
  66. lastTilesCulled: -1,
  67. lastTilesRendered: -1,
  68. lastTilesWaitingForChildren: -1,
  69. suspendLodUpdate: false,
  70. };
  71. const tilingScheme = this._tileProvider.tilingScheme;
  72. const ellipsoid = tilingScheme.ellipsoid;
  73. this._tilesToRender = [];
  74. this._tileLoadQueueHigh = []; // high priority tiles are preventing refinement
  75. this._tileLoadQueueMedium = []; // medium priority tiles are being rendered
  76. this._tileLoadQueueLow = []; // low priority tiles were refined past or are non-visible parts of quads.
  77. this._tileReplacementQueue = new TileReplacementQueue();
  78. this._levelZeroTiles = undefined;
  79. this._loadQueueTimeSlice = 5.0;
  80. this._tilesInvalidated = false;
  81. this._addHeightCallbacks = [];
  82. this._removeHeightCallbacks = [];
  83. this._tileToUpdateHeights = [];
  84. this._lastTileIndex = 0;
  85. this._updateHeightsTimeSlice = 2.0;
  86. // If a culled tile contains _cameraPositionCartographic or _cameraReferenceFrameOriginCartographic, it will be marked
  87. // TileSelectionResult.CULLED_BUT_NEEDED and added to the list of tiles to update heights,
  88. // even though it is not rendered.
  89. // These are updated each frame in `selectTilesForRendering`.
  90. this._cameraPositionCartographic = undefined;
  91. this._cameraReferenceFrameOriginCartographic = undefined;
  92. /**
  93. * Gets or sets the maximum screen-space error, in pixels, that is allowed.
  94. * A higher maximum error will render fewer tiles and improve performance, while a lower
  95. * value will improve visual quality.
  96. * @type {Number}
  97. * @default 2
  98. */
  99. this.maximumScreenSpaceError = defaultValue(
  100. options.maximumScreenSpaceError,
  101. 2
  102. );
  103. /**
  104. * Gets or sets the maximum number of tiles that will be retained in the tile cache.
  105. * Note that tiles will never be unloaded if they were used for rendering the last
  106. * frame, so the actual number of resident tiles may be higher. The value of
  107. * this property will not affect visual quality.
  108. * @type {Number}
  109. * @default 100
  110. */
  111. this.tileCacheSize = defaultValue(options.tileCacheSize, 100);
  112. /**
  113. * Gets or sets the number of loading descendant tiles that is considered "too many".
  114. * If a tile has too many loading descendants, that tile will be loaded and rendered before any of
  115. * its descendants are loaded and rendered. This means more feedback for the user that something
  116. * is happening at the cost of a longer overall load time. Setting this to 0 will cause each
  117. * tile level to be loaded successively, significantly increasing load time. Setting it to a large
  118. * number (e.g. 1000) will minimize the number of tiles that are loaded but tend to make
  119. * detail appear all at once after a long wait.
  120. * @type {Number}
  121. * @default 20
  122. */
  123. this.loadingDescendantLimit = 20;
  124. /**
  125. * Gets or sets a value indicating whether the ancestors of rendered tiles should be preloaded.
  126. * Setting this to true optimizes the zoom-out experience and provides more detail in
  127. * newly-exposed areas when panning. The down side is that it requires loading more tiles.
  128. * @type {Boolean}
  129. * @default true
  130. */
  131. this.preloadAncestors = true;
  132. /**
  133. * Gets or sets a value indicating whether the siblings of rendered tiles should be preloaded.
  134. * Setting this to true causes tiles with the same parent as a rendered tile to be loaded, even
  135. * if they are culled. Setting this to true may provide a better panning experience at the
  136. * cost of loading more tiles.
  137. * @type {Boolean}
  138. * @default false
  139. */
  140. this.preloadSiblings = false;
  141. this._occluders = new QuadtreeOccluders({
  142. ellipsoid: ellipsoid,
  143. });
  144. this._tileLoadProgressEvent = new Event();
  145. this._lastTileLoadQueueLength = 0;
  146. this._lastSelectionFrameNumber = undefined;
  147. }
  148. Object.defineProperties(QuadtreePrimitive.prototype, {
  149. /**
  150. * Gets the provider of {@link QuadtreeTile} instances for this quadtree.
  151. * @type {QuadtreeTile}
  152. * @memberof QuadtreePrimitive.prototype
  153. */
  154. tileProvider: {
  155. get: function () {
  156. return this._tileProvider;
  157. },
  158. },
  159. /**
  160. * Gets an event that's raised when the length of the tile load queue has changed since the last render frame. When the load queue is empty,
  161. * all terrain and imagery for the current view have been loaded. The event passes the new length of the tile load queue.
  162. *
  163. * @memberof QuadtreePrimitive.prototype
  164. * @type {Event}
  165. */
  166. tileLoadProgressEvent: {
  167. get: function () {
  168. return this._tileLoadProgressEvent;
  169. },
  170. },
  171. occluders: {
  172. get: function () {
  173. return this._occluders;
  174. },
  175. },
  176. });
  177. /**
  178. * Invalidates and frees all the tiles in the quadtree. The tiles must be reloaded
  179. * before they can be displayed.
  180. *
  181. * @memberof QuadtreePrimitive
  182. */
  183. QuadtreePrimitive.prototype.invalidateAllTiles = function () {
  184. this._tilesInvalidated = true;
  185. };
  186. function invalidateAllTiles(primitive) {
  187. // Clear the replacement queue
  188. const replacementQueue = primitive._tileReplacementQueue;
  189. replacementQueue.head = undefined;
  190. replacementQueue.tail = undefined;
  191. replacementQueue.count = 0;
  192. clearTileLoadQueue(primitive);
  193. // Free and recreate the level zero tiles.
  194. const levelZeroTiles = primitive._levelZeroTiles;
  195. if (defined(levelZeroTiles)) {
  196. for (let i = 0; i < levelZeroTiles.length; ++i) {
  197. const tile = levelZeroTiles[i];
  198. const customData = tile.customData;
  199. const customDataLength = customData.length;
  200. for (let j = 0; j < customDataLength; ++j) {
  201. const data = customData[j];
  202. data.level = 0;
  203. primitive._addHeightCallbacks.push(data);
  204. }
  205. levelZeroTiles[i].freeResources();
  206. }
  207. }
  208. primitive._levelZeroTiles = undefined;
  209. primitive._tileProvider.cancelReprojections();
  210. }
  211. /**
  212. * Invokes a specified function for each {@link QuadtreeTile} that is partially
  213. * or completely loaded.
  214. *
  215. * @param {Function} tileFunction The function to invoke for each loaded tile. The
  216. * function is passed a reference to the tile as its only parameter.
  217. */
  218. QuadtreePrimitive.prototype.forEachLoadedTile = function (tileFunction) {
  219. let tile = this._tileReplacementQueue.head;
  220. while (defined(tile)) {
  221. if (tile.state !== QuadtreeTileLoadState.START) {
  222. tileFunction(tile);
  223. }
  224. tile = tile.replacementNext;
  225. }
  226. };
  227. /**
  228. * Invokes a specified function for each {@link QuadtreeTile} that was rendered
  229. * in the most recent frame.
  230. *
  231. * @param {Function} tileFunction The function to invoke for each rendered tile. The
  232. * function is passed a reference to the tile as its only parameter.
  233. */
  234. QuadtreePrimitive.prototype.forEachRenderedTile = function (tileFunction) {
  235. const tilesRendered = this._tilesToRender;
  236. for (let i = 0, len = tilesRendered.length; i < len; ++i) {
  237. tileFunction(tilesRendered[i]);
  238. }
  239. };
  240. /**
  241. * Calls the callback when a new tile is rendered that contains the given cartographic. The only parameter
  242. * is the cartesian position on the tile.
  243. *
  244. * @param {Cartographic} cartographic The cartographic position.
  245. * @param {Function} callback The function to be called when a new tile is loaded containing cartographic.
  246. * @returns {Function} The function to remove this callback from the quadtree.
  247. */
  248. QuadtreePrimitive.prototype.updateHeight = function (cartographic, callback) {
  249. const primitive = this;
  250. const object = {
  251. positionOnEllipsoidSurface: undefined,
  252. positionCartographic: cartographic,
  253. level: -1,
  254. callback: callback,
  255. };
  256. object.removeFunc = function () {
  257. const addedCallbacks = primitive._addHeightCallbacks;
  258. const length = addedCallbacks.length;
  259. for (let i = 0; i < length; ++i) {
  260. if (addedCallbacks[i] === object) {
  261. addedCallbacks.splice(i, 1);
  262. break;
  263. }
  264. }
  265. primitive._removeHeightCallbacks.push(object);
  266. if (object.callback) {
  267. object.callback = undefined;
  268. }
  269. };
  270. primitive._addHeightCallbacks.push(object);
  271. return object.removeFunc;
  272. };
  273. /**
  274. * Updates the tile provider imagery and continues to process the tile load queue.
  275. * @private
  276. */
  277. QuadtreePrimitive.prototype.update = function (frameState) {
  278. if (defined(this._tileProvider.update)) {
  279. this._tileProvider.update(frameState);
  280. }
  281. };
  282. function clearTileLoadQueue(primitive) {
  283. const debug = primitive._debug;
  284. debug.maxDepth = 0;
  285. debug.maxDepthVisited = 0;
  286. debug.tilesVisited = 0;
  287. debug.tilesCulled = 0;
  288. debug.tilesRendered = 0;
  289. debug.tilesWaitingForChildren = 0;
  290. primitive._tileLoadQueueHigh.length = 0;
  291. primitive._tileLoadQueueMedium.length = 0;
  292. primitive._tileLoadQueueLow.length = 0;
  293. }
  294. /**
  295. * Initializes values for a new render frame and prepare the tile load queue.
  296. * @private
  297. */
  298. QuadtreePrimitive.prototype.beginFrame = function (frameState) {
  299. const passes = frameState.passes;
  300. if (!passes.render) {
  301. return;
  302. }
  303. if (this._tilesInvalidated) {
  304. invalidateAllTiles(this);
  305. this._tilesInvalidated = false;
  306. }
  307. // Gets commands for any texture re-projections
  308. this._tileProvider.initialize(frameState);
  309. clearTileLoadQueue(this);
  310. if (this._debug.suspendLodUpdate) {
  311. return;
  312. }
  313. this._tileReplacementQueue.markStartOfRenderFrame();
  314. };
  315. /**
  316. * Selects new tiles to load based on the frame state and creates render commands.
  317. * @private
  318. */
  319. QuadtreePrimitive.prototype.render = function (frameState) {
  320. const passes = frameState.passes;
  321. const tileProvider = this._tileProvider;
  322. if (passes.render) {
  323. tileProvider.beginUpdate(frameState);
  324. selectTilesForRendering(this, frameState);
  325. createRenderCommandsForSelectedTiles(this, frameState);
  326. tileProvider.endUpdate(frameState);
  327. }
  328. if (passes.pick && this._tilesToRender.length > 0) {
  329. tileProvider.updateForPick(frameState);
  330. }
  331. };
  332. /**
  333. * Checks if the load queue length has changed since the last time we raised a queue change event - if so, raises
  334. * a new change event at the end of the render cycle.
  335. * @private
  336. */
  337. function updateTileLoadProgress(primitive, frameState) {
  338. const currentLoadQueueLength =
  339. primitive._tileLoadQueueHigh.length +
  340. primitive._tileLoadQueueMedium.length +
  341. primitive._tileLoadQueueLow.length;
  342. if (
  343. currentLoadQueueLength !== primitive._lastTileLoadQueueLength ||
  344. primitive._tilesInvalidated
  345. ) {
  346. frameState.afterRender.push(
  347. Event.prototype.raiseEvent.bind(
  348. primitive._tileLoadProgressEvent,
  349. currentLoadQueueLength
  350. )
  351. );
  352. primitive._lastTileLoadQueueLength = currentLoadQueueLength;
  353. }
  354. const debug = primitive._debug;
  355. if (debug.enableDebugOutput && !debug.suspendLodUpdate) {
  356. debug.maxDepth = primitive._tilesToRender.reduce(function (max, tile) {
  357. return Math.max(max, tile.level);
  358. }, -1);
  359. debug.tilesRendered = primitive._tilesToRender.length;
  360. if (
  361. debug.tilesVisited !== debug.lastTilesVisited ||
  362. debug.tilesRendered !== debug.lastTilesRendered ||
  363. debug.tilesCulled !== debug.lastTilesCulled ||
  364. debug.maxDepth !== debug.lastMaxDepth ||
  365. debug.tilesWaitingForChildren !== debug.lastTilesWaitingForChildren ||
  366. debug.maxDepthVisited !== debug.lastMaxDepthVisited
  367. ) {
  368. console.log(
  369. `Visited ${debug.tilesVisited}, Rendered: ${debug.tilesRendered}, Culled: ${debug.tilesCulled}, Max Depth Rendered: ${debug.maxDepth}, Max Depth Visited: ${debug.maxDepthVisited}, Waiting for children: ${debug.tilesWaitingForChildren}`
  370. );
  371. debug.lastTilesVisited = debug.tilesVisited;
  372. debug.lastTilesRendered = debug.tilesRendered;
  373. debug.lastTilesCulled = debug.tilesCulled;
  374. debug.lastMaxDepth = debug.maxDepth;
  375. debug.lastTilesWaitingForChildren = debug.tilesWaitingForChildren;
  376. debug.lastMaxDepthVisited = debug.maxDepthVisited;
  377. }
  378. }
  379. }
  380. /**
  381. * Updates terrain heights.
  382. * @private
  383. */
  384. QuadtreePrimitive.prototype.endFrame = function (frameState) {
  385. const passes = frameState.passes;
  386. if (!passes.render || frameState.mode === SceneMode.MORPHING) {
  387. // Only process the load queue for a single pass.
  388. // Don't process the load queue or update heights during the morph flights.
  389. return;
  390. }
  391. // Load/create resources for terrain and imagery. Prepare texture re-projections for the next frame.
  392. processTileLoadQueue(this, frameState);
  393. updateHeights(this, frameState);
  394. updateTileLoadProgress(this, frameState);
  395. };
  396. /**
  397. * Returns true if this object was destroyed; otherwise, false.
  398. * <br /><br />
  399. * If this object was destroyed, it should not be used; calling any function other than
  400. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
  401. *
  402. * @memberof QuadtreePrimitive
  403. *
  404. * @returns {Boolean} True if this object was destroyed; otherwise, false.
  405. *
  406. * @see QuadtreePrimitive#destroy
  407. */
  408. QuadtreePrimitive.prototype.isDestroyed = function () {
  409. return false;
  410. };
  411. /**
  412. * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
  413. * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
  414. * <br /><br />
  415. * Once an object is destroyed, it should not be used; calling any function other than
  416. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore,
  417. * assign the return value (<code>undefined</code>) to the object as done in the example.
  418. *
  419. * @memberof QuadtreePrimitive
  420. *
  421. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  422. *
  423. *
  424. * @example
  425. * primitive = primitive && primitive.destroy();
  426. *
  427. * @see QuadtreePrimitive#isDestroyed
  428. */
  429. QuadtreePrimitive.prototype.destroy = function () {
  430. this._tileProvider = this._tileProvider && this._tileProvider.destroy();
  431. };
  432. let comparisonPoint;
  433. const centerScratch = new Cartographic();
  434. function compareDistanceToPoint(a, b) {
  435. let center = Rectangle.center(a.rectangle, centerScratch);
  436. const alon = center.longitude - comparisonPoint.longitude;
  437. const alat = center.latitude - comparisonPoint.latitude;
  438. center = Rectangle.center(b.rectangle, centerScratch);
  439. const blon = center.longitude - comparisonPoint.longitude;
  440. const blat = center.latitude - comparisonPoint.latitude;
  441. return alon * alon + alat * alat - (blon * blon + blat * blat);
  442. }
  443. const cameraOriginScratch = new Cartesian3();
  444. let rootTraversalDetails = [];
  445. function selectTilesForRendering(primitive, frameState) {
  446. const debug = primitive._debug;
  447. if (debug.suspendLodUpdate) {
  448. return;
  449. }
  450. // Clear the render list.
  451. const tilesToRender = primitive._tilesToRender;
  452. tilesToRender.length = 0;
  453. // We can't render anything before the level zero tiles exist.
  454. let i;
  455. const tileProvider = primitive._tileProvider;
  456. if (!defined(primitive._levelZeroTiles)) {
  457. if (tileProvider.ready) {
  458. const tilingScheme = tileProvider.tilingScheme;
  459. primitive._levelZeroTiles = QuadtreeTile.createLevelZeroTiles(
  460. tilingScheme
  461. );
  462. const numberOfRootTiles = primitive._levelZeroTiles.length;
  463. if (rootTraversalDetails.length < numberOfRootTiles) {
  464. rootTraversalDetails = new Array(numberOfRootTiles);
  465. for (i = 0; i < numberOfRootTiles; ++i) {
  466. if (rootTraversalDetails[i] === undefined) {
  467. rootTraversalDetails[i] = new TraversalDetails();
  468. }
  469. }
  470. }
  471. } else {
  472. // Nothing to do until the provider is ready.
  473. return;
  474. }
  475. }
  476. primitive._occluders.ellipsoid.cameraPosition = frameState.camera.positionWC;
  477. let tile;
  478. const levelZeroTiles = primitive._levelZeroTiles;
  479. const occluders =
  480. levelZeroTiles.length > 1 ? primitive._occluders : undefined;
  481. // Sort the level zero tiles by the distance from the center to the camera.
  482. // The level zero tiles aren't necessarily a nice neat quad, so we can't use the
  483. // quadtree ordering we use elsewhere in the tree
  484. comparisonPoint = frameState.camera.positionCartographic;
  485. levelZeroTiles.sort(compareDistanceToPoint);
  486. const customDataAdded = primitive._addHeightCallbacks;
  487. const customDataRemoved = primitive._removeHeightCallbacks;
  488. const frameNumber = frameState.frameNumber;
  489. let len;
  490. if (customDataAdded.length > 0 || customDataRemoved.length > 0) {
  491. for (i = 0, len = levelZeroTiles.length; i < len; ++i) {
  492. tile = levelZeroTiles[i];
  493. tile._updateCustomData(frameNumber, customDataAdded, customDataRemoved);
  494. }
  495. customDataAdded.length = 0;
  496. customDataRemoved.length = 0;
  497. }
  498. const camera = frameState.camera;
  499. primitive._cameraPositionCartographic = camera.positionCartographic;
  500. const cameraFrameOrigin = Matrix4.getTranslation(
  501. camera.transform,
  502. cameraOriginScratch
  503. );
  504. primitive._cameraReferenceFrameOriginCartographic = primitive.tileProvider.tilingScheme.ellipsoid.cartesianToCartographic(
  505. cameraFrameOrigin,
  506. primitive._cameraReferenceFrameOriginCartographic
  507. );
  508. // Traverse in depth-first, near-to-far order.
  509. for (i = 0, len = levelZeroTiles.length; i < len; ++i) {
  510. tile = levelZeroTiles[i];
  511. primitive._tileReplacementQueue.markTileRendered(tile);
  512. if (!tile.renderable) {
  513. queueTileLoad(primitive, primitive._tileLoadQueueHigh, tile, frameState);
  514. ++debug.tilesWaitingForChildren;
  515. } else {
  516. visitIfVisible(
  517. primitive,
  518. tile,
  519. tileProvider,
  520. frameState,
  521. occluders,
  522. false,
  523. rootTraversalDetails[i]
  524. );
  525. }
  526. }
  527. primitive._lastSelectionFrameNumber = frameNumber;
  528. }
  529. function queueTileLoad(primitive, queue, tile, frameState) {
  530. if (!tile.needsLoading) {
  531. return;
  532. }
  533. if (primitive.tileProvider.computeTileLoadPriority !== undefined) {
  534. tile._loadPriority = primitive.tileProvider.computeTileLoadPriority(
  535. tile,
  536. frameState
  537. );
  538. }
  539. queue.push(tile);
  540. }
  541. /**
  542. * Tracks details of traversing a tile while selecting tiles for rendering.
  543. * @alias TraversalDetails
  544. * @constructor
  545. * @private
  546. */
  547. function TraversalDetails() {
  548. /**
  549. * True if all selected (i.e. not culled or refined) tiles in this tile's subtree
  550. * are renderable. If the subtree is renderable, we'll render it; no drama.
  551. */
  552. this.allAreRenderable = true;
  553. /**
  554. * True if any tiles in this tile's subtree were rendered last frame. If any
  555. * were, we must render the subtree rather than this tile, because rendering
  556. * this tile would cause detail to vanish that was visible last frame, and
  557. * that's no good.
  558. */
  559. this.anyWereRenderedLastFrame = false;
  560. /**
  561. * Counts the number of selected tiles in this tile's subtree that are
  562. * not yet ready to be rendered because they need more loading. Note that
  563. * this value will _not_ necessarily be zero when
  564. * {@link TraversalDetails#allAreRenderable} is true, for subtle reasons.
  565. * When {@link TraversalDetails#allAreRenderable} and
  566. * {@link TraversalDetails#anyWereRenderedLastFrame} are both false, we
  567. * will render this tile instead of any tiles in its subtree and
  568. * the `allAreRenderable` value for this tile will reflect only whether _this_
  569. * tile is renderable. The `notYetRenderableCount` value, however, will still
  570. * reflect the total number of tiles that we are waiting on, including the
  571. * ones that we're not rendering. `notYetRenderableCount` is only reset
  572. * when a subtree is removed from the render queue because the
  573. * `notYetRenderableCount` exceeds the
  574. * {@link QuadtreePrimitive#loadingDescendantLimit}.
  575. */
  576. this.notYetRenderableCount = 0;
  577. }
  578. function TraversalQuadDetails() {
  579. this.southwest = new TraversalDetails();
  580. this.southeast = new TraversalDetails();
  581. this.northwest = new TraversalDetails();
  582. this.northeast = new TraversalDetails();
  583. }
  584. TraversalQuadDetails.prototype.combine = function (result) {
  585. const southwest = this.southwest;
  586. const southeast = this.southeast;
  587. const northwest = this.northwest;
  588. const northeast = this.northeast;
  589. result.allAreRenderable =
  590. southwest.allAreRenderable &&
  591. southeast.allAreRenderable &&
  592. northwest.allAreRenderable &&
  593. northeast.allAreRenderable;
  594. result.anyWereRenderedLastFrame =
  595. southwest.anyWereRenderedLastFrame ||
  596. southeast.anyWereRenderedLastFrame ||
  597. northwest.anyWereRenderedLastFrame ||
  598. northeast.anyWereRenderedLastFrame;
  599. result.notYetRenderableCount =
  600. southwest.notYetRenderableCount +
  601. southeast.notYetRenderableCount +
  602. northwest.notYetRenderableCount +
  603. northeast.notYetRenderableCount;
  604. };
  605. const traversalQuadsByLevel = new Array(31); // level 30 tiles are ~2cm wide at the equator, should be good enough.
  606. for (let i = 0; i < traversalQuadsByLevel.length; ++i) {
  607. traversalQuadsByLevel[i] = new TraversalQuadDetails();
  608. }
  609. /**
  610. * Visits a tile for possible rendering. When we call this function with a tile:
  611. *
  612. * * the tile has been determined to be visible (possibly based on a bounding volume that is not very tight-fitting)
  613. * * its parent tile does _not_ meet the SSE (unless ancestorMeetsSse=true, see comments below)
  614. * * the tile may or may not be renderable
  615. *
  616. * @private
  617. *
  618. * @param {Primitive} primitive The QuadtreePrimitive.
  619. * @param {FrameState} frameState The frame state.
  620. * @param {QuadtreeTile} tile The tile to visit
  621. * @param {Boolean} ancestorMeetsSse True if a tile higher in the tile tree already met the SSE and we're refining further only
  622. * to maintain detail while that higher tile loads.
  623. * @param {TraversalDetails} traveralDetails On return, populated with details of how the traversal of this tile went.
  624. */
  625. function visitTile(
  626. primitive,
  627. frameState,
  628. tile,
  629. ancestorMeetsSse,
  630. traversalDetails
  631. ) {
  632. const debug = primitive._debug;
  633. ++debug.tilesVisited;
  634. primitive._tileReplacementQueue.markTileRendered(tile);
  635. tile._updateCustomData(frameState.frameNumber);
  636. if (tile.level > debug.maxDepthVisited) {
  637. debug.maxDepthVisited = tile.level;
  638. }
  639. const meetsSse =
  640. screenSpaceError(primitive, frameState, tile) <
  641. primitive.maximumScreenSpaceError;
  642. const southwestChild = tile.southwestChild;
  643. const southeastChild = tile.southeastChild;
  644. const northwestChild = tile.northwestChild;
  645. const northeastChild = tile.northeastChild;
  646. const lastFrame = primitive._lastSelectionFrameNumber;
  647. const lastFrameSelectionResult =
  648. tile._lastSelectionResultFrame === lastFrame
  649. ? tile._lastSelectionResult
  650. : TileSelectionResult.NONE;
  651. const tileProvider = primitive.tileProvider;
  652. if (meetsSse || ancestorMeetsSse) {
  653. // This tile (or an ancestor) is the one we want to render this frame, but we'll do different things depending
  654. // on the state of this tile and on what we did _last_ frame.
  655. // We can render it if _any_ of the following are true:
  656. // 1. We rendered it (or kicked it) last frame.
  657. // 2. This tile was culled last frame, or it wasn't even visited because an ancestor was culled.
  658. // 3. The tile is completely done loading.
  659. // 4. a) Terrain is ready, and
  660. // b) All necessary imagery is ready. Necessary imagery is imagery that was rendered with this tile
  661. // or any descendants last frame. Such imagery is required because rendering this tile without
  662. // it would cause detail to disappear.
  663. //
  664. // Determining condition 4 is more expensive, so we check the others first.
  665. //
  666. // Note that even if we decide to render a tile here, it may later get "kicked" in favor of an ancestor.
  667. const oneRenderedLastFrame =
  668. TileSelectionResult.originalResult(lastFrameSelectionResult) ===
  669. TileSelectionResult.RENDERED;
  670. const twoCulledOrNotVisited =
  671. TileSelectionResult.originalResult(lastFrameSelectionResult) ===
  672. TileSelectionResult.CULLED ||
  673. lastFrameSelectionResult === TileSelectionResult.NONE;
  674. const threeCompletelyLoaded = tile.state === QuadtreeTileLoadState.DONE;
  675. let renderable =
  676. oneRenderedLastFrame || twoCulledOrNotVisited || threeCompletelyLoaded;
  677. if (!renderable) {
  678. // Check the more expensive condition 4 above. This requires details of the thing
  679. // we're rendering (e.g. the globe surface), so delegate it to the tile provider.
  680. if (defined(tileProvider.canRenderWithoutLosingDetail)) {
  681. renderable = tileProvider.canRenderWithoutLosingDetail(tile);
  682. }
  683. }
  684. if (renderable) {
  685. // Only load this tile if it (not just an ancestor) meets the SSE.
  686. if (meetsSse) {
  687. queueTileLoad(
  688. primitive,
  689. primitive._tileLoadQueueMedium,
  690. tile,
  691. frameState
  692. );
  693. }
  694. addTileToRenderList(primitive, tile);
  695. traversalDetails.allAreRenderable = tile.renderable;
  696. traversalDetails.anyWereRenderedLastFrame =
  697. lastFrameSelectionResult === TileSelectionResult.RENDERED;
  698. traversalDetails.notYetRenderableCount = tile.renderable ? 0 : 1;
  699. tile._lastSelectionResultFrame = frameState.frameNumber;
  700. tile._lastSelectionResult = TileSelectionResult.RENDERED;
  701. if (!traversalDetails.anyWereRenderedLastFrame) {
  702. // Tile is newly-rendered this frame, so update its heights.
  703. primitive._tileToUpdateHeights.push(tile);
  704. }
  705. return;
  706. }
  707. // Otherwise, we can't render this tile (or its fill) because doing so would cause detail to disappear
  708. // that was visible last frame. Instead, keep rendering any still-visible descendants that were rendered
  709. // last frame and render fills for newly-visible descendants. E.g. if we were rendering level 15 last
  710. // frame but this frame we want level 14 and the closest renderable level <= 14 is 0, rendering level
  711. // zero would be pretty jarring so instead we keep rendering level 15 even though its SSE is better
  712. // than required. So fall through to continue traversal...
  713. ancestorMeetsSse = true;
  714. // Load this blocker tile with high priority, but only if this tile (not just an ancestor) meets the SSE.
  715. if (meetsSse) {
  716. queueTileLoad(primitive, primitive._tileLoadQueueHigh, tile, frameState);
  717. }
  718. }
  719. if (tileProvider.canRefine(tile)) {
  720. const allAreUpsampled =
  721. southwestChild.upsampledFromParent &&
  722. southeastChild.upsampledFromParent &&
  723. northwestChild.upsampledFromParent &&
  724. northeastChild.upsampledFromParent;
  725. if (allAreUpsampled) {
  726. // No point in rendering the children because they're all upsampled. Render this tile instead.
  727. addTileToRenderList(primitive, tile);
  728. // Rendered tile that's not waiting on children loads with medium priority.
  729. queueTileLoad(
  730. primitive,
  731. primitive._tileLoadQueueMedium,
  732. tile,
  733. frameState
  734. );
  735. // Make sure we don't unload the children and forget they're upsampled.
  736. primitive._tileReplacementQueue.markTileRendered(southwestChild);
  737. primitive._tileReplacementQueue.markTileRendered(southeastChild);
  738. primitive._tileReplacementQueue.markTileRendered(northwestChild);
  739. primitive._tileReplacementQueue.markTileRendered(northeastChild);
  740. traversalDetails.allAreRenderable = tile.renderable;
  741. traversalDetails.anyWereRenderedLastFrame =
  742. lastFrameSelectionResult === TileSelectionResult.RENDERED;
  743. traversalDetails.notYetRenderableCount = tile.renderable ? 0 : 1;
  744. tile._lastSelectionResultFrame = frameState.frameNumber;
  745. tile._lastSelectionResult = TileSelectionResult.RENDERED;
  746. if (!traversalDetails.anyWereRenderedLastFrame) {
  747. // Tile is newly-rendered this frame, so update its heights.
  748. primitive._tileToUpdateHeights.push(tile);
  749. }
  750. return;
  751. }
  752. // SSE is not good enough, so refine.
  753. tile._lastSelectionResultFrame = frameState.frameNumber;
  754. tile._lastSelectionResult = TileSelectionResult.REFINED;
  755. const firstRenderedDescendantIndex = primitive._tilesToRender.length;
  756. const loadIndexLow = primitive._tileLoadQueueLow.length;
  757. const loadIndexMedium = primitive._tileLoadQueueMedium.length;
  758. const loadIndexHigh = primitive._tileLoadQueueHigh.length;
  759. const tilesToUpdateHeightsIndex = primitive._tileToUpdateHeights.length;
  760. // No need to add the children to the load queue because they'll be added (if necessary) when they're visited.
  761. visitVisibleChildrenNearToFar(
  762. primitive,
  763. southwestChild,
  764. southeastChild,
  765. northwestChild,
  766. northeastChild,
  767. frameState,
  768. ancestorMeetsSse,
  769. traversalDetails
  770. );
  771. // If no descendant tiles were added to the render list by the function above, it means they were all
  772. // culled even though this tile was deemed visible. That's pretty common.
  773. if (firstRenderedDescendantIndex !== primitive._tilesToRender.length) {
  774. // At least one descendant tile was added to the render list.
  775. // The traversalDetails tell us what happened while visiting the children.
  776. const allAreRenderable = traversalDetails.allAreRenderable;
  777. const anyWereRenderedLastFrame =
  778. traversalDetails.anyWereRenderedLastFrame;
  779. const notYetRenderableCount = traversalDetails.notYetRenderableCount;
  780. let queuedForLoad = false;
  781. if (!allAreRenderable && !anyWereRenderedLastFrame) {
  782. // Some of our descendants aren't ready to render yet, and none were rendered last frame,
  783. // so kick them all out of the render list and render this tile instead. Continue to load them though!
  784. // Mark the rendered descendants and their ancestors - up to this tile - as kicked.
  785. const renderList = primitive._tilesToRender;
  786. for (let i = firstRenderedDescendantIndex; i < renderList.length; ++i) {
  787. let workTile = renderList[i];
  788. while (
  789. workTile !== undefined &&
  790. workTile._lastSelectionResult !== TileSelectionResult.KICKED &&
  791. workTile !== tile
  792. ) {
  793. workTile._lastSelectionResult = TileSelectionResult.kick(
  794. workTile._lastSelectionResult
  795. );
  796. workTile = workTile.parent;
  797. }
  798. }
  799. // Remove all descendants from the render list and add this tile.
  800. primitive._tilesToRender.length = firstRenderedDescendantIndex;
  801. primitive._tileToUpdateHeights.length = tilesToUpdateHeightsIndex;
  802. addTileToRenderList(primitive, tile);
  803. tile._lastSelectionResult = TileSelectionResult.RENDERED;
  804. // If we're waiting on heaps of descendants, the above will take too long. So in that case,
  805. // load this tile INSTEAD of loading any of the descendants, and tell the up-level we're only waiting
  806. // on this tile. Keep doing this until we actually manage to render this tile.
  807. const wasRenderedLastFrame =
  808. lastFrameSelectionResult === TileSelectionResult.RENDERED;
  809. if (
  810. !wasRenderedLastFrame &&
  811. notYetRenderableCount > primitive.loadingDescendantLimit
  812. ) {
  813. // Remove all descendants from the load queues.
  814. primitive._tileLoadQueueLow.length = loadIndexLow;
  815. primitive._tileLoadQueueMedium.length = loadIndexMedium;
  816. primitive._tileLoadQueueHigh.length = loadIndexHigh;
  817. queueTileLoad(
  818. primitive,
  819. primitive._tileLoadQueueMedium,
  820. tile,
  821. frameState
  822. );
  823. traversalDetails.notYetRenderableCount = tile.renderable ? 0 : 1;
  824. queuedForLoad = true;
  825. }
  826. traversalDetails.allAreRenderable = tile.renderable;
  827. traversalDetails.anyWereRenderedLastFrame = wasRenderedLastFrame;
  828. if (!wasRenderedLastFrame) {
  829. // Tile is newly-rendered this frame, so update its heights.
  830. primitive._tileToUpdateHeights.push(tile);
  831. }
  832. ++debug.tilesWaitingForChildren;
  833. }
  834. if (primitive.preloadAncestors && !queuedForLoad) {
  835. queueTileLoad(primitive, primitive._tileLoadQueueLow, tile, frameState);
  836. }
  837. }
  838. return;
  839. }
  840. tile._lastSelectionResultFrame = frameState.frameNumber;
  841. tile._lastSelectionResult = TileSelectionResult.RENDERED;
  842. // We'd like to refine but can't because we have no availability data for this tile's children,
  843. // so we have no idea if refinining would involve a load or an upsample. We'll have to finish
  844. // loading this tile first in order to find that out, so load this refinement blocker with
  845. // high priority.
  846. addTileToRenderList(primitive, tile);
  847. queueTileLoad(primitive, primitive._tileLoadQueueHigh, tile, frameState);
  848. traversalDetails.allAreRenderable = tile.renderable;
  849. traversalDetails.anyWereRenderedLastFrame =
  850. lastFrameSelectionResult === TileSelectionResult.RENDERED;
  851. traversalDetails.notYetRenderableCount = tile.renderable ? 0 : 1;
  852. }
  853. function visitVisibleChildrenNearToFar(
  854. primitive,
  855. southwest,
  856. southeast,
  857. northwest,
  858. northeast,
  859. frameState,
  860. ancestorMeetsSse,
  861. traversalDetails
  862. ) {
  863. const cameraPosition = frameState.camera.positionCartographic;
  864. const tileProvider = primitive._tileProvider;
  865. const occluders = primitive._occluders;
  866. const quadDetails = traversalQuadsByLevel[southwest.level];
  867. const southwestDetails = quadDetails.southwest;
  868. const southeastDetails = quadDetails.southeast;
  869. const northwestDetails = quadDetails.northwest;
  870. const northeastDetails = quadDetails.northeast;
  871. if (cameraPosition.longitude < southwest.rectangle.east) {
  872. if (cameraPosition.latitude < southwest.rectangle.north) {
  873. // Camera in southwest quadrant
  874. visitIfVisible(
  875. primitive,
  876. southwest,
  877. tileProvider,
  878. frameState,
  879. occluders,
  880. ancestorMeetsSse,
  881. southwestDetails
  882. );
  883. visitIfVisible(
  884. primitive,
  885. southeast,
  886. tileProvider,
  887. frameState,
  888. occluders,
  889. ancestorMeetsSse,
  890. southeastDetails
  891. );
  892. visitIfVisible(
  893. primitive,
  894. northwest,
  895. tileProvider,
  896. frameState,
  897. occluders,
  898. ancestorMeetsSse,
  899. northwestDetails
  900. );
  901. visitIfVisible(
  902. primitive,
  903. northeast,
  904. tileProvider,
  905. frameState,
  906. occluders,
  907. ancestorMeetsSse,
  908. northeastDetails
  909. );
  910. } else {
  911. // Camera in northwest quadrant
  912. visitIfVisible(
  913. primitive,
  914. northwest,
  915. tileProvider,
  916. frameState,
  917. occluders,
  918. ancestorMeetsSse,
  919. northwestDetails
  920. );
  921. visitIfVisible(
  922. primitive,
  923. southwest,
  924. tileProvider,
  925. frameState,
  926. occluders,
  927. ancestorMeetsSse,
  928. southwestDetails
  929. );
  930. visitIfVisible(
  931. primitive,
  932. northeast,
  933. tileProvider,
  934. frameState,
  935. occluders,
  936. ancestorMeetsSse,
  937. northeastDetails
  938. );
  939. visitIfVisible(
  940. primitive,
  941. southeast,
  942. tileProvider,
  943. frameState,
  944. occluders,
  945. ancestorMeetsSse,
  946. southeastDetails
  947. );
  948. }
  949. } else if (cameraPosition.latitude < southwest.rectangle.north) {
  950. // Camera southeast quadrant
  951. visitIfVisible(
  952. primitive,
  953. southeast,
  954. tileProvider,
  955. frameState,
  956. occluders,
  957. ancestorMeetsSse,
  958. southeastDetails
  959. );
  960. visitIfVisible(
  961. primitive,
  962. southwest,
  963. tileProvider,
  964. frameState,
  965. occluders,
  966. ancestorMeetsSse,
  967. southwestDetails
  968. );
  969. visitIfVisible(
  970. primitive,
  971. northeast,
  972. tileProvider,
  973. frameState,
  974. occluders,
  975. ancestorMeetsSse,
  976. northeastDetails
  977. );
  978. visitIfVisible(
  979. primitive,
  980. northwest,
  981. tileProvider,
  982. frameState,
  983. occluders,
  984. ancestorMeetsSse,
  985. northwestDetails
  986. );
  987. } else {
  988. // Camera in northeast quadrant
  989. visitIfVisible(
  990. primitive,
  991. northeast,
  992. tileProvider,
  993. frameState,
  994. occluders,
  995. ancestorMeetsSse,
  996. northeastDetails
  997. );
  998. visitIfVisible(
  999. primitive,
  1000. northwest,
  1001. tileProvider,
  1002. frameState,
  1003. occluders,
  1004. ancestorMeetsSse,
  1005. northwestDetails
  1006. );
  1007. visitIfVisible(
  1008. primitive,
  1009. southeast,
  1010. tileProvider,
  1011. frameState,
  1012. occluders,
  1013. ancestorMeetsSse,
  1014. southeastDetails
  1015. );
  1016. visitIfVisible(
  1017. primitive,
  1018. southwest,
  1019. tileProvider,
  1020. frameState,
  1021. occluders,
  1022. ancestorMeetsSse,
  1023. southwestDetails
  1024. );
  1025. }
  1026. quadDetails.combine(traversalDetails);
  1027. }
  1028. function containsNeededPosition(primitive, tile) {
  1029. const rectangle = tile.rectangle;
  1030. return (
  1031. (defined(primitive._cameraPositionCartographic) &&
  1032. Rectangle.contains(rectangle, primitive._cameraPositionCartographic)) ||
  1033. (defined(primitive._cameraReferenceFrameOriginCartographic) &&
  1034. Rectangle.contains(
  1035. rectangle,
  1036. primitive._cameraReferenceFrameOriginCartographic
  1037. ))
  1038. );
  1039. }
  1040. function visitIfVisible(
  1041. primitive,
  1042. tile,
  1043. tileProvider,
  1044. frameState,
  1045. occluders,
  1046. ancestorMeetsSse,
  1047. traversalDetails
  1048. ) {
  1049. if (
  1050. tileProvider.computeTileVisibility(tile, frameState, occluders) !==
  1051. Visibility.NONE
  1052. ) {
  1053. return visitTile(
  1054. primitive,
  1055. frameState,
  1056. tile,
  1057. ancestorMeetsSse,
  1058. traversalDetails
  1059. );
  1060. }
  1061. ++primitive._debug.tilesCulled;
  1062. primitive._tileReplacementQueue.markTileRendered(tile);
  1063. traversalDetails.allAreRenderable = true;
  1064. traversalDetails.anyWereRenderedLastFrame = false;
  1065. traversalDetails.notYetRenderableCount = 0;
  1066. if (containsNeededPosition(primitive, tile)) {
  1067. // Load the tile(s) that contains the camera's position and
  1068. // the origin of its reference frame with medium priority.
  1069. // But we only need to load until the terrain is available, no need to load imagery.
  1070. if (!defined(tile.data) || !defined(tile.data.vertexArray)) {
  1071. queueTileLoad(
  1072. primitive,
  1073. primitive._tileLoadQueueMedium,
  1074. tile,
  1075. frameState
  1076. );
  1077. }
  1078. const lastFrame = primitive._lastSelectionFrameNumber;
  1079. const lastFrameSelectionResult =
  1080. tile._lastSelectionResultFrame === lastFrame
  1081. ? tile._lastSelectionResult
  1082. : TileSelectionResult.NONE;
  1083. if (
  1084. lastFrameSelectionResult !== TileSelectionResult.CULLED_BUT_NEEDED &&
  1085. lastFrameSelectionResult !== TileSelectionResult.RENDERED
  1086. ) {
  1087. primitive._tileToUpdateHeights.push(tile);
  1088. }
  1089. tile._lastSelectionResult = TileSelectionResult.CULLED_BUT_NEEDED;
  1090. } else if (primitive.preloadSiblings || tile.level === 0) {
  1091. // Load culled level zero tiles with low priority.
  1092. // For all other levels, only load culled tiles if preloadSiblings is enabled.
  1093. queueTileLoad(primitive, primitive._tileLoadQueueLow, tile, frameState);
  1094. tile._lastSelectionResult = TileSelectionResult.CULLED;
  1095. } else {
  1096. tile._lastSelectionResult = TileSelectionResult.CULLED;
  1097. }
  1098. tile._lastSelectionResultFrame = frameState.frameNumber;
  1099. }
  1100. function screenSpaceError(primitive, frameState, tile) {
  1101. if (
  1102. frameState.mode === SceneMode.SCENE2D ||
  1103. frameState.camera.frustum instanceof OrthographicFrustum ||
  1104. frameState.camera.frustum instanceof OrthographicOffCenterFrustum
  1105. ) {
  1106. return screenSpaceError2D(primitive, frameState, tile);
  1107. }
  1108. const maxGeometricError = primitive._tileProvider.getLevelMaximumGeometricError(
  1109. tile.level
  1110. );
  1111. const distance = tile._distance;
  1112. const height = frameState.context.drawingBufferHeight;
  1113. const sseDenominator = frameState.camera.frustum.sseDenominator;
  1114. let error = (maxGeometricError * height) / (distance * sseDenominator);
  1115. if (frameState.fog.enabled) {
  1116. error -=
  1117. CesiumMath.fog(distance, frameState.fog.density) * frameState.fog.sse;
  1118. }
  1119. error /= frameState.pixelRatio;
  1120. return error;
  1121. }
  1122. function screenSpaceError2D(primitive, frameState, tile) {
  1123. const camera = frameState.camera;
  1124. let frustum = camera.frustum;
  1125. if (defined(frustum._offCenterFrustum)) {
  1126. frustum = frustum._offCenterFrustum;
  1127. }
  1128. const context = frameState.context;
  1129. const width = context.drawingBufferWidth;
  1130. const height = context.drawingBufferHeight;
  1131. const maxGeometricError = primitive._tileProvider.getLevelMaximumGeometricError(
  1132. tile.level
  1133. );
  1134. const pixelSize =
  1135. Math.max(frustum.top - frustum.bottom, frustum.right - frustum.left) /
  1136. Math.max(width, height);
  1137. let error = maxGeometricError / pixelSize;
  1138. if (frameState.fog.enabled && frameState.mode !== SceneMode.SCENE2D) {
  1139. error -=
  1140. CesiumMath.fog(tile._distance, frameState.fog.density) *
  1141. frameState.fog.sse;
  1142. }
  1143. error /= frameState.pixelRatio;
  1144. return error;
  1145. }
  1146. function addTileToRenderList(primitive, tile) {
  1147. primitive._tilesToRender.push(tile);
  1148. }
  1149. function processTileLoadQueue(primitive, frameState) {
  1150. const tileLoadQueueHigh = primitive._tileLoadQueueHigh;
  1151. const tileLoadQueueMedium = primitive._tileLoadQueueMedium;
  1152. const tileLoadQueueLow = primitive._tileLoadQueueLow;
  1153. if (
  1154. tileLoadQueueHigh.length === 0 &&
  1155. tileLoadQueueMedium.length === 0 &&
  1156. tileLoadQueueLow.length === 0
  1157. ) {
  1158. return;
  1159. }
  1160. // Remove any tiles that were not used this frame beyond the number
  1161. // we're allowed to keep.
  1162. primitive._tileReplacementQueue.trimTiles(primitive.tileCacheSize);
  1163. const endTime = getTimestamp() + primitive._loadQueueTimeSlice;
  1164. const tileProvider = primitive._tileProvider;
  1165. let didSomeLoading = processSinglePriorityLoadQueue(
  1166. primitive,
  1167. frameState,
  1168. tileProvider,
  1169. endTime,
  1170. tileLoadQueueHigh,
  1171. false
  1172. );
  1173. didSomeLoading = processSinglePriorityLoadQueue(
  1174. primitive,
  1175. frameState,
  1176. tileProvider,
  1177. endTime,
  1178. tileLoadQueueMedium,
  1179. didSomeLoading
  1180. );
  1181. processSinglePriorityLoadQueue(
  1182. primitive,
  1183. frameState,
  1184. tileProvider,
  1185. endTime,
  1186. tileLoadQueueLow,
  1187. didSomeLoading
  1188. );
  1189. }
  1190. function sortByLoadPriority(a, b) {
  1191. return a._loadPriority - b._loadPriority;
  1192. }
  1193. function processSinglePriorityLoadQueue(
  1194. primitive,
  1195. frameState,
  1196. tileProvider,
  1197. endTime,
  1198. loadQueue,
  1199. didSomeLoading
  1200. ) {
  1201. if (tileProvider.computeTileLoadPriority !== undefined) {
  1202. loadQueue.sort(sortByLoadPriority);
  1203. }
  1204. for (
  1205. let i = 0, len = loadQueue.length;
  1206. i < len && (getTimestamp() < endTime || !didSomeLoading);
  1207. ++i
  1208. ) {
  1209. const tile = loadQueue[i];
  1210. primitive._tileReplacementQueue.markTileRendered(tile);
  1211. tileProvider.loadTile(frameState, tile);
  1212. didSomeLoading = true;
  1213. }
  1214. return didSomeLoading;
  1215. }
  1216. const scratchRay = new Ray();
  1217. const scratchCartographic = new Cartographic();
  1218. const scratchPosition = new Cartesian3();
  1219. const scratchArray = [];
  1220. function updateHeights(primitive, frameState) {
  1221. if (!primitive.tileProvider.ready) {
  1222. return;
  1223. }
  1224. const tryNextFrame = scratchArray;
  1225. tryNextFrame.length = 0;
  1226. const tilesToUpdateHeights = primitive._tileToUpdateHeights;
  1227. const startTime = getTimestamp();
  1228. const timeSlice = primitive._updateHeightsTimeSlice;
  1229. const endTime = startTime + timeSlice;
  1230. const mode = frameState.mode;
  1231. const projection = frameState.mapProjection;
  1232. const ellipsoid = primitive.tileProvider.tilingScheme.ellipsoid;
  1233. let i;
  1234. while (tilesToUpdateHeights.length > 0) {
  1235. const tile = tilesToUpdateHeights[0];
  1236. if (!defined(tile.data) || !defined(tile.data.mesh)) {
  1237. // Tile isn't loaded enough yet, so try again next frame if this tile is still
  1238. // being rendered.
  1239. const selectionResult =
  1240. tile._lastSelectionResultFrame === primitive._lastSelectionFrameNumber
  1241. ? tile._lastSelectionResult
  1242. : TileSelectionResult.NONE;
  1243. if (
  1244. selectionResult === TileSelectionResult.RENDERED ||
  1245. selectionResult === TileSelectionResult.CULLED_BUT_NEEDED
  1246. ) {
  1247. tryNextFrame.push(tile);
  1248. }
  1249. tilesToUpdateHeights.shift();
  1250. primitive._lastTileIndex = 0;
  1251. continue;
  1252. }
  1253. const customData = tile.customData;
  1254. const customDataLength = customData.length;
  1255. let timeSliceMax = false;
  1256. for (i = primitive._lastTileIndex; i < customDataLength; ++i) {
  1257. const data = customData[i];
  1258. // No need to run this code when the tile is upsampled, because the height will be the same as its parent.
  1259. const terrainData = tile.data.terrainData;
  1260. const upsampledGeometryFromParent =
  1261. defined(terrainData) && terrainData.wasCreatedByUpsampling();
  1262. if (tile.level > data.level && !upsampledGeometryFromParent) {
  1263. if (!defined(data.positionOnEllipsoidSurface)) {
  1264. // cartesian has to be on the ellipsoid surface for `ellipsoid.geodeticSurfaceNormal`
  1265. data.positionOnEllipsoidSurface = Cartesian3.fromRadians(
  1266. data.positionCartographic.longitude,
  1267. data.positionCartographic.latitude,
  1268. 0.0,
  1269. ellipsoid
  1270. );
  1271. }
  1272. if (mode === SceneMode.SCENE3D) {
  1273. const surfaceNormal = ellipsoid.geodeticSurfaceNormal(
  1274. data.positionOnEllipsoidSurface,
  1275. scratchRay.direction
  1276. );
  1277. // compute origin point
  1278. // Try to find the intersection point between the surface normal and z-axis.
  1279. // minimum height (-11500.0) for the terrain set, need to get this information from the terrain provider
  1280. const rayOrigin = ellipsoid.getSurfaceNormalIntersectionWithZAxis(
  1281. data.positionOnEllipsoidSurface,
  1282. 11500.0,
  1283. scratchRay.origin
  1284. );
  1285. // Theoretically, not with Earth datums, the intersection point can be outside the ellipsoid
  1286. if (!defined(rayOrigin)) {
  1287. // intersection point is outside the ellipsoid, try other value
  1288. // minimum height (-11500.0) for the terrain set, need to get this information from the terrain provider
  1289. let minimumHeight = 0.0;
  1290. if (defined(tile.data.tileBoundingRegion)) {
  1291. minimumHeight = tile.data.tileBoundingRegion.minimumHeight;
  1292. }
  1293. const magnitude = Math.min(minimumHeight, -11500.0);
  1294. // multiply by the *positive* value of the magnitude
  1295. const vectorToMinimumPoint = Cartesian3.multiplyByScalar(
  1296. surfaceNormal,
  1297. Math.abs(magnitude) + 1,
  1298. scratchPosition
  1299. );
  1300. Cartesian3.subtract(
  1301. data.positionOnEllipsoidSurface,
  1302. vectorToMinimumPoint,
  1303. scratchRay.origin
  1304. );
  1305. }
  1306. } else {
  1307. Cartographic.clone(data.positionCartographic, scratchCartographic);
  1308. // minimum height for the terrain set, need to get this information from the terrain provider
  1309. scratchCartographic.height = -11500.0;
  1310. projection.project(scratchCartographic, scratchPosition);
  1311. Cartesian3.fromElements(
  1312. scratchPosition.z,
  1313. scratchPosition.x,
  1314. scratchPosition.y,
  1315. scratchPosition
  1316. );
  1317. Cartesian3.clone(scratchPosition, scratchRay.origin);
  1318. Cartesian3.clone(Cartesian3.UNIT_X, scratchRay.direction);
  1319. }
  1320. const position = tile.data.pick(
  1321. scratchRay,
  1322. mode,
  1323. projection,
  1324. false,
  1325. scratchPosition
  1326. );
  1327. if (defined(position)) {
  1328. if (defined(data.callback)) {
  1329. data.callback(position);
  1330. }
  1331. data.level = tile.level;
  1332. }
  1333. }
  1334. if (getTimestamp() >= endTime) {
  1335. timeSliceMax = true;
  1336. break;
  1337. }
  1338. }
  1339. if (timeSliceMax) {
  1340. primitive._lastTileIndex = i;
  1341. break;
  1342. } else {
  1343. primitive._lastTileIndex = 0;
  1344. tilesToUpdateHeights.shift();
  1345. }
  1346. }
  1347. for (i = 0; i < tryNextFrame.length; i++) {
  1348. tilesToUpdateHeights.push(tryNextFrame[i]);
  1349. }
  1350. }
  1351. function createRenderCommandsForSelectedTiles(primitive, frameState) {
  1352. const tileProvider = primitive._tileProvider;
  1353. const tilesToRender = primitive._tilesToRender;
  1354. for (let i = 0, len = tilesToRender.length; i < len; ++i) {
  1355. const tile = tilesToRender[i];
  1356. tileProvider.showTileThisFrame(tile, frameState);
  1357. }
  1358. }
  1359. export default QuadtreePrimitive;