traverseFunctions.js 9.0 KB

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