@@ -24,10 +24,14 @@ const DRAG_PLANE_THRESHOLD = 0.05;
2424const DRAG_UP_THRESHOLD = 0.025 ;
2525
2626const _rotMatrix = /* @__PURE__ */ new Matrix4 ( ) ;
27+ const _invMatrix = /* @__PURE__ */ new Matrix4 ( ) ;
2728const _delta = /* @__PURE__ */ new Vector3 ( ) ;
2829const _vec = /* @__PURE__ */ new Vector3 ( ) ;
30+ const _pos = /* @__PURE__ */ new Vector3 ( ) ;
31+ const _center = /* @__PURE__ */ new Vector3 ( ) ;
2932const _forward = /* @__PURE__ */ new Vector3 ( ) ;
3033const _right = /* @__PURE__ */ new Vector3 ( ) ;
34+ const _targetRight = /* @__PURE__ */ new Vector3 ( ) ;
3135const _rotationAxis = /* @__PURE__ */ new Vector3 ( ) ;
3236const _quaternion = /* @__PURE__ */ new Quaternion ( ) ;
3337const _plane = /* @__PURE__ */ new Plane ( ) ;
@@ -105,8 +109,8 @@ export class EnvironmentControls extends EventDispatcher {
105109 this . useFallbackPlane = true ;
106110
107111 // settings for GlobeControls
108- this . reorientOnDrag = true ;
109112 this . scaleZoomOrientationAtEdges = false ;
113+ this . autoAdjustCameraRotation = true ;
110114
111115 // internal state
112116 this . state = NONE ;
@@ -523,6 +527,20 @@ export class EnvironmentControls extends EventDispatcher {
523527
524528 }
525529
530+ detach ( ) {
531+
532+ this . domElement = null ;
533+
534+ if ( this . _detachCallback ) {
535+
536+ this . _detachCallback ( ) ;
537+ this . _detachCallback = null ;
538+ this . pointerTracker . reset ( ) ;
539+
540+ }
541+
542+ }
543+
526544 // override-able functions for retrieving the up direction at a point
527545 getUpDirection ( point , target ) {
528546
@@ -588,20 +606,6 @@ export class EnvironmentControls extends EventDispatcher {
588606
589607 }
590608
591- detach ( ) {
592-
593- this . domElement = null ;
594-
595- if ( this . _detachCallback ) {
596-
597- this . _detachCallback ( ) ;
598- this . _detachCallback = null ;
599- this . pointerTracker . reset ( ) ;
600-
601- }
602-
603- }
604-
605609 resetState ( ) {
606610
607611 if ( this . state !== NONE ) {
@@ -660,6 +664,7 @@ export class EnvironmentControls extends EventDispatcher {
660664 up,
661665 state,
662666 adjustHeight,
667+ autoAdjustCameraRotation,
663668 } = this ;
664669
665670 camera . updateMatrixWorld ( ) ;
@@ -673,8 +678,12 @@ export class EnvironmentControls extends EventDispatcher {
673678
674679 }
675680
681+ // we need to update the zoom point whenever we update in case the scene is animating or changing
682+ this . zoomPointSet = false ;
683+
676684 // update the actions
677685 const inertiaNeedsUpdate = this . _inertiaNeedsUpdate ( ) ;
686+ const adjustCameraRotation = this . needsUpdate || inertiaNeedsUpdate ;
678687 if ( this . needsUpdate || inertiaNeedsUpdate ) {
679688
680689 const zoomDelta = this . zoomDelta ;
@@ -686,7 +695,7 @@ export class EnvironmentControls extends EventDispatcher {
686695 if ( state === DRAG || state === ROTATE ) {
687696
688697 _forward . set ( 0 , 0 , - 1 ) . transformDirection ( camera . matrixWorld ) ;
689- this . inertiaTargetDistance = _vec . copy ( this . pivotPoint ) . sub ( camera . position ) . dot ( _forward ) ;
698+ this . inertiaTargetDistance = _vec . copy ( pivotPoint ) . sub ( camera . position ) . dot ( _forward ) ;
690699
691700 } else if ( state === NONE ) {
692701
@@ -708,9 +717,8 @@ export class EnvironmentControls extends EventDispatcher {
708717 // if using an orthographic camera then rotate around drag pivot
709718 // reuse the "hit" information since it can be slow to perform multiple hits
710719 const hit = camera . isOrthographicCamera ? null : adjustHeight && this . _getPointBelowCamera ( ) || null ;
711- const rotationPoint = camera . isOrthographicCamera ? pivotPoint : hit && hit . point || null ;
712720 this . getCameraUpDirection ( _localUp ) ;
713- this . _setFrame ( _localUp , rotationPoint ) ;
721+ this . _setFrame ( _localUp ) ;
714722
715723 // when dragging the camera and drag point may be moved
716724 // to accommodate terrain so we try to move it back down
@@ -748,6 +756,17 @@ export class EnvironmentControls extends EventDispatcher {
748756
749757 this . pointerTracker . updateFrame ( ) ;
750758
759+ if ( adjustCameraRotation && autoAdjustCameraRotation ) {
760+
761+ this . getCameraUpDirection ( _localUp ) ;
762+ this . _alignCameraUp ( _localUp , 1 ) ;
763+
764+ this . getCameraUpDirection ( _localUp ) ;
765+ this . _clampRotation ( _localUp ) ;
766+
767+
768+ }
769+
751770 }
752771
753772 // updates the camera to position it based on the constraints of the controls
@@ -1256,20 +1275,29 @@ export class EnvironmentControls extends EventDispatcher {
12561275
12571276 // calculate current angles and clamp
12581277 _forward . set ( 0 , 0 , 1 ) . transformDirection ( camera . matrixWorld ) ;
1259-
1278+ _right . set ( 1 , 0 , 0 ) . transformDirection ( camera . matrixWorld ) ;
12601279 this . getUpDirection ( pivotPoint , _localUp ) ;
12611280
12621281 // get the signed angle relative to the top down view
1263- _vec . crossVectors ( _localUp , _forward ) . normalize ( ) ;
1264- _right . set ( 1 , 0 , 0 ) . transformDirection ( camera . matrixWorld ) . normalize ( ) ;
1265- const sign = Math . sign ( _vec . dot ( _right ) ) ;
1266- const angle = sign * _localUp . angleTo ( _forward ) ;
1282+ let angle ;
1283+ if ( _localUp . dot ( _forward ) > 1 - 1e-10 ) {
1284+
1285+ angle = 0 ;
1286+
1287+ } else {
1288+
1289+ _vec . crossVectors ( _localUp , _forward ) . normalize ( ) ;
1290+
1291+ const sign = Math . sign ( _vec . dot ( _right ) ) ;
1292+ angle = sign * _localUp . angleTo ( _forward ) ;
1293+
1294+ }
12671295
12681296 // clamp the rotation to be within the provided limits
12691297 // clamp to 0 here, as well, so we don't "pop" to the the value range
12701298 if ( altitude > 0 ) {
12711299
1272- altitude = Math . min ( angle - minAltitude - 1e-2 , altitude ) ;
1300+ altitude = Math . min ( angle - minAltitude , altitude ) ;
12731301 altitude = Math . max ( 0 , altitude ) ;
12741302
12751303 } else {
@@ -1285,9 +1313,8 @@ export class EnvironmentControls extends EventDispatcher {
12851313 camera . matrixWorld . premultiply ( _rotMatrix ) ;
12861314
12871315 // get a rotation axis for altitude and rotate
1288- _rotationAxis . set ( - 1 , 0 , 0 ) . transformDirection ( camera . matrixWorld ) ;
1289-
1290- _quaternion . setFromAxisAngle ( _rotationAxis , altitude ) ;
1316+ _right . set ( 1 , 0 , 0 ) . transformDirection ( camera . matrixWorld ) ;
1317+ _quaternion . setFromAxisAngle ( _right , - altitude ) ;
12911318 makeRotateAroundPoint ( pivotPoint , _quaternion , _rotMatrix ) ;
12921319 camera . matrixWorld . premultiply ( _rotMatrix ) ;
12931320
@@ -1297,32 +1324,27 @@ export class EnvironmentControls extends EventDispatcher {
12971324 }
12981325
12991326 // sets the "up" axis for the current surface of the tile set
1300- _setFrame ( newUp , pivot ) {
1327+ _setFrame ( newUp ) {
13011328
13021329 const {
13031330 up,
13041331 camera,
1305- state,
13061332 zoomPoint,
13071333 zoomDirectionSet,
13081334 zoomPointSet,
1309- reorientOnDrag,
13101335 scaleZoomOrientationAtEdges,
13111336 } = this ;
13121337
1313- camera . updateMatrixWorld ( ) ;
1314-
1315- // get the amount needed to rotate
1316- _quaternion . setFromUnitVectors ( up , newUp ) ;
1317-
13181338 // If we're zooming then reorient around the zoom point
1319- const action = state ;
13201339 if ( zoomDirectionSet && ( zoomPointSet || this . _updateZoomPoint ( ) ) ) {
13211340
1322- this . getUpDirection ( zoomPoint , _vec ) ;
1341+ // get the amount needed to rotate
1342+ _quaternion . setFromUnitVectors ( up , newUp ) ;
13231343
13241344 if ( scaleZoomOrientationAtEdges ) {
13251345
1346+ this . getUpDirection ( zoomPoint , _vec ) ;
1347+
13261348 let amt = Math . max ( _vec . dot ( up ) - 0.6 , 0 ) / 0.4 ;
13271349 amt = MathUtils . mapLinear ( amt , 0 , 0.5 , 0 , 1 ) ;
13281350 amt = Math . min ( amt , 1 ) ;
@@ -1341,29 +1363,15 @@ export class EnvironmentControls extends EventDispatcher {
13411363
13421364 // rotates the camera position around the point being zoomed in to
13431365 makeRotateAroundPoint ( zoomPoint , _quaternion , _rotMatrix ) ;
1366+
1367+ camera . updateMatrixWorld ( ) ;
13441368 camera . matrixWorld . premultiply ( _rotMatrix ) ;
13451369 camera . matrixWorld . decompose ( camera . position , camera . quaternion , _vec ) ;
13461370
13471371 // recompute the zoom direction after updating rotation to align with frame
13481372 this . zoomDirectionSet = false ;
13491373 this . _updateZoomDirection ( ) ;
13501374
1351- } else if ( action === DRAG && reorientOnDrag ) {
1352-
1353- // If we're dragging then reorient around the drag point
1354-
1355- // NOTE: We used to derive the pivot point here by getting the point below the camera
1356- // but decided to pass it in via "update" to avoid multiple ray casts
1357-
1358- if ( pivot ) {
1359-
1360- // perform a simple realignment by rotating the camera around the pivot
1361- makeRotateAroundPoint ( pivot , _quaternion , _rotMatrix ) ;
1362- camera . matrixWorld . premultiply ( _rotMatrix ) ;
1363- camera . matrixWorld . decompose ( camera . position , camera . quaternion , _vec ) ;
1364-
1365- }
1366-
13671375 }
13681376
13691377 up . copy ( newUp ) ;
@@ -1401,4 +1409,139 @@ export class EnvironmentControls extends EventDispatcher {
14011409
14021410 }
14031411
1412+ // tilt the camera to align with the provided "up" value
1413+ _alignCameraUp ( up , alpha = 1 ) {
1414+
1415+ const { camera, state, pivotPoint, zoomPoint, zoomPointSet } = this ;
1416+
1417+ // get the transform vectors
1418+ camera . updateMatrixWorld ( ) ;
1419+ _forward . set ( 0 , 0 , - 1 ) . transformDirection ( camera . matrixWorld ) ;
1420+ _right . set ( - 1 , 0 , 0 ) . transformDirection ( camera . matrixWorld ) ;
1421+
1422+ // compute an alpha based on the camera direction so we don't try to update the up direction
1423+ // when the camera is facing that way.
1424+ let multiplier = MathUtils . mapLinear ( 1 - Math . abs ( _forward . dot ( up ) ) , 0 , 0.2 , 0 , 1 ) ;
1425+ multiplier = MathUtils . clamp ( multiplier , 0 , 1 ) ;
1426+ alpha *= multiplier ;
1427+
1428+ // calculate the target direction for the right-facing vector
1429+ _targetRight . crossVectors ( up , _forward ) ;
1430+ _targetRight . lerp ( _right , 1 - alpha ) . normalize ( ) ;
1431+
1432+ // adjust the camera transformation
1433+ _quaternion . setFromUnitVectors ( _right , _targetRight ) ;
1434+ camera . quaternion . premultiply ( _quaternion ) ;
1435+
1436+ // calculate the active point
1437+ let fixedPoint = null ;
1438+ if ( state === DRAG || state === ROTATE ) {
1439+
1440+ fixedPoint = _pos . copy ( pivotPoint ) ;
1441+
1442+ } else if ( zoomPointSet ) {
1443+
1444+ fixedPoint = _pos . copy ( zoomPoint ) ;
1445+
1446+ }
1447+
1448+ // shift the camera in an effort to keep the fixed point in the same spot
1449+ if ( fixedPoint ) {
1450+
1451+ _invMatrix . copy ( camera . matrixWorld ) . invert ( ) ;
1452+ _vec . copy ( fixedPoint ) . applyMatrix4 ( _invMatrix ) ;
1453+
1454+ camera . updateMatrixWorld ( ) ;
1455+ _vec . applyMatrix4 ( camera . matrixWorld ) ;
1456+
1457+ _center . subVectors ( fixedPoint , _vec ) ;
1458+ camera . position . add ( _center ) ;
1459+
1460+ }
1461+
1462+ camera . updateMatrixWorld ( ) ;
1463+
1464+ }
1465+
1466+ // clamp rotation to the given "up" vector
1467+ _clampRotation ( up ) {
1468+
1469+ const { camera, minAltitude, maxAltitude, state, pivotPoint, zoomPoint, zoomPointSet } = this ;
1470+
1471+ camera . updateMatrixWorld ( ) ;
1472+
1473+ // calculate current angles and clamp
1474+ _forward . set ( 0 , 0 , 1 ) . transformDirection ( camera . matrixWorld ) ;
1475+ _right . set ( 1 , 0 , 0 ) . transformDirection ( camera . matrixWorld ) ;
1476+
1477+ // get the signed angle relative to the top down view
1478+ let angle ;
1479+ if ( up . dot ( _forward ) > 1 - 1e-10 ) {
1480+
1481+ angle = 0 ;
1482+
1483+ } else {
1484+
1485+ _vec . crossVectors ( up , _forward ) ;
1486+
1487+ const sign = Math . sign ( _vec . dot ( _right ) ) ;
1488+ angle = sign * up . angleTo ( _forward ) ;
1489+
1490+ }
1491+
1492+ // find the angle to target
1493+ let targetAngle ;
1494+ if ( angle > maxAltitude ) {
1495+
1496+ targetAngle = maxAltitude ;
1497+
1498+ } else if ( angle < minAltitude ) {
1499+
1500+ targetAngle = minAltitude ;
1501+
1502+ } else {
1503+
1504+ return ;
1505+
1506+ }
1507+
1508+ // construct a rotation basis
1509+ _forward . copy ( up ) ;
1510+ _quaternion . setFromAxisAngle ( _right , targetAngle ) ;
1511+ _forward . applyQuaternion ( _quaternion ) . normalize ( ) ;
1512+ _vec . crossVectors ( _forward , _right ) . normalize ( ) ;
1513+
1514+ _rotMatrix . makeBasis ( _right , _vec , _forward ) ;
1515+ camera . quaternion . setFromRotationMatrix ( _rotMatrix ) ;
1516+
1517+ // calculate the active point
1518+ let fixedPoint = null ;
1519+ if ( state === DRAG || state === ROTATE ) {
1520+
1521+ fixedPoint = _pos . copy ( pivotPoint ) ;
1522+
1523+ } else if ( zoomPointSet ) {
1524+
1525+ fixedPoint = _pos . copy ( zoomPoint ) ;
1526+
1527+ }
1528+
1529+ // shift the camera in an effort to keep the fixed point in the same spot
1530+ if ( fixedPoint ) {
1531+
1532+ _invMatrix . copy ( camera . matrixWorld ) . invert ( ) ;
1533+ _vec . copy ( fixedPoint ) . applyMatrix4 ( _invMatrix ) ;
1534+
1535+ camera . updateMatrixWorld ( ) ;
1536+ _vec . applyMatrix4 ( camera . matrixWorld ) ;
1537+
1538+ _center . subVectors ( fixedPoint , _vec ) ;
1539+ camera . position . add ( _center ) ;
1540+
1541+ }
1542+
1543+ camera . updateMatrixWorld ( ) ;
1544+
1545+ }
1546+
14041547}
0 commit comments