diff --git a/PLUGINS.md b/PLUGINS.md index db49ea6aa..a02ce5e41 100644 --- a/PLUGINS.md +++ b/PLUGINS.md @@ -673,6 +673,14 @@ getPivotPoint( target : Vector3 ) : target Gets the last used interaction point. +### .adjustCamera + +```js +updateCameraClipPlanes( camera : Camera ) : void +``` + +Updates the clip planes and position of the given camera so the globe is encapsulated correctly and is positioned appropriately above the terrain. Used when working with the transition manager to make sure both cameras being transitioned are positioned properly. + ## GlobeControls _extends [EnvironmentControls](#environmentcontrols)_ @@ -690,14 +698,6 @@ constructor( Takes the same items as `EnvironmentControls` in addition to the Google globe tiles renderer. -### .updateCameraClipPlanes - -```js -updateCameraClipPlanes( camera : Camera ) : void -``` - -Updates the clip planes (and position if orthographic) of the given camera so the globe is encapsulated correctly. Used when working with the transition manager to make sure both cameras being transitioned are positioned properly. - ## CameraTransitionManager Helper class for performing a transition animation between a perspective and orthographic camera. diff --git a/example/fadingTiles.js b/example/fadingTiles.js index 3a08732ad..f7a6bc240 100644 --- a/example/fadingTiles.js +++ b/example/fadingTiles.js @@ -51,6 +51,8 @@ function init() { ); transition.camera.position.set( 20, 10, 20 ); transition.camera.lookAt( 0, 0, 0 ); + transition.autoSync = false; + transition.addEventListener( 'camera-changed', ( { camera, prevCamera } ) => { skyTiles.deleteCamera( prevCamera ); @@ -86,6 +88,11 @@ function init() { cameraFolder.add( params, 'orthographic' ).onChange( v => { transition.fixedPoint.copy( controls.pivotPoint ); + + // adjust the camera before the transition begins + transition.syncCameras(); + controls.adjustCamera( transition.perspectiveCamera ); + controls.adjustCamera( transition.orthographicCamera ); transition.toggle(); } ); diff --git a/example/googleMapsExample.js b/example/googleMapsExample.js index c5c4a515e..711dc2808 100644 --- a/example/googleMapsExample.js +++ b/example/googleMapsExample.js @@ -95,9 +95,9 @@ function init() { new PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 160000000 ), new OrthographicCamera( - 1, 1, 1, - 1, 1, 160000000 ), ); - transition.perspectiveCamera.position.set( 4800000, 2570000, 14720000 ); transition.perspectiveCamera.lookAt( 0, 0, 0 ); + transition.autoSync = false; transition.addEventListener( 'camera-changed', ( { camera, prevCamera } ) => { @@ -127,13 +127,12 @@ function init() { gui.add( params, 'orthographic' ).onChange( v => { - // TODO: this updates the clip planes based on the current position of the camera which is - // synced by the transition manager. However the position and clip planes that would be applied - // by the globe controls when using the controls with the camera are different resulting in - // some clipping artifacts during transition that "pop" once finished. controls.getPivotPoint( transition.fixedPoint ); - controls.updateCameraClipPlanes( transition.perspectiveCamera ); - controls.updateCameraClipPlanes( transition.orthographicCamera ); + + // sync the camera positions and then adjust the camera views + transition.syncCameras(); + controls.adjustCamera( transition.perspectiveCamera ); + controls.adjustCamera( transition.orthographicCamera ); transition.toggle(); diff --git a/example/src/camera/CameraTransitionManager.js b/example/src/camera/CameraTransitionManager.js index 062b9e121..8b28e10ad 100644 --- a/example/src/camera/CameraTransitionManager.js +++ b/example/src/camera/CameraTransitionManager.js @@ -2,7 +2,8 @@ import { Clock, EventDispatcher, MathUtils, OrthographicCamera, PerspectiveCamer const _forward = new Vector3(); const _vec = new Vector3(); -const _virtOrthoPos = new Vector3(); +const _orthographicCamera = new OrthographicCamera(); +const _targetPos = new Vector3(); export class CameraTransitionManager extends EventDispatcher { @@ -33,6 +34,7 @@ export class CameraTransitionManager extends EventDispatcher { this.orthographicOffset = 50; this.fixedPoint = new Vector3(); this.duration = 200; + this.autoSync = true; this._target = 0; this._alpha = 0; @@ -49,7 +51,11 @@ export class CameraTransitionManager extends EventDispatcher { update() { // update transforms - this.syncCameras(); + if ( this.autoSync ) { + + this.syncCameras(); + + } // perform transition const { perspectiveCamera, orthographicCamera, transitionCamera, camera } = this; @@ -205,57 +211,46 @@ export class CameraTransitionManager extends EventDispatcher { _updateTransitionCamera() { + // Perform transition interpolation between the orthographic and perspective camera + // alpha === 0 : perspective + // alpha === 1 : orthographic + const { perspectiveCamera, orthographicCamera, transitionCamera, fixedPoint } = this; const alpha = this._alpha; // get the forward vector - _forward.set( 0, 0, - 1 ).transformDirection( perspectiveCamera.matrixWorld ).normalize(); + _forward.set( 0, 0, - 1 ).transformDirection( orthographicCamera.matrixWorld ).normalize(); + + _orthographicCamera.copy( orthographicCamera ); + _orthographicCamera.position.addScaledVector( _forward, orthographicCamera.near ); + orthographicCamera.far -= orthographicCamera.near; + orthographicCamera.near = 0; // compute the projection height based on the perspective camera + _forward.set( 0, 0, - 1 ).transformDirection( perspectiveCamera.matrixWorld ).normalize(); const distToPoint = Math.abs( _vec.subVectors( perspectiveCamera.position, fixedPoint ).dot( _forward ) ); const projectionHeight = 2 * Math.tan( MathUtils.DEG2RAD * perspectiveCamera.fov * 0.5 ) * distToPoint; - // Perform transition interpolation between the orthographic and perspective camera - - // alpha === 0 : perspective - // alpha === 1 : orthographic - // calculate the target distance and fov to position the camera at const targetFov = MathUtils.lerp( perspectiveCamera.fov, 1, alpha ); const targetDistance = projectionHeight * 0.5 / Math.tan( MathUtils.DEG2RAD * targetFov * 0.5 ); + const targetPos = _targetPos.lerpVectors( perspectiveCamera.position, _orthographicCamera.position, alpha ); + targetPos.addScaledVector( _forward, Math.abs( _vec.subVectors( targetPos, fixedPoint ).dot( _forward ) ) - targetDistance ); - // virtual orthographic position to support negative near plane - const virtOrthoNear = 0; - const virtOrthoFar = orthographicCamera.far - orthographicCamera.near; - _virtOrthoPos.copy( orthographicCamera.position ).addScaledVector( _forward, orthographicCamera.near ); + const distToPersp = _vec.subVectors( perspectiveCamera.position, targetPos ).dot( _forward ); + const distToOrtho = _vec.subVectors( _orthographicCamera.position, targetPos ).dot( _forward ); - // calculate all distance to the focus point - const perspDistanceToPoint = Math.abs( _vec.subVectors( perspectiveCamera.position, fixedPoint ).dot( _forward ) ); - const orthoDistanceToPoint = Math.abs( _vec.subVectors( _virtOrthoPos, fixedPoint ).dot( _forward ) ); - - // find the target near and far positions - const perspNearPlane = perspDistanceToPoint - perspectiveCamera.near; - const orthoNearPlane = orthoDistanceToPoint - virtOrthoNear; - const targetNearPlane = MathUtils.lerp( perspNearPlane, orthoNearPlane, alpha ); - - const perspFarPlane = perspDistanceToPoint - perspectiveCamera.far; - const orthoFarPlane = orthoDistanceToPoint - virtOrthoFar; - const targetFarPlane = MathUtils.lerp( perspFarPlane, orthoFarPlane, alpha ); - - // compute delta between planes so we can scale the minimum near plane value - // and avoid depth artifacts from floating point calculations - const finalNearPlane = targetDistance - targetNearPlane; - const finalFarPlane = targetDistance - targetFarPlane; - const planeDelta = finalFarPlane - finalNearPlane; + const targetNearPlane = MathUtils.lerp( distToPersp + perspectiveCamera.near, distToOrtho + _orthographicCamera.near, alpha ); + const targetFarPlane = MathUtils.lerp( distToPersp + perspectiveCamera.far, distToOrtho + _orthographicCamera.far, alpha ); + const planeDelta = targetFarPlane - targetNearPlane; // update the camera state transitionCamera.aspect = perspectiveCamera.aspect; transitionCamera.fov = targetFov; - transitionCamera.near = Math.max( finalNearPlane, planeDelta * 1e-5 ); - transitionCamera.far = finalFarPlane; - transitionCamera.position.copy( perspectiveCamera.position ).addScaledVector( _forward, perspDistanceToPoint - targetDistance ); + transitionCamera.near = Math.max( targetNearPlane, planeDelta * 1e-5 ); + transitionCamera.far = targetFarPlane; + transitionCamera.position.copy( targetPos ); transitionCamera.rotation.copy( perspectiveCamera.rotation ); - transitionCamera.updateProjectionMatrix(); transitionCamera.updateMatrixWorld(); diff --git a/src/three/controls/EnvironmentControls.js b/src/three/controls/EnvironmentControls.js index 993302e8f..28e2cf5a3 100644 --- a/src/three/controls/EnvironmentControls.js +++ b/src/three/controls/EnvironmentControls.js @@ -708,6 +708,30 @@ export class EnvironmentControls extends EventDispatcher { } + // updates the camera to position it based on the constraints of the controls + adjustCamera( camera ) { + + const { adjustHeight, cameraRadius } = this; + if ( camera.isPerspectiveCamera ) { + + // adjust the camera height + this.getUpDirection( camera.position, _localUp ); + const hit = adjustHeight && this._getPointBelowCamera( camera.position, _localUp ) || null; + if ( hit ) { + + const dist = hit.distance; + if ( dist < cameraRadius ) { + + camera.position.addScaledVector( _localUp, cameraRadius - dist ); + + } + + } + + } + + } + dispose() { this.detach(); @@ -930,11 +954,11 @@ export class EnvironmentControls extends EventDispatcher { } // returns the point below the camera - _getPointBelowCamera() { + _getPointBelowCamera( point = this.camera.position, up = this.up ) { - const { camera, raycaster, up } = this; + const { raycaster } = this; raycaster.ray.direction.copy( up ).multiplyScalar( - 1 ); - raycaster.ray.origin.copy( camera.position ).addScaledVector( up, 1e5 ); + raycaster.ray.origin.copy( point ).addScaledVector( up, 1e5 ); raycaster.near = 0; raycaster.far = Infinity; diff --git a/src/three/controls/GlobeControls.js b/src/three/controls/GlobeControls.js index a9231a55b..99adcedab 100644 --- a/src/three/controls/GlobeControls.js +++ b/src/three/controls/GlobeControls.js @@ -187,21 +187,20 @@ export class GlobeControls extends EnvironmentControls { super.update( deltaTime ); // update the camera planes and the ortho camera position - this.updateCameraClipPlanes( camera ); + this.adjustCamera( camera ); } - // Updates the passed camera near and far clip planes to encapsulate the - // ellipsoid from their current position. - updateCameraClipPlanes( camera ) { + // Updates the passed camera near and far clip planes to encapsulate the ellipsoid from the + // current position in addition to adjusting the height. + adjustCamera( camera ) { - const { - tilesGroup, - ellipsoid, - } = this; + super.adjustCamera( camera ); + const { tilesGroup, ellipsoid } = this; if ( camera.isPerspectiveCamera ) { + // adjust the clip planes const distanceToCenter = _vec .setFromMatrixPosition( tilesGroup.matrixWorld ) .sub( camera.position ).length();