Browse Source

Merge pull request #189 from matterport/mp_tiles_143

[143] - Prioritize load of closer tiles
Garrett Johnson 4 years ago
parent
commit
064495fc29

+ 1 - 1
README.md

@@ -586,7 +586,7 @@ The maximum number of jobs to be processing at once.
 ### .priorityCallback
 ### .priorityCallback
 
 
 ```js
 ```js
-priorityCallback = null : ( item ) => Number
+priorityCallback = null : ( itemA, itemB ) => Number
 ```
 ```
 
 
 Function to derive the job priority of the given item. Higher priority values get processed first.
 Function to derive the job priority of the given item. Higher priority values get processed first.

+ 39 - 0
src/base/TilesRendererBase.d.ts

@@ -30,3 +30,42 @@ export class TilesRendererBase {
 	dispose() : void;
 	dispose() : void;
 
 
 }
 }
+
+/** Documented 3d-tile state managed by the TilesRenderer* / traverseFunctions! */
+export interface Tile {
+
+	/**
+	 * Hierarchy Depth from the TileGroup
+	 */
+	__depth : Number;
+	/**
+	 * The screen space error for this tile
+	 */
+	__error : Number;
+	/**
+	 * How far is this tiles bounds from the nearest active Camera.
+	 * Expected to be filled in during calculateError implementations.
+	 */
+	 __distanceFromCamera : Number;
+	/**
+	 * This tile is currently active if:
+	 *  1: Tile content is loaded and ready to be made visible if needed
+	 */
+	__active : Boolean;
+	/**
+	 * This tile is currently visible if:
+	 *  1: Tile content is loaded
+	 *  2: Tile is within a camera frustum
+	 *  3: Tile meets the SSE requirements
+	 */
+	 __visible : Boolean;
+	/**
+	 * Frame number that this tile was last used: active+visible
+	 */
+	 __lastFrameVisited : Number;
+	/**
+	 * TODO: Document this if it is useful enough to be the default property in the LRU sorting.
+	 */
+	 __depthFromRenderedParent : Number;
+
+}

+ 53 - 15
src/base/TilesRendererBase.js

@@ -5,8 +5,43 @@ import { PriorityQueue } from '../utilities/PriorityQueue.js';
 import { determineFrustumSet, toggleTiles, skipTraversal, markUsedSetLeaves, traverseSet } from './traverseFunctions.js';
 import { determineFrustumSet, toggleTiles, skipTraversal, markUsedSetLeaves, traverseSet } from './traverseFunctions.js';
 import { UNLOADED, LOADING, PARSING, LOADED, FAILED } from './constants.js';
 import { UNLOADED, LOADING, PARSING, LOADED, FAILED } from './constants.js';
 
 
