Browse Source

Merge pull request #48 from NASA-AMMOS/second-view

Add second camera view example
Garrett Johnson 5 years ago
parent
commit
9b652d42ec
3 changed files with 225 additions and 64 deletions
  1. 6 0
      example/index.html
  2. 190 55
      example/index.js
  3. 29 9
      src/three/TilesRenderer.js

+ 6 - 0
example/index.html

@@ -10,7 +10,13 @@
             html {
                 overflow: hidden;
 				font-family: Arial, Helvetica, sans-serif;
+				user-select: none;
             }
+
+			canvas {
+				image-rendering: pixelated;
+				outline: none;
+			}
         </style>
     </head>
     <body>

+ 190 - 55
example/index.js

@@ -24,9 +24,10 @@ import Stats from 'three/examples/jsm/libs/stats.module.js';
 
 let camera, controls, scene, renderer, tiles, cameraHelper;
 let thirdPersonCamera, thirdPersonRenderer, thirdPersonControls;
+let secondRenderer, secondCameraHelper, secondControls, secondCamera;
 let orthoCamera, orthoCameraHelper;
 let box;
-let raycaster, mouse, rayIntersect;
+let raycaster, mouse, rayIntersect, lastHoveredElement;
 let offsetParent;
 let statsContainer, stats;
 
@@ -42,11 +43,13 @@ let params = {
 	'maxDepth': 15,
 	'loadSiblings': true,
 	'displayActiveTiles': false,
+	'resolutionScale': 1.0,
 
 	'up': '+Y',
 	'displayBoxBounds': false,
 	'colorMode': 0,
 	'showThirdPerson': false,
+	'showSecondView': false,
 	'reload': reinstantiateTiles,
 
 };
@@ -71,29 +74,9 @@ function reinstantiateTiles() {
 
 function init() {
 
-	// Third person camera view
-	thirdPersonCamera = new PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 4000 );
-	thirdPersonCamera.position.set( 50, 40, 40 );
-	thirdPersonCamera.lookAt( 0, 0, 0 );
-
-	thirdPersonRenderer = new WebGLRenderer( { antialias: true } );
-	thirdPersonRenderer.setPixelRatio( window.devicePixelRatio );
-	thirdPersonRenderer.setSize( window.innerWidth, window.innerHeight );
-	thirdPersonRenderer.setClearColor( 0x0f1416 );
-
-	document.body.appendChild( thirdPersonRenderer.domElement );
-	thirdPersonRenderer.domElement.style.position = 'fixed';
-	thirdPersonRenderer.domElement.style.left = '5px';
-	thirdPersonRenderer.domElement.style.bottom = '5px';
-
-	thirdPersonControls = new OrbitControls( thirdPersonCamera, thirdPersonRenderer.domElement );
-	thirdPersonControls.screenSpacePanning = false;
-	thirdPersonControls.minDistance = 1;
-	thirdPersonControls.maxDistance = 2000;
-
-	// primary camera view
 	scene = new Scene();
 
+	// primary camera view
 	renderer = new WebGLRenderer( { antialias: true } );
 	renderer.setPixelRatio( window.devicePixelRatio );
 	renderer.setSize( window.innerWidth, window.innerHeight );
@@ -104,15 +87,58 @@ function init() {
 
 	camera = new PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 4000 );
 	camera.position.set( 400, 400, 400 );
-
 	cameraHelper = new CameraHelper( camera );
 	scene.add( cameraHelper );
 
 	orthoCamera = new OrthographicCamera();
-
 	orthoCameraHelper = new CameraHelper( orthoCamera );
 	scene.add( orthoCameraHelper );
 
+	// secondary camera view
+	secondCamera = new PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 4000 );
+	secondCamera.position.set( 400, 400, - 400 );
+	secondCamera.lookAt( 0, 0, 0 );
+
+	secondRenderer = new WebGLRenderer( { antialias: true } );
+	secondRenderer.setPixelRatio( window.devicePixelRatio );
+	secondRenderer.setSize( window.innerWidth, window.innerHeight );
+	secondRenderer.setClearColor( 0x151c1f );
+	secondRenderer.outputEncoding = sRGBEncoding;
+
+	document.body.appendChild( secondRenderer.domElement );
+	secondRenderer.domElement.style.position = 'absolute';
+	secondRenderer.domElement.style.right = '0';
+	secondRenderer.domElement.style.top = '0';
+	secondRenderer.domElement.style.outline = '#0f1416 solid 2px';
+
+	secondControls = new OrbitControls( secondCamera, secondRenderer.domElement );
+	secondControls.screenSpacePanning = false;
+	secondControls.minDistance = 1;
+	secondControls.maxDistance = 2000;
+
+	secondCameraHelper = new CameraHelper( secondCamera );
+	scene.add( secondCameraHelper );
+
+	// Third person camera view
+	thirdPersonCamera = new PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 4000 );
+	thirdPersonCamera.position.set( 50, 40, 40 );
+	thirdPersonCamera.lookAt( 0, 0, 0 );
+
+	thirdPersonRenderer = new WebGLRenderer( { antialias: true } );
+	thirdPersonRenderer.setPixelRatio( window.devicePixelRatio );
+	thirdPersonRenderer.setSize( window.innerWidth, window.innerHeight );
+	thirdPersonRenderer.setClearColor( 0x0f1416 );
+	thirdPersonRenderer.outputEncoding = sRGBEncoding;
+
+	document.body.appendChild( thirdPersonRenderer.domElement );
+	thirdPersonRenderer.domElement.style.position = 'fixed';
+	thirdPersonRenderer.domElement.style.left = '5px';
+	thirdPersonRenderer.domElement.style.bottom = '5px';
+
+	thirdPersonControls = new OrbitControls( thirdPersonCamera, thirdPersonRenderer.domElement );
+	thirdPersonControls.screenSpacePanning = false;
+	thirdPersonControls.minDistance = 1;
+	thirdPersonControls.maxDistance = 2000;
 
 	// controls
 	controls = new OrbitControls( camera, renderer.domElement );
