Cesium3DTilesetTraversal.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770
  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. var traversal = {
  14. stack: new ManagedArray(),
  15. stackMaximumLength: 0,
  16. };
  17. var emptyTraversal = {
  18. stack: new ManagedArray(),
  19. stackMaximumLength: 0,
  20. };
  21. var descendantTraversal = {
  22. stack: new ManagedArray(),
  23. stackMaximumLength: 0,
  24. };
  25. var selectionTraversal = {
  26. stack: new ManagedArray(),
  27. stackMaximumLength: 0,
  28. ancestorStack: new ManagedArray(),
  29. ancestorStackMaximumLength: 0,
  30. };
  31. var 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. var 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. var requestedTiles = tileset._requestedTiles;
  71. var length = requestedTiles.length;
  72. for (var i = 0; i < length; ++i) {
  73. requestedTiles[i].updatePriority();
  74. }
  75. };
  76. function executeBaseTraversal(tileset, root, frameState) {
  77. var baseScreenSpaceError = tileset._maximumScreenSpaceError;
  78. var maximumScreenSpaceError = tileset._maximumScreenSpaceError;
  79. executeTraversal(
  80. tileset,
  81. root,
  82. baseScreenSpaceError,
  83. maximumScreenSpaceError,
  84. frameState
  85. );
  86. }
  87. function executeSkipTraversal(tileset, root, frameState) {
  88. var baseScreenSpaceError = Number.MAX_VALUE;
  89. var 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. var baseScreenSpaceError = Math.max(
  101. tileset.baseScreenSpaceError,
  102. tileset.maximumScreenSpaceError
  103. );
  104. var 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. var 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. var 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. var tile = stack.pop();
  145. var children = tile.children;
  146. var childrenLength = children.length;
  147. for (var i = 0; i < childrenLength; ++i) {
  148. var 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. var 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. var sphere = tile.boundingSphere;
  235. var diameter = Math.max(sphere.radius * 2.0, 1.0);
  236. var camera = frameState.camera;
  237. var deltaMagnitude =
  238. camera.positionWCDeltaMagnitude !== 0.0
  239. ? camera.positionWCDeltaMagnitude
  240. : camera.positionWCDeltaMagnitudeLastFrame;
  241. var 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. var 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. var anyVisible = false;
  274. var children = tile.children;
  275. var length = children.length;
  276. for (var i = 0; i < length; ++i) {
  277. var 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. var parent = tile.parent;
  285. if (
  286. !defined(parent) ||
  287. parent.hasTilesetContent ||
  288. parent.refine !== Cesium3DTileRefine.ADD
  289. ) {
  290. return false;
  291. }
  292. // Use parent's geometric error with child's box to see if the tile already meet the SSE
  293. return (
  294. tile.getScreenSpaceError(frameState, true) <=
  295. tileset._maximumScreenSpaceError
  296. );
  297. }
  298. function updateTileVisibility(tileset, tile, frameState) {
  299. updateVisibility(tileset, tile, frameState);
  300. if (!isVisible(tile)) {
  301. return;
  302. }
  303. var hasChildren = tile.children.length > 0;
  304. if (tile.hasTilesetContent && hasChildren) {
  305. // Use the root tile's visibility instead of this tile's visibility.
  306. // The root tile may be culled by the children bounds optimization in which
  307. // case this tile should also be culled.
  308. var child = tile.children[0];
  309. updateTileVisibility(tileset, child, frameState);
  310. tile._visible = child._visible;
  311. return;
  312. }
  313. if (meetsScreenSpaceErrorEarly(tileset, tile, frameState)) {
  314. tile._visible = false;
  315. return;
  316. }
  317. // Optimization - if none of the tile's children are visible then this tile isn't visible
  318. var replace = tile.refine === Cesium3DTileRefine.REPLACE;
  319. var useOptimization =
  320. tile._optimChildrenWithinParent ===
  321. Cesium3DTileOptimizationHint.USE_OPTIMIZATION;
  322. if (replace && useOptimization && hasChildren) {
  323. if (!anyChildrenVisible(tileset, tile, frameState)) {
  324. ++tileset._statistics.numberOfTilesCulledWithChildrenUnion;
  325. tile._visible = false;
  326. return;
  327. }
  328. }
  329. }
  330. function updateTile(tileset, tile, frameState) {
  331. // Reset some of the tile's flags and re-evaluate visibility
  332. updateTileVisibility(tileset, tile, frameState);
  333. tile.updateExpiration();
  334. // Request priority
  335. tile._wasMinPriorityChild = false;
  336. tile._priorityHolder = tile;
  337. updateMinimumMaximumPriority(tileset, tile);
  338. // SkipLOD
  339. tile._shouldSelect = false;
  340. tile._finalResolution = true;
  341. }
  342. function updateTileAncestorContentLinks(tile, frameState) {
  343. tile._ancestorWithContent = undefined;
  344. tile._ancestorWithContentAvailable = undefined;
  345. var parent = tile.parent;
  346. if (defined(parent)) {
  347. // ancestorWithContent is an ancestor that has content or has the potential to have
  348. // content. Used in conjunction with tileset.skipLevels to know when to skip a tile.
  349. // ancestorWithContentAvailable is an ancestor that is rendered if a desired tile is not loaded.
  350. var hasContent =
  351. !hasUnloadedContent(parent) ||
  352. parent._requestedFrame === frameState.frameNumber;
  353. tile._ancestorWithContent = hasContent
  354. ? parent
  355. : parent._ancestorWithContent;
  356. tile._ancestorWithContentAvailable = parent.contentAvailable
  357. ? parent
  358. : parent._ancestorWithContentAvailable; // Links a descendant up to its contentAvailable ancestor as the traversal progresses.
  359. }
  360. }
  361. function hasEmptyContent(tile) {
  362. return tile.hasEmptyContent || tile.hasTilesetContent;
  363. }
  364. function hasUnloadedContent(tile) {
  365. return !hasEmptyContent(tile) && tile.contentUnloaded;
  366. }
  367. function reachedSkippingThreshold(tileset, tile) {
  368. var ancestor = tile._ancestorWithContent;
  369. return (
  370. !tileset.immediatelyLoadDesiredLevelOfDetail &&
  371. (tile._priorityProgressiveResolutionScreenSpaceErrorLeaf ||
  372. (defined(ancestor) &&
  373. tile._screenSpaceError <
  374. ancestor._screenSpaceError / tileset.skipScreenSpaceErrorFactor &&
  375. tile._depth > ancestor._depth + tileset.skipLevels))
  376. );
  377. }
  378. function sortChildrenByDistanceToCamera(a, b) {
  379. // Sort by farthest child first since this is going on a stack
  380. if (b._distanceToCamera === 0 && a._distanceToCamera === 0) {
  381. return b._centerZDepth - a._centerZDepth;
  382. }
  383. return b._distanceToCamera - a._distanceToCamera;
  384. }
  385. function updateAndPushChildren(tileset, tile, stack, frameState) {
  386. var i;
  387. var replace = tile.refine === Cesium3DTileRefine.REPLACE;
  388. var children = tile.children;
  389. var length = children.length;
  390. for (i = 0; i < length; ++i) {
  391. updateTile(tileset, children[i], frameState);
  392. }
  393. // Sort by distance to take advantage of early Z and reduce artifacts for skipLevelOfDetail
  394. children.sort(sortChildrenByDistanceToCamera);
  395. // For traditional replacement refinement only refine if all children are loaded.
  396. // Empty tiles are exempt since it looks better if children stream in as they are loaded to fill the empty space.
  397. var checkRefines =
  398. !skipLevelOfDetail(tileset) && replace && !hasEmptyContent(tile);
  399. var refines = true;
  400. var anyChildrenVisible = false;
  401. // Determining min child
  402. var minIndex = -1;
  403. var minimumPriority = Number.MAX_VALUE;
  404. var child;
  405. for (i = 0; i < length; ++i) {
  406. child = children[i];
  407. if (isVisible(child)) {
  408. stack.push(child);
  409. if (child._foveatedFactor < minimumPriority) {
  410. minIndex = i;
  411. minimumPriority = child._foveatedFactor;
  412. }
  413. anyChildrenVisible = true;
  414. } else if (checkRefines || tileset.loadSiblings) {
  415. // Keep non-visible children loaded since they are still needed before the parent can refine.
  416. // Or loadSiblings is true so always load tiles regardless of visibility.
  417. if (child._foveatedFactor < minimumPriority) {
  418. minIndex = i;
  419. minimumPriority = child._foveatedFactor;
  420. }
  421. loadTile(tileset, child, frameState);
  422. touchTile(tileset, child, frameState);
  423. }
  424. if (checkRefines) {
  425. var childRefines;
  426. if (!child._inRequestVolume) {
  427. childRefines = false;
  428. } else if (hasEmptyContent(child)) {
  429. childRefines = executeEmptyTraversal(tileset, child, frameState);
  430. } else {
  431. childRefines = child.contentAvailable;
  432. }
  433. refines = refines && childRefines;
  434. }
  435. }
  436. if (!anyChildrenVisible) {
  437. refines = false;
  438. }
  439. if (minIndex !== -1 && !skipLevelOfDetail(tileset) && replace) {
  440. // 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.
  441. // Priority of all tiles that refer to the _foveatedFactor and _distanceToCamera stored in the common ancestor will be differentiated based on their _depth.
  442. var minPriorityChild = children[minIndex];
  443. minPriorityChild._wasMinPriorityChild = true;
  444. var priorityHolder =
  445. (tile._wasMinPriorityChild || tile === tileset.root) &&
  446. minimumPriority <= tile._priorityHolder._foveatedFactor
  447. ? tile._priorityHolder
  448. : tile; // This is where priority dependency chains are wired up or started anew.
  449. priorityHolder._foveatedFactor = Math.min(
  450. minPriorityChild._foveatedFactor,
  451. priorityHolder._foveatedFactor
  452. );
  453. priorityHolder._distanceToCamera = Math.min(
  454. minPriorityChild._distanceToCamera,
  455. priorityHolder._distanceToCamera
  456. );
  457. for (i = 0; i < length; ++i) {
  458. child = children[i];
  459. child._priorityHolder = priorityHolder;
  460. }
  461. }
  462. return refines;
  463. }
  464. function inBaseTraversal(tileset, tile, baseScreenSpaceError) {
  465. if (!skipLevelOfDetail(tileset)) {
  466. return true;
  467. }
  468. if (tileset.immediatelyLoadDesiredLevelOfDetail) {
  469. return false;
  470. }
  471. if (!defined(tile._ancestorWithContent)) {
  472. // Include root or near-root tiles in the base traversal so there is something to select up to
  473. return true;
  474. }
  475. if (tile._screenSpaceError === 0.0) {
  476. // If a leaf, use parent's SSE
  477. return tile.parent._screenSpaceError > baseScreenSpaceError;
  478. }
  479. return tile._screenSpaceError > baseScreenSpaceError;
  480. }
  481. function canTraverse(tileset, tile) {
  482. if (tile.children.length === 0) {
  483. return false;
  484. }
  485. if (tile.hasTilesetContent) {
  486. // Traverse external tileset to visit its root tile
  487. // Don't traverse if the subtree is expired because it will be destroyed
  488. return !tile.contentExpired;
  489. }
  490. return tile._screenSpaceError > tileset._maximumScreenSpaceError;
  491. }
  492. function executeTraversal(
  493. tileset,
  494. root,
  495. baseScreenSpaceError,
  496. maximumScreenSpaceError,
  497. frameState
  498. ) {
  499. // Depth-first traversal that traverses all visible tiles and marks tiles for selection.
  500. // If skipLevelOfDetail is off then a tile does not refine until all children are loaded.
  501. // This is the traditional replacement refinement approach and is called the base traversal.
  502. // Tiles that have a greater screen space error than the base screen space error are part of the base traversal,
  503. // all other tiles are part of the skip traversal. The skip traversal allows for skipping levels of the tree
  504. // and rendering children and parent tiles simultaneously.
  505. var stack = traversal.stack;
  506. stack.push(root);
  507. while (stack.length > 0) {
  508. traversal.stackMaximumLength = Math.max(
  509. traversal.stackMaximumLength,
  510. stack.length
  511. );
  512. var tile = stack.pop();
  513. updateTileAncestorContentLinks(tile, frameState);
  514. var baseTraversal = inBaseTraversal(tileset, tile, baseScreenSpaceError);
  515. var add = tile.refine === Cesium3DTileRefine.ADD;
  516. var replace = tile.refine === Cesium3DTileRefine.REPLACE;
  517. var parent = tile.parent;
  518. var parentRefines = !defined(parent) || parent._refines;
  519. var refines = false;
  520. if (canTraverse(tileset, tile)) {
  521. refines =
  522. updateAndPushChildren(tileset, tile, stack, frameState) &&
  523. parentRefines;
  524. }
  525. var stoppedRefining = !refines && parentRefines;
  526. if (hasEmptyContent(tile)) {
  527. // Add empty tile just to show its debug bounding volume
  528. // If the tile has tileset content load the external tileset
  529. // If the tile cannot refine further select its nearest loaded ancestor
  530. addEmptyTile(tileset, tile, frameState);
  531. loadTile(tileset, tile, frameState);
  532. if (stoppedRefining) {
  533. selectDesiredTile(tileset, tile, frameState);
  534. }
  535. } else if (add) {
  536. // Additive tiles are always loaded and selected
  537. selectDesiredTile(tileset, tile, frameState);
  538. loadTile(tileset, tile, frameState);
  539. } else if (replace) {
  540. if (baseTraversal) {
  541. // Always load tiles in the base traversal
  542. // Select tiles that can't refine further
  543. loadTile(tileset, tile, frameState);
  544. if (stoppedRefining) {
  545. selectDesiredTile(tileset, tile, frameState);
  546. }
  547. } else if (stoppedRefining) {
  548. // In skip traversal, load and select tiles that can't refine further
  549. selectDesiredTile(tileset, tile, frameState);
  550. loadTile(tileset, tile, frameState);
  551. } else if (reachedSkippingThreshold(tileset, tile)) {
  552. // In skip traversal, load tiles that aren't skipped. In practice roughly half the tiles stay unloaded.
  553. loadTile(tileset, tile, frameState);
  554. }
  555. }
  556. visitTile(tileset, tile, frameState);
  557. touchTile(tileset, tile, frameState);
  558. tile._refines = refines;
  559. }
  560. }
  561. function executeEmptyTraversal(tileset, root, frameState) {
  562. // Depth-first traversal that checks if all nearest descendants with content are loaded. Ignores visibility.
  563. var allDescendantsLoaded = true;
  564. var stack = emptyTraversal.stack;
  565. stack.push(root);
  566. while (stack.length > 0) {
  567. emptyTraversal.stackMaximumLength = Math.max(
  568. emptyTraversal.stackMaximumLength,
  569. stack.length
  570. );
  571. var tile = stack.pop();
  572. var children = tile.children;
  573. var childrenLength = children.length;
  574. // Only traverse if the tile is empty - traversal stop at descendants with content
  575. var emptyContent = hasEmptyContent(tile);
  576. var traverse = emptyContent && canTraverse(tileset, tile);
  577. var emptyLeaf = emptyContent && tile.children.length === 0;
  578. // Traversal stops but the tile does not have content yet
  579. // There will be holes if the parent tries to refine to its children, so don't refine
  580. // One exception: a parent may refine even if one of its descendants is an empty leaf
  581. if (!traverse && !tile.contentAvailable && !emptyLeaf) {
  582. allDescendantsLoaded = false;
  583. }
  584. updateTile(tileset, tile, frameState);
  585. if (!isVisible(tile)) {
  586. // Load tiles that aren't visible since they are still needed for the parent to refine
  587. loadTile(tileset, tile, frameState);
  588. touchTile(tileset, tile, frameState);
  589. }
  590. if (traverse) {
  591. for (var i = 0; i < childrenLength; ++i) {
  592. var child = children[i];
  593. stack.push(child);
  594. }
  595. }
  596. }
  597. return allDescendantsLoaded;
  598. }
  599. /**
  600. * Traverse the tree and check if their selected frame is the current frame. If so, add it to a selection queue.
  601. * This is a preorder traversal so children tiles are selected before ancestor tiles.
  602. *
  603. * The reason for the preorder traversal is so that tiles can easily be marked with their
  604. * selection depth. A tile's _selectionDepth is its depth in the tree where all non-selected tiles are removed.
  605. * This property is important for use in the stencil test because we want to render deeper tiles on top of their
  606. * ancestors. If a tileset is very deep, the depth is unlikely to fit into the stencil buffer.
  607. *
  608. * We want to select children before their ancestors because there is no guarantee on the relationship between
  609. * 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
  610. * of ancestor regardless of true depth. The stencil tests used require children to be drawn first.
  611. *
  612. * NOTE: 3D Tiles uses 3 bits from the stencil buffer meaning this will not work when there is a chain of
  613. * selected tiles that is deeper than 7. This is not very likely.
  614. * @private
  615. */
  616. function traverseAndSelect(tileset, root, frameState) {
  617. var stack = selectionTraversal.stack;
  618. var ancestorStack = selectionTraversal.ancestorStack;
  619. var lastAncestor;
  620. stack.push(root);
  621. while (stack.length > 0 || ancestorStack.length > 0) {
  622. selectionTraversal.stackMaximumLength = Math.max(
  623. selectionTraversal.stackMaximumLength,
  624. stack.length
  625. );
  626. selectionTraversal.ancestorStackMaximumLength = Math.max(
  627. selectionTraversal.ancestorStackMaximumLength,
  628. ancestorStack.length
  629. );
  630. if (ancestorStack.length > 0) {
  631. var waitingTile = ancestorStack.peek();
  632. if (waitingTile._stackLength === stack.length) {
  633. ancestorStack.pop();
  634. if (waitingTile !== lastAncestor) {
  635. waitingTile._finalResolution = false;
  636. }
  637. selectTile(tileset, waitingTile, frameState);
  638. continue;
  639. }
  640. }
  641. var tile = stack.pop();
  642. if (!defined(tile)) {
  643. // stack is empty but ancestorStack isn't
  644. continue;
  645. }
  646. var add = tile.refine === Cesium3DTileRefine.ADD;
  647. var shouldSelect = tile._shouldSelect;
  648. var children = tile.children;
  649. var childrenLength = children.length;
  650. var traverse = canTraverse(tileset, tile);
  651. if (shouldSelect) {
  652. if (add) {
  653. selectTile(tileset, tile, frameState);
  654. } else {
  655. tile._selectionDepth = ancestorStack.length;
  656. if (tile._selectionDepth > 0) {
  657. tileset._hasMixedContent = true;
  658. }
  659. lastAncestor = tile;
  660. if (!traverse) {
  661. selectTile(tileset, tile, frameState);
  662. continue;
  663. }
  664. ancestorStack.push(tile);
  665. tile._stackLength = stack.length;
  666. }
  667. }
  668. if (traverse) {
  669. for (var i = 0; i < childrenLength; ++i) {
  670. var child = children[i];
  671. if (isVisible(child)) {
  672. stack.push(child);
  673. }
  674. }
  675. }
  676. }
  677. }
  678. export default Cesium3DTilesetTraversal;