Cesium3DTilesetTraversal.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773
  1. import defined from "../Core/defined.js";
  2. import Intersect from "../Core/Intersect.js";
  3. import ManagedArray from "../Core/ManagedArray.js";
  4. import Cesium3DTileOptimizationHint from "./Cesium3DTileOptimizationHint.js";
  5. import Cesium3DTileRefine from "./Cesium3DTileRefine.js";
  6. /**
  7. * @private
  8. */
  9. function Cesium3DTilesetTraversal() {}
  10. function isVisible(tile) {
  11. return tile._visible && tile._inRequestVolume;
  12. }
  13. const traversal = {
  14. stack: new ManagedArray(),
  15. stackMaximumLength: 0,
  16. };
  17. const emptyTraversal = {
  18. stack: new ManagedArray(),
  19. stackMaximumLength: 0,
  20. };
  21. const descendantTraversal = {
  22. stack: new ManagedArray(),
  23. stackMaximumLength: 0,
  24. };
  25. const selectionTraversal = {
  26. stack: new ManagedArray(),
  27. stackMaximumLength: 0,
  28. ancestorStack: new ManagedArray(),
  29. ancestorStackMaximumLength: 0,
  30. };
  31. const descendantSelectionDepth = 2;
  32. Cesium3DTilesetTraversal.selectTiles = function (tileset, frameState) {
  33. tileset._requestedTiles.length = 0;
  34. if (tileset.debugFreezeFrame) {
  35. return;
  36. }
  37. tileset._selectedTiles.length = 0;
  38. tileset._selectedTilesToStyle.length = 0;
  39. tileset._emptyTiles.length = 0;
  40. tileset._hasMixedContent = false;
  41. const root = tileset.root;
  42. updateTile(tileset, root, frameState);
  43. // The root tile is not visible
  44. if (!isVisible(root)) {
  45. return;
  46. }
  47. // The tileset doesn't meet the SSE requirement, therefore the tree does not need to be rendered
  48. if (
  49. root.getScreenSpaceError(frameState, true) <=
  50. tileset._maximumScreenSpaceError
  51. ) {
  52. return;
  53. }
  54. if (!skipLevelOfDetail(tileset)) {
  55. executeBaseTraversal(tileset, root, frameState);
  56. } else if (tileset.immediatelyLoadDesiredLevelOfDetail) {
  57. executeSkipTraversal(tileset, root, frameState);
  58. } else {
  59. executeBaseAndSkipTraversal(tileset, root, frameState);
  60. }
  61. traversal.stack.trim(traversal.stackMaximumLength);
  62. emptyTraversal.stack.trim(emptyTraversal.stackMaximumLength);
  63. descendantTraversal.stack.trim(descendantTraversal.stackMaximumLength);
  64. selectionTraversal.stack.trim(selectionTraversal.stackMaximumLength);
  65. selectionTraversal.ancestorStack.trim(
  66. selectionTraversal.ancestorStackMaximumLength
  67. );
  68. // Update the priority for any requests found during traversal
  69. // Update after traversal so that min and max values can be used to normalize priority values
  70. const requestedTiles = tileset._requestedTiles;
  71. const length = requestedTiles.length;
  72. for (let i = 0; i < length; ++i) {
  73. requestedTiles[i].updatePriority();
  74. }
  75. };
  76. function executeBaseTraversal(tileset, root, frameState) {
  77. const baseScreenSpaceError = tileset._maximumScreenSpaceError;
  78. const maximumScreenSpaceError = tileset._maximumScreenSpaceError;
  79. executeTraversal(
  80. tileset,
  81. root,
  82. baseScreenSpaceError,
  83. maximumScreenSpaceError,
  84. frameState
  85. );
  86. }
  87. function executeSkipTraversal(tileset, root, frameState) {
  88. const baseScreenSpaceError = Number.MAX_VALUE;
  89. const maximumScreenSpaceError = tileset._maximumScreenSpaceError;
  90. executeTraversal(
  91. tileset,
  92. root,
  93. baseScreenSpaceError,
  94. maximumScreenSpaceError,
  95. frameState
  96. );
  97. traverseAndSelect(tileset, root, frameState);
  98. }
  99. function executeBaseAndSkipTraversal(tileset, root, frameState) {
  100. const baseScreenSpaceError = Math.max(
  101. tileset.baseScreenSpaceError,
  102. tileset.maximumScreenSpaceError
  103. );
  104. const maximumScreenSpaceError = tileset.maximumScreenSpaceError;
  105. executeTraversal(
  106. tileset,
  107. root,
  108. baseScreenSpaceError,
  109. maximumScreenSpaceError,
  110. frameState
  111. );
  112. traverseAndSelect(tileset, root, frameState);
  113. }
  114. function skipLevelOfDetail(tileset) {
  115. return tileset._skipLevelOfDetail;
  116. }
  117. function addEmptyTile(tileset, tile) {
  118. tileset._emptyTiles.push(tile);
  119. }
  120. function selectTile(tileset, tile, frameState) {
  121. if (tile.contentVisibility(frameState) !== Intersect.OUTSIDE) {
  122. const tileContent = tile.content;
  123. if (tileContent.featurePropertiesDirty) {
  124. // A feature's property in this tile changed, the tile needs to be re-styled.
  125. tileContent.featurePropertiesDirty = false;
  126. tile.lastStyleTime = 0; // Force applying the style to this tile
  127. tileset._selectedTilesToStyle.push(tile);
  128. } else if (tile._selectedFrame < frameState.frameNumber - 1) {
  129. // Tile is newly selected; it is selected this frame, but was not selected last frame.
  130. tileset._selectedTilesToStyle.push(tile);
  131. }
  132. tile._selectedFrame = frameState.frameNumber;
  133. tileset._selectedTiles.push(tile);
  134. }
  135. }
  136. function selectDescendants(tileset, root, frameState) {
  137. const stack = descendantTraversal.stack;
  138. stack.push(root);
  139. while (stack.length > 0) {
  140. descendantTraversal.stackMaximumLength = Math.max(
  141. descendantTraversal.stackMaximumLength,
  142. stack.length
  143. );
  144. const tile = stack.pop();
  145. const children = tile.children;
  146. const childrenLength = children.length;
  147. for (let i = 0; i < childrenLength; ++i) {
  148. const child = children[i];
  149. if (isVisible(child)) {
  150. if (child.contentAvailable) {
  151. updateTile(tileset, child, frameState);
  152. touchTile(tileset, child, frameState);
  153. selectTile(tileset, child, frameState);
  154. } else if (child._depth - root._depth < descendantSelectionDepth) {
  155. // Continue traversing, but not too far
  156. stack.push(child);
  157. }
  158. }
  159. }
  160. }
  161. }
  162. function selectDesiredTile(tileset, tile, frameState) {
  163. if (!skipLevelOfDetail(tileset)) {
  164. if (tile.contentAvailable) {
  165. // The tile can be selected right away and does not require traverseAndSelect
  166. selectTile(tileset, tile, frameState);
  167. }
  168. return;
  169. }
  170. // If this tile is not loaded attempt to select its ancestor instead
  171. const loadedTile = tile.contentAvailable
  172. ? tile
  173. : tile._ancestorWithContentAvailable;
  174. if (defined(loadedTile)) {
  175. // Tiles will actually be selected in traverseAndSelect
  176. loadedTile._shouldSelect = true;
  177. } else {
  178. // If no ancestors are ready traverse down and select tiles to minimize empty regions.
  179. // This happens often for immediatelyLoadDesiredLevelOfDetail where parent tiles are not necessarily loaded before zooming out.
  180. selectDescendants(tileset, tile, frameState);
  181. }
  182. }
  183. function visitTile(tileset, tile, frameState) {
  184. ++tileset._statistics.visited;
  185. tile._visitedFrame = frameState.frameNumber;
  186. }
  187. function touchTile(tileset, tile, frameState) {
  188. if (tile._touchedFrame === frameState.frameNumber) {
  189. // Prevents another pass from touching the frame again
  190. return;
  191. }
  192. tileset._cache.touch(tile);
  193. tile._touchedFrame = frameState.frameNumber;
  194. }
  195. function updateMinimumMaximumPriority(tileset, tile) {
  196. tileset._maximumPriority.distance = Math.max(
  197. tile._priorityHolder._distanceToCamera,
  198. tileset._maximumPriority.distance
  199. );
  200. tileset._minimumPriority.distance = Math.min(
  201. tile._priorityHolder._distanceToCamera,
  202. tileset._minimumPriority.distance
  203. );
  204. tileset._maximumPriority.depth = Math.max(
  205. tile._depth,
  206. tileset._maximumPriority.depth
  207. );
  208. tileset._minimumPriority.depth = Math.min(
  209. tile._depth,
  210. tileset._minimumPriority.depth
  211. );
  212. tileset._maximumPriority.foveatedFactor = Math.max(
  213. tile._priorityHolder._foveatedFactor,
  214. tileset._maximumPriority.foveatedFactor
  215. );
  216. tileset._minimumPriority.foveatedFactor = Math.min(
  217. tile._priorityHolder._foveatedFactor,
  218. tileset._minimumPriority.foveatedFactor
  219. );
  220. tileset._maximumPriority.reverseScreenSpaceError = Math.max(
  221. tile._priorityReverseScreenSpaceError,
  222. tileset._maximumPriority.reverseScreenSpaceError
  223. );
  224. tileset._minimumPriority.reverseScreenSpaceError = Math.min(
  225. tile._priorityReverseScreenSpaceError,
  226. tileset._minimumPriority.reverseScreenSpaceError
  227. );
  228. }
  229. function isOnScreenLongEnough(tileset, tile, frameState) {
  230. // Prevent unnecessary loads while camera is moving by getting the ratio of travel distance to tile size.
  231. if (!tileset._cullRequestsWhileMoving) {
  232. return true;
  233. }
  234. const sphere = tile.boundingSphere;
  235. const diameter = Math.max(sphere.radius * 2.0, 1.0);
  236. const camera = frameState.camera;
  237. const deltaMagnitude =
  238. camera.positionWCDeltaMagnitude !== 0.0
  239. ? camera.positionWCDeltaMagnitude
  240. : camera.positionWCDeltaMagnitudeLastFrame;
  241. const movementRatio =
  242. (tileset.cullRequestsWhileMovingMultiplier * deltaMagnitude) / diameter; // How do n frames of this movement compare to the tile's physical size.
  243. return movementRatio < 1.0;
  244. }
  245. function loadTile(tileset, tile, frameState) {
  246. if (
  247. tile._requestedFrame === frameState.frameNumber ||
  248. (!hasUnloadedContent(tile) && !tile.contentExpired)
  249. ) {
  250. return;
  251. }
  252. if (!isOnScreenLongEnough(tileset, tile, frameState)) {
  253. return;
  254. }
  255. const cameraHasNotStoppedMovingLongEnough =
  256. frameState.camera.timeSinceMoved < tileset.foveatedTimeDelay;
  257. if (tile.priorityDeferred && cameraHasNotStoppedMovingLongEnough) {
  258. return;
  259. }
  260. tile._requestedFrame = frameState.frameNumber;
  261. tileset._requestedTiles.push(tile);
  262. }
  263. function updateVisibility(tileset, tile, frameState) {
  264. if (tile._updatedVisibilityFrame === tileset._updatedVisibilityFrame) {
  265. // Return early if visibility has already been checked during the traversal.
  266. // The visibility may have already been checked if the cullWithChildrenBounds optimization is used.
  267. return;
  268. }
  269. tile.updateVisibility(frameState);
  270. tile._updatedVisibilityFrame = tileset._updatedVisibilityFrame;
  271. }
  272. function anyChildrenVisible(tileset, tile, frameState) {
  273. let anyVisible = false;
  274. const children = tile.children;
  275. const length = children.length;
  276. for (let i = 0; i < length; ++i) {
  277. const child = children[i];
  278. updateVisibility(tileset, child, frameState);
  279. anyVisible = anyVisible || isVisible(child);
  280. }
  281. return anyVisible;
  282. }
  283. function meetsScreenSpaceErrorEarly(tileset, tile, frameState) {
  284. const parent = tile.parent;
  285. if (
  286. !defined(parent) ||
  287. parent.hasTilesetContent ||
  288. parent.hasImplicitContent ||
  289. parent.refine !== Cesium3DTileRefine.ADD
  290. ) {
  291. return false;
  292. }
  293. // Use parent's geometric error with child's box to see if the tile already meet the SSE
  294. return (
  295. tile.getScreenSpaceError(frameState, true) <=
  296. tileset._maximumScreenSpaceError
  297. );
  298. }
  299. function updateTileVisibility(tileset, tile, frameState) {
  300. updateVisibility(tileset, tile, frameState);
  301. if (!isVisible(tile)) {
  302. return;
  303. }
  304. const hasChildren = tile.children.length > 0;
  305. if ((tile.hasTilesetContent || tile.hasImplicitContent) && hasChildren) {
  306. // Use the root tile's visibility instead of this tile's visibility.
  307. // The root tile may be culled by the children bounds optimization in which
  308. // case this tile should also be culled.
  309. const child = tile.children[0];
  310. updateTileVisibility(tileset, child, frameState);
  311. tile._visible = child._visible;
  312. return;
  313. }
  314. if (meetsScreenSpaceErrorEarly(tileset, tile, frameState)) {
  315. tile._visible = false;
  316. return;
  317. }
  318. // Optimization - if none of the tile's children are visible then this tile isn't visible
  319. const replace = tile.refine === Cesium3DTileRefine.REPLACE;
  320. const useOptimization =
  321. tile._optimChildrenWithinParent ===
  322. Cesium3DTileOptimizationHint.USE_OPTIMIZATION;
  323. if (replace && useOptimization && hasChildren) {
  324. if (!anyChildrenVisible(tileset, tile, frameState)) {
  325. ++tileset._statistics.numberOfTilesCulledWithChildrenUnion;
  326. tile._visible = false;
  327. return;
  328. }
  329. }
  330. }
  331. function updateTile(tileset, tile, frameState) {
  332. // Reset some of the tile's flags and re-evaluate visibility
  333. updateTileVisibility(tileset, tile, frameState);
  334. tile.updateExpiration();
  335. // Request priority
  336. tile._wasMinPriorityChild = false;
  337. tile._priorityHolder = tile;
  338. updateMinimumMaximumPriority(tileset, tile);
  339. // SkipLOD
  340. tile._shouldSelect = false;
  341. tile._finalResolution = true;
  342. }
  343. function updateTileAncestorContentLinks(tile, frameState) {
  344. tile._ancestorWithContent = undefined;
  345. tile._ancestorWithContentAvailable = undefined;
  346. const parent = tile.parent;
  347. if (defined(parent)) {
  348. // ancestorWithContent is an ancestor that has content or has the potential to have
  349. // content. Used in conjunction with tileset.skipLevels to know when to skip a tile.
  350. // ancestorWithContentAvailable is an ancestor that is rendered if a desired tile is not loaded.
  351. const hasContent =
  352. !hasUnloadedContent(parent) ||
  353. parent._requestedFrame === frameState.frameNumber;
  354. tile._ancestorWithContent = hasContent
  355. ? parent
  356. : parent._ancestorWithContent;
  357. tile._ancestorWithContentAvailable = parent.contentAvailable
  358. ? parent
  359. : parent._ancestorWithContentAvailable; // Links a descendant up to its contentAvailable ancestor as the traversal progresses.
  360. }
  361. }
  362. function hasEmptyContent(tile) {
  363. return (
  364. tile.hasEmptyContent || tile.hasTilesetContent || tile.hasImplicitContent
  365. );
  366. }
  367. function hasUnloadedContent(tile) {
  368. return !hasEmptyContent(tile) && tile.contentUnloaded;
  369. }
  370. function reachedSkippingThreshold(tileset, tile) {
  371. const ancestor = tile._ancestorWithContent;
  372. return (
  373. !tileset.immediatelyLoadDesiredLevelOfDetail &&
  374. (tile._priorityProgressiveResolutionScreenSpaceErrorLeaf ||
  375. (defined(ancestor) &&
  376. tile._screenSpaceError <
  377. ancestor._screenSpaceError / tileset.skipScreenSpaceErrorFactor &&
  378. tile._depth > ancestor._depth + tileset.skipLevels))
  379. );
  380. }
  381. function sortChildrenByDistanceToCamera(a, b) {
  382. // Sort by farthest child first since this is going on a stack
  383. if (b._distanceToCamera === 0 && a._distanceToCamera === 0) {
  384. return b._centerZDepth - a._centerZDepth;
  385. }
  386. return b._distanceToCamera - a._distanceToCamera;
  387. }
  388. function updateAndPushChildren(tileset, tile, stack, frameState) {
  389. let i;
  390. const replace = tile.refine === Cesium3DTileRefine.REPLACE;
  391. const children = tile.children;
  392. const length = children.length;
  393. for (i = 0; i < length; ++i) {
  394. updateTile(tileset, children[i], frameState);
  395. }
  396. // Sort by distance to take advantage of early Z and reduce artifacts for skipLevelOfDetail
  397. children.sort(sortChildrenByDistanceToCamera);
  398. // For traditional replacement refinement only refine if all children are loaded.
  399. // Empty tiles are exempt since it looks better if children stream in as they are loaded to fill the empty space.
  400. const checkRefines =
  401. !skipLevelOfDetail(tileset) && replace && !hasEmptyContent(tile);
  402. let refines = true;
  403. let anyChildrenVisible = false;
  404. // Determining min child
  405. let minIndex = -1;
  406. let minimumPriority = Number.MAX_VALUE;
  407. let child;
  408. for (i = 0; i < length; ++i) {
  409. child = children[i];
  410. if (isVisible(child)) {
  411. stack.push(child);
  412. if (child._foveatedFactor < minimumPriority) {
  413. minIndex = i;
  414. minimumPriority = child._foveatedFactor;
  415. }
  416. anyChildrenVisible = true;
  417. } else if (checkRefines || tileset.loadSiblings) {
  418. // Keep non-visible children loaded since they are still needed before the parent can refine.
  419. // Or loadSiblings is true so always load tiles regardless of visibility.
  420. if (child._foveatedFactor < minimumPriority) {
  421. minIndex = i;
  422. minimumPriority = child._foveatedFactor;
  423. }
  424. loadTile(tileset, child, frameState);
  425. touchTile(tileset, child, frameState);
  426. }
  427. if (checkRefines) {
  428. let childRefines;
  429. if (!child._inRequestVolume) {
  430. childRefines = false;
  431. } else if (hasEmptyContent(child)) {
  432. childRefines = executeEmptyTraversal(tileset, child, frameState);
  433. } else {
  434. childRefines = child.contentAvailable;
  435. }
  436. refines = refines && childRefines;
  437. }
  438. }
  439. if (!anyChildrenVisible) {
  440. refines = false;
  441. }
  442. if (minIndex !== -1 && !skipLevelOfDetail(tileset) && replace) {
  443. // An ancestor will hold the _foveatedFactor and _distanceToCamera for descendants between itself and its highest priority descendant. Siblings of a min children along the way use this ancestor as their priority holder as well.
  444. // Priority of all tiles that refer to the _foveatedFactor and _distanceToCamera stored in the common ancestor will be differentiated based on their _depth.
  445. const minPriorityChild = children[minIndex];
  446. minPriorityChild._wasMinPriorityChild = true;
  447. const priorityHolder =
  448. (tile._wasMinPriorityChild || tile === tileset.root) &&
  449. minimumPriority <= tile._priorityHolder._foveatedFactor
  450. ? tile._priorityHolder
  451. : tile; // This is where priority dependency chains are wired up or started anew.
  452. priorityHolder._foveatedFactor = Math.min(
  453. minPriorityChild._foveatedFactor,
  454. priorityHolder._foveatedFactor
  455. );
  456. priorityHolder._distanceToCamera = Math.min(
  457. minPriorityChild._distanceToCamera,
  458. priorityHolder._distanceToCamera
  459. );
  460. for (i = 0; i < length; ++i) {
  461. child = children[i];
  462. child._priorityHolder = priorityHolder;
  463. }
  464. }
  465. return refines;
  466. }
  467. function inBaseTraversal(tileset, tile, baseScreenSpaceError) {
  468. if (!skipLevelOfDetail(tileset)) {
  469. return true;
  470. }
  471. if (tileset.immediatelyLoadDesiredLevelOfDetail) {
  472. return false;
  473. }
  474. if (!defined(tile._ancestorWithContent)) {
  475. // Include root or near-root tiles in the base traversal so there is something to select up to
  476. return true;
  477. }
  478. if (tile._screenSpaceError === 0.0) {
  479. // If a leaf, use parent's SSE
  480. return tile.parent._screenSpaceError > baseScreenSpaceError;
  481. }
  482. return tile._screenSpaceError > baseScreenSpaceError;
  483. }
  484. function canTraverse(tileset, tile) {
  485. if (tile.children.length === 0) {
  486. return false;
  487. }
  488. if (tile.hasTilesetContent || tile.hasImplicitContent) {
  489. // Traverse external tileset to visit its root tile
  490. // Don't traverse if the subtree is expired because it will be destroyed
  491. return !tile.contentExpired;
  492. }
  493. return tile._screenSpaceError > tileset._maximumScreenSpaceError;
  494. }
  495. function executeTraversal(
  496. tileset,
  497. root,
  498. baseScreenSpaceError,
  499. maximumScreenSpaceError,
  500. frameState
  501. ) {
  502. // Depth-first traversal that traverses all visible tiles and marks tiles for selection.
  503. // If skipLevelOfDetail is off then a tile does not refine until all children are loaded.
  504. // This is the traditional replacement refinement approach and is called the base traversal.
  505. // Tiles that have a greater screen space error than the base screen space error are part of the base traversal,
  506. // all other tiles are part of the skip traversal. The skip traversal allows for skipping levels of the tree
  507. // and rendering children and parent tiles simultaneously.
  508. const stack = traversal.stack;
  509. stack.push(root);
  510. while (stack.length > 0) {
  511. traversal.stackMaximumLength = Math.max(
  512. traversal.stackMaximumLength,
  513. stack.length
  514. );
  515. const tile = stack.pop();
  516. updateTileAncestorContentLinks(tile, frameState);
  517. const baseTraversal = inBaseTraversal(tileset, tile, baseScreenSpaceError);
  518. const add = tile.refine === Cesium3DTileRefine.ADD;
  519. const replace = tile.refine === Cesium3DTileRefine.REPLACE;
  520. const parent = tile.parent;
  521. const parentRefines = !defined(parent) || parent._refines;
  522. let refines = false;
  523. if (canTraverse(tileset, tile)) {
  524. refines =
  525. updateAndPushChildren(tileset, tile, stack, frameState) &&
  526. parentRefines;
  527. }
  528. const stoppedRefining = !refines && parentRefines;
  529. if (hasEmptyContent(tile)) {
  530. // Add empty tile just to show its debug bounding volume
  531. // If the tile has tileset content load the external tileset
  532. // If the tile cannot refine further select its nearest loaded ancestor
  533. addEmptyTile(tileset, tile, frameState);
  534. loadTile(tileset, tile, frameState);
  535. if (stoppedRefining) {
  536. selectDesiredTile(tileset, tile, frameState);
  537. }
  538. } else if (add) {
  539. // Additive tiles are always loaded and selected
  540. selectDesiredTile(tileset, tile, frameState);
  541. loadTile(tileset, tile, frameState);
  542. } else if (replace) {
  543. if (baseTraversal) {
  544. // Always load tiles in the base traversal
  545. // Select tiles that can't refine further
  546. loadTile(tileset, tile, frameState);
  547. if (stoppedRefining) {
  548. selectDesiredTile(tileset, tile, frameState);
  549. }
  550. } else if (stoppedRefining) {
  551. // In skip traversal, load and select tiles that can't refine further
  552. selectDesiredTile(tileset, tile, frameState);
  553. loadTile(tileset, tile, frameState);
  554. } else if (reachedSkippingThreshold(tileset, tile)) {
  555. // In skip traversal, load tiles that aren't skipped. In practice roughly half the tiles stay unloaded.
  556. loadTile(tileset, tile, frameState);
  557. }
  558. }
  559. visitTile(tileset, tile, frameState);
  560. touchTile(tileset, tile, frameState);
  561. tile._refines = refines;
  562. }
  563. }
  564. function executeEmptyTraversal(tileset, root, frameState) {
  565. // Depth-first traversal that checks if all nearest descendants with content are loaded. Ignores visibility.
  566. let allDescendantsLoaded = true;
  567. const stack = emptyTraversal.stack;
  568. stack.push(root);
  569. while (stack.length > 0) {
  570. emptyTraversal.stackMaximumLength = Math.max(
  571. emptyTraversal.stackMaximumLength,
  572. stack.length
  573. );
  574. const tile = stack.pop();
  575. const children = tile.children;
  576. const childrenLength = children.length;
  577. // Only traverse if the tile is empty - traversal stop at descendants with content
  578. const emptyContent = hasEmptyContent(tile);
  579. const traverse = emptyContent && canTraverse(tileset, tile);
  580. const emptyLeaf = emptyContent && tile.children.length === 0;
  581. // Traversal stops but the tile does not have content yet
  582. // There will be holes if the parent tries to refine to its children, so don't refine
  583. // One exception: a parent may refine even if one of its descendants is an empty leaf
  584. if (!traverse && !tile.contentAvailable && !emptyLeaf) {
  585. allDescendantsLoaded = false;
  586. }
  587. updateTile(tileset, tile, frameState);
  588. if (!isVisible(tile)) {
  589. // Load tiles that aren't visible since they are still needed for the parent to refine
  590. loadTile(tileset, tile, frameState);
  591. touchTile(tileset, tile, frameState);
  592. }
  593. if (traverse) {
  594. for (let i = 0; i < childrenLength; ++i) {
  595. const child = children[i];
  596. stack.push(child);
  597. }
  598. }
  599. }
  600. return allDescendantsLoaded;
  601. }
  602. /**
  603. * Traverse the tree and check if their selected frame is the current frame. If so, add it to a selection queue.
  604. * This is a preorder traversal so children tiles are selected before ancestor tiles.
  605. *
  606. * The reason for the preorder traversal is so that tiles can easily be marked with their
  607. * selection depth. A tile's _selectionDepth is its depth in the tree where all non-selected tiles are removed.
  608. * This property is important for use in the stencil test because we want to render deeper tiles on top of their
  609. * ancestors. If a tileset is very deep, the depth is unlikely to fit into the stencil buffer.
  610. *
  611. * We want to select children before their ancestors because there is no guarantee on the relationship between
  612. * the children's z-depth and the ancestor's z-depth. We cannot rely on Z because we want the child to appear on top
  613. * of ancestor regardless of true depth. The stencil tests used require children to be drawn first.
  614. *
  615. * NOTE: 3D Tiles uses 3 bits from the stencil buffer meaning this will not work when there is a chain of
  616. * selected tiles that is deeper than 7. This is not very likely.
  617. * @private
  618. */
  619. function traverseAndSelect(tileset, root, frameState) {
  620. const stack = selectionTraversal.stack;
  621. const ancestorStack = selectionTraversal.ancestorStack;
  622. let lastAncestor;
  623. stack.push(root);
  624. while (stack.length > 0 || ancestorStack.length > 0) {
  625. selectionTraversal.stackMaximumLength = Math.max(
  626. selectionTraversal.stackMaximumLength,
  627. stack.length
  628. );
  629. selectionTraversal.ancestorStackMaximumLength = Math.max(
  630. selectionTraversal.ancestorStackMaximumLength,
  631. ancestorStack.length
  632. );
  633. if (ancestorStack.length > 0) {
  634. const waitingTile = ancestorStack.peek();
  635. if (waitingTile._stackLength === stack.length) {
  636. ancestorStack.pop();
  637. if (waitingTile !== lastAncestor) {
  638. waitingTile._finalResolution = false;
  639. }
  640. selectTile(tileset, waitingTile, frameState);
  641. continue;
  642. }
  643. }
  644. const tile = stack.pop();
  645. if (!defined(tile)) {
  646. // stack is empty but ancestorStack isn't
  647. continue;
  648. }
  649. const add = tile.refine === Cesium3DTileRefine.ADD;
  650. const shouldSelect = tile._shouldSelect;
  651. const children = tile.children;
  652. const childrenLength = children.length;
  653. const traverse = canTraverse(tileset, tile);
  654. if (shouldSelect) {
  655. if (add) {
  656. selectTile(tileset, tile, frameState);
  657. } else {
  658. tile._selectionDepth = ancestorStack.length;
  659. if (tile._selectionDepth > 0) {
  660. tileset._hasMixedContent = true;
  661. }
  662. lastAncestor = tile;
  663. if (!traverse) {
  664. selectTile(tileset, tile, frameState);
  665. continue;
  666. }
  667. ancestorStack.push(tile);
  668. tile._stackLength = stack.length;
  669. }
  670. }
  671. if (traverse) {
  672. for (let i = 0; i < childrenLength; ++i) {
  673. const child = children[i];
  674. if (isVisible(child)) {
  675. stack.push(child);
  676. }
  677. }
  678. }
  679. }
  680. }
  681. export default Cesium3DTilesetTraversal;