Skip to content

Commit

Permalink
Fix 3D zoom wheel distance formula
Browse files Browse the repository at this point in the history
The previous distance coefficient would go negative for high enough
accumulated values, throwing the user somewhere far below the terrain.

The expected behaviour here is that successive scroll wheel events,
accumulated or not, bring the camera closer to the zoom point, but never
beyond.
  • Loading branch information
dvdkon committed Feb 27, 2025
1 parent b80eb8f commit e5407c5
Show file tree
Hide file tree
Showing 6 changed files with 31 additions and 29 deletions.
10 changes: 6 additions & 4 deletions src/3d/qgscameracontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <QDomDocument>
#include <Qt3DRender/QCamera>
#include <Qt3DInput>
#include <cmath>

#include "qgslogger.h"

Expand Down Expand Up @@ -520,10 +521,9 @@ void QgsCameraController::handleTerrainNavigationWheelZoom()
}
}

float f = mCumulatedWheelY / ( 120.0 * 24.0 );

double oldDist = ( mZoomPoint - mCameraBefore->position() ).length();
double newDist = ( 1 - f ) * oldDist;
// Each step of the scroll wheel decreases distance by 20%
double newDist = std::pow( 0.8, mCumulatedWheelY ) * oldDist;
double zoomFactor = newDist / oldDist;

zoomCameraAroundPivot( mCameraBefore->position(), zoomFactor, mZoomPoint );
Expand All @@ -548,7 +548,9 @@ void QgsCameraController::onWheel( Qt3DInput::QWheelEvent *wheel )

