traverseFunctions.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. import { LOADED, FAILED } from './constants.js';
  2. function isDownloadFinished( value ) {
  3. return value === LOADED || value === FAILED;
  4. }
  5. // Checks whether this tile was last used on the given frame.
  6. function isUsedThisFrame( tile, frameCount ) {
  7. return tile.__lastFrameVisited === frameCount && tile.__used;
  8. }
  9. // Resets the frame frame information for the given tile
  10. function resetFrameState( tile, frameCount ) {
  11. if ( tile.__lastFrameVisited !== frameCount ) {
  12. tile.__lastFrameVisited = frameCount;
  13. tile.__used = false;
  14. tile.__inFrustum = false;
  15. tile.__isLeaf = false;
  16. tile.__visible = false;
  17. tile.__active = false;
  18. tile.__error = Infinity;
  19. tile.__distanceFromCamera = Infinity;
  20. tile.__childrenWereVisible = false;
  21. tile.__allChildrenLoaded = false;
  22. }
  23. }
  24. // Recursively mark tiles used down to the next tile with content
  25. function recursivelyMarkUsed( tile, frameCount, lruCache ) {
  26. resetFrameState( tile, frameCount );
  27. tile.__used = true;
  28. lruCache.markUsed( tile );
  29. if ( tile.__contentEmpty ) {
  30. const children = tile.children;
  31. for ( let i = 0, l = children.length; i < l; i ++ ) {
  32. recursivelyMarkUsed( children[ i ], frameCount, lruCache );
  33. }
  34. }
  35. }
  36. function recursivelyLoadTiles( tile, depthFromRenderedParent, renderer ) {
  37. // Try to load any external tile set children if the external tile set has loaded.
  38. const doTraverse =
  39. tile.__contentEmpty && (
  40. ! tile.__externalTileSet ||
  41. isDownloadFinished( tile.__loadingState )
  42. );
  43. if ( doTraverse ) {
  44. const children = tile.children;
  45. for ( let i = 0, l = children.length; i < l; i ++ ) {
  46. // don't increment depth to rendered parent here because we should treat
  47. // the next layer of rendered children as just a single depth away for the
  48. // sake of sorting.
  49. const child = children[ i ];
  50. child.__depthFromRenderedParent = depthFromRenderedParent;
  51. recursivelyLoadTiles( child, depthFromRenderedParent, renderer );
  52. }
  53. } else {
  54. renderer.requestTileContents( tile );
  55. }
  56. }
  57. // Helper function for recursively traversing a tile set. If `beforeCb` returns `true` then the
  58. // traversal will end early.
  59. export function traverseSet( tile, beforeCb = null, afterCb = null, parent = null, depth = 0 ) {
  60. if ( beforeCb && beforeCb( tile, parent, depth ) ) {
  61. if ( afterCb ) {
  62. afterCb( tile, parent, depth );
  63. }
  64. return;
  65. }
  66. const children = tile.children;
  67. for ( let i = 0, l = children.length; i < l; i ++ ) {
  68. traverseSet( children[ i ], beforeCb, afterCb, tile, depth + 1 );
  69. }
  70. if ( afterCb ) {
  71. afterCb( tile, parent, depth );
  72. }
  73. }
  74. // Determine which tiles are within the camera frustum.
  75. // TODO: this is marking items as used in the lrucache, which means some data is
  76. // being kept around that isn't being used -- is that okay?
  77. export function determineFrustumSet( tile, renderer ) {
  78. const stats = renderer.stats;
  79. const frameCount = renderer.frameCount;
  80. const errorTarget = renderer.errorTarget;
  81. const maxDepth = renderer.maxDepth;
  82. const loadSiblings = renderer.loadSiblings;
  83. const lruCache = renderer.lruCache;
  84. const stopAtEmptyTiles = renderer.stopAtEmptyTiles;
  85. resetFrameState( tile, frameCount );
  86. // Early out if this tile is not within view.
  87. const inFrustum = renderer.tileInView( tile );
  88. if ( inFrustum === false ) {
  89. return false;
  90. }
  91. tile.__used = true;
  92. lruCache.markUsed( tile );
  93. tile.__inFrustum = true;
  94. stats.inFrustum ++;
  95. // Early out if this tile has less error than we're targeting but don't stop
  96. // at an external tile set.
  97. if ( ( stopAtEmptyTiles || ! tile.__contentEmpty ) && ! tile.__externalTileSet ) {
  98. // compute the _error and __distanceFromCamera fields
  99. renderer.calculateError( tile );
  100. const error = tile.__error;
  101. if ( error <= errorTarget ) {
  102. return true;
  103. }
  104. // Early out if we've reached the maximum allowed depth.
  105. if ( renderer.maxDepth > 0 && tile.__depth + 1 >= maxDepth ) {
  106. return true;
  107. }
  108. }
  109. // Traverse children and see if any children are in view.
  110. let anyChildrenUsed = false;
  111. const children = tile.children;
  112. for ( let i = 0, l = children.length; i < l; i ++ ) {
  113. const c = children[ i ];
  114. const r = determineFrustumSet( c, renderer );
  115. anyChildrenUsed = anyChildrenUsed || r;
  116. }
  117. // If there are children within view and we are loading siblings then mark
  118. // all sibling tiles as used, as well.
  119. if ( anyChildrenUsed && loadSiblings ) {
  120. for ( let i = 0, l = children.length; i < l; i ++ ) {
  121. const c = children[ i ];
  122. recursivelyMarkUsed( c, frameCount, lruCache );
  123. }
  124. }
  125. return true;
  126. }
  127. // Traverse and mark the tiles that are at the leaf nodes of the "used" tree.
  128. export function markUsedSetLeaves( tile, renderer ) {
  129. const stats = renderer.stats;
  130. const frameCount = renderer.frameCount;
  131. if ( ! isUsedThisFrame( tile, frameCount ) ) {
  132. return;
  133. }
  134. stats.used ++;
  135. // This tile is a leaf if none of the children had been used.
  136. const children = tile.children;
  137. let anyChildrenUsed = false;
  138. for ( let i = 0, l = children.length; i < l; i ++ ) {
  139. const c = children[ i ];
  140. anyChildrenUsed = anyChildrenUsed || isUsedThisFrame( c, frameCount );
  141. }
  142. if ( ! anyChildrenUsed ) {
  143. // TODO: This isn't necessarily right because it's possible that a parent tile is considered in the
  144. // frustum while the child tiles are not, making them unused. If all children have loaded and were properly
  145. // considered to be in the used set then we shouldn't set ourselves to a leaf here.
  146. tile.__isLeaf = true;
  147. } else {
  148. let childrenWereVisible = false;
  149. let allChildrenLoaded = true;
  150. for ( let i = 0, l = children.length; i < l; i ++ ) {
  151. const c = children[ i ];
  152. markUsedSetLeaves( c, renderer );
  153. childrenWereVisible = childrenWereVisible || c.__wasSetVisible || c.__childrenWereVisible;
  154. if ( isUsedThisFrame( c, frameCount ) ) {
  155. const childLoaded =
  156. c.__allChildrenLoaded ||
  157. ( ! c.__contentEmpty && isDownloadFinished( c.__loadingState ) ) ||
  158. ( c.__externalTileSet && c.__loadingState === FAILED );
  159. allChildrenLoaded = allChildrenLoaded && childLoaded;
  160. }
  161. }
  162. tile.__childrenWereVisible = childrenWereVisible;
  163. tile.__allChildrenLoaded = allChildrenLoaded;
  164. }
  165. }
  166. // Skip past tiles we consider unrenderable because they are outside the error threshold.
  167. export function skipTraversal( tile, renderer ) {
  168. const stats = renderer.stats;
  169. const frameCount = renderer.frameCount;
  170. if ( ! isUsedThisFrame( tile, frameCount ) ) {
  171. return;
  172. }
  173. const parent = tile.parent;
  174. const parentDepthToParent = parent ? parent.__depthFromRenderedParent : - 1;
  175. tile.__depthFromRenderedParent = parentDepthToParent;
  176. // Request the tile contents or mark it as visible if we've found a leaf.
  177. const lruCache = renderer.lruCache;
  178. if ( tile.__isLeaf ) {
  179. tile.__depthFromRenderedParent ++;
  180. if ( tile.__loadingState === LOADED ) {
  181. if ( tile.__inFrustum ) {
  182. tile.__visible = true;
  183. stats.visible ++;
  184. }
  185. tile.__active = true;
  186. stats.active ++;
  187. } else if ( ! lruCache.isFull() && ( ! tile.__contentEmpty || tile.__externalTileSet ) ) {
  188. renderer.requestTileContents( tile );
  189. }
  190. return;
  191. }
  192. const errorRequirement = ( renderer.errorTarget + 1 ) * renderer.errorThreshold;
  193. const meetsSSE = tile.__error <= errorRequirement;
  194. const includeTile = meetsSSE || tile.refine === 'ADD';
  195. const hasModel = ! tile.__contentEmpty;
  196. const hasContent = hasModel || tile.__externalTileSet;
  197. const loadedContent = isDownloadFinished( tile.__loadingState ) && hasContent;
  198. const childrenWereVisible = tile.__childrenWereVisible;
  199. const children = tile.children;
  200. const allChildrenHaveContent = tile.__allChildrenLoaded;
  201. // Increment the relative depth of the node to the nearest rendered parent if it has content
  202. // and is being rendered.
  203. if ( includeTile && hasModel ) {
  204. tile.__depthFromRenderedParent ++;
  205. }
  206. // If we've met the SSE requirements and we can load content then fire a fetch.
  207. if ( includeTile && ! loadedContent && ! lruCache.isFull() && hasContent ) {
  208. renderer.requestTileContents( tile );
  209. }
  210. // Only mark this tile as visible if it meets the screen space error requirements, has loaded content, not
  211. // all children have loaded yet, and if no children were visible last frame. We want to keep children visible
  212. // that _were_ visible to avoid a pop in level of detail as the camera moves around and parent / sibling tiles
  213. // load in.
  214. // Skip the tile entirely if there's no content to load
  215. if (
  216. ( meetsSSE && ! allChildrenHaveContent && ! childrenWereVisible && loadedContent )
  217. || ( tile.refine === 'ADD' && loadedContent )
  218. ) {
  219. if ( tile.__inFrustum ) {
  220. tile.__visible = true;
  221. stats.visible ++;
  222. }
  223. tile.__active = true;
  224. stats.active ++;
  225. }
  226. // If we're additive then don't stop the traversal here because it doesn't matter whether the children load in
  227. // at the same rate.
  228. if ( tile.refine !== 'ADD' && meetsSSE && ! allChildrenHaveContent && loadedContent ) {
  229. // load the child content if we've found that we've been loaded so we can move down to the next tile
  230. // layer when the data has loaded.
  231. for ( let i = 0, l = children.length; i < l; i ++ ) {
  232. const c = children[ i ];
  233. if ( isUsedThisFrame( c, frameCount ) && ! lruCache.isFull() ) {
  234. c.__depthFromRenderedParent = tile.__depthFromRenderedParent + 1;
  235. recursivelyLoadTiles( c, c.__depthFromRenderedParent, renderer );
  236. }
  237. }
  238. } else {
  239. for ( let i = 0, l = children.length; i < l; i ++ ) {
  240. const c = children[ i ];
  241. if ( isUsedThisFrame( c, frameCount ) ) {
  242. skipTraversal( c, renderer );
  243. }
  244. }
  245. }
  246. }
  247. // Final traverse to toggle tile visibility.
  248. export function toggleTiles( tile, renderer ) {
  249. const frameCount = renderer.frameCount;
  250. const isUsed = isUsedThisFrame( tile, frameCount );
  251. if ( isUsed || tile.__usedLastFrame ) {
  252. let setActive = false;
  253. let setVisible = false;
  254. if ( isUsed ) {
  255. // enable visibility if active due to shadows
  256. setActive = tile.__active;
  257. if ( renderer.displayActiveTiles ) {
  258. setVisible = tile.__active || tile.__visible;
  259. } else {
  260. setVisible = tile.__visible;
  261. }
  262. }
  263. // If the active or visible state changed then call the functions.
  264. if ( ! tile.__contentEmpty && tile.__loadingState === LOADED ) {
  265. if ( tile.__wasSetActive !== setActive ) {
  266. renderer.setTileActive( tile, setActive );
  267. }
  268. if ( tile.__wasSetVisible !== setVisible ) {
  269. renderer.setTileVisible( tile, setVisible );
  270. }
  271. }
  272. tile.__wasSetActive = setActive;
  273. tile.__wasSetVisible = setVisible;
  274. tile.__usedLastFrame = isUsed;
  275. const children = tile.children;
  276. for ( let i = 0, l = children.length; i < l; i ++ ) {
  277. const c = children[ i ];
  278. toggleTiles( c, renderer );
  279. }
  280. }
  281. }