|
@@ -5,7 +5,10 @@ import {
|
|
|
Spherical,
|
|
|
TOUCH,
|
|
|
Vector2,
|
|
|
- Vector3
|
|
|
+ Vector3,
|
|
|
+ Plane,
|
|
|
+ Ray,
|
|
|
+ MathUtils
|
|
|
} from 'three';
|
|
|
|
|
|
// OrbitControls performs orbiting, dollying (zooming), and panning.
|
|
@@ -18,6 +21,9 @@ import {
|
|
|
const _changeEvent = { type: 'change' };
|
|
|
const _startEvent = { type: 'start' };
|
|
|
const _endEvent = { type: 'end' };
|
|
|
+const _ray = new Ray();
|
|
|
+const _plane = new Plane();
|
|
|
+const TILT_LIMIT = Math.cos( 70 * MathUtils.DEG2RAD );
|
|
|
|
|
|
class OrbitControls extends EventDispatcher {
|
|
|
|
|
@@ -72,6 +78,7 @@ class OrbitControls extends EventDispatcher {
|
|
|
this.panSpeed = 1.0;
|
|
|
this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up
|
|
|
this.keyPanSpeed = 7.0; // pixels moved per arrow key push
|
|
|
+ this.zoomToCursor = false;
|
|
|
|
|
|
// Set to true to automatically rotate around the target
|
|
|
// If auto-rotate is enabled, you must call controls.update() in your animation loop
|
|
@@ -230,11 +237,6 @@ class OrbitControls extends EventDispatcher {
|
|
|
spherical.makeSafe();
|
|
|
|
|
|
|
|
|
- spherical.radius *= scale;
|
|
|
-
|
|
|
- // restrict radius to be between desired limits
|
|
|
- spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) );
|
|
|
-
|
|
|
// move target to panned location
|
|
|
|
|
|
if ( scope.enableDamping === true ) {
|
|
@@ -247,6 +249,19 @@ class OrbitControls extends EventDispatcher {
|
|
|
|
|
|
}
|
|
|
|
|
|
+ // adjust the camera position based on zoom only if we're not zooming to the cursor or if it's an ortho camera
|
|
|
+ // we adjust zoom later in these cases
|
|
|
+ if ( scope.zoomToCursor && performCursorZoom || scope.object.isOrthographicCamera ) {
|
|
|
+
|
|
|
+ spherical.radius = clampDistance( spherical.radius );
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ spherical.radius = clampDistance( spherical.radius * scale );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
offset.setFromSpherical( spherical );
|
|
|
|
|
|
// rotate offset back to "camera-up-vector-is-up" space
|
|
@@ -271,7 +286,91 @@ class OrbitControls extends EventDispatcher {
|
|
|
|
|
|
}
|
|
|
|
|
|
+ // adjust camera position
|
|
|
+ let zoomChanged = false;
|
|
|
+ if ( scope.zoomToCursor && performCursorZoom ) {
|
|
|
+
|
|
|
+ let newRadius = null;
|
|
|
+ if ( scope.object.isPerspectiveCamera ) {
|
|
|
+
|
|
|
+ // move the camera down the pointer ray
|
|
|
+ // this method avoids floating point error
|
|
|
+ const prevRadius = offset.length();
|
|
|
+ newRadius = clampDistance( prevRadius * scale );
|
|
|
+
|
|
|
+ const radiusDelta = prevRadius - newRadius;
|
|
|
+ scope.object.position.addScaledVector( dollyDirection, radiusDelta );
|
|
|
+ scope.object.updateMatrixWorld();
|
|
|
+
|
|
|
+ } else if ( scope.object.isOrthographicCamera ) {
|
|
|
+
|
|
|
+ // adjust the ortho camera position based on zoom changes
|
|
|
+ const mouseBefore = new Vector3( mouse.x, mouse.y, 0 );
|
|
|
+ mouseBefore.unproject( scope.object );
|
|
|
+
|
|
|
+ scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / scale ) );
|
|
|
+ scope.object.updateProjectionMatrix();
|
|
|
+ zoomChanged = true;
|
|
|
+
|
|
|
+ const mouseAfter = new Vector3( mouse.x, mouse.y, 0 );
|
|
|
+ mouseAfter.unproject( scope.object );
|
|
|
+
|
|
|
+ scope.object.position.sub( mouseAfter ).add( mouseBefore );
|
|
|
+ scope.object.updateMatrixWorld();
|
|
|
+
|
|
|
+ newRadius = offset.length();
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - zoom to cursor disabled.' );
|
|
|
+ scope.zoomToCursor = false;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ // handle the placement of the target
|
|
|
+ if ( newRadius !== null ) {
|
|
|
+
|
|
|
+ if ( this.screenSpacePanning ) {
|
|
|
+
|
|
|
+ // position the orbit target in front of the new camera position
|
|
|
+ scope.target.set( 0, 0, - 1 )
|
|
|
+ .transformDirection( scope.object.matrix )
|
|
|
+ .multiplyScalar( newRadius )
|
|
|
+ .add( scope.object.position );
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ // get the ray and translation plane to compute target
|
|
|
+ _ray.origin.copy( scope.object.position );
|
|
|
+ _ray.direction.set( 0, 0, - 1 ).transformDirection( scope.object.matrix );
|
|
|
+
|
|
|
+ // if the camera is 20 degrees above the horizon then don't adjust the focus target to avoid
|
|
|
+ // extremely large values
|
|
|
+ if ( Math.abs( scope.object.up.dot( _ray.direction ) ) < TILT_LIMIT ) {
|
|
|
+
|
|
|
+ object.lookAt( scope.target );
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ _plane.setFromNormalAndCoplanarPoint( scope.object.up, scope.target );
|
|
|
+ _ray.intersectPlane( _plane, scope.target );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ } else if ( scope.object.isOrthographicCamera ) {
|
|
|
+
|
|
|
+ scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / scale ) );
|
|
|
+ scope.object.updateProjectionMatrix();
|
|
|
+ zoomChanged = true;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
scale = 1;
|
|
|
+ performCursorZoom = false;
|
|
|
|
|
|
// update condition is:
|
|
|
// min(camera displacement, camera rotation in radians)^2 > EPS
|
|
@@ -350,7 +449,6 @@ class OrbitControls extends EventDispatcher {
|
|
|
|
|
|
let scale = 1;
|
|
|
const panOffset = new Vector3();
|
|
|
- let zoomChanged = false;
|
|
|
|
|
|
const rotateStart = new Vector2();
|
|
|
const rotateEnd = new Vector2();
|
|
@@ -364,6 +462,10 @@ class OrbitControls extends EventDispatcher {
|
|
|
const dollyEnd = new Vector2();
|
|
|
const dollyDelta = new Vector2();
|
|
|
|
|
|
+ const dollyDirection = new Vector3();
|
|
|
+ const mouse = new Vector2();
|
|
|
+ let performCursorZoom = false;
|
|
|
+
|
|
|
const pointers = [];
|
|
|
const pointerPositions = {};
|
|
|
|
|
@@ -474,16 +576,10 @@ class OrbitControls extends EventDispatcher {
|
|
|
|
|
|
function dollyOut( dollyScale ) {
|
|
|
|
|
|
- if ( scope.object.isPerspectiveCamera ) {
|
|
|
+ if ( scope.object.isPerspectiveCamera || scope.object.isOrthographicCamera ) {
|
|
|
|
|
|
scale /= dollyScale;
|
|
|
|
|
|
- } else if ( scope.object.isOrthographicCamera ) {
|
|
|
-
|
|
|
- scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) );
|
|
|
- scope.object.updateProjectionMatrix();
|
|
|
- zoomChanged = true;
|
|
|
-
|
|
|
} else {
|
|
|
|
|
|
console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
|
|
@@ -495,16 +591,10 @@ class OrbitControls extends EventDispatcher {
|
|
|
|
|
|
function dollyIn( dollyScale ) {
|
|
|
|
|
|
- if ( scope.object.isPerspectiveCamera ) {
|
|
|
+ if ( scope.object.isPerspectiveCamera || scope.object.isOrthographicCamera ) {
|
|
|
|
|
|
scale *= dollyScale;
|
|
|
|
|
|
- } else if ( scope.object.isOrthographicCamera ) {
|
|
|
-
|
|
|
- scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) );
|
|
|
- scope.object.updateProjectionMatrix();
|
|
|
- zoomChanged = true;
|
|
|
-
|
|
|
} else {
|
|
|
|
|
|
console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
|
|
@@ -514,6 +604,35 @@ class OrbitControls extends EventDispatcher {
|
|
|
|
|
|
}
|
|
|
|
|
|
+ function updateMouseParameters( event ) {
|
|
|
+
|
|
|
+ if ( ! scope.zoomToCursor ) {
|
|
|
+
|
|
|
+ return;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ performCursorZoom = true;
|
|
|
+
|
|
|
+ const rect = scope.domElement.getBoundingClientRect();
|
|
|
+ const x = event.clientX - rect.left;
|
|
|
+ const y = event.clientY - rect.top;
|
|
|
+ const w = rect.width;
|
|
|
+ const h = rect.height;
|
|
|
+
|
|
|
+ mouse.x = ( x / w ) * 2 - 1;
|
|
|
+ mouse.y = - ( y / h ) * 2 + 1;
|
|
|
+
|
|
|
+ dollyDirection.set( mouse.x, mouse.y, 1 ).unproject( object ).sub( object.position ).normalize();
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function clampDistance( dist ) {
|
|
|
+
|
|
|
+ return Math.max( scope.minDistance, Math.min( scope.maxDistance, dist ) );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
//
|
|
|
// event callbacks - update the object state
|
|
|
//
|
|
@@ -526,6 +645,7 @@ class OrbitControls extends EventDispatcher {
|
|
|
|
|
|
function handleMouseDownDolly( event ) {
|
|
|
|
|
|
+ updateMouseParameters( event );
|
|
|
dollyStart.set( event.clientX, event.clientY );
|
|
|
|
|
|
}
|
|
@@ -592,6 +712,8 @@ class OrbitControls extends EventDispatcher {
|
|
|
|
|
|
function handleMouseWheel( event ) {
|
|
|
|
|
|
+ updateMouseParameters( event );
|
|
|
+
|
|
|
if ( event.deltaY < 0 ) {
|
|
|
|
|
|
dollyIn( getZoomScale() );
|