@@ -5,7 +5,10 @@ import {
55 Spherical ,
66 TOUCH ,
77 Vector2 ,
8- Vector3
8+ Vector3 ,
9+ Plane ,
10+ Ray ,
11+ MathUtils
912} from 'three' ;
1013
1114// OrbitControls performs orbiting, dollying (zooming), and panning.
@@ -18,6 +21,9 @@ import {
1821const _changeEvent = { type : 'change' } ;
1922const _startEvent = { type : 'start' } ;
2023const _endEvent = { type : 'end' } ;
24+ const _ray = new Ray ( ) ;
25+ const _plane = new Plane ( ) ;
26+ const TILT_LIMIT = Math . cos ( 70 * MathUtils . DEG2RAD ) ;
2127
2228class OrbitControls extends EventDispatcher {
2329
@@ -72,6 +78,7 @@ class OrbitControls extends EventDispatcher {
7278 this . panSpeed = 1.0 ;
7379 this . screenSpacePanning = true ; // if false, pan orthogonal to world-space direction camera.up
7480 this . keyPanSpeed = 7.0 ; // pixels moved per arrow key push
81+ this . zoomToCursor = false ;
7582
7683 // Set to true to automatically rotate around the target
7784 // If auto-rotate is enabled, you must call controls.update() in your animation loop
@@ -230,11 +237,6 @@ class OrbitControls extends EventDispatcher {
230237 spherical . makeSafe ( ) ;
231238
232239
233- spherical . radius *= scale ;
234-
235- // restrict radius to be between desired limits
236- spherical . radius = Math . max ( scope . minDistance , Math . min ( scope . maxDistance , spherical . radius ) ) ;
237-
238240 // move target to panned location
239241
240242 if ( scope . enableDamping === true ) {
@@ -247,6 +249,19 @@ class OrbitControls extends EventDispatcher {
247249
248250 }
249251
252+ // adjust the camera position based on zoom only if we're not zooming to the cursor or if it's an ortho camera
253+ // we adjust zoom later in these cases
254+ if ( scope . zoomToCursor && performCursorZoom || scope . object . isOrthographicCamera ) {
255+
256+ spherical . radius = clampDistance ( spherical . radius ) ;
257+
258+ } else {
259+
260+ spherical . radius = clampDistance ( spherical . radius * scale ) ;
261+
262+ }
263+
264+
250265 offset . setFromSpherical ( spherical ) ;
251266
252267 // rotate offset back to "camera-up-vector-is-up" space
@@ -271,7 +286,91 @@ class OrbitControls extends EventDispatcher {
271286
272287 }
273288
289+ // adjust camera position
290+ let zoomChanged = false ;
291+ if ( scope . zoomToCursor && performCursorZoom ) {
292+
293+ let newRadius = null ;
294+ if ( scope . object . isPerspectiveCamera ) {
295+
296+ // move the camera down the pointer ray
297+ // this method avoids floating point error
298+ const prevRadius = offset . length ( ) ;
299+ newRadius = clampDistance ( prevRadius * scale ) ;
300+
301+ const radiusDelta = prevRadius - newRadius ;
302+ scope . object . position . addScaledVector ( dollyDirection , radiusDelta ) ;
303+ scope . object . updateMatrixWorld ( ) ;
304+
305+ } else if ( scope . object . isOrthographicCamera ) {
306+
307+ // adjust the ortho camera position based on zoom changes
308+ const mouseBefore = new Vector3 ( mouse . x , mouse . y , 0 ) ;
309+ mouseBefore . unproject ( scope . object ) ;
310+
311+ scope . object . zoom = Math . max ( scope . minZoom , Math . min ( scope . maxZoom , scope . object . zoom / scale ) ) ;
312+ scope . object . updateProjectionMatrix ( ) ;
313+ zoomChanged = true ;
314+
315+ const mouseAfter = new Vector3 ( mouse . x , mouse . y , 0 ) ;
316+ mouseAfter . unproject ( scope . object ) ;
317+
318+ scope . object . position . sub ( mouseAfter ) . add ( mouseBefore ) ;
319+ scope . object . updateMatrixWorld ( ) ;
320+
321+ newRadius = offset . length ( ) ;
322+
323+ } else {
324+
325+ console . warn ( 'WARNING: OrbitControls.js encountered an unknown camera type - zoom to cursor disabled.' ) ;
326+ scope . zoomToCursor = false ;
327+
328+ }
329+
330+ // handle the placement of the target
331+ if ( newRadius !== null ) {
332+
333+ if ( this . screenSpacePanning ) {
334+
335+ // position the orbit target in front of the new camera position
336+ scope . target . set ( 0 , 0 , - 1 )
337+ . transformDirection ( scope . object . matrix )
338+ . multiplyScalar ( newRadius )
339+ . add ( scope . object . position ) ;
340+
341+ } else {
342+
343+ // get the ray and translation plane to compute target
344+ _ray . origin . copy ( scope . object . position ) ;
345+ _ray . direction . set ( 0 , 0 , - 1 ) . transformDirection ( scope . object . matrix ) ;
346+
347+ // if the camera is 20 degrees above the horizon then don't adjust the focus target to avoid
348+ // extremely large values
349+ if ( Math . abs ( scope . object . up . dot ( _ray . direction ) ) < TILT_LIMIT ) {
350+
351+ object . lookAt ( scope . target ) ;
352+
353+ } else {
354+
355+ _plane . setFromNormalAndCoplanarPoint ( scope . object . up , scope . target ) ;
356+ _ray . intersectPlane ( _plane , scope . target ) ;
357+
358+ }
359+
360+ }
361+
362+ }
363+
364+ } else if ( scope . object . isOrthographicCamera ) {
365+
366+ scope . object . zoom = Math . max ( scope . minZoom , Math . min ( scope . maxZoom , scope . object . zoom / scale ) ) ;
367+ scope . object . updateProjectionMatrix ( ) ;
368+ zoomChanged = true ;
369+
370+ }
371+
274372 scale = 1 ;
373+ performCursorZoom = false ;
275374
276375 // update condition is:
277376 // min(camera displacement, camera rotation in radians)^2 > EPS
@@ -350,7 +449,6 @@ class OrbitControls extends EventDispatcher {
350449
351450 let scale = 1 ;
352451 const panOffset = new Vector3 ( ) ;
353- let zoomChanged = false ;
354452
355453 const rotateStart = new Vector2 ( ) ;
356454 const rotateEnd = new Vector2 ( ) ;
@@ -364,6 +462,10 @@ class OrbitControls extends EventDispatcher {
364462 const dollyEnd = new Vector2 ( ) ;
365463 const dollyDelta = new Vector2 ( ) ;
366464
465+ const dollyDirection = new Vector3 ( ) ;
466+ const mouse = new Vector2 ( ) ;
467+ let performCursorZoom = false ;
468+
367469 const pointers = [ ] ;
368470 const pointerPositions = { } ;
369471
@@ -474,16 +576,10 @@ class OrbitControls extends EventDispatcher {
474576
475577 function dollyOut ( dollyScale ) {
476578
477- if ( scope . object . isPerspectiveCamera ) {
579+ if ( scope . object . isPerspectiveCamera || scope . object . isOrthographicCamera ) {
478580
479581 scale /= dollyScale ;
480582
481- } else if ( scope . object . isOrthographicCamera ) {
482-
483- scope . object . zoom = Math . max ( scope . minZoom , Math . min ( scope . maxZoom , scope . object . zoom * dollyScale ) ) ;
484- scope . object . updateProjectionMatrix ( ) ;
485- zoomChanged = true ;
486-
487583 } else {
488584
489585 console . warn ( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ) ;
@@ -495,16 +591,10 @@ class OrbitControls extends EventDispatcher {
495591
496592 function dollyIn ( dollyScale ) {
497593
498- if ( scope . object . isPerspectiveCamera ) {
594+ if ( scope . object . isPerspectiveCamera || scope . object . isOrthographicCamera ) {
499595
500596 scale *= dollyScale ;
501597
502- } else if ( scope . object . isOrthographicCamera ) {
503-
504- scope . object . zoom = Math . max ( scope . minZoom , Math . min ( scope . maxZoom , scope . object . zoom / dollyScale ) ) ;
505- scope . object . updateProjectionMatrix ( ) ;
506- zoomChanged = true ;
507-
508598 } else {
509599
510600 console . warn ( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ) ;
@@ -514,6 +604,35 @@ class OrbitControls extends EventDispatcher {
514604
515605 }
516606
607+ function updateMouseParameters ( event ) {
608+
609+ if ( ! scope . zoomToCursor ) {
610+
611+ return ;
612+
613+ }
614+
615+ performCursorZoom = true ;
616+
617+ const rect = scope . domElement . getBoundingClientRect ( ) ;
618+ const x = event . clientX - rect . left ;
619+ const y = event . clientY - rect . top ;
620+ const w = rect . width ;
621+ const h = rect . height ;
622+
623+ mouse . x = ( x / w ) * 2 - 1 ;
624+ mouse . y = - ( y / h ) * 2 + 1 ;
625+
626+ dollyDirection . set ( mouse . x , mouse . y , 1 ) . unproject ( object ) . sub ( object . position ) . normalize ( ) ;
627+
628+ }
629+
630+ function clampDistance ( dist ) {
631+
632+ return Math . max ( scope . minDistance , Math . min ( scope . maxDistance , dist ) ) ;
633+
634+ }
635+
517636 //
518637 // event callbacks - update the object state
519638 //
@@ -526,6 +645,7 @@ class OrbitControls extends EventDispatcher {
526645
527646 function handleMouseDownDolly ( event ) {
528647
648+ updateMouseParameters ( event ) ;
529649 dollyStart . set ( event . clientX , event . clientY ) ;
530650
531651 }
@@ -592,6 +712,8 @@ class OrbitControls extends EventDispatcher {
592712
593713 function handleMouseWheel ( event ) {
594714
715+ updateMouseParameters ( event ) ;
716+
595717 if ( event . deltaY < 0 ) {
596718
597719 dollyIn ( getZoomScale ( ) ) ;
0 commit comments