Cesium3DTilesetBaseTraversal.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. import defined from "../Core/defined.js";
  2. import ManagedArray from "../Core/ManagedArray.js";
  3. import Cesium3DTileRefine from "./Cesium3DTileRefine.js";
  4. import Cesium3DTilesetTraversal from "./Cesium3DTilesetTraversal.js";
  5. /**
  6. * Depth-first traversal that traverses all visible tiles and marks tiles for selection.
  7. * A tile does not refine until all children are loaded.
  8. * This is the traditional replacement refinement approach and is called the base traversal.
  9. *
  10. * @alias Cesium3DTilesetBaseTraversal
  11. * @constructor
  12. *
  13. * @private
  14. */
  15. function Cesium3DTilesetBaseTraversal() {}
  16. const traversal = {
  17. stack: new ManagedArray(),
  18. stackMaximumLength: 0,
  19. };
  20. const emptyTraversal = {
  21. stack: new ManagedArray(),
  22. stackMaximumLength: 0,
  23. };
  24. /**
  25. * Traverses a {@link Cesium3DTileset} to determine which tiles to load and render.
  26. *
  27. * @private
  28. * @param {Cesium3DTileset} tileset
  29. * @param {FrameState} frameState
  30. */
  31. Cesium3DTilesetBaseTraversal.selectTiles = function (tileset, frameState) {
  32. tileset._requestedTiles.length = 0;
  33. if (tileset.debugFreezeFrame) {
  34. return;
  35. }
  36. tileset._selectedTiles.length = 0;
  37. tileset._selectedTilesToStyle.length = 0;
  38. tileset._emptyTiles.length = 0;
  39. tileset.hasMixedContent = false;
  40. const root = tileset.root;
  41. Cesium3DTilesetTraversal.updateTile(root, frameState);
  42. if (!root.isVisible) {
  43. return;
  44. }
  45. if (
  46. root.getScreenSpaceError(frameState, true) <=
  47. tileset._maximumScreenSpaceError
  48. ) {
  49. return;
  50. }
  51. executeTraversal(root, frameState);
  52. traversal.stack.trim(traversal.stackMaximumLength);
  53. emptyTraversal.stack.trim(emptyTraversal.stackMaximumLength);
  54. // Update the priority for any requests found during traversal
  55. // Update after traversal so that min and max values can be used to normalize priority values
  56. const requestedTiles = tileset._requestedTiles;
  57. for (let i = 0; i < requestedTiles.length; ++i) {
  58. requestedTiles[i].updatePriority();
  59. }
  60. };
  61. /**
  62. * Mark a tile as selected if it has content available.
  63. *
  64. * @private
  65. * @param {Cesium3DTile} tile
  66. * @param {FrameState} frameState
  67. */
  68. function selectDesiredTile(tile, frameState) {
  69. if (tile.contentAvailable) {
  70. Cesium3DTilesetTraversal.selectTile(tile, frameState);
  71. }
  72. }
  73. /**
  74. * @private
  75. * @param {Cesium3DTile} tile
  76. * @param {ManagedArray} stack
  77. * @param {FrameState} frameState
  78. * @returns {boolean}
  79. */
  80. function updateAndPushChildren(tile, stack, frameState) {
  81. const replace = tile.refine === Cesium3DTileRefine.REPLACE;
  82. const { tileset, children } = tile;
  83. const { updateTile, loadTile, touchTile } = Cesium3DTilesetTraversal;
  84. for (let i = 0; i < children.length; ++i) {
  85. updateTile(children[i], frameState);
  86. }
  87. // Sort by distance to take advantage of early Z and reduce artifacts for skipLevelOfDetail
  88. children.sort(Cesium3DTilesetTraversal.sortChildrenByDistanceToCamera);
  89. // For traditional replacement refinement only refine if all children are loaded.
  90. // Empty tiles are exempt since it looks better if children stream in as they are loaded to fill the empty space.
  91. const checkRefines = replace && tile.hasRenderableContent;
  92. let refines = true;
  93. let anyChildrenVisible = false;
  94. // Determining min child
  95. let minIndex = -1;
  96. let minimumPriority = Number.MAX_VALUE;
  97. for (let i = 0; i < children.length; ++i) {
  98. const child = children[i];
  99. if (child.isVisible) {
  100. stack.push(child);
  101. if (child._foveatedFactor < minimumPriority) {
  102. minIndex = i;
  103. minimumPriority = child._foveatedFactor;
  104. }
  105. anyChildrenVisible = true;
  106. } else if (checkRefines || tileset.loadSiblings) {
  107. // Keep non-visible children loaded since they are still needed before the parent can refine.
  108. // Or loadSiblings is true so always load tiles regardless of visibility.
  109. if (child._foveatedFactor < minimumPriority) {
  110. minIndex = i;
  111. minimumPriority = child._foveatedFactor;
  112. }
  113. loadTile(child, frameState);
  114. touchTile(child, frameState);
  115. }
  116. if (checkRefines) {
  117. let childRefines;
  118. if (!child._inRequestVolume) {
  119. childRefines = false;
  120. } else if (!child.hasRenderableContent) {
  121. childRefines = executeEmptyTraversal(child, frameState);
  122. } else {
  123. childRefines = child.contentAvailable;
  124. }
  125. refines = refines && childRefines;
  126. }
  127. }
  128. if (!anyChildrenVisible) {
  129. refines = false;
  130. }
  131. if (minIndex !== -1 && replace) {
  132. // 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.
  133. // Priority of all tiles that refer to the _foveatedFactor and _distanceToCamera stored in the common ancestor will be differentiated based on their _depth.
  134. const minPriorityChild = children[minIndex];
  135. minPriorityChild._wasMinPriorityChild = true;
  136. const priorityHolder =
  137. (tile._wasMinPriorityChild || tile === tileset.root) &&
  138. minimumPriority <= tile._priorityHolder._foveatedFactor
  139. ? tile._priorityHolder
  140. : tile; // This is where priority dependency chains are wired up or started anew.
  141. priorityHolder._foveatedFactor = Math.min(
  142. minPriorityChild._foveatedFactor,
  143. priorityHolder._foveatedFactor
  144. );
  145. priorityHolder._distanceToCamera = Math.min(
  146. minPriorityChild._distanceToCamera,
  147. priorityHolder._distanceToCamera
  148. );
  149. for (let i = 0; i < children.length; ++i) {
  150. children[i]._priorityHolder = priorityHolder;
  151. }
  152. }
  153. return refines;
  154. }
  155. /**
  156. * Depth-first traversal that traverses all visible tiles and marks tiles for selection.
  157. * A tile does not refine until all children are loaded.
  158. * This is the traditional replacement refinement approach and is called the base traversal.
  159. *
  160. * @private
  161. * @param {Cesium3DTile} root
  162. * @param {FrameState} frameState
  163. */
  164. function executeTraversal(root, frameState) {
  165. const { tileset } = root;
  166. const {
  167. canTraverse,
  168. loadTile,
  169. visitTile,
  170. touchTile,
  171. } = Cesium3DTilesetTraversal;
  172. const stack = traversal.stack;
  173. stack.push(root);
  174. while (stack.length > 0) {
  175. traversal.stackMaximumLength = Math.max(
  176. traversal.stackMaximumLength,
  177. stack.length
  178. );
  179. const tile = stack.pop();
  180. const parent = tile.parent;
  181. const parentRefines = !defined(parent) || parent._refines;
  182. tile._refines = canTraverse(tile)
  183. ? updateAndPushChildren(tile, stack, frameState) && parentRefines
  184. : false;
  185. const stoppedRefining = !tile._refines && parentRefines;
  186. if (!tile.hasRenderableContent) {
  187. // Add empty tile just to show its debug bounding volume
  188. // If the tile has tileset content load the external tileset
  189. tileset._emptyTiles.push(tile);
  190. loadTile(tile, frameState);
  191. if (stoppedRefining) {
  192. selectDesiredTile(tile, frameState);
  193. }
  194. } else if (tile.refine === Cesium3DTileRefine.ADD) {
  195. // Additive tiles are always loaded and selected
  196. selectDesiredTile(tile, frameState);
  197. loadTile(tile, frameState);
  198. } else if (tile.refine === Cesium3DTileRefine.REPLACE) {
  199. loadTile(tile, frameState);
  200. if (stoppedRefining) {
  201. selectDesiredTile(tile, frameState);
  202. }
  203. }
  204. visitTile(tile, frameState);
  205. touchTile(tile, frameState);
  206. }
  207. }
  208. /**
  209. * Depth-first traversal that checks if all nearest descendants with content are loaded.
  210. * Ignores visibility.
  211. *
  212. * @private
  213. * @param {Cesium3DTile} root
  214. * @param {FrameState} frameState
  215. * @returns {boolean}
  216. */
  217. function executeEmptyTraversal(root, frameState) {
  218. const {
  219. canTraverse,
  220. updateTile,
  221. loadTile,
  222. touchTile,
  223. } = Cesium3DTilesetTraversal;
  224. let allDescendantsLoaded = true;
  225. const stack = emptyTraversal.stack;
  226. stack.push(root);
  227. while (stack.length > 0) {
  228. emptyTraversal.stackMaximumLength = Math.max(
  229. emptyTraversal.stackMaximumLength,
  230. stack.length
  231. );
  232. const tile = stack.pop();
  233. const children = tile.children;
  234. const childrenLength = children.length;
  235. // Only traverse if the tile is empty - traversal stops at descendants with content
  236. const traverse = !tile.hasRenderableContent && canTraverse(tile);
  237. const emptyLeaf = !tile.hasRenderableContent && tile.children.length === 0;
  238. // Traversal stops but the tile does not have content yet
  239. // There will be holes if the parent tries to refine to its children, so don't refine
  240. // One exception: a parent may refine even if one of its descendants is an empty leaf
  241. if (!traverse && !tile.contentAvailable && !emptyLeaf) {
  242. allDescendantsLoaded = false;
  243. }
  244. updateTile(tile, frameState);
  245. if (!tile.isVisible) {
  246. // Load tiles that aren't visible since they are still needed for the parent to refine
  247. loadTile(tile, frameState);
  248. touchTile(tile, frameState);
  249. }
  250. if (traverse) {
  251. for (let i = 0; i < childrenLength; ++i) {
  252. const child = children[i];
  253. stack.push(child);
  254. }
  255. }
  256. }
  257. return allDescendantsLoaded;
  258. }
  259. export default Cesium3DTilesetBaseTraversal;