Skip to content

Commit

Permalink
Fix perspective drag when moving above the horizon (#621)
Browse files Browse the repository at this point in the history
* Add a helper

* Update utility

* Related to #619

* fixes

* comments
  • Loading branch information
gkjohnson committed Jul 16, 2024
1 parent ca81c73 commit c076d8b
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 16 deletions.
6 changes: 6 additions & 0 deletions example/googleMapsExample.js
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,12 @@ function animate() {
controls.update();
transition.update();

if ( transition.animating ) {

controls.updateCameraClipPlanes( transition.camera );

}

const camera = transition.camera;

// update options
Expand Down
49 changes: 34 additions & 15 deletions src/three/controls/GlobeControls.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
Ray,
} from 'three';
import { EnvironmentControls, NONE } from './EnvironmentControls.js';
import { closestRayEllipsoidSurfacePointEstimate, makeRotateAroundPoint, mouseToCoords, setRaycasterFromCamera } from './utils.js';
import { closestRayEllipsoidSurfacePointEstimate, closestRaySpherePointFromRotation, makeRotateAroundPoint, mouseToCoords, setRaycasterFromCamera } from './utils.js';
import { Ellipsoid } from '../math/Ellipsoid.js';

const _invMatrix = new Matrix4();
Expand Down Expand Up @@ -78,20 +78,27 @@ export class GlobeControls extends EnvironmentControls {
getPivotPoint( target ) {

const { camera, tilesGroup, ellipsoid } = this;
if ( super.getPivotPoint( target ) === null ) {

// get camera values
_forward.set( 0, 0, - 1 ).transformDirection( camera.matrixWorld );
_invMatrix.copy( tilesGroup.matrixWorld ).invert();
// get camera values
_forward.set( 0, 0, - 1 ).transformDirection( camera.matrixWorld );
_invMatrix.copy( tilesGroup.matrixWorld ).invert();

// set a ray in the local ellipsoid frame
_ray.origin.copy( camera.position );
_ray.direction.copy( _forward );
_ray.applyMatrix4( _invMatrix );

// set a ray in the local ellipsoid frame
_ray.origin.copy( camera.position );
_ray.direction.copy( _forward );
_ray.applyMatrix4( _invMatrix );
// get the estimated closest point
closestRayEllipsoidSurfacePointEstimate( _ray, ellipsoid, _vec );
_vec.applyMatrix4( tilesGroup.matrixWorld );

// get the estimated closest point
closestRayEllipsoidSurfacePointEstimate( _ray, ellipsoid, target );
target.applyMatrix4( tilesGroup.matrixWorld );
// use the closest point if no pivot was provided or it's closer
if (
super.getPivotPoint( target ) === null ||
target.distanceTo( _ray.origin ) > _vec.distanceTo( _ray.origin )
) {

target.copy( _vec );

}

Expand Down Expand Up @@ -240,7 +247,7 @@ export class GlobeControls extends EnvironmentControls {
// plane approaching zero as the camera goes to or below sea level.
const elevation = Math.max( ellipsoid.getPositionElevation( _pos ), MIN_ELEVATION );
const horizonDistance = ellipsoid.calculateHorizonDistance( _latLon.lat, elevation );
camera.far = horizonDistance + 0.1;
camera.far = horizonDistance * 3 + 0.1;

camera.updateProjectionMatrix();

Expand Down Expand Up @@ -315,8 +322,20 @@ export class GlobeControls extends EnvironmentControls {
const pivotRadius = _vec.copy( pivotPoint ).applyMatrix4( _invMatrix ).length();
_ellipsoid.radius.setScalar( pivotRadius );

// find the hit point
closestRayEllipsoidSurfacePointEstimate( raycaster.ray, _ellipsoid, _vec );
// find the hit point and use the closest point on the horizon if we miss
if ( camera.isPerspectiveCamera ) {

if ( ! _ellipsoid.intersectRay( raycaster.ray, _vec ) ) {

closestRaySpherePointFromRotation( raycaster.ray, pivotRadius, _vec );

}

} else {

closestRayEllipsoidSurfacePointEstimate( raycaster.ray, _ellipsoid, _vec );

}
_vec.applyMatrix4( tilesGroup.matrixWorld );

// get the point directions
Expand Down
30 changes: 29 additions & 1 deletion src/three/controls/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ export function mouseToCoords( clientX, clientY, element, target ) {

// Returns an estimate of the closest point on the ellipsoid to the ray. Returns
// the surface intersection if they collide.
// TODO: this will possibly be unused
export function closestRayEllipsoidSurfacePointEstimate( ray, ellipsoid, target ) {

if ( ellipsoid.intersectRay( ray, target ) ) {
Expand All @@ -57,6 +56,35 @@ export function closestRayEllipsoidSurfacePointEstimate( ray, ellipsoid, target

}

// find the closest ray on the horizon when the ray passes above the sphere
export function closestRaySpherePointFromRotation( ray, radius, target ) {

const hypotenuse = ray.origin.length();

// angle inside the sphere
const theta = Math.acos( radius / hypotenuse );

// the direction to the camera
target
.copy( ray.origin )
.multiplyScalar( - 1 )
.normalize();

// get the normal of the plane the ray and origin lie in
const rotationVec = _vec
.crossVectors( target, ray.direction )
.normalize();

// rotate the camera direction by angle and scale it to the surface
target
.multiplyScalar( - 1 )
.applyAxisAngle( rotationVec, - theta )
.normalize()
.multiplyScalar( radius );

}


// custom version of set raycaster from camera that relies on the underlying matrices
// so the ray origin is position at the camera near clip.
export function setRaycasterFromCamera( raycaster, mouse, camera ) {
Expand Down

0 comments on commit c076d8b

Please sign in to comment.