case Qgis::NavigationMode::TerrainBased:
{
const float scaling = ( ( wheel->modifiers() & Qt::ControlModifier ) != 0 ? 0.5f : 5.f );
// Scale our variable to roughly "number of normal steps", with Ctrl
// increasing granularity 10x
const double scaling = ( 1.0 / 120.0 ) * ( ( wheel->modifiers() & Qt::ControlModifier ) != 0 ? 0.1 : 1.0 );

// Apparently angleDelta needs to be accumulated
// see: https://doc.qt.io/qt-5/qwheelevent.html#angleDelta
Expand Down
24 changes: 12 additions & 12 deletions tests/src/3d/testqgs3dcameracontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -265,15 +265,15 @@ void TestQgs3DCameraController::testZoomWheel()
scene->cameraController()->onWheel( new Qt3DInput::QWheelEvent( wheelEvent ) );
QCOMPARE( scene->cameraController()->mClickPoint, QPoint() );
QCOMPARE( scene->cameraController()->mCurrentOperation, QgsCameraController::MouseOperation::ZoomWheel );
QCOMPARE( scene->cameraController()->mCumulatedWheelY, wheelEvent.angleDelta().y() * 5 );
QCOMPARE( scene->cameraController()->mCumulatedWheelY, wheelEvent.angleDelta().y() / 120.0 );
QCOMPARE( scene->cameraController()->mCameraBefore->viewCenter(), scene->cameraController()->cameraPose().centerPoint().toVector3D() );

QImage depthImage = Qgs3DUtils::captureSceneDepthBuffer( engine, scene );
scene->cameraController()->depthBufferCaptured( depthImage );

QGSCOMPARENEARVECTOR3D( scene->cameraController()->mZoomPoint, QVector3D( -1381.3, 1036.7, 1.0 ), 5.0 );
QGSCOMPARENEARVECTOR3D( scene->cameraController()->cameraPose().centerPoint(), QVector3D( -479.2, 359.4, 1.6 ), 5.0 );
QGSCOMPARENEAR( scene->cameraController()->cameraPose().distanceFromCenterPoint(), 1631.9, 5.0 );
QGSCOMPARENEARVECTOR3D( scene->cameraController()->cameraPose().centerPoint(), QVector3D( -428.79, 321.59, 0.049 ), 5.0 );
QGSCOMPARENEAR( scene->cameraController()->cameraPose().distanceFromCenterPoint(), 1723.55, 5.0 );
QCOMPARE( scene->cameraController()->mCumulatedWheelY, 0 );
QCOMPARE( scene->cameraController()->mClickPoint, QPoint() );
QCOMPARE( scene->cameraController()->mCurrentOperation, QgsCameraController::MouseOperation::None );
Expand Down Expand Up @@ -515,16 +515,16 @@ void TestQgs3DCameraController::testRotationCenterZoomWheelRotationCenter()
scene->cameraController()->onWheel( new Qt3DInput::QWheelEvent( wheelEvent ) );
QCOMPARE( scene->cameraController()->mClickPoint, midPos );
QCOMPARE( scene->cameraController()->mCurrentOperation, QgsCameraController::MouseOperation::ZoomWheel );
QCOMPARE( scene->cameraController()->mCumulatedWheelY, wheelEvent.angleDelta().y() * 5 );
QCOMPARE( scene->cameraController()->mCumulatedWheelY, wheelEvent.angleDelta().y() / 120.0 );
QCOMPARE( scene->cameraController()->mCameraBefore->viewCenter(), scene->cameraController()->cameraPose().centerPoint().toVector3D() );


depthImage = Qgs3DUtils::captureSceneDepthBuffer( engine, scene );
scene->cameraController()->depthBufferCaptured( depthImage );

QGSCOMPARENEARVECTOR3D( scene->cameraController()->mZoomPoint, QVector3D( 283.2, -923.1, -27.0 ), 1.5 );
QGSCOMPARENEARVECTOR3D( scene->cameraController()->cameraPose().centerPoint(), QVector3D( 99.4, -319.9, -8.8 ), 2.0 );
QGSCOMPARENEAR( scene->cameraController()->cameraPose().distanceFromCenterPoint(), 1631.9, 2.0 );
QGSCOMPARENEARVECTOR3D( scene->cameraController()->mZoomPoint, QVector3D( 312.936, -950.772, -125.381 ), 3.0 );
QGSCOMPARENEARVECTOR3D( scene->cameraController()->cameraPose().centerPoint(), QVector3D( 98.46, -294.61, -38.78 ), 3.0 );
QGSCOMPARENEAR( scene->cameraController()->cameraPose().distanceFromCenterPoint(), 1723.55, 3.0 );
QCOMPARE( scene->cameraController()->pitch(), initialPitch );
QCOMPARE( scene->cameraController()->yaw(), initialYaw );
QCOMPARE( scene->cameraController()->mCumulatedWheelY, 0 );
Expand Down Expand Up @@ -558,9 +558,9 @@ void TestQgs3DCameraController::testRotationCenterZoomWheelRotationCenter()
QCOMPARE( scene->cameraController()->mCurrentOperation, QgsCameraController::MouseOperation::RotationCenter );

diffViewCenter = scene->cameraController()->camera()->viewCenter() - initialCamViewCenter;
QGSCOMPARENEARVECTOR3D( diffViewCenter, QVector3D( 25.9, 7.1, 5.2 ), 1.0 );
QGSCOMPARENEARVECTOR3D( diffViewCenter, QVector3D( 28.25, 7.70, 5.75 ), 2.0 );
diffPosition = scene->cameraController()->camera()->position() - initialCamPosition;
QGSCOMPARENEARVECTOR3D( diffPosition, QVector3D( -44.3, -9.1, -11.7 ), 1.0 );
QGSCOMPARENEARVECTOR3D( diffPosition, QVector3D( -45.84, -9.70, -11.83 ), 1.0 );
diffPitch = scene->cameraController()->pitch() - initialPitch;
diffYaw = scene->cameraController()->yaw() - initialYaw;
QGSCOMPARENEAR( diffPitch, 2.5, 0.1 );
Expand Down Expand Up @@ -808,16 +808,16 @@ void TestQgs3DCameraController::testTranslateZoomWheelTranslate()
scene->cameraController()->onWheel( new Qt3DInput::QWheelEvent( wheelEvent ) );
QCOMPARE( scene->cameraController()->mClickPoint, midPos );
QCOMPARE( scene->cameraController()->mCurrentOperation, QgsCameraController::MouseOperation::ZoomWheel );
QCOMPARE( scene->cameraController()->mCumulatedWheelY, wheelEvent.angleDelta().y() * 5 );
QCOMPARE( scene->cameraController()->mCumulatedWheelY, wheelEvent.angleDelta().y() / 120.0 );
QCOMPARE( scene->cameraController()->mCameraBefore->viewCenter(), scene->cameraController()->cameraPose().centerPoint().toVector3D() );


depthImage = Qgs3DUtils::captureSceneDepthBuffer( engine, scene );
scene->cameraController()->depthBufferCaptured( depthImage );

QGSCOMPARENEARVECTOR3D( scene->cameraController()->mZoomPoint, QVector3D( 4.8, -4.4, 9.9 ), 1.0 );
QGSCOMPARENEARVECTOR3D( scene->cameraController()->cameraPose().centerPoint(), QVector3D( -615.5, 116.2, 3.4 ), 4.0 );
QGSCOMPARENEAR( scene->cameraController()->cameraPose().distanceFromCenterPoint(), 1631.9, 2.0 );
QGSCOMPARENEARVECTOR3D( scene->cameraController()->cameraPose().centerPoint(), QVector3D( -650.47, 123.10, 3.10 ), 4.0 );
QGSCOMPARENEAR( scene->cameraController()->cameraPose().distanceFromCenterPoint(), 1723.55, 2.0 );
QCOMPARE( scene->cameraController()->mCumulatedWheelY, 0 );
QCOMPARE( scene->cameraController()->mClickPoint, QPoint() );
QCOMPARE( scene->cameraController()->mCurrentOperation, QgsCameraController::MouseOperation::None );
Expand Down
26 changes: 13 additions & 13 deletions tests/src/3d/testqgs3drendering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2110,7 +2110,7 @@ void TestQgs3DRendering::testDepthBuffer()
// Check first wheel action
QWheelEvent wheelEvent( midPos, midPos, QPoint(), QPoint( 0, 120 ), Qt::NoButton, Qt::NoModifier, Qt::NoScrollPhase, false, Qt::MouseEventSynthesizedByApplication );
scene->cameraController()->onWheel( new Qt3DInput::QWheelEvent( wheelEvent ) );
QCOMPARE( scene->cameraController()->mCumulatedWheelY, wheelEvent.angleDelta().y() * 5 );
QCOMPARE( scene->cameraController()->mCumulatedWheelY, wheelEvent.angleDelta().y() / 120.0 );
QCOMPARE( scene->cameraController()->mCameraBefore->viewCenter(), scene->cameraController()->cameraPose().centerPoint().toVector3D() );

depthImage = Qgs3DUtils::captureSceneDepthBuffer( engine, scene );
Expand All @@ -2120,14 +2120,14 @@ void TestQgs3DRendering::testDepthBuffer()
scene->cameraController()->depthBufferCaptured( depthImage );

QGSCOMPARENEARVECTOR3D( scene->cameraController()->mZoomPoint, QVector3D( -32.7, -185.5, 224.6 ), 1.0 );
QGSCOMPARENEARVECTOR3D( scene->cameraController()->cameraPose().centerPoint(), QVector3D( -6.8, -38.6, 46.7 ), 1.0 );
QGSCOMPARENEAR( scene->cameraController()->cameraPose().distanceFromCenterPoint(), 1187.5, 1.0 );
QGSCOMPARENEARVECTOR3D( scene->cameraController()->cameraPose().centerPoint(), QVector3D( -6.5, -37.1, 44.9 ), 1.0 );
QGSCOMPARENEAR( scene->cameraController()->cameraPose().distanceFromCenterPoint(), 1200, 1.0 );
QCOMPARE( scene->cameraController()->mCumulatedWheelY, 0 );

// Checking second wheel action
QWheelEvent wheelEvent2( midPos, midPos, QPoint(), QPoint( 0, 120 ), Qt::NoButton, Qt::NoModifier, Qt::NoScrollPhase, false, Qt::MouseEventSynthesizedByApplication );
scene->cameraController()->onWheel( new Qt3DInput::QWheelEvent( wheelEvent2 ) );
QCOMPARE( scene->cameraController()->mCumulatedWheelY, wheelEvent2.angleDelta().y() * 5 );
QCOMPARE( scene->cameraController()->mCumulatedWheelY, wheelEvent2.angleDelta().y() / 120.0 );
QCOMPARE( scene->cameraController()->mCameraBefore->viewCenter(), scene->cameraController()->cameraPose().centerPoint().toVector3D() );

depthImage = Qgs3DUtils::captureSceneDepthBuffer( engine, scene );
Expand All @@ -2137,14 +2137,14 @@ void TestQgs3DRendering::testDepthBuffer()
scene->cameraController()->depthBufferCaptured( depthImage );

QGSCOMPARENEARVECTOR3D( scene->cameraController()->mZoomPoint, QVector3D( -32.5, -184.7, 223.5 ), 1.0 );
QGSCOMPARENEARVECTOR3D( scene->cameraController()->cameraPose().centerPoint(), QVector3D( -12.1, -69.0, 83.5 ), 1.0 );
QGSCOMPARENEAR( scene->cameraController()->cameraPose().distanceFromCenterPoint(), 940.1, 1.0 );
QGSCOMPARENEARVECTOR3D( scene->cameraController()->cameraPose().centerPoint(), QVector3D( -11.7, -66.6, 80.6 ), 1.0 );
QGSCOMPARENEAR( scene->cameraController()->cameraPose().distanceFromCenterPoint(), 960, 1.0 );
QCOMPARE( scene->cameraController()->mCumulatedWheelY, 0 );

// Checking third wheel action
QWheelEvent wheelEvent3( midPos, midPos, QPoint(), QPoint( 0, 480 ), Qt::NoButton, Qt::NoModifier, Qt::NoScrollPhase, false, Qt::MouseEventSynthesizedByApplication );
scene->cameraController()->onWheel( new Qt3DInput::QWheelEvent( wheelEvent3 ) );
QCOMPARE( scene->cameraController()->mCumulatedWheelY, wheelEvent3.angleDelta().y() * 5 );
QCOMPARE( scene->cameraController()->mCumulatedWheelY, wheelEvent3.angleDelta().y() / 120.0 );
QCOMPARE( scene->cameraController()->mCameraBefore->viewCenter(), scene->cameraController()->cameraPose().centerPoint().toVector3D() );

depthImage = Qgs3DUtils::captureSceneDepthBuffer( engine, scene );
Expand All @@ -2154,14 +2154,14 @@ void TestQgs3DRendering::testDepthBuffer()
scene->cameraController()->depthBufferCaptured( depthImage );

QGSCOMPARENEARVECTOR3D( scene->cameraController()->mZoomPoint, QVector3D( -32.4, -184.1, 222.8 ), 1.0 );
QGSCOMPARENEARVECTOR3D( scene->cameraController()->cameraPose().centerPoint(), QVector3D( -29.0, -164.9, 199.6 ), 1.0 );
QGSCOMPARENEAR( scene->cameraController()->cameraPose().distanceFromCenterPoint(), 156.6, 0.1 );
QGSCOMPARENEARVECTOR3D( scene->cameraController()->cameraPose().centerPoint(), QVector3D( -24.0, -136.0, 164.6 ), 1.0 );
QGSCOMPARENEAR( scene->cameraController()->cameraPose().distanceFromCenterPoint(), 393.216, 0.1 );
QCOMPARE( scene->cameraController()->mCumulatedWheelY, 0 );

// Checking fourth wheel action
QWheelEvent wheelEvent4( midPos, midPos, QPoint(), QPoint( 0, 120 ), Qt::NoButton, Qt::NoModifier, Qt::NoScrollPhase, false, Qt::MouseEventSynthesizedByApplication );
scene->cameraController()->onWheel( new Qt3DInput::QWheelEvent( wheelEvent4 ) );
QCOMPARE( scene->cameraController()->mCumulatedWheelY, wheelEvent4.angleDelta().y() * 5 );
QCOMPARE( scene->cameraController()->mCumulatedWheelY, wheelEvent4.angleDelta().y() / 120.0 );
QCOMPARE( scene->cameraController()->mCameraBefore->viewCenter(), scene->cameraController()->cameraPose().centerPoint().toVector3D() );

depthImage = Qgs3DUtils::captureSceneDepthBuffer( engine, scene );
Expand All @@ -2171,13 +2171,13 @@ void TestQgs3DRendering::testDepthBuffer()
scene->cameraController()->depthBufferCaptured( depthImage );

QGSCOMPARENEARVECTOR3D( scene->cameraController()->mZoomPoint, QVector3D( -32.3, -183.2, 221.7 ), 1.0 );
QGSCOMPARENEARVECTOR3D( scene->cameraController()->cameraPose().centerPoint(), QVector3D( -29.7, -168.7, 204.2 ), 1.0 );
QGSCOMPARENEAR( scene->cameraController()->cameraPose().distanceFromCenterPoint(), 124.0, 0.1 );
QGSCOMPARENEARVECTOR3D( scene->cameraController()->cameraPose().centerPoint(), QVector3D( -25.7, -145.6, 176.2 ), 1.0 );
QGSCOMPARENEAR( scene->cameraController()->cameraPose().distanceFromCenterPoint(), 314.6, 0.1 );
QCOMPARE( scene->cameraController()->mCumulatedWheelY, 0 );

// Checking camera position
QVector3D diff = scene->cameraController()->camera()->position() - startPos;
QGSCOMPARENEARVECTOR3D( diff, QVector3D( 125, 700, -850 ), 3.0 );
QGSCOMPARENEARVECTOR3D( diff, QVector3D( 106, 605, -732 ), 3.0 );

delete scene;
mapSettings.setLayers( {} );
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit e5407c5

Please sign in to comment.