瀏覽代碼

Merge branch 'master' of github.com:NASA-AMMOS/3DTilesRendererJS into master

Garrett Johnson 3 年之前
父節點
當前提交
03bc725e78
共有 6 個文件被更改,包括 130 次插入52 次删除
  1. 8 0
      CHANGELOG.md
  2. 8 0
      README.md
  3. 75 27
      example/vr.js
  4. 22 20
      src/three/TilesRenderer.js
  5. 2 0
      src/utilities/PriorityQueue.d.ts
  6. 15 5
      src/utilities/PriorityQueue.js

+ 8 - 0
CHANGELOG.md

@@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
 and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
 
+## Unreleased
+### Added
+- PriorityQueue: Added `schedulingCallback` to afford flexibility in job scheduling callback for scenarios where `requestAnimationFrame` will not work, such as with WebXR.
+
+### Fixed
+- `autoDisableRendererCulling` incorrectly applying the inverse of the documented effect.
+- Screen space error calculations now use the camera projectionMatrix rather than camera type to determine frustum type.
+
 ## [0.3.3] - 2021-09-08
 ### Added
 - Support for embedded tileset / tile geometry URLs with hashes, search query parameters.

+ 8 - 0
README.md

@@ -612,6 +612,14 @@ priorityCallback = null : ( itemA, itemB ) => Number
 
 Function to derive the job priority of the given item. Higher priority values get processed first.
 
+### .schedulingCallback
+
+```js
+schedulingCallback = requestAnimationFrame : ( cb : Function ) => void
+```
+
+A function used for scheduling when to run jobs next so more work doesn't happen in a single frame than there is time for -- defaults to the next frame. This should be overriden in scenarios where requestAnimationFrame is not reliable, such as when running in WebXR. See the VR demo for one example on how to handle this with WebXR.
+
 ## LRUCache
 
 Utility class for the TilesRenderer to keep track of currently used items so rendered items will not be unloaded.

+ 75 - 27
example/vr.js

@@ -42,6 +42,8 @@ let box, sphere, grid;
 let raycaster, fwdVector, intersectRing;
 let offsetParent;
 let controller, controllerGrip;