@@ -157,13 +183,19 @@ function init() {
 	renderer.domElement.addEventListener( 'mousemove', onMouseMove, false );
 	renderer.domElement.addEventListener( 'mousedown', onMouseDown, false );
 	renderer.domElement.addEventListener( 'mouseup', onMouseUp, false );
+	renderer.domElement.addEventListener( 'mouseleave', onMouseLeave, false );
+
+	secondRenderer.domElement.addEventListener( 'mousemove', onMouseMove, false );
+	secondRenderer.domElement.addEventListener( 'mousedown', onMouseDown, false );
+	secondRenderer.domElement.addEventListener( 'mouseup', onMouseUp, false );
+	secondRenderer.domElement.addEventListener( 'mouseleave', onMouseLeave, false );
+
 
 	// GUI
 	const gui = new dat.GUI();
 	gui.width = 300;
 
 	const tileOptions = gui.addFolder( 'Tiles Options' );
-	tileOptions.add( params, 'orthographic' );
 	tileOptions.add( params, 'loadSiblings' );
 	tileOptions.add( params, 'displayActiveTiles' );
 	tileOptions.add( params, 'errorTarget' ).min( 0 ).max( 50 );
@@ -187,12 +219,17 @@ function init() {
 	} );
 	debug.open();
 
-	gui.add( params, 'showThirdPerson' );
-	gui.add( params, 'enableUpdate' );
-	gui.add( params, 'enableRaycast' );
-	gui.add( params, 'enableCacheDisplay' );
-	gui.add( params, 'reload' );
+	const exampleOptions = gui.addFolder( 'Example Options' );
+	exampleOptions.add( params, 'resolutionScale' ).min( 0.01 ).max( 2.0 ).step( 0.01 ).onChange( onWindowResize );
+	exampleOptions.add( params, 'orthographic' );
+	exampleOptions.add( params, 'showThirdPerson' );
+	exampleOptions.add( params, 'showSecondView' ).onChange( onWindowResize );
+	exampleOptions.add( params, 'enableUpdate' );
+	exampleOptions.add( params, 'enableRaycast' );
+	exampleOptions.add( params, 'enableCacheDisplay' );
+	exampleOptions.open();
 
