123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388 |
- import { LOADED, FAILED } from './constants.js';
- function isDownloadFinished( value ) {
- return value === LOADED || value === FAILED;
- }
- // Checks whether this tile was last used on the given frame.
- function isUsedThisFrame( tile, frameCount ) {
- return tile.__lastFrameVisited === frameCount && tile.__used;
- }
- // Resets the frame frame information for the given tile
- function resetFrameState( tile, frameCount ) {
- if ( tile.__lastFrameVisited !== frameCount ) {
- tile.__lastFrameVisited = frameCount;
- tile.__used = false;
- tile.__inFrustum = false;
- tile.__isLeaf = false;
- tile.__visible = false;
- tile.__active = false;
- tile.__error = 0;
- tile.__childrenWereVisible = false;
- tile.__allChildrenLoaded = false;
- }
- }
- // Recursively mark tiles used down to the next tile with content
- function recursivelyMarkUsed( tile, frameCount, lruCache ) {
- resetFrameState( tile, frameCount );
- tile.__used = true;
- lruCache.markUsed( tile );
- if ( tile.__contentEmpty ) {
- const children = tile.children;
- for ( let i = 0, l = children.length; i < l; i ++ ) {
- recursivelyMarkUsed( children[ i ], frameCount, lruCache );
- }
- }
- }
- // Helper function for recursively traversing a tileset. If `beforeCb` returns `true` then the
- // traversal will end early.
- export function traverseSet( tile, beforeCb = null, afterCb = null, parent = null, depth = 0 ) {
- if ( beforeCb && beforeCb( tile, parent, depth ) ) {
- if ( afterCb ) {
- afterCb( tile, parent, depth );
- }
- return;
- }
- const children = tile.children;
- for ( let i = 0, l = children.length; i < l; i ++ ) {
- traverseSet( children[ i ], beforeCb, afterCb, tile, depth + 1 );
- }
- if ( afterCb ) {
- afterCb( tile, parent, depth );
- }
- }
- // Determine which tiles are within the camera frustum.
- // TODO: this is marking items as used in the lrucache, which means some data is
- // being kept around that isn't being used -- is that okay?
- export function determineFrustumSet( tile, renderer ) {
- const stats = renderer.stats;
- const frameCount = renderer.frameCount;
- const errorTarget = renderer.errorTarget;
- const maxDepth = renderer.maxDepth;
- const loadSiblings = renderer.loadSiblings;
- const lruCache = renderer.lruCache;
- resetFrameState( tile, frameCount );
- // Early out if this tile is not within view.
- const inFrustum = renderer.tileInView( tile );
- if ( inFrustum === false ) {
- return false;
- }
- tile.__used = true;
- lruCache.markUsed( tile );
- tile.__inFrustum = true;
- stats.inFrustum ++;
- // Early out if this tile has less error than we're targeting.
- if ( ! tile.__contentEmpty ) {
- const error = renderer.calculateError( tile );
- tile.__error = error;
- if ( error <= errorTarget ) {
- return true;
- }
- }
- // Early out if we've reached the maximum allowed depth.
- if ( renderer.maxDepth > 0 && tile.__depth + 1 >= maxDepth ) {
- return true;
- }
- // Traverse children and see if any children are in view.
- let anyChildrenUsed = false;
- const children = tile.children;
- for ( let i = 0, l = children.length; i < l; i ++ ) {
- const c = children[ i ];
- const r = determineFrustumSet( c, renderer );
- anyChildrenUsed = anyChildrenUsed || r;
- }
- // If there are children within view and we are loading siblings then mark
- // all sibling tiles as used, as well.
- if ( anyChildrenUsed && loadSiblings ) {
- for ( let i = 0, l = children.length; i < l; i ++ ) {
- const c = children[ i ];
- recursivelyMarkUsed( c, frameCount, lruCache );
- }
- }
- return true;
- }
- // Traverse and mark the tiles that are at the leaf nodes of the "used" tree.
- export function markUsedSetLeaves( tile, renderer ) {
- const stats = renderer.stats;
- const frameCount = renderer.frameCount;
- if ( ! isUsedThisFrame( tile, frameCount ) ) {
- return;
- }
- stats.used ++;
- // This tile is a leaf if none of the children had been used.
- const children = tile.children;
- let anyChildrenUsed = false;
- for ( let i = 0, l = children.length; i < l; i ++ ) {
- const c = children[ i ];
- anyChildrenUsed = anyChildrenUsed || isUsedThisFrame( c, frameCount );
- }
- if ( ! anyChildrenUsed ) {
- // TODO: This isn't necessarily right because it's possible that a parent tile is considered in the
- // frustum while the child tiles are not, making them unused. If all children have loaded and were properly
- // considered to be in the used set then we shouldn't set ourselves to a leaf here.
- tile.__isLeaf = true;
- } else {
- let childrenWereVisible = false;
- let allChildrenLoaded = true;
- for ( let i = 0, l = children.length; i < l; i ++ ) {
- const c = children[ i ];
- markUsedSetLeaves( c, renderer );
- childrenWereVisible = childrenWereVisible || c.__wasSetVisible || c.__childrenWereVisible;
- if ( isUsedThisFrame( c, frameCount ) ) {
- const childLoaded = ( ! c.__contentEmpty && isDownloadFinished( c.__loadingState ) ) || c.__allChildrenLoaded;
- allChildrenLoaded = allChildrenLoaded && childLoaded;
- }
- }
- tile.__childrenWereVisible = childrenWereVisible;
- tile.__allChildrenLoaded = allChildrenLoaded;
- }
- }
- // Skip past tiles we consider unrenderable because they are outside the error threshold.
- export function skipTraversal( tile, renderer ) {
- const stats = renderer.stats;
- const frameCount = renderer.frameCount;
- if ( ! isUsedThisFrame( tile, frameCount ) ) {
- return;
- }
- const parent = tile.parent;
- const parentDepthToParent = parent ? parent.__depthFromRenderedParent : - 1;
- tile.__depthFromRenderedParent = parentDepthToParent;
- // Request the tile contents or mark it as visible if we've found a leaf.
- const lruCache = renderer.lruCache;
- if ( tile.__isLeaf ) {
- tile.__depthFromRenderedParent ++;
- if ( tile.__loadingState === LOADED ) {
- if ( tile.__inFrustum ) {
- tile.__visible = true;
- stats.visible ++;
- }
- tile.__active = true;
- stats.active ++;
- } else if ( ! lruCache.isFull() ) {
- renderer.requestTileContents( tile );
- }
- return;
- }
- const errorRequirement = ( renderer.errorTarget + 1 ) * renderer.errorThreshold;
- const meetsSSE = tile.__error <= errorRequirement;
- const hasContent = ! tile.__contentEmpty;
- const loadedContent = isDownloadFinished( tile.__loadingState ) && ! tile.__contentEmpty;
- const childrenWereVisible = tile.__childrenWereVisible;
- const children = tile.children;
- let allChildrenHaveContent = tile.__allChildrenLoaded;
- // Increment the relative depth of the node to the nearest rendered parent if it has content
- // and is being rendered.
- if ( meetsSSE && hasContent ) {
- tile.__depthFromRenderedParent ++;
- }
- // If we've met the SSE requirements and we can load content then fire a fetch.
- if ( meetsSSE && ! loadedContent && ! lruCache.isFull() && hasContent ) {
- renderer.requestTileContents( tile );
- }
- // Only mark this tile as visible if it meets the screen space error requirements, has loaded content, not
- // all children have loaded yet, and if no children were visible last frame. We want to keep children visible
- // that _were_ visible to avoid a pop in level of detail as the camera moves around and parent / sibling tiles
- // load in.
- // Skip the tile entirely if there's no content to load
- if ( meetsSSE && ! allChildrenHaveContent && ! childrenWereVisible && hasContent ) {
- if ( loadedContent ) {
- if ( tile.__inFrustum ) {
- tile.__visible = true;
- stats.visible ++;
- }
- tile.__active = true;
- stats.active ++;
- // load the child content if we've found that we've been loaded so we can move down to the next tile
- // layer when the data has loaded.
- for ( let i = 0, l = children.length; i < l; i ++ ) {
- const c = children[ i ];
- if ( isUsedThisFrame( c, frameCount ) && ! lruCache.isFull() ) {
- c.__depthFromRenderedParent = tile.__depthFromRenderedParent + 1;
- renderer.requestTileContents( c );
- }
- }
- }
- return;
- }
- for ( let i = 0, l = children.length; i < l; i ++ ) {
- const c = children[ i ];
- if ( isUsedThisFrame( c, frameCount ) ) {
- skipTraversal( c, renderer );
- }
- }
- }
- // Final traverse to toggle tile visibility.
- export function toggleTiles( tile, renderer ) {
- const frameCount = renderer.frameCount;
- const isUsed = isUsedThisFrame( tile, frameCount );
- if ( isUsed || tile.__usedLastFrame ) {
- let setActive = false;
- let setVisible = false;
- if ( isUsed ) {
- // enable visibility if active due to shadows
- setActive = tile.__active;
- if ( renderer.displayActiveTiles ) {
- setVisible = tile.__active || tile.__visible;
- } else {
- setVisible = tile.__visible;
- }
- }
- // If the active or visible state changed then call the functions.
- if ( ! tile.__contentEmpty && tile.__loadingState === LOADED ) {
- if ( tile.__wasSetActive !== setActive ) {
- renderer.setTileActive( tile, setActive );
- }
- if ( tile.__wasSetVisible !== setVisible ) {
- renderer.setTileVisible( tile, setVisible );
- }
- }
- tile.__wasSetActive = setActive;
- tile.__wasSetVisible = setVisible;
- tile.__usedLastFrame = isUsed;
- const children = tile.children;
- for ( let i = 0, l = children.length; i < l; i ++ ) {
- const c = children[ i ];
- toggleTiles( c, renderer );
- }
- }
- }
|