智慧水务管理系统 - 精河县供水工程综合管理平台

Cesium3DTilesetSkipTraversal.js 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  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. * Allows for skipping levels of the tree and rendering children and parent tiles simultaneously.
  8. *
  9. * @alias Cesium3DTilesetSkipTraversal
  10. * @constructor
  11. *
  12. * @private
  13. */
  14. function Cesium3DTilesetSkipTraversal() {}
  15. const traversal = {
  16. stack: new ManagedArray(),
  17. stackMaximumLength: 0,
  18. };
  19. const descendantTraversal = {
  20. stack: new ManagedArray(),
  21. stackMaximumLength: 0,
  22. };
  23. const selectionTraversal = {
  24. stack: new ManagedArray(),
  25. stackMaximumLength: 0,
  26. ancestorStack: new ManagedArray(),
  27. ancestorStackMaximumLength: 0,
  28. };
  29. const descendantSelectionDepth = 2;
  30. /**
  31. * Traverses a {@link Cesium3DTileset} to determine which tiles to load and render.
  32. *
  33. * @private
  34. * @param {Cesium3DTileset} tileset
  35. * @param {FrameState} frameState
  36. */
  37. Cesium3DTilesetSkipTraversal.selectTiles = function (tileset, frameState) {
  38. tileset._requestedTiles.length = 0;
  39. if (tileset.debugFreezeFrame) {
  40. return;
  41. }
  42. tileset._selectedTiles.length = 0;
  43. tileset._selectedTilesToStyle.length = 0;
  44. tileset._emptyTiles.length = 0;
  45. tileset.hasMixedContent = false;
  46. const root = tileset.root;
  47. Cesium3DTilesetTraversal.updateTile(root, frameState);
  48. if (!root.isVisible) {
  49. return;
  50. }
  51. if (
  52. root.getScreenSpaceError(frameState, true) <=
  53. tileset.memoryAdjustedScreenSpaceError
  54. ) {
  55. return;
  56. }
  57. executeTraversal(root, frameState);
  58. traverseAndSelect(root, frameState);
  59. traversal.stack.trim(traversal.stackMaximumLength);
  60. descendantTraversal.stack.trim(descendantTraversal.stackMaximumLength);
  61. selectionTraversal.stack.trim(selectionTraversal.stackMaximumLength);
  62. selectionTraversal.ancestorStack.trim(
  63. selectionTraversal.ancestorStackMaximumLength,
  64. );
  65. // Update the priority for any requests found during traversal
  66. // Update after traversal so that min and max values can be used to normalize priority values
  67. const requestedTiles = tileset._requestedTiles;
  68. for (let i = 0; i < requestedTiles.length; ++i) {
  69. requestedTiles[i].updatePriority();
  70. }
  71. };
  72. /**
  73. * Mark descendant tiles for rendering, and update as needed
  74. *
  75. * @private
  76. * @param {Cesium3DTile} root
  77. * @param {FrameState} frameState
  78. */
  79. function selectDescendants(root, frameState) {
  80. const { updateTile, touchTile, selectTile } = Cesium3DTilesetTraversal;
  81. const stack = descendantTraversal.stack;
  82. stack.push(root);
  83. while (stack.length > 0) {
  84. descendantTraversal.stackMaximumLength = Math.max(
  85. descendantTraversal.stackMaximumLength,
  86. stack.length,
  87. );
  88. const tile = stack.pop();
  89. const children = tile.children;
  90. for (let i = 0; i < children.length; ++i) {
  91. const child = children[i];
  92. if (child.isVisible) {
  93. if (child.contentAvailable) {
  94. updateTile(child, frameState);
  95. touchTile(child, frameState);
  96. selectTile(child, frameState);
  97. } else if (child._depth - root._depth < descendantSelectionDepth) {
  98. // Continue traversing, but not too far
  99. stack.push(child);
  100. }
  101. }
  102. }
  103. }
  104. }
  105. /**
  106. * Mark a tile as selected if it has content available.
  107. * If its content is not available, and we are skipping levels of detail,
  108. * select an ancestor or descendant tile instead
  109. *
  110. * @private
  111. * @param {Cesium3DTile} tile
  112. * @param {FrameState} frameState
  113. */
  114. function selectDesiredTile(tile, frameState) {
  115. // If this tile is not loaded attempt to select its ancestor instead
  116. const loadedTile = tile.contentAvailable
  117. ? tile
  118. : tile._ancestorWithContentAvailable;
  119. if (defined(loadedTile)) {
  120. // Tiles will actually be selected in traverseAndSelect
  121. loadedTile._shouldSelect = true;
  122. } else {
  123. // If no ancestors are ready traverse down and select tiles to minimize empty regions.
  124. // This happens often for immediatelyLoadDesiredLevelOfDetail where parent tiles are not necessarily loaded before zooming out.
  125. selectDescendants(tile, frameState);
  126. }
  127. }
  128. /**
  129. * Update links to the ancestor tiles that have content
  130. *
  131. * @private
  132. * @param {Cesium3DTile} tile
  133. * @param {FrameState} frameState
  134. */
  135. function updateTileAncestorContentLinks(tile, frameState) {
  136. tile._ancestorWithContent = undefined;
  137. tile._ancestorWithContentAvailable = undefined;
  138. const { parent } = tile;
  139. if (!defined(parent)) {
  140. return;
  141. }
  142. const parentHasContent =
  143. !parent.hasUnloadedRenderableContent ||
  144. parent._requestedFrame === frameState.frameNumber;
  145. // ancestorWithContent is an ancestor that has content or has the potential to have
  146. // content. Used in conjunction with tileset.skipLevels to know when to skip a tile.
  147. tile._ancestorWithContent = parentHasContent
  148. ? parent
  149. : parent._ancestorWithContent;
  150. // ancestorWithContentAvailable is an ancestor that is rendered if a desired tile is not loaded
  151. tile._ancestorWithContentAvailable = parent.contentAvailable
  152. ? parent
  153. : parent._ancestorWithContentAvailable;
  154. }
  155. /**
  156. * Determine if a tile has reached the limit of level of detail skipping.
  157. * If so, it should _not_ be skipped: it should be loaded and rendered
  158. *
  159. * @private
  160. * @param {Cesium3DTileset} tileset
  161. * @param {Cesium3DTile} tile
  162. * @returns {boolean} true if this tile should not be skipped
  163. */
  164. function reachedSkippingThreshold(tileset, tile) {
  165. const ancestor = tile._ancestorWithContent;
  166. return (
  167. !tileset.immediatelyLoadDesiredLevelOfDetail &&
  168. (tile._priorityProgressiveResolutionScreenSpaceErrorLeaf ||
  169. (defined(ancestor) &&
  170. tile._screenSpaceError <
  171. ancestor._screenSpaceError / tileset.skipScreenSpaceErrorFactor &&
  172. tile._depth > ancestor._depth + tileset.skipLevels))
  173. );
  174. }
  175. /**
  176. * @private
  177. * @param {Cesium3DTile} tile
  178. * @param {ManagedArray} stack
  179. * @param {FrameState} frameState
  180. * @returns {boolean}
  181. */
  182. function updateAndPushChildren(tile, stack, frameState) {
  183. const { tileset, children } = tile;
  184. const { updateTile, loadTile, touchTile } = Cesium3DTilesetTraversal;
  185. for (let i = 0; i < children.length; ++i) {
  186. updateTile(children[i], frameState);
  187. }
  188. // Sort by distance to take advantage of early Z and reduce artifacts
  189. children.sort(Cesium3DTilesetTraversal.sortChildrenByDistanceToCamera);
  190. let anyChildrenVisible = false;
  191. for (let i = 0; i < children.length; ++i) {
  192. const child = children[i];
  193. if (child.isVisible) {
  194. stack.push(child);
  195. anyChildrenVisible = true;
  196. } else if (tileset.loadSiblings) {
  197. loadTile(child, frameState);
  198. touchTile(child, frameState);
  199. }
  200. }
  201. return anyChildrenVisible;
  202. }
  203. /**
  204. * Determine if a tile is part of the base traversal.
  205. * If not, this tile could be considered for level of detail skipping
  206. *
  207. * @private
  208. * @param {Cesium3DTile} tile
  209. * @param {number} baseScreenSpaceError
  210. * @returns {boolean}
  211. */
  212. function inBaseTraversal(tile, baseScreenSpaceError) {
  213. const { tileset } = tile;
  214. if (tileset.immediatelyLoadDesiredLevelOfDetail) {
  215. return false;
  216. }
  217. if (!defined(tile._ancestorWithContent)) {
  218. // Include root or near-root tiles in the base traversal so there is something to select up to
  219. return true;
  220. }
  221. if (tile._screenSpaceError === 0.0) {
  222. // If a leaf, use parent's SSE
  223. return tile.parent._screenSpaceError > baseScreenSpaceError;
  224. }
  225. return tile._screenSpaceError > baseScreenSpaceError;
  226. }
  227. /**
  228. * Depth-first traversal that traverses all visible tiles and marks tiles for selection.
  229. * Tiles that have a greater screen space error than the base screen space error are part of the base traversal,
  230. * all other tiles are part of the skip traversal. The skip traversal allows for skipping levels of the tree
  231. * and rendering children and parent tiles simultaneously.
  232. *
  233. * @private
  234. * @param {Cesium3DTile} root
  235. * @param {FrameState} frameState
  236. */
  237. function executeTraversal(root, frameState) {
  238. const { tileset } = root;
  239. const baseScreenSpaceError = tileset.immediatelyLoadDesiredLevelOfDetail
  240. ? Number.MAX_VALUE
  241. : Math.max(
  242. tileset.baseScreenSpaceError,
  243. tileset.memoryAdjustedScreenSpaceError,
  244. );
  245. const { canTraverse, loadTile, visitTile, touchTile } =
  246. Cesium3DTilesetTraversal;
  247. const stack = traversal.stack;
  248. stack.push(root);
  249. while (stack.length > 0) {
  250. traversal.stackMaximumLength = Math.max(
  251. traversal.stackMaximumLength,
  252. stack.length,
  253. );
  254. const tile = stack.pop();
  255. updateTileAncestorContentLinks(tile, frameState);
  256. const parent = tile.parent;
  257. const parentRefines = !defined(parent) || parent._refines;
  258. tile._refines = canTraverse(tile)
  259. ? updateAndPushChildren(tile, stack, frameState) && parentRefines
  260. : false;
  261. const stoppedRefining = !tile._refines && parentRefines;
  262. if (!tile.hasRenderableContent) {
  263. // Add empty tile just to show its debug bounding volume
  264. // If the tile has tileset content load the external tileset
  265. // If the tile cannot refine further select its nearest loaded ancestor
  266. tileset._emptyTiles.push(tile);
  267. loadTile(tile, frameState);
  268. if (stoppedRefining) {
  269. selectDesiredTile(tile, frameState);
  270. }
  271. } else if (tile.refine === Cesium3DTileRefine.ADD) {
  272. // Additive tiles are always loaded and selected
  273. selectDesiredTile(tile, frameState);
  274. loadTile(tile, frameState);
  275. } else if (tile.refine === Cesium3DTileRefine.REPLACE) {
  276. if (inBaseTraversal(tile, baseScreenSpaceError)) {
  277. // Always load tiles in the base traversal
  278. // Select tiles that can't refine further
  279. loadTile(tile, frameState);
  280. if (stoppedRefining) {
  281. selectDesiredTile(tile, frameState);
  282. }
  283. } else if (stoppedRefining) {
  284. // In skip traversal, load and select tiles that can't refine further
  285. selectDesiredTile(tile, frameState);
  286. loadTile(tile, frameState);
  287. } else if (reachedSkippingThreshold(tileset, tile)) {
  288. // In skip traversal, load tiles that aren't skipped
  289. loadTile(tile, frameState);
  290. }
  291. }
  292. visitTile(tile, frameState);
  293. touchTile(tile, frameState);
  294. }
  295. }
  296. /**
  297. * Traverse the tree and check if their selected frame is the current frame. If so, add it to a selection queue.
  298. * This is a preorder traversal so children tiles are selected before ancestor tiles.
  299. *
  300. * The reason for the preorder traversal is so that tiles can easily be marked with their
  301. * selection depth. A tile's _selectionDepth is its depth in the tree where all non-selected tiles are removed.
  302. * This property is important for use in the stencil test because we want to render deeper tiles on top of their
  303. * ancestors. If a tileset is very deep, the depth is unlikely to fit into the stencil buffer.
  304. *
  305. * We want to select children before their ancestors because there is no guarantee on the relationship between
  306. * 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
  307. * of ancestor regardless of true depth. The stencil tests used require children to be drawn first.
  308. *
  309. * NOTE: 3D Tiles uses 3 bits from the stencil buffer meaning this will not work when there is a chain of
  310. * selected tiles that is deeper than 7. This is not very likely.
  311. *
  312. * @private
  313. * @param {Cesium3DTile} root
  314. * @param {FrameState} frameState
  315. */
  316. function traverseAndSelect(root, frameState) {
  317. const { selectTile, canTraverse } = Cesium3DTilesetTraversal;
  318. const { stack, ancestorStack } = selectionTraversal;
  319. let lastAncestor;
  320. stack.push(root);
  321. while (stack.length > 0 || ancestorStack.length > 0) {
  322. selectionTraversal.stackMaximumLength = Math.max(
  323. selectionTraversal.stackMaximumLength,
  324. stack.length,
  325. );
  326. selectionTraversal.ancestorStackMaximumLength = Math.max(
  327. selectionTraversal.ancestorStackMaximumLength,
  328. ancestorStack.length,
  329. );
  330. if (ancestorStack.length > 0) {
  331. const waitingTile = ancestorStack.peek();
  332. if (waitingTile._stackLength === stack.length) {
  333. ancestorStack.pop();
  334. if (waitingTile !== lastAncestor) {
  335. waitingTile._finalResolution = false;
  336. }
  337. selectTile(waitingTile, frameState);
  338. continue;
  339. }
  340. }
  341. const tile = stack.pop();
  342. if (!defined(tile)) {
  343. // stack is empty but ancestorStack isn't
  344. continue;
  345. }
  346. const traverse = canTraverse(tile);
  347. if (tile._shouldSelect) {
  348. if (tile.refine === Cesium3DTileRefine.ADD) {
  349. selectTile(tile, frameState);
  350. } else {
  351. tile._selectionDepth = ancestorStack.length;
  352. if (tile._selectionDepth > 0) {
  353. tile.tileset.hasMixedContent = true;
  354. }
  355. lastAncestor = tile;
  356. if (!traverse) {
  357. selectTile(tile, frameState);
  358. continue;
  359. }
  360. ancestorStack.push(tile);
  361. tile._stackLength = stack.length;
  362. }
  363. }
  364. if (traverse) {
  365. const children = tile.children;
  366. for (let i = 0; i < children.length; ++i) {
  367. const child = children[i];
  368. if (child.isVisible) {
  369. stack.push(child);
  370. }
  371. }
  372. }
  373. }
  374. }
  375. export default Cesium3DTilesetSkipTraversal;