Skip to content

Commit ba66c6c

Browse files
authored
Controls simplification (#1201)
* Remove reorient on drag * More changes * Small fixes * Update camera focus * Add initial clamp rotation logic * Move functions * Cleanup * Adjust clamp logic * Fix shifting camera * Adjust environment controls rotations
1 parent e6d939f commit ba66c6c

File tree

3 files changed

+213
-86
lines changed

3 files changed

+213
-86
lines changed

example/googleMapsExample.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ function updateHash() {
207207

208208
}
209209

210-
if ( transition.mode !== 'perspective' ) {
210+
if ( transition.mode !== 'perspective' && ! transition.animating ) {
211211

212212
controls.getPivotPoint( transition.fixedPoint );
213213
transition.syncCameras();

src/three/controls/EnvironmentControls.js

Lines changed: 196 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,14 @@ const DRAG_PLANE_THRESHOLD = 0.05;
2424
const DRAG_UP_THRESHOLD = 0.025;
2525

2626
const _rotMatrix = /* @__PURE__ */ new Matrix4();
27+
const _invMatrix = /* @__PURE__ */ new Matrix4();
2728
const _delta = /* @__PURE__ */ new Vector3();
2829
const _vec = /* @__PURE__ */ new Vector3();
30+
const _pos = /* @__PURE__ */ new Vector3();
31+
const _center = /* @__PURE__ */ new Vector3();
2932
const _forward = /* @__PURE__ */ new Vector3();
3033
const _right = /* @__PURE__ */ new Vector3();
34+
const _targetRight = /* @__PURE__ */ new Vector3();
3135
const _rotationAxis = /* @__PURE__ */ new Vector3();
3236
const _quaternion = /* @__PURE__ */ new Quaternion();
3337
const _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

Comments
 (0)