Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EnvironmentControls updates #531

Closed
wants to merge 16 commits into from
101 changes: 63 additions & 38 deletions src/three/controls/EnvironmentControls.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export class EnvironmentControls extends EventDispatcher {
this.needsUpdate = false;
this.actionHeightOffset = 0;

// pivot point for drag and rotation
this.pivotPoint = new Vector3();

this.zoomDirectionSet = false;
Expand Down Expand Up @@ -182,7 +183,6 @@ export class EnvironmentControls extends EventDispatcher {
e.preventDefault();

const {
camera,
raycaster,
domElement,
up,
Expand Down Expand Up @@ -218,8 +218,7 @@ export class EnvironmentControls extends EventDispatcher {
mouseToCoords( _pointer.x, _pointer.y, domElement, _pointer );

// find the hit point
raycaster.setFromCamera( _pointer, camera );
const hit = this._raycast( raycaster );
const hit = this._raycastAtMousePosition( _pointer, raycaster );
if ( hit ) {

// if two fingers, right click, or shift click are being used then we trigger
Expand Down Expand Up @@ -448,6 +447,15 @@ export class EnvironmentControls extends EventDispatcher {

}

getLastInteractionPoint( target ) {

// TODO: Ensure are handling the last zoom point if possible
// TODO: Return null (or get center point?) if it can't be provided

return target.copy( this.pivotPoint );

}

detach() {

this.domElement = null;
Expand Down Expand Up @@ -633,6 +641,7 @@ export class EnvironmentControls extends EventDispatcher {

}

// addjust the orthographic camera zoom
if ( camera.isOrthographicCamera ) {

// get the mouse position before zoom
Expand All @@ -654,52 +663,55 @@ export class EnvironmentControls extends EventDispatcher {
camera.position.sub( _mouseAfter ).add( _mouseBefore );
camera.updateMatrixWorld();

} else {
}

// initialize the zoom direction
mouseToCoords( _pointer.x, _pointer.y, domElement, _pointer );
raycaster.setFromCamera( _pointer, camera );
zoomDirection.copy( raycaster.ray.direction ).normalize();
this.zoomDirectionSet = true;
// Adjust zoom position
// Needed for orthographic camera, as well, to avoid floating point error when
// shifting the camera position to keep the world under the mouse cursor
// ie due to the local "up" vector used for the camera orientation

// track the zoom direction we're going to use
const finalZoomDirection = _vec.copy( zoomDirection );
// initialize the zoom direction
mouseToCoords( _pointer.x, _pointer.y, domElement, _pointer );
raycaster.setFromCamera( _pointer, camera );
zoomDirection.copy( raycaster.ray.direction ).normalize();
this.zoomDirectionSet = true;

// always update the zoom target point in case the tiles are changing
if ( this._updateZoomPoint() ) {
// track the zoom direction we're going to use
const finalZoomDirection = _vec.copy( zoomDirection );

const dist = zoomPoint.distanceTo( camera.position );
// always update the zoom target point in case the tiles are changing
if ( this._updateZoomPoint() ) {

// scale the distance based on how far there is to move
if ( scale < 0 ) {
const dist = zoomPoint.distanceTo( camera.position );

const remainingDistance = Math.min( 0, dist - maxDistance );
scale = scale * dist * zoomSpeed * 0.0025;
scale = Math.max( scale, remainingDistance );
// scale the distance based on how far there is to move
if ( scale < 0 ) {

} else {
const remainingDistance = Math.min( 0, dist - maxDistance );
scale = scale * dist * zoomSpeed * 0.0025;
scale = Math.max( scale, remainingDistance );

const remainingDistance = Math.max( 0, dist - minDistance );
scale = scale * ( dist - minDistance ) * zoomSpeed * 0.0025;
scale = Math.min( scale, remainingDistance );
} else {

}
const remainingDistance = Math.max( 0, dist - minDistance );
scale = scale * ( dist - minDistance ) * zoomSpeed * 0.0025;
scale = Math.min( scale, remainingDistance );

camera.position.addScaledVector( zoomDirection, scale );
camera.updateMatrixWorld();
}

} else {
camera.position.addScaledVector( zoomDirection, scale );
camera.updateMatrixWorld();

// if we're zooming into nothing then use the distance from the ground to scale movement
const hit = this._getPointBelowCamera();
if ( hit ) {
} else {

const dist = hit.distance;
finalZoomDirection.set( 0, 0, - 1 ).transformDirection( camera.matrixWorld );
camera.position.addScaledVector( finalZoomDirection, scale * dist * 0.01 );
camera.updateMatrixWorld();
// if we're zooming into nothing then use the distance from the ground to scale movement
const hit = this._getPointBelowCamera();
if ( hit ) {

}
const dist = hit.distance;
finalZoomDirection.set( 0, 0, - 1 ).transformDirection( camera.matrixWorld );
camera.position.addScaledVector( finalZoomDirection, scale * dist * 0.01 );
camera.updateMatrixWorld();

}

Expand Down Expand Up @@ -835,7 +847,6 @@ export class EnvironmentControls extends EventDispatcher {
pivotPoint,
minAltitude,
maxAltitude,
up,
pointerTracker,
rotationSpeed,
} = this;
Expand All @@ -857,10 +868,10 @@ export class EnvironmentControls extends EventDispatcher {
this.getUpDirection( pivotPoint, _localUp );

// get the signed angle relative to the top down view
_vec.crossVectors( up, _forward ).normalize();
_vec.crossVectors( _localUp, _forward ).normalize();
_right.set( 1, 0, 0 ).transformDirection( camera.matrixWorld ).normalize();
const sign = Math.sign( _vec.dot( _right ) );
const angle = sign * up.angleTo( _forward );
const angle = sign * _localUp.angleTo( _forward );

// clamp the rotation to be within the provided limits
// clamp to 0 here, as well, so we don't "pop" to the the value range
Expand Down Expand Up @@ -937,6 +948,20 @@ export class EnvironmentControls extends EventDispatcher {

}

_raycastAtMousePosition( coords, raycaster ) {

// position the ray for the raycaster at the near clip plane
const camera = this.camera;
const ray = raycaster.ray;
ray.origin.setFromMatrixPosition( camera.matrixWorld );
ray.origin.set( coords.x, coords.y, - 1 ).unproject( camera );
ray.direction.set( coords.x, coords.y, 0.5 ).unproject( camera ).sub( ray.origin ).normalize();
raycaster.camera = camera;

return this._raycast( raycaster );

}

_raycast( raycaster ) {

const { scene, useFallbackPlane, fallbackPlane } = this;
Expand Down
123 changes: 109 additions & 14 deletions src/three/controls/GlobeControls.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,28 @@ export class GlobeControls extends EnvironmentControls {

}

getClosestPointOnSurface( target ) {

const { ellipsoid, camera, tilesGroup } = this;

_invMatrix
.copy( tilesGroup.matrixWorld )
.invert();

// get camera position in local frame
target
.setFromMatrixPosition( camera.matrixWorld )
.applyMatrix4( _invMatrix );

// get point on surface
ellipsoid
.getPositionToSurfacePoint( target, target )
.applyMatrix4( tilesGroup.matrixWorld );

return target;

}

// get the vector to the center of the provided globe
getVectorToCenter( target ) {

Expand Down Expand Up @@ -141,8 +163,9 @@ export class GlobeControls extends EnvironmentControls {
}

// if we're outside the transition threshold then we toggle some reorientation behavior
// when adjusting the up frame while moving hte camera
if ( distanceToCenter > GLOBE_TRANSITION_THRESHOLD ) {
// when adjusting the up frame
// whether controls are based on a far distance from the camera while moving hte camera
if ( ! this._isNearControls() ) {

if ( this.state !== NONE && this._dragMode !== 1 && this._rotationMode !== 1 ) {

Expand Down Expand Up @@ -179,6 +202,19 @@ export class GlobeControls extends EnvironmentControls {
const horizonDistance = ellipsoid.calculateHorizonDistance( _latLon.lat, elevation );
camera.far = horizonDistance + 0.1;



if ( camera.isOrthographicCamera ) {

_invMatrix.copy( camera.matrixWorld ).invert();

const v = new Vector3().setFromMatrixPosition( this.tilesGroup.matrixWorld ).applyMatrix4( _invMatrix );

camera.near = - Math.max( ...this.ellipsoid.radius );
camera.far = - v.z;

}

camera.updateProjectionMatrix();

}
Expand All @@ -197,17 +233,18 @@ export class GlobeControls extends EnvironmentControls {

super._setFrame( ...args );

if ( this.getDistanceToCenter() < GLOBE_TRANSITION_THRESHOLD ) {
if ( this._isNearControls() ) {

this._alignCameraUp( this.up );
this._alignCameraUp( this.up, 1 );

}

}

_updatePosition( ...args ) {

if ( this._dragMode === 1 || this.getDistanceToCenter() < GLOBE_TRANSITION_THRESHOLD ) {
// whether controls are based on a far distance from the camera
if ( this._dragMode === 1 || this._isNearControls() ) {

this._dragMode = 1;

Expand Down Expand Up @@ -264,7 +301,8 @@ export class GlobeControls extends EnvironmentControls {
// disable rotation once we're outside the control transition
_updateRotation( ...args ) {

if ( this._rotationMode === 1 || this.getDistanceToCenter() < GLOBE_TRANSITION_THRESHOLD ) {
// whether controls are based on a far distance from the camera
if ( this._rotationMode === 1 || this._isNearControls() ) {

this._rotationMode = 1;
super._updateRotation( ...args );
Expand All @@ -280,21 +318,56 @@ export class GlobeControls extends EnvironmentControls {

_updateZoom() {

const scale = this.zoomDelta;
if ( this.getDistanceToCenter() < GLOBE_TRANSITION_THRESHOLD || scale > 0 ) {
window.CONTROLS = this;
const { zoomDelta, camera, zoomSpeed } = this;

// whether controls are based on a far distance from the camera
if ( this._isNearControls() || zoomDelta > 0 ) {

super._updateZoom();

} else {

// orient the camera to focus on the earth during the zoom
const alpha = MathUtils.mapLinear( this.getDistanceToCenter(), GLOBE_TRANSITION_THRESHOLD, MAX_GLOBE_DISTANCE, 0, 1 );
this._tiltTowardsCenter( MathUtils.lerp( 1, 0.8, alpha ) );
this._alignCameraUpToNorth( MathUtils.lerp( 1, 0.9, alpha ) );
if ( camera.isOrthographicCamera ) {

// zoom the camera
const normalizedDelta = Math.pow( 0.95, Math.abs( zoomDelta * 0.05 ) );
const scaleFactor = zoomDelta > 0 ? 1 / Math.abs( normalizedDelta ) : normalizedDelta;
const maxDiameter = 2.0 * Math.max( ...this.ellipsoid.radius );
const minZoom = Math.min( camera.right - camera.left, camera.top - camera.bottom ) / maxDiameter;

camera.zoom = Math.max( 0.75 * minZoom, camera.zoom * scaleFactor * zoomSpeed );
camera.updateProjectionMatrix();

let alpha = MathUtils.mapLinear( camera.zoom, minZoom * 1.25, minZoom * 0.75, 0, 1 );
if ( alpha > 0 ) {

alpha = MathUtils.clamp( alpha, 0, 1 );
this._tiltTowardsCenter( MathUtils.lerp( 1, 0.8, alpha ) );
this._alignCameraUpToNorth( MathUtils.lerp( 1, 0.9, alpha ) );

}

// this.zoomDelta = 0;

}

if ( camera.isPerspectiveCamera ) {

// orient the camera to focus on the earth during the zoom
const alpha = MathUtils.mapLinear( this.getDistanceToCenter(), GLOBE_TRANSITION_THRESHOLD, MAX_GLOBE_DISTANCE, 0, 1 );
this._tiltTowardsCenter( MathUtils.lerp( 1, 0.8, alpha ) );
this._alignCameraUpToNorth( MathUtils.lerp( 1, 0.9, alpha ) );

}

// get the distance to the surface of the sphere and compute teh zoom scale
const dist = this.getClosestPointOnSurface( _vec ).distanceTo( camera.position );
const scale = zoomDelta * dist * zoomSpeed * 0.0025;

// zoom out directly from the globe center
this.getVectorToCenter( _vec );
this.camera.position.addScaledVector( _vec, scale * 0.0025 );
this.getVectorToCenter( _vec ).normalize();
this.camera.position.addScaledVector( _vec, scale );
this.camera.updateMatrixWorld();

this.zoomDelta = 0;
Expand Down Expand Up @@ -352,4 +425,26 @@ export class GlobeControls extends EnvironmentControls {

}

// whether controls are based on a far distance from the camera
_isNearControls() {

const { camera, ellipsoid } = this;
const maxDiameter = 2.0 * Math.max( ...ellipsoid.radius );

let isFullyInView = false;
if ( camera.isOrthographicCamera ) {

const maxView = Math.min( camera.right - camera.left, camera.top - camera.bottom ) / camera.zoom;
isFullyInView = 0.5 * maxDiameter > maxView;

} else {

isFullyInView = this.getDistanceToCenter() < GLOBE_TRANSITION_THRESHOLD;

}

return isFullyInView;

}

}
Loading