+	gui.add( params, 'reload' );
 	gui.open();
 
 	statsContainer = document.createElement( 'div' );
@@ -220,18 +257,48 @@ function onWindowResize() {
 	thirdPersonCamera.updateProjectionMatrix();
 	thirdPersonRenderer.setSize( Math.floor( window.innerWidth / 3 ), Math.floor( window.innerHeight / 3 ) );
 
-	camera.aspect = window.innerWidth / window.innerHeight;
+	if ( params.showSecondView ) {
+
+		camera.aspect = 0.5 * window.innerWidth / window.innerHeight;
+		renderer.setSize( 0.5 * window.innerWidth, window.innerHeight );
+
+		secondCamera.aspect = 0.5 * window.innerWidth / window.innerHeight;
+		secondRenderer.setSize( 0.5 * window.innerWidth, window.innerHeight );
+		secondRenderer.domElement.style.display = 'block';
+
+	} else {
+
+		camera.aspect = window.innerWidth / window.innerHeight;
+		renderer.setSize( window.innerWidth, window.innerHeight );
+
+		secondRenderer.domElement.style.display = 'none';
+
+	}
 	camera.updateProjectionMatrix();
-	renderer.setSize( window.innerWidth, window.innerHeight );
+	renderer.setPixelRatio( window.devicePixelRatio * params.resolutionScale );
+
+	secondCamera.updateProjectionMatrix();
+	secondRenderer.setPixelRatio( window.devicePixelRatio );
 
 	updateOrthoCamera();
 
 }
 
+function onMouseLeave( e ) {
+
+	lastHoveredElement = null;
+
+}
+
 function onMouseMove( e ) {
 
-	mouse.x = ( e.clientX / window.innerWidth ) * 2 - 1;
-	mouse.y = - ( e.clientY / window.innerHeight ) * 2 + 1;
+	const bounds = this.getBoundingClientRect();
+	mouse.x = e.clientX - bounds.x;
+	mouse.y = e.clientY - bounds.y;
+	mouse.x = ( mouse.x / bounds.width ) * 2 - 1;
+	mouse.y = - ( mouse.y / bounds.height ) * 2 + 1;
+
+	lastHoveredElement = this;
 
 }
 
