traverseFunctions.js 9.9 KB

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