Cesium3DTilesetTraversal.js 28 KB

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