@@ -239,20 +306,31 @@ const startPos = new Vector2();
 const endPos = new Vector2();
 function onMouseDown( e ) {
 
-	startPos.set( e.clientX, e.clientY );
+	const bounds = this.getBoundingClientRect();
+	startPos.set( e.clientX - bounds.x, e.clientY - bounds.y );
 
 }
 
 function onMouseUp( e ) {
 
-	endPos.set( e.clientX, e.clientY );
+	const bounds = this.getBoundingClientRect();
+	endPos.set( e.clientX - bounds.x, e.clientY - bounds.y );
 	if ( startPos.distanceTo( endPos ) > 2 ) {
 
 		return;
 
 	}
 
-	raycaster.setFromCamera( mouse, params.orthographic ? orthoCamera : camera );
+	if ( lastHoveredElement === secondRenderer.domElement ) {
+
+		raycaster.setFromCamera( mouse, secondCamera );
+
+	} else {
+
+		raycaster.setFromCamera( mouse, params.orthographic ? orthoCamera : camera );
+
+	}
+
 	raycaster.firstHitOnly = true;
 	const results = raycaster.intersectObject( tiles.group, true );
 	if ( results.length ) {
@@ -292,7 +370,12 @@ function updateOrthoCamera() {
 	orthoCamera.rotation.copy( camera.rotation );
 
 	const scale = camera.position.distanceTo( controls.target ) / 2.0;
-	const aspect = window.innerWidth / window.innerHeight;
+	let aspect = window.innerWidth / window.innerHeight;
+	if ( params.showSecondView ) {
+
+		aspect *= 0.5;
+
+	}
 	orthoCamera.left = - aspect * scale;
 	orthoCamera.right = aspect * scale;
 	orthoCamera.bottom = - scale;
@@ -316,10 +399,30 @@ function animate() {
 	tiles.displayBoxBounds = params.displayBoxBounds;
 	tiles.colorMode = parseFloat( params.colorMode );
 
-	tiles.setCamera( params.orthographic ? orthoCamera : camera );
-	tiles.setResolutionFromRenderer( orthoCamera, renderer );
-	tiles.setResolutionFromRenderer( camera, renderer );
-	tiles.deleteCamera( params.orthographic ? camera : orthoCamera );
+	if ( params.orthographic ) {
+
+		tiles.deleteCamera( camera );
+		tiles.setCamera( orthoCamera );
+		tiles.setResolutionFromRenderer( orthoCamera, renderer );
+
+	} else {
+
+		tiles.deleteCamera( orthoCamera );
+		tiles.setCamera( camera );
+		tiles.setResolutionFromRenderer( camera, renderer );
+
+	}
+
+	if ( params.showSecondView ) {
+
+		tiles.setCamera( secondCamera );
+		tiles.setResolutionFromRenderer( secondCamera, secondRenderer );
+
+	} else {
+
+		tiles.deleteCamera( secondCamera );
+
+	}
 
 	offsetParent.rotation.set( 0, 0, 0 );
 	if ( params.up === '-Z' ) {
@@ -337,9 +440,18 @@ function animate() {
 
 	}
 
-	if ( params.enableRaycast ) {
+	if ( params.enableRaycast && lastHoveredElement !== null ) {
+
+		if ( lastHoveredElement === renderer.domElement ) {
+
+			raycaster.setFromCamera( mouse, params.orthographic ? orthoCamera : camera );
+
+		} else {
+
+			raycaster.setFromCamera( mouse, secondCamera );
+
+		}
 
-		raycaster.setFromCamera( mouse, params.orthographic ? orthoCamera : camera );
 		raycaster.firstHitOnly = true;
 		const results = raycaster.intersectObject( tiles.group, true );
 		if ( results.length ) {
@@ -361,16 +473,6 @@ function animate() {
 
 			}
 
-			if ( params.orthographic ) {
-
-				rayIntersect.scale.setScalar( closestHit.distance / 150 );
-
-			} else {
-
-				rayIntersect.scale.setScalar( closestHit.distance * camera.fov / 6000 );
-
-			}
-
 			rayIntersect.visible = true;
 
 		} else {
@@ -389,6 +491,7 @@ function animate() {
 	window.tiles = tiles;
 	if ( params.enableUpdate ) {
 
+		secondCamera.updateMatrixWorld();
 		camera.updateMatrixWorld();
 		orthoCamera.updateMatrixWorld();
 		tiles.update();
@@ -404,11 +507,33 @@ function render() {
 
 	updateOrthoCamera();
 
-	// render primary view
 	cameraHelper.visible = false;
 	orthoCameraHelper.visible = false;
+	secondCameraHelper.visible = false;
+
+	// render primary view
+	if ( params.orthographic ) {
+
+		const dist = orthoCamera.position.distanceTo( rayIntersect.position );
+		rayIntersect.scale.setScalar( dist / 150 );
+
+	} else {
+
+		const dist = camera.position.distanceTo( rayIntersect.position );
+		rayIntersect.scale.setScalar( dist * camera.fov / 6000 );
+
+	}
 	renderer.render( scene, params.orthographic ? orthoCamera : camera );
 
+	// render secondary view
+	if ( params.showSecondView ) {
+
+		const dist = secondCamera.position.distanceTo( rayIntersect.position );
+		rayIntersect.scale.setScalar( dist * secondCamera.fov / 6000 );
+		secondRenderer.render( scene, secondCamera );
+
+	}
+
 	// render third person view
 	thirdPersonRenderer.domElement.style.visibility = params.showThirdPerson ? 'visible' : 'hidden';
 	if ( params.showThirdPerson ) {
@@ -418,6 +543,16 @@ function render() {
 
 		orthoCameraHelper.update();
 		orthoCameraHelper.visible = params.orthographic;
+
+		if ( params.showSecondView ) {
+
+			secondCameraHelper.update();
+			secondCameraHelper.visible = true;
+
+		}
+
+		const dist = thirdPersonCamera.position.distanceTo( rayIntersect.position );
+		rayIntersect.scale.setScalar( dist * thirdPersonCamera.fov / 6000 );
 		thirdPersonRenderer.render( scene, thirdPersonCamera );
 
 	}

+ 29 - 9
src/three/TilesRenderer.js

@@ -253,11 +253,13 @@ export class TilesRenderer extends TilesRendererBase {
 
 			info.invScale = invScale;
 
+			// get frustum in grop root frame
 			tempMat.copy( group.matrixWorld );
 			tempMat.premultiply( camera.matrixWorldInverse );
 			tempMat.premultiply( camera.projectionMatrix );
 			frustum.setFromProjectionMatrix( tempMat );
 
+			// get transform position in group root frame
 			position.set( 0, 0, 0 );
 			position.applyMatrix4( camera.matrixWorld );
 			position.applyMatrix4( tempMat2 );
@@ -364,6 +366,7 @@ export class TilesRenderer extends TilesRendererBase {
 			loadIndex: 0,
 			transform,
 			active: false,
+			inFrustum: [],
 
 			box,
 			boxTransform,
@@ -597,6 +600,7 @@ export class TilesRenderer extends TilesRendererBase {
 		}
 
 		const cached = tile.cached;
+		const inFrustum = cached.inFrustum;
 		const cameras = this.cameras;
 		const cameraInfo = this.cameraInfo;
 
@@ -607,13 +611,17 @@ export class TilesRenderer extends TilesRendererBase {
 			const boundingBox = cached.box;
 			const boxTransformInverse = cached.boxTransformInverse;
 
-			let minError = Infinity;
+			let maxError = - Infinity;
+			let minDistance = Infinity;
 			for ( let i = 0, l = cameras.length; i < l; i ++ ) {
 
-				// TODO: move this logic (and the distance scale extraction) into the update preprocess step
+				if ( ! inFrustum[ i ] ) {
+
+					continue;
+
+				}
 
 				// transform camera position into local frame of the tile bounding box
-				// TODO: this should be the cameras world position
 				const camera = cameras[ i ];
 				const info = cameraInfo[ i ];
 				const invScale = info.invScale;
@@ -633,15 +641,17 @@ export class TilesRenderer extends TilesRendererBase {
 					const sseDenominator = info.sseDenominator;
 					error = tile.geometricError / ( scaledDistance * sseDenominator );
 
-					tile.cached.distance = scaledDistance;
+					minDistance = Math.min( minDistance, scaledDistance );
 
 				}
 
-				minError = Math.min( minError, error );
+				maxError = Math.max( maxError, error );
 
 			}
 
-			return minError;
+			tile.cached.distance = minDistance;
+
+			return maxError;
 
 		} else if ( 'sphere' in boundingVolume ) {
 
@@ -666,22 +676,32 @@ export class TilesRenderer extends TilesRendererBase {
 		// cache the root-space planes
 		// Use separating axis theorem for frustum and obb
 
-		const sphere = tile.cached.sphere;
+		const cached = tile.cached;
+		const sphere = cached.sphere;
+		const inFrustum = cached.inFrustum;
 		if ( sphere ) {
 
 			const cameraInfo = this.cameraInfo;
+			let inView = false;
 			for ( let i = 0, l = cameraInfo.length; i < l; i ++ ) {
 
+				// Track which camera frustums this tile is in so we can use it
+				// to ignore the error calculations for cameras that can't see it
 				const frustum = cameraInfo[ i ].frustum;
 				if ( frustum.intersectsSphere( sphere ) ) {
 
-					return true;
+					inView = true;
+					inFrustum[ i ] = true;
+
+				} else {
+
+					inFrustum[ i ] = false;
 
 				}
 
 			}
 
-			return false;
+			return inView;
 
 		}