-// Function for sorting the evicted LRU items. We should evict the shallowest depth first.
-const priorityCallback = tile => 1 / ( tile.__depthFromRenderedParent + 1 );
+/**
+ * Function for provided to sort all tiles for prioritizing loading/unloading.
+ *
+ * @param {Tile} a
+ * @param {Tile} b
+ * @returns number
+ */
+const priorityCallback = ( a, b ) => {
+
+	if ( a.__lastFrameVisited !== b.__lastFrameVisited ) {
+
+		// the lastFrameVisited tracks the last frame where a tile was used
+		return a.__lastFrameVisited - b.__lastFrameVisited;
+
+	} else if ( a.__error !== b.__error ) {
+
+		// tiles which have greater error next
+		return a.__error - b.__error;
+
+	} else if ( a.__distanceFromCamera !== b.__distanceFromCamera ) {
+
+		// and finally visible tiles which have equal error (ex: if geometricError === 0)
+		// should prioritize based on distance.
+		return a.__distanceFromCamera - b.__distanceFromCamera;
+
+	}
+
+	return 0;
+
+};
+
+/**
+ * Function for sorting the evicted LRU items. We should evict the shallowest depth first.
+ * @param {Tile} tile
+ * @returns number
+ */
+const lruPriorityCallback = ( tile ) => 1 / ( tile.__depthFromRenderedParent + 1 );
 
 
 export class TilesRendererBase {
 export class TilesRendererBase {
 
 
@@ -42,7 +77,7 @@ export class TilesRendererBase {
 		this.preprocessURL = null;
 		this.preprocessURL = null;
 
 
 		const lruCache = new LRUCache();
 		const lruCache = new LRUCache();
-		lruCache.unloadPriorityCallback = priorityCallback;
+		lruCache.unloadPriorityCallback = lruPriorityCallback;
 
 
 		const downloadQueue = new PriorityQueue();
 		const downloadQueue = new PriorityQueue();
 		downloadQueue.maxJobs = 4;
 		downloadQueue.maxJobs = 4;
@@ -185,7 +220,10 @@ export class TilesRendererBase {
 
 
 		}
 		}
 
 
-		tile.__error = 0.0;
+		// Expected to be set during calculateError()
+		tile.__distanceFromCamera = Infinity;
+		tile.__error = Infinity;
+
 		tile.__inFrustum = false;
 		tile.__inFrustum = false;
 		tile.__isLeaf = false;
 		tile.__isLeaf = false;
 
 
@@ -420,17 +458,17 @@ export class TilesRendererBase {
 
 
 		if ( isExternalTileSet ) {
 		if ( isExternalTileSet ) {
 
 
-			downloadQueue.add( tile, tile => {
+			downloadQueue.add( tile, tileCb => {
 
 
 				// if it has been unloaded then the tile has been disposed
 				// if it has been unloaded then the tile has been disposed
-				if ( tile.__loadIndex !== loadIndex ) {
+				if ( tileCb.__loadIndex !== loadIndex ) {
 
 
 					return Promise.resolve();
 					return Promise.resolve();
 
 
 				}
 				}
 
 
-				const uri = this.preprocessURL ? this.preprocessURL( tile.content.uri ) : tile.content.uri;
-				return this.fetchTileSet( uri, Object.assign( { signal }, this.fetchOptions ), tile );
+				const uri = this.preprocessURL ? this.preprocessURL( tileCb.content.uri ) : tileCb.content.uri;
+				return this.fetchTileSet( uri, Object.assign( { signal }, this.fetchOptions ), tileCb );
 
 
 			} )
 			} )
 				.then( json => {
 				.then( json => {
@@ -453,15 +491,15 @@ export class TilesRendererBase {
 
 
 		} else {
 		} else {
 
 
-			downloadQueue.add( tile, tile => {
+			downloadQueue.add( tile, downloadTile => {
 
 
-				if ( tile.__loadIndex !== loadIndex ) {
+				if ( downloadTile.__loadIndex !== loadIndex ) {
 
 
 					return Promise.resolve();
 					return Promise.resolve();
 
 
 				}
 				}
 
 
-				const uri = this.preprocessURL ? this.preprocessURL( tile.content.uri ) : tile.content.uri;
+				const uri = this.preprocessURL ? this.preprocessURL( downloadTile.content.uri ) : downloadTile.content.uri;
 				return fetch( uri, Object.assign( { signal }, this.fetchOptions ) );
 				return fetch( uri, Object.assign( { signal }, this.fetchOptions ) );
 
 
 			} )
 			} )
@@ -498,19 +536,19 @@ export class TilesRendererBase {
 					tile.__loadAbort = null;
 					tile.__loadAbort = null;
 					tile.__loadingState = PARSING;
 					tile.__loadingState = PARSING;
 
 
-					return parseQueue.add( tile, tile => {
+					return parseQueue.add( tile, parseTile => {
 
 
 						// if it has been unloaded then the tile has been disposed
 						// if it has been unloaded then the tile has been disposed
-						if ( tile.__loadIndex !== loadIndex ) {
+						if ( parseTile.__loadIndex !== loadIndex ) {
 
 
 							return Promise.resolve();
 							return Promise.resolve();
 
 
 						}
 						}
 
 
-						const uri = tile.content.uri;
+						const uri = parseTile.content.uri;
 						const extension = uri.split( /\./g ).pop();
 						const extension = uri.split( /\./g ).pop();
 
 
-						return this.parseTile( buffer, tile, extension );
+						return this.parseTile( buffer, parseTile, extension );
 
 
 					} );
 					} );
 
 

+ 2 - 1
src/base/traverseFunctions.js

@@ -24,7 +24,8 @@ function resetFrameState( tile, frameCount ) {
 		tile.__isLeaf = false;
 		tile.__isLeaf = false;
 		tile.__visible = false;
 		tile.__visible = false;
 		tile.__active = false;
 		tile.__active = false;
-		tile.__error = 0;
+		tile.__error = Infinity;
+		tile.__distanceFromCamera = Infinity;
 		tile.__childrenWereVisible = false;
 		tile.__childrenWereVisible = false;
 		tile.__allChildrenLoaded = false;
 		tile.__allChildrenLoaded = false;
 
 

+ 2 - 2
src/three/DebugTilesRenderer.js

@@ -115,7 +115,7 @@ export class DebugTilesRenderer extends TilesRenderer {
 
 
 			return {
 			return {
 
 
-				distanceToCamera: targetTile.cached.distance,
+				distanceToCamera: targetTile.__distanceFromCamera,
 				geometricError: targetTile.geometricError,
 				geometricError: targetTile.geometricError,
 				screenSpaceError: targetTile.__error,
 				screenSpaceError: targetTile.__error,
 				depth: targetTile.__depth,
 				depth: targetTile.__depth,
@@ -274,7 +274,7 @@ export class DebugTilesRenderer extends TilesRenderer {
 
 
 							// We don't update the distance if the geometric error is 0.0 so
 							// We don't update the distance if the geometric error is 0.0 so
 							// it will always be black.
 							// it will always be black.
-							const val = Math.min( tile.cached.distance / maxDistance, 1 );
+							const val = Math.min( tile.__distanceFromCamera / maxDistance, 1 );
 							c.material.color.setRGB( val, val, val );
 							c.material.color.setRGB( val, val, val );
 							break;
 							break;
 
 

+ 3 - 7
src/three/TilesRenderer.js

@@ -767,12 +767,6 @@ export class TilesRenderer extends TilesRendererBase {
 
 
 	calculateError( tile ) {
 	calculateError( tile ) {
 
 
-		if ( tile.geometricError === 0.0 ) {
-
-			return 0.0;
-
-		}
-
 		const cached = tile.cached;
 		const cached = tile.cached;
 		const inFrustum = cached.inFrustum;
 		const inFrustum = cached.inFrustum;
 		const cameras = this.cameras;
 		const cameras = this.cameras;
@@ -787,6 +781,7 @@ export class TilesRenderer extends TilesRendererBase {
 
 
 			let maxError = - Infinity;
 			let maxError = - Infinity;
 			let minDistance = Infinity;
 			let minDistance = Infinity;
+
 			for ( let i = 0, l = cameras.length; i < l; i ++ ) {
 			for ( let i = 0, l = cameras.length; i < l; i ++ ) {
 
 
 				if ( ! inFrustum[ i ] ) {
 				if ( ! inFrustum[ i ] ) {
@@ -823,7 +818,8 @@ export class TilesRenderer extends TilesRendererBase {
 
 
 			}
 			}
 
 
-			tile.cached.distance = minDistance;
+			tile.__distanceFromCamera = minDistance;
+			tile.__error = maxError;
 
 
 			return maxError;
 			return maxError;
 
 

+ 1 - 1
src/utilities/PriorityQueue.d.ts

@@ -2,7 +2,7 @@ export class PriorityQueue {
 
 
 	maxJobs : Number;
 	maxJobs : Number;
 	autoUpdate : Boolean;
 	autoUpdate : Boolean;
-	priorityCallback : ( item : any ) => Number;
+	priorityCallback : ( itemA : any , itemB : any ) => Number;
 
 
 	sort() : void;
 	sort() : void;
 	add( item : any, callback : ( item : any ) => any ) : Promise< any >;
 	add( item : any, callback : ( item : any ) => any ) : Promise< any >;

+ 1 - 5
src/utilities/PriorityQueue.js

@@ -23,11 +23,7 @@ class PriorityQueue {
 
 
 		const priorityCallback = this.priorityCallback;
 		const priorityCallback = this.priorityCallback;
 		const items = this.items;
 		const items = this.items;
-		items.sort( ( a, b ) => {
-
-			return priorityCallback( a ) - priorityCallback( b );
-
-		} );
+		items.sort( priorityCallback );
 
 
 	}
 	}
 
 

+ 6 - 6
test/PriorityQueue.test.js

@@ -8,7 +8,7 @@ describe( 'PriorityQueue', () => {
 
 
 		const queue = new PriorityQueue();
 		const queue = new PriorityQueue();
 		queue.maxJobs = 6;
 		queue.maxJobs = 6;
-		queue.priorityCallback = item => item.priority;
+		queue.priorityCallback = ( itemA, itemB ) => itemA.priority - itemB.priority;
 		queue.add( { priority: 6 }, () => new Promise( () => {} ) );
 		queue.add( { priority: 6 }, () => new Promise( () => {} ) );
 		queue.add( { priority: 3 }, () => new Promise( () => {} ) );
 		queue.add( { priority: 3 }, () => new Promise( () => {} ) );
 		queue.add( { priority: 4 }, () => new Promise( () => {} ) );
 		queue.add( { priority: 4 }, () => new Promise( () => {} ) );
@@ -36,7 +36,7 @@ describe( 'PriorityQueue', () => {
 
 
 		const queue = new PriorityQueue();
 		const queue = new PriorityQueue();
 		queue.maxJobs = 1;
 		queue.maxJobs = 1;
-		queue.priorityCallback = item => item.priority;
+		queue.priorityCallback = ( itemA, itemB ) => itemA.priority - itemB.priority;
 		queue.add( { priority: 6 }, cb );
 		queue.add( { priority: 6 }, cb );
 		queue.add( { priority: 3 }, cb );
 		queue.add( { priority: 3 }, cb );
 		queue.add( { priority: 4 }, cb );
 		queue.add( { priority: 4 }, cb );
@@ -65,7 +65,7 @@ describe( 'PriorityQueue', () => {
 		const C = { priority: 2 };
 		const C = { priority: 2 };
 		const D = { priority: 3 };
 		const D = { priority: 3 };
 		const queue = new PriorityQueue();
 		const queue = new PriorityQueue();
-		queue.priorityCallback = item => item.priority;
+		queue.priorityCallback = ( itemA, itemB ) => itemA.priority - itemB.priority;
 		queue.add( A, () => new Promise( () => {} ) );
 		queue.add( A, () => new Promise( () => {} ) );
 		queue.add( B, () => new Promise( () => {} ) );
 		queue.add( B, () => new Promise( () => {} ) );
 		queue.add( C, () => new Promise( () => {} ) );
 		queue.add( C, () => new Promise( () => {} ) );
@@ -95,7 +95,7 @@ describe( 'PriorityQueue', () => {
 		let resolveFunc = null;
 		let resolveFunc = null;
 		const queue = new PriorityQueue();
 		const queue = new PriorityQueue();
 		queue.maxJobs = 1;
 		queue.maxJobs = 1;
-		queue.priorityCallback = item => item.priority;
+		queue.priorityCallback = ( itemA, itemB ) => itemA.priority - itemB.priority;
 
 
 		queue.add( { priority: 1 }, () => new Promise( resolve => {
 		queue.add( { priority: 1 }, () => new Promise( resolve => {
 
 
@@ -134,7 +134,7 @@ describe( 'PriorityQueue', () => {
 
 
 		const A = { priority: 100 };
 		const A = { priority: 100 };
 		const queue = new PriorityQueue();
 		const queue = new PriorityQueue();
-		queue.priorityCallback = item => item.priority;
+		queue.priorityCallback = ( itemA, itemB ) => itemA.priority - itemB.priority;
 
 
 		queue.add( A, item => new Promise( () => {
 		queue.add( A, item => new Promise( () => {
 
 
@@ -149,7 +149,7 @@ describe( 'PriorityQueue', () => {
 	it( 'should return a promise that resolves from the add function.', async () => {
 	it( 'should return a promise that resolves from the add function.', async () => {
 
 
 		const queue = new PriorityQueue();
 		const queue = new PriorityQueue();
-		queue.priorityCallback = item => item.priority;
+		queue.priorityCallback = ( itemA, itemB ) => itemA.priority - itemB.priority;
 
 
 		let result = null;
 		let result = null;
 		queue.add( { priority: 0 }, item => Promise.resolve( 1000 ) ).then( res => result = res );
 		queue.add( { priority: 0 }, item => Promise.resolve( 1000 ) ).then( res => result = res );