+let xrSession = null;
+let tasks = [];
 
 let params = {
 
@@ -52,7 +54,6 @@ let params = {
 };
 
 init();
-animate();
 
 function init() {
 
@@ -69,6 +70,8 @@ function init() {
 	document.body.appendChild( renderer.domElement );
 	renderer.domElement.tabIndex = 1;
 
+	renderer.setAnimationLoop( animate );
+
 	// create workspace
 	workspace = new Group();
 	scene.add( workspace );
@@ -102,6 +105,23 @@ function init() {
 	tiles = new TilesRenderer( '../data/tileset.json' );
 	offsetParent.add( tiles.group );
 
+	// We set camera for tileset
+	tiles.setCamera( camera );
+	tiles.setResolutionFromRenderer( camera, renderer );
+
+
+	// We define a custom scheduling callback to handle also active WebXR sessions
+	const tilesSchedulingCB = func => {
+
+		tasks.push( func );
+
+	};
+
+	// We set our scheduling callback for tiles downloading and parsing
+	tiles.downloadQueue.schedulingCallback = tilesSchedulingCB;
+	tiles.parseQueue.schedulingCallback = tilesSchedulingCB;
+
+
 	// Raycasting init
 	raycaster = new Raycaster();
 	fwdVector = new Vector3( 0, 0, 1 );
@@ -206,16 +226,62 @@ function onWindowResize() {
 
 }
 
-function animate() {
+function handleCamera() {
+
+	// get the XR camera with a combined frustum for culling
+	if ( renderer.xr.isPresenting ) {
+
+		if ( xrSession === null ) { // We setup XR camera once
+
+			// remove all cameras so we can use the VR camera instead
+			tiles.cameras.forEach( c => tiles.deleteCamera( camera ) );
+
+			const currCamera = renderer.xr.getCamera( camera );
+			tiles.setCamera( currCamera );
+
+			const leftCam = currCamera.cameras[ 0 ];
+			if ( leftCam ) {
+
+				tiles.setResolution( currCamera, leftCam.viewport.z, leftCam.viewport.w );
+
+			}
+
+			xrSession = renderer.xr.getSession();
+
+		}
+
+	} else {
+
+		// Reset default camera (exiting WebXR session)
+		if ( xrSession !== null ) {
+
+			tiles.cameras.forEach( c => tiles.deleteCamera( camera ) );
 
-	renderer.setAnimationLoop( render );
+			tiles.setCamera( camera );
+			tiles.setResolutionFromRenderer( camera, renderer );
+
+			camera.position.set( 0, 1, 0 );
+
+			xrSession = null;
+
+		}
+
+	}
 
 }
 
+function handleTasks() {
 
-function render() {
+	for ( let t = 0, l = tasks.length; t < l; t ++ ) {
 
-	requestAnimationFrame( animate );
+		tasks[ t ]();
+
+	}
+	tasks.length = 0;
+
+}
+
+function animate() {
 
 	grid.visible = params.displayGrid;
 
@@ -236,32 +302,14 @@ function render() {
 
 	}
 
-	// remove all cameras so we can use the VR camera instead
-	tiles.cameras.forEach( c => tiles.deleteCamera( camera ) );
+	// We check for tiles camera setup (default and XR sessions)
+	handleCamera();
 
-	// get the XR camera with a combined frustum for culling
-	if ( renderer.xr.isPresenting ) {
-
-		const currCamera = renderer.xr.getCamera( camera );
-		tiles.setCamera( currCamera );
-
-		const leftCam = currCamera.cameras[ 0 ];
-		if ( leftCam ) {
-
-			tiles.setResolution( currCamera, leftCam.viewport.z, leftCam.viewport.w );
-
-		}
-
-	} else {
-
-		tiles.setCamera( camera );
-		tiles.setResolutionFromRenderer( camera, renderer );
-
-	}
+	// We handle pending tasks
+	handleTasks();
 
 	tiles.update();
 
-
 	if ( controller.controllerActive ) {
 
 		const { ray } = raycaster;

+ 22 - 20
src/three/TilesRenderer.js

@@ -10,14 +10,12 @@ import {
 	Sphere,
 	Vector3,
 	Vector2,
-	Math as MathUtils,
 	Frustum,
 	LoadingManager
 } from 'three';
 import { raycastTraverse, raycastTraverseFirstHit } from './raycastTraverse.js';
 
 const INITIAL_FRUSTUM_CULLED = Symbol( 'INITIAL_FRUSTUM_CULLED' );
-const DEG2RAD = MathUtils.DEG2RAD;
 const tempMat = new Matrix4();
 const tempMat2 = new Matrix4();
 const tempVector = new Vector3();
@@ -51,13 +49,9 @@ export class TilesRenderer extends TilesRendererBase {
 		if ( this._autoDisableRendererCulling !== value ) {
 
 			super._autoDisableRendererCulling = value;
-			this.traverse( tile => {
+			this.forEachLoadedModel( ( scene ) => {
 
-				if ( tile.scene ) {
-
-					updateFrustumCulled( tile.scene, value );
-
-				}
+				updateFrustumCulled( scene, ! value );
 
 			} );
 
@@ -355,10 +349,11 @@ export class TilesRenderer extends TilesRendererBase {
 			cameraInfo.push( {
 
 				frustum: new Frustum(),
-				sseDenominator: - 1,
+				isOrthographic: false,
+				sseDenominator: - 1, // used if isOrthographic:false
 				position: new Vector3(),
 				invScale: - 1,
-				pixelSize: 0,
+				pixelSize: 0, // used if isOrthographic:true
 
 			} );
 
@@ -392,18 +387,26 @@ export class TilesRenderer extends TilesRendererBase {
 
 			}
 
-			if ( camera.isPerspectiveCamera ) {
-
-				info.sseDenominator = 2 * Math.tan( 0.5 * camera.fov * DEG2RAD ) / resolution.height;
+			// Read the calculated projection matrix directly to support custom Camera implementations
+			const projection = camera.projectionMatrix.elements;
 
-			}
+			// The last element of the projection matrix is 1 for orthographic, 0 for perspective
+			info.isOrthographic = projection[ 15 ] === 1;
 
-			if ( camera.isOrthographicCamera ) {
+			if ( info.isOrthographic ) {
 
-				const w = camera.right - camera.left;
-				const h = camera.top - camera.bottom;
+				// See OrthographicCamera.updateProjectionMatrix and Matrix4.makeOrthographic:
+				// the view width and height are used to populate matrix elements 0 and 5.
+				const w = 2 / projection[ 0 ];
+				const h = 2 / projection[ 5 ];
 				info.pixelSize = Math.max( h / resolution.height, w / resolution.width );
 
+			} else {
+
+				// See PerspectiveCamera.updateProjectionMatrix and Matrix4.makePerspective:
+				// the vertical FOV is used to populate matrix element 5.
+				info.sseDenominator = ( 2 / projection[ 5 ] ) / resolution.height;
+
 			}
 
 			info.invScale = invScale;
@@ -673,7 +676,7 @@ export class TilesRenderer extends TilesRendererBase {
 				c[ INITIAL_FRUSTUM_CULLED ] = c.frustumCulled;
 
 			} );
-			updateFrustumCulled( scene, this.autoDisableRendererCulling );
+			updateFrustumCulled( scene, ! this.autoDisableRendererCulling );
 
 			cached.scene = scene;
 
@@ -842,12 +845,11 @@ export class TilesRenderer extends TilesRendererBase {
 				}
 
 				// transform camera position into local frame of the tile bounding box
-				const camera = cameras[ i ];
 				const info = cameraInfo[ i ];
 				const invScale = info.invScale;
 
 				let error;
-				if ( camera.isOrthographicCamera ) {
+				if ( info.isOrthographic ) {
 
 					const pixelSize = info.pixelSize;
 					error = tile.geometricError / ( pixelSize * invScale );

+ 2 - 0
src/utilities/PriorityQueue.d.ts

@@ -3,6 +3,8 @@ export class PriorityQueue {
 	maxJobs : Number;
 	autoUpdate : Boolean;
 	priorityCallback : ( itemA : any , itemB : any ) => Number;
+	
+	schedulingCallback : ( func : Function ) => void;
 
 	sort() : void;
 	add( item : any, callback : ( item : any ) => any ) : Promise< any >;

+ 15 - 5
src/utilities/PriorityQueue.js

@@ -17,6 +17,20 @@ class PriorityQueue {
 
 		};
 
+		// Customizable scheduling callback. Default using requestAnimationFrame()
+		this.schedulingCallback = func => {
+
+			requestAnimationFrame( func );
+
+		};
+
+		this._runjobs = () => {
+
+			this.tryRunJobs();
+			this.scheduled = false;
+
+		};
+
 	}
 
 	sort() {
@@ -110,12 +124,8 @@ class PriorityQueue {
 
 		if ( ! this.scheduled ) {
 
-			requestAnimationFrame( () => {
-
-				this.tryRunJobs();
-				this.scheduled = false;
+			this.schedulingCallback( this._runjobs );
 
-			} );
 			this.scheduled = true;
 
 		}