Skip to content

Commit

Permalink
CameraTransitionManager: Support positional differences, globe contro…
Browse files Browse the repository at this point in the history
…ls clamping (#830)

* Initial pass at fix

* updates

* Add option for autosync

* Remove unused field

* Fix transition clip planes

* Adjust the camera height

* Rename function

* Adjust the camera name in demos

* docs update

* Fix example
  • Loading branch information
gkjohnson authored Oct 28, 2024
1 parent e6b0d6a commit c8e32e6
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 60 deletions.
16 changes: 8 additions & 8 deletions PLUGINS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)_
Expand All @@ -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.
Expand Down
7 changes: 7 additions & 0 deletions example/fadingTiles.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand Down Expand Up @@ -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();

} );
Expand Down
13 changes: 6 additions & 7 deletions example/googleMapsExample.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 } ) => {

Expand Down Expand Up @@ -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();

Expand Down
63 changes: 29 additions & 34 deletions example/src/camera/CameraTransitionManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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();

Expand Down
30 changes: 27 additions & 3 deletions src/three/controls/EnvironmentControls.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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;

Expand Down
15 changes: 7 additions & 8 deletions src/three/controls/GlobeControls.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down

0 comments on commit c8e32e6

Please sign in to comment.