33#include <QDomDocument>
36#include <QStringLiteral>
38#include <Qt3DRender/QCamera>
40#include "moc_qgscameracontroller.cpp"
42using namespace Qt::StringLiterals;
47 , mCamera( scene->engine()->
camera() )
49 , mMouseHandler( new
Qt3DInput::QMouseHandler )
50 , mKeyboardHandler( new
Qt3DInput::QKeyboardHandler )
51 , mOrigin( scene->mapSettings()->
origin() )
53 mMouseHandler->setSourceDevice(
new Qt3DInput::QMouseDevice() );
54 connect( mMouseHandler, &Qt3DInput::QMouseHandler::positionChanged,
this, &QgsCameraController::onPositionChanged );
55 connect( mMouseHandler, &Qt3DInput::QMouseHandler::wheel,
this, &QgsCameraController::onWheel );
56 connect( mMouseHandler, &Qt3DInput::QMouseHandler::pressed,
this, &QgsCameraController::onMousePressed );
57 connect( mMouseHandler, &Qt3DInput::QMouseHandler::released,
this, &QgsCameraController::onMouseReleased );
58 addComponent( mMouseHandler );
61 connect(
this, &Qt3DCore::QEntity::enabledChanged, mMouseHandler, &Qt3DInput::QMouseHandler::setEnabled );
62 connect(
this, &Qt3DCore::QEntity::enabledChanged, mKeyboardHandler, &Qt3DInput::QKeyboardHandler::setEnabled );
64 mFpsNavTimer =
new QTimer(
this );
65 mFpsNavTimer->setInterval( 10 );
66 connect( mFpsNavTimer, &QTimer::timeout,
this, &QgsCameraController::applyFlyModeKeyMovements );
67 mFpsNavTimer->start();
72 mGlobeCrsToLatLon =
QgsCoordinateTransform( mScene->mapSettings()->crs(), mScene->mapSettings()->crs().toGeographicCrs(), mScene->mapSettings()->transformContext() );
78QWindow *QgsCameraController::window()
const
81 return windowEngine ? windowEngine->
window() :
nullptr;
86 if ( navigationMode == mCameraNavigationMode )
89 mCameraNavigationMode = navigationMode;
90 mIgnoreNextMouseMove =
true;
96 if ( movementSpeed == mCameraMovementSpeed )
101 mCameraMovementSpeed = std::clamp( movementSpeed, 0.05, 150.0 );
107 mVerticalAxisInversion = inversion;
112 float newPitch = mCameraPose.pitchAngle() + diffPitch;
113 float newHeading = mCameraPose.headingAngle() + diffHeading;
115 newPitch = std::clamp( newPitch, 0.f, 180.f );
117 switch ( mScene->mapSettings()->sceneMode() )
128 viewCenterLatLon = mGlobeCrsToLatLon.transform( mCameraPose.centerPoint() + mOrigin );
132 QgsDebugError( u
"rotateCamera: ECEF -> lat,lon transform failed!"_s );
135 QQuaternion qLatLon = QQuaternion::fromAxisAndAngle( QVector3D( 0, 0, 1 ),
static_cast<float>( viewCenterLatLon.
x() ) )
136 * QQuaternion::fromAxisAndAngle( QVector3D( 0, -1, 0 ),
static_cast<float>( viewCenterLatLon.
y() ) );
137 QQuaternion qPitchHeading = QQuaternion::fromAxisAndAngle( QVector3D( 1, 0, 0 ), newHeading )
138 * QQuaternion::fromAxisAndAngle( QVector3D( 0, 1, 0 ), newPitch );
139 QVector3D newCameraToCenter = ( qLatLon * qPitchHeading * QVector3D( -1, 0, 0 ) ) * mCameraPose.distanceFromCenterPoint();
141 mCameraPose.setCenterPoint( mCamera->position() + newCameraToCenter );
142 mCameraPose.setPitchAngle( newPitch );
143 mCameraPose.setHeadingAngle( newHeading );
144 updateCameraFromPose();
151 QVector3D newCameraToCenter = q * QVector3D( 0, 0, -mCameraPose.distanceFromCenterPoint() );
152 mCameraPose.setCenterPoint( mCamera->position() + newCameraToCenter );
153 mCameraPose.setPitchAngle( newPitch );
154 mCameraPose.setHeadingAngle( newHeading );
155 updateCameraFromPose();
163 const float oldPitch = mCameraPose.pitchAngle();
164 const float oldHeading = mCameraPose.headingAngle();
166 newPitch = std::clamp( newPitch, 0.f, 180.f );
172 const QQuaternion q = qNew * qOld.conjugated();
174 const QVector3D newViewCenter = q * ( mCamera->viewCenter() - pivotPoint ) + pivotPoint;
176 mCameraPose.setCenterPoint( newViewCenter );
177 mCameraPose.setPitchAngle( newPitch );
178 mCameraPose.setHeadingAngle( newHeading );
179 updateCameraFromPose();
185 QVector3D newCamPosition = pivotPoint + ( oldCameraPosition - pivotPoint ) * zoomFactor;
186 double newDistance = mCameraPose.distanceFromCenterPoint() * zoomFactor;
190 QVector3D cameraToCenter = q * QVector3D( 0, 0, -newDistance );
191 QVector3D newViewCenter = newCamPosition + cameraToCenter;
193 mCameraPose.setDistanceFromCenterPoint( newDistance );
194 mCameraPose.setCenterPoint( newViewCenter );
195 updateCameraFromPose();
201 QVector3D newCamPosition = pivotPoint + ( oldCameraPosition - pivotPoint ) * zoomFactor;
202 const double newDistance = oldDistanceFromCenterPoint * zoomFactor;
206 QVector3D cameraToCenter = q * QVector3D( 0, 0, -newDistance );
207 QVector3D newViewCenter = newCamPosition + cameraToCenter;
209 mCameraPose.setDistanceFromCenterPoint( newDistance );
210 mCameraPose.setCenterPoint( newViewCenter );
211 updateCameraFromPose();
218 if ( mCameraChanged )
221 mCameraChanged =
false;
227 QgsPointXY extentCenter = mScene->mapSettings()->extent().center();
236 QgsDebugError( u
"setViewFromTop() should not be used with globe!"_s );
241 QgsTerrainEntity *terrain = mScene->terrainEntity();
242 const float terrainElevationOffset = terrain ? terrain->terrainElevationOffset() : 0.0f;
248 mCamera->setNearPlane(
distance / 2 );
249 mCamera->setFarPlane(
distance * 2 );
256 return mCameraPose.centerPoint();
281 if ( camPose == mCameraPose && !force )
284 mCameraPose = camPose;
285 updateCameraFromPose();
290 QDomElement elemCamera = doc.createElement( u
"camera"_s );
293 QgsVector3D centerPoint = mCameraPose.centerPoint() + mOrigin;
294 elemCamera.setAttribute( u
"xMap"_s, centerPoint.
x() );
295 elemCamera.setAttribute( u
"yMap"_s, centerPoint.
y() );
296 elemCamera.setAttribute( u
"zMap"_s, centerPoint.
z() );
297 elemCamera.setAttribute( u
"dist"_s, mCameraPose.distanceFromCenterPoint() );
298 elemCamera.setAttribute( u
"pitch"_s, mCameraPose.pitchAngle() );
299 elemCamera.setAttribute( u
"yaw"_s, mCameraPose.headingAngle() );
305 const float dist = elem.attribute( u
"dist"_s ).toFloat();
306 const float pitch = elem.attribute( u
"pitch"_s ).toFloat();
307 const float yaw = elem.attribute( u
"yaw"_s ).toFloat();
310 if ( elem.hasAttribute(
"xMap" ) )
313 const double x = elem.attribute( u
"xMap"_s ).toDouble();
314 const double y = elem.attribute( u
"yMap"_s ).toDouble();
315 const double z = elem.attribute( u
"zMap"_s ).toDouble();
321 const double x = elem.attribute( u
"x"_s ).toDouble();
322 const double y = elem.attribute( u
"y"_s ).toDouble();
323 const double elev = elem.attribute( u
"elev"_s ).toDouble();
324 centerPoint =
QgsVector3D( x, elev, y ) - savedOrigin + mOrigin;
329double QgsCameraController::sampleDepthBuffer(
int px,
int py )
331 if ( !mDepthBufferIsReady )
333 QgsDebugError( u
"Asked to sample depth buffer, but depth buffer not ready!"_s );
338 if ( QWindow *win = window() )
342 px =
static_cast<int>( px * win->devicePixelRatio() );
343 py =
static_cast<int>( py * win->devicePixelRatio() );
347 for (
int x = px - 3; x <= px + 3; ++x )
349 for (
int y = py - 3; y <= py + 3; ++y )
351 if ( mDepthBufferImage.valid( x, y ) )
360double QgsCameraController::depthBufferNonVoidAverage()
363 if ( mDepthBufferNonVoidAverage != -1 )
364 return mDepthBufferNonVoidAverage;
368 int samplesCount = 0;
370 Q_ASSERT( mDepthBufferImage.format() == QImage::Format_RGB32 );
371 for (
int y = 0; y < mDepthBufferImage.height(); ++y )
373 const QRgb *line =
reinterpret_cast<const QRgb *
>( mDepthBufferImage.constScanLine( y ) );
374 for (
int x = 0; x < mDepthBufferImage.width(); ++x )
386 if ( samplesCount == 0 )
389 depth /= samplesCount;
391 mDepthBufferNonVoidAverage = depth;
396QgsVector3D QgsCameraController::moveGeocentricPoint(
const QgsVector3D &point,
double latDiff,
double lonDiff )
400 QgsVector3D pointLatLon = mGlobeCrsToLatLon.transform( point );
401 pointLatLon.
setX( pointLatLon.
x() + lonDiff );
402 pointLatLon.
setY( std::clamp( pointLatLon.
y() + latDiff, -90., 90. ) );
406 catch (
const QgsCsException & )
408 QgsDebugError( u
"moveGeocentricPoint: transform failed!"_s );
415 const QgsVector3D viewCenter = mCameraPose.centerPoint() + mOrigin;
416 const QgsVector3D newViewCenter = moveGeocentricPoint( viewCenter, latDiff, lonDiff );
417 mCameraPose.setCenterPoint( newViewCenter - mOrigin );
418 updateCameraFromPose();
423 mCameraPose.setDistanceFromCenterPoint( mCameraPose.distanceFromCenterPoint() * factor );
424 updateCameraFromPose();
429 mCameraPose.setPitchAngle( std::clamp( mCameraPose.pitchAngle() + angleDiff, 0.f, 90.f ) );
430 updateCameraFromPose();
435 mCameraPose.setHeadingAngle( mCameraPose.headingAngle() + angleDiff );
436 updateCameraFromPose();
458void QgsCameraController::updateCameraFromPose()
469 viewCenterLatLon = mGlobeCrsToLatLon.
transform( viewCenter );
471 catch (
const QgsCsException & )
473 QgsDebugError( u
"updateCameraFromPose: transform failed!"_s );
477 mCameraPose.updateCameraGlobe( mCamera, viewCenterLatLon.
y(), viewCenterLatLon.
x() );
481 mCameraPose.updateCamera( mCamera );
483 mCameraChanged =
true;
489 mCameraPose.setCenterPoint( mCameraPose.centerPoint() + posDiff );
490 updateCameraFromPose();
493void QgsCameraController::onPositionChanged( Qt3DInput::QMouseEvent *mouse )
495 if ( !mInputHandlersEnabled )
498 QgsEventTracing::ScopedEvent traceEvent( u
"3D"_s, u
"QgsCameraController::onPositionChanged"_s );
500 switch ( mCameraNavigationMode )
503 onPositionChangedTerrainNavigation( mouse );
507 onPositionChangedFlyNavigation( mouse );
511 onPositionChangedGlobeTerrainNavigation( mouse );
516bool QgsCameraController::screenPointToWorldPos( QPoint position,
double &depth, QVector3D &worldPosition )
518 depth = sampleDepthBuffer( position.x(), position.y() );
524 depth = depthBufferNonVoidAverage();
528 if ( !std::isfinite( worldPosition.x() ) || !std::isfinite( worldPosition.y() ) || !std::isfinite( worldPosition.z() ) )
530 QgsDebugMsgLevel( u
"screenPointToWorldPos: position is NaN or Inf. This should not happen."_s, 2 );
537void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseEvent *mouse )
539 if ( mIgnoreNextMouseMove )
541 mIgnoreNextMouseMove =
false;
542 mMousePos = QPoint( mouse->x(), mouse->y() );
546 const int dx = mouse->x() - mMousePos.x();
547 const int dy = mouse->y() - mMousePos.y();
549 const bool hasShift = ( mouse->modifiers() & Qt3DInput::QMouseEvent::Modifiers::ShiftModifier );
550 const bool hasCtrl = ( mouse->modifiers() & Qt3DInput::QMouseEvent::Modifiers::ControlModifier );
551 const bool hasLeftButton = ( mouse->buttons() & Qt::LeftButton );
552 const bool hasMiddleButton = ( mouse->buttons() & Qt::MiddleButton );
553 const bool hasRightButton = ( mouse->buttons() & Qt::RightButton );
555 if ( ( hasLeftButton && hasShift && !hasCtrl ) || ( hasMiddleButton && !hasShift && !hasCtrl ) )
558 setMouseParameters( MouseOperation::RotationCenter, mMousePos );
560 float scale =
static_cast<float>( std::max( mScene->engine()->size().width(), mScene->engine()->size().height() ) );
561 float pitchDiff = 180.0f *
static_cast<float>( mouse->y() - mClickPoint.y() ) / scale;
562 float yawDiff = -180.0f *
static_cast<float>( mouse->x() - mClickPoint.x() ) / scale;
564 if ( !mDepthBufferIsReady )
567 if ( !mRotationCenterCalculated )
570 QVector3D worldPosition;
571 if ( screenPointToWorldPos( mClickPoint, depth, worldPosition ) )
573 mRotationCenter = worldPosition;
574 mRotationDistanceFromCenter = ( mRotationCenter - mCameraBefore->position() ).length();
576 mRotationCenterCalculated =
true;
582 else if ( hasLeftButton && hasCtrl && !hasShift )
584 setMouseParameters( MouseOperation::RotationCamera );
586 const float diffPitch = 0.2f * dy;
587 const float diffYaw = -0.2f * dx;
590 else if ( hasLeftButton && !hasShift && !hasCtrl )
593 setMouseParameters( MouseOperation::Translation, mMousePos );
595 if ( !mDepthBufferIsReady )
598 if ( !mDragPointCalculated )
601 QVector3D worldPosition;
602 if ( screenPointToWorldPos( mClickPoint, depth, worldPosition ) )
605 mDragPoint = worldPosition;
606 mDragPointCalculated =
true;
610 QVector3D cameraBeforeDragPos = mCameraBefore->position();
613 QVector3D shiftVector;
616 float angle = std::fabs( std::acos( QVector3D::dotProduct( QVector3D( 0, 0, 1 ), mCameraBefore->viewVector().normalized() ) ) - M_PI / 2 );
617 bool changeAltitude =
false;
619 switch ( mScene->mapSettings()->projectionType() )
621 case Qt3DRender::QCameraLens::PerspectiveProjection:
622 changeAltitude =
angle < M_PI / 30;
624 case Qt3DRender::QCameraLens::OrthographicProjection:
625 changeAltitude =
angle < M_PI / 3;
631 if ( changeAltitude )
632 shiftVector = mDragPoint - moveToPosition;
634 switch ( mScene->mapSettings()->projectionType() )
636 case Qt3DRender::QCameraLens::OrthographicProjection:
641 shiftVector = { mDragPoint.x() - moveToPosition.x(), mDragPoint.y() - moveToPosition.y(), 0 };
644 case Qt3DRender::QCameraLens::PerspectiveProjection:
646 QVector3D cameraBeforeToMoveToPos = ( moveToPosition - mCameraBefore->position() ).normalized();
647 QVector3D cameraBeforeToDragPointPos = ( mDragPoint - mCameraBefore->position() ).normalized();
650 if ( cameraBeforeToMoveToPos.z() == 0 )
652 cameraBeforeToMoveToPos.setZ( 0.01 );
653 cameraBeforeToMoveToPos = cameraBeforeToMoveToPos.normalized();
656 if ( cameraBeforeToDragPointPos.z() == 0 )
658 cameraBeforeToDragPointPos.setZ( 0.01 );
659 cameraBeforeToDragPointPos = cameraBeforeToDragPointPos.normalized();
662 float d1 = ( mDragPoint.z() - cameraBeforeDragPos.z() ) / cameraBeforeToMoveToPos.z();
663 float d2 = ( mDragPoint.z() - cameraBeforeDragPos.z() ) / cameraBeforeToDragPointPos.z();
665 QVector3D from = cameraBeforeDragPos + d1 * cameraBeforeToMoveToPos;
666 QVector3D to = cameraBeforeDragPos + d2 * cameraBeforeToDragPointPos;
668 shiftVector = to - from;
675 mCameraPose.setCenterPoint( mCameraBefore->viewCenter() + shiftVector );
676 updateCameraFromPose();
678 else if ( hasLeftButton && hasShift && hasCtrl )
681 QgsVector3D center = mCameraPose.centerPoint();
682 double tElev = mMousePos.y() - mouse->y();
683 center.
set( center.
x(), center.
y(), center.
z() + tElev * 0.5 );
684 mCameraPose.setCenterPoint( center );
685 updateCameraFromPose();
687 else if ( hasRightButton && !hasShift && !hasCtrl )
689 setMouseParameters( MouseOperation::Zoom, mMousePos );
690 if ( !mDepthBufferIsReady )
693 if ( !mDragPointCalculated )
696 QVector3D worldPosition;
697 if ( screenPointToWorldPos( mClickPoint, depth, worldPosition ) )
699 mDragPoint = worldPosition;
700 mDragPointCalculated =
true;
704 const double oldDist = ( QgsVector3D( mCameraBefore->position() ) - QgsVector3D( mDragPoint ) ).length();
705 double newDist = oldDist;
708 int screenHeight = mScene->engine()->size().height();
709 QWindow *win = window();
712 yOffset = win->mapToGlobal( QPoint( 0, 0 ) ).y();
713 screenHeight = win->screen()->virtualSize().height();
717 if ( mMousePos.y() > mClickPoint.y() )
719 double f = ( double ) ( mMousePos.y() - mClickPoint.y() ) / ( double ) ( screenHeight - mClickPoint.y() - yOffset );
720 f = std::max( 0.0, std::min( 1.0, f ) );
721 f = 1 - ( std::expm1( -2 * f ) ) / ( std::expm1( -2 ) );
722 newDist = newDist * f;
726 double f = 1 - ( double ) ( mMousePos.y() + yOffset ) / ( double ) ( mClickPoint.y() + yOffset );
727 f = std::max( 0.0, std::min( 1.0, f ) );
728 f = ( std::expm1( 2 * f ) ) / ( std::expm1( 2 ) );
729 newDist = newDist + 2 * newDist * f;
732 const double zoomFactor = newDist / oldDist;
733 zoomCameraAroundPivot( mCameraBefore->position(), mCameraBefore->viewCenter().distanceToPoint( mCameraBefore->position() ), zoomFactor, mDragPoint );
736 mMousePos = QPoint( mouse->x(), mouse->y() );
739void QgsCameraController::onPositionChangedGlobeTerrainNavigation( Qt3DInput::QMouseEvent *mouse )
741 const bool hasShift = ( mouse->modifiers() & Qt3DInput::QMouseEvent::Modifiers::ShiftModifier );
742 const bool hasCtrl = ( mouse->modifiers() & Qt3DInput::QMouseEvent::Modifiers::ControlModifier );
743 const bool hasLeftButton = ( mouse->buttons() & Qt::LeftButton );
744 const bool hasMiddleButton = ( mouse->buttons() & Qt::MiddleButton );
746 if ( ( hasLeftButton && hasShift && !hasCtrl ) || ( hasMiddleButton && !hasShift && !hasCtrl ) )
748 setMouseParameters( MouseOperation::RotationCenter, mMousePos );
750 const float scale =
static_cast<float>( std::max( mScene->engine()->size().width(), mScene->engine()->size().height() ) );
751 const float pitchDiff = 180.0f *
static_cast<float>( mouse->y() - mClickPoint.y() ) / scale;
752 const float yawDiff = -180.0f *
static_cast<float>( mouse->x() - mClickPoint.x() ) / scale;
754 mCameraPose.setPitchAngle( mRotationPitch + pitchDiff );
755 mCameraPose.setHeadingAngle( mRotationYaw + yawDiff );
756 updateCameraFromPose();
760 if ( !( mouse->buttons() & Qt::LeftButton ) )
764 setMouseParameters( MouseOperation::Translation, mMousePos );
766 if ( !mDepthBufferIsReady )
769 if ( !mDragPointCalculated )
772 QVector3D worldPosition;
773 if ( !screenPointToWorldPos( mClickPoint, depth, worldPosition ) )
777 mDragPoint = worldPosition;
778 mDragPointCalculated =
true;
783 const QgsVector3D startPosMap = QgsVector3D( mDragPoint ) + mOrigin;
784 const double sphereRadiusMap = startPosMap.
length();
787 const QgsVector3D rayOriginMap = QgsVector3D( ray.
origin() ) + mOrigin;
791 const double quadC =
QgsVector3D::dotProduct( rayOriginMap, rayOriginMap ) - sphereRadiusMap * sphereRadiusMap;
792 const double disc = quadB * quadB - 4 * quadA * quadC;
797 const double rayDistMap = ( -quadB - sqrt( disc ) ) / ( 2 * quadA );
798 if ( rayDistMap < 0 )
800 QgsDebugError( u
"Sphere intersection result negative, canceling move"_s );
803 const QgsVector3D newPosMap = rayOriginMap + QgsVector3D( ray.
direction() ) * rayDistMap;
808 QgsVector3D oldLatLon, newLatLon;
811 oldLatLon = mGlobeCrsToLatLon.transform( startPosMap );
812 newLatLon = mGlobeCrsToLatLon.transform( newPosMap );
814 catch (
const QgsCsException & )
816 QgsDebugError( u
"onPositionChangedGlobeTerrainNavigation: transform failed!"_s );
820 const double latDiff = oldLatLon.
y() - newLatLon.
y();
821 const double lonDiff = oldLatLon.
x() - newLatLon.
x();
823 const QgsVector3D newVC = moveGeocentricPoint( mMousePressViewCenter, latDiff, lonDiff );
824 const QgsVector3D newVCWorld = newVC - mOrigin;
826 mCameraPose.setCenterPoint( newVCWorld );
827 updateCameraFromPose();
834 float dist = mCameraPose.distanceFromCenterPoint();
835 dist -= dist * factor * 0.01f;
836 mCameraPose.setDistanceFromCenterPoint( dist );
837 updateCameraFromPose();
840void QgsCameraController::handleTerrainNavigationWheelZoom()
842 if ( !mDepthBufferIsReady )
845 if ( !mZoomPointCalculated )
848 QVector3D worldPosition;
849 if ( screenPointToWorldPos( mMousePos, depth, worldPosition ) )
851 mZoomPoint = worldPosition;
852 mZoomPointCalculated =
true;
856 double oldDist = ( mZoomPoint - mCameraBefore->position() ).length();
858 double newDist = std::pow( 0.8, mCumulatedWheelY ) * oldDist;
860 newDist = std::max( newDist, 2.0 );
861 double zoomFactor = newDist / oldDist;
863 zoomFactor = std::clamp( zoomFactor, 0.01, 100.0 );
865 zoomCameraAroundPivot( mCameraBefore->position(), mCameraBefore->viewCenter().distanceToPoint( mCameraBefore->position() ), zoomFactor, mZoomPoint );
867 mCumulatedWheelY = 0;
868 setMouseParameters( MouseOperation::None );
871void QgsCameraController::onWheel( Qt3DInput::QWheelEvent *wheel )
873 if ( !mInputHandlersEnabled )
876 switch ( mCameraNavigationMode )
880 const float scaling = ( ( wheel->modifiers() & Qt3DInput::QWheelEvent::Modifiers::ControlModifier ) != 0 ? 0.1f : 1.0f ) / 1000.f;
881 setCameraMovementSpeed( mCameraMovementSpeed + mCameraMovementSpeed * scaling * wheel->angleDelta().y() );
889 const double scaling = ( 1.0 / 120.0 ) * ( ( wheel->modifiers() & Qt3DInput::QWheelEvent::Modifiers::ControlModifier ) != 0 ? 0.1 : 1.0 );
893 mCumulatedWheelY += scaling * wheel->angleDelta().y();
895 if ( mCurrentOperation != MouseOperation::ZoomWheel )
897 setMouseParameters( MouseOperation::ZoomWheel );
902 handleTerrainNavigationWheelZoom();
909 float wheelAmount =
static_cast<float>( wheel->angleDelta().y() );
910 float factor = abs( wheelAmount ) / 1000.f;
911 float mulFactor = wheelAmount > 0 ? ( 1 - factor ) : ( 1 + factor );
912 mCameraPose.setDistanceFromCenterPoint( mCameraPose.distanceFromCenterPoint() * mulFactor );
913 updateCameraFromPose();
919void QgsCameraController::onMousePressed( Qt3DInput::QMouseEvent *mouse )
921 if ( !mInputHandlersEnabled )
924 mKeyboardHandler->setFocus(
true );
926 if ( mouse->button() == Qt3DInput::QMouseEvent::MiddleButton || ( ( mouse->modifiers() & Qt3DInput::QMouseEvent::Modifiers::ShiftModifier ) != 0 && mouse->button() == Qt3DInput::QMouseEvent::LeftButton ) || ( ( mouse->modifiers() & Qt3DInput::QMouseEvent::Modifiers::ControlModifier ) != 0 && mouse->button() == Qt3DInput::QMouseEvent::LeftButton ) )
928 mMousePos = QPoint( mouse->x(), mouse->y() );
930 if ( mCaptureFpsMouseMovements )
931 mIgnoreNextMouseMove =
true;
933 const MouseOperation operation {
934 ( mouse->modifiers() & Qt3DInput::QMouseEvent::Modifiers::ControlModifier ) != 0 && mouse->button() == Qt3DInput::QMouseEvent::LeftButton ? MouseOperation::RotationCamera : MouseOperation::RotationCenter
936 setMouseParameters( operation, mMousePos );
939 else if ( mouse->button() == Qt3DInput::QMouseEvent::LeftButton || mouse->button() == Qt3DInput::QMouseEvent::RightButton )
941 mMousePos = QPoint( mouse->x(), mouse->y() );
943 if ( mCaptureFpsMouseMovements )
944 mIgnoreNextMouseMove =
true;
946 const MouseOperation operation = ( mouse->button() == Qt3DInput::QMouseEvent::LeftButton ) ? MouseOperation::Translation : MouseOperation::Zoom;
947 setMouseParameters( operation, mMousePos );
951void QgsCameraController::onMouseReleased( Qt3DInput::QMouseEvent *mouse )
954 if ( !mInputHandlersEnabled )
958 setMouseParameters( MouseOperation::None );
961bool QgsCameraController::onKeyPressedTerrainNavigation( QKeyEvent *event )
963 const bool hasShift = (
event->modifiers() & Qt::ShiftModifier );
964 const bool hasCtrl = (
event->modifiers() & Qt::ControlModifier );
966 int tx = 0, ty = 0, tElev = 0;
967 switch ( event->key() )
983 case Qt::Key_PageDown:
995 if ( !hasShift && !hasCtrl )
999 else if ( hasShift && !hasCtrl )
1005 else if ( hasCtrl && !hasShift )
1008 const float diffPitch = ty;
1009 const float diffYaw = -tx;
1017 QgsVector3D center = mCameraPose.centerPoint();
1018 center.
set( center.
x(), center.
y(), center.
z() + tElev * 10 );
1019 mCameraPose.setCenterPoint( center );
1026bool QgsCameraController::onKeyPressedGlobeTerrainNavigation( QKeyEvent *event )
1030 constexpr float MOVE_FACTOR = 0.000001f;
1031 constexpr float ZOOM_FACTOR = 0.9f;
1033 const bool hasShift = (
event->modifiers() & Qt::ShiftModifier );
1035 switch ( event->key() )
1061 case Qt::Key_PageDown:
1064 case Qt::Key_PageUp:
1073static const QSet<int> walkNavigationSavedKeys = {
1088bool QgsCameraController::onKeyPressedFlyNavigation( QKeyEvent *event )
1090 switch ( event->key() )
1092 case Qt::Key_QuoteLeft:
1095 mCaptureFpsMouseMovements = !mCaptureFpsMouseMovements;
1096 mIgnoreNextMouseMove =
true;
1097 if ( mCaptureFpsMouseMovements )
1099 qApp->setOverrideCursor( QCursor( Qt::BlankCursor ) );
1103 qApp->restoreOverrideCursor();
1109 case Qt::Key_Escape:
1112 if ( mCaptureFpsMouseMovements )
1114 mCaptureFpsMouseMovements =
false;
1115 mIgnoreNextMouseMove =
true;
1116 qApp->restoreOverrideCursor();
1126 if ( walkNavigationSavedKeys.contains( event->key() ) )
1128 if ( !event->isAutoRepeat() )
1130 mDepressedKeys.insert( event->key() );
1140 const QVector3D cameraUp = mCamera->upVector().normalized();
1141 const QVector3D cameraFront = ( QVector3D( mCameraPose.centerPoint().x(), mCameraPose.centerPoint().y(), mCameraPose.centerPoint().z() ) - mCamera->position() ).normalized();
1142 const QVector3D cameraLeft = QVector3D::crossProduct( cameraUp, cameraFront );
1144 QVector3D cameraPosDiff( 0.0f, 0.0f, 0.0f );
1148 cameraPosDiff +=
static_cast<float>( tx ) * cameraFront;
1152 cameraPosDiff +=
static_cast<float>( ty ) * cameraLeft;
1156 cameraPosDiff +=
static_cast<float>( tz ) * QVector3D( 0.0f, 0.0f, 1.0f );
1162void QgsCameraController::applyFlyModeKeyMovements()
1168 const bool shiftPressed = mDepressedKeys.contains( Qt::Key_Shift );
1169 const bool ctrlPressed = mDepressedKeys.contains( Qt::Key_Control );
1171 const double movementSpeed = mCameraMovementSpeed * ( shiftPressed ? 2 : 1 ) * ( ctrlPressed ? 0.1 : 1 );
1173 bool changed =
false;
1177 if ( mDepressedKeys.contains( Qt::Key_Left ) || mDepressedKeys.contains( Qt::Key_A ) )
1183 if ( mDepressedKeys.contains( Qt::Key_Right ) || mDepressedKeys.contains( Qt::Key_D ) )
1189 if ( mDepressedKeys.contains( Qt::Key_Up ) || mDepressedKeys.contains( Qt::Key_W ) )
1195 if ( mDepressedKeys.contains( Qt::Key_Down ) || mDepressedKeys.contains( Qt::Key_S ) )
1203 static constexpr double ELEVATION_MOVEMENT_SCALE = 0.5;
1204 if ( mDepressedKeys.contains( Qt::Key_PageUp ) || mDepressedKeys.contains( Qt::Key_E ) )
1207 z += ELEVATION_MOVEMENT_SCALE * movementSpeed;
1210 if ( mDepressedKeys.contains( Qt::Key_PageDown ) || mDepressedKeys.contains( Qt::Key_Q ) )
1213 z -= ELEVATION_MOVEMENT_SCALE * movementSpeed;
1220void QgsCameraController::onPositionChangedFlyNavigation( Qt3DInput::QMouseEvent *mouse )
1222 const bool hasMiddleButton = ( mouse->buttons() & Qt::MiddleButton );
1223 const bool hasRightButton = ( mouse->buttons() & Qt::RightButton );
1225 const double dx = mCaptureFpsMouseMovements ? QCursor::pos().x() - mMousePos.x() : mouse->x() - mMousePos.x();
1226 const double dy = mCaptureFpsMouseMovements ? QCursor::pos().y() - mMousePos.y() : mouse->y() - mMousePos.y();
1227 mMousePos = mCaptureFpsMouseMovements ? QCursor::pos() : QPoint( mouse->x(), mouse->y() );
1229 if ( mIgnoreNextMouseMove )
1231 mIgnoreNextMouseMove =
false;
1235 if ( hasMiddleButton )
1238 const QVector3D cameraUp = mCamera->upVector().normalized();
1239 const QVector3D cameraFront = ( QVector3D( mCameraPose.centerPoint().x(), mCameraPose.centerPoint().y(), mCameraPose.centerPoint().z() ) - mCamera->position() ).normalized();
1240 const QVector3D cameraLeft = QVector3D::crossProduct( cameraUp, cameraFront );
1241 const QVector3D cameraPosDiff = -dx * cameraLeft - dy * cameraUp;
1242 moveCenterPoint(
static_cast<float>( mCameraMovementSpeed ) * cameraPosDiff / 10.0 );
1244 else if ( hasRightButton )
1247 const QVector3D cameraFront = ( QVector3D( mCameraPose.centerPoint().x(), mCameraPose.centerPoint().y(), mCameraPose.centerPoint().z() ) - mCamera->position() ).normalized();
1248 const QVector3D cameraPosDiff = dy * cameraFront;
1249 moveCenterPoint(
static_cast<float>( mCameraMovementSpeed ) * cameraPosDiff / 5.0 );
1253 if ( mCaptureFpsMouseMovements )
1255 float diffPitch = -0.2f * dy;
1256 switch ( mVerticalAxisInversion )
1267 const float diffYaw = -0.2f * dx;
1270 else if ( mouse->buttons() & Qt::LeftButton )
1272 float diffPitch = -0.2f * dy;
1273 switch ( mVerticalAxisInversion )
1283 const float diffYaw = -0.2f * dx;
1288 if ( mCaptureFpsMouseMovements )
1290 mIgnoreNextMouseMove =
true;
1293 emit
setCursorPosition( QPoint( mScene->engine()->size().width() / 2, mScene->engine()->size().height() / 2 ) );
1300 float pitch = mCameraPose.pitchAngle();
1301 pitch -= deltaPitch;
1302 mCameraPose.setPitchAngle(
pitch );
1303 updateCameraFromPose();
1309 float yaw = mCameraPose.headingAngle();
1311 mCameraPose.setHeadingAngle(
yaw );
1312 updateCameraFromPose();
1317 mCameraPose.setHeadingAngle( angle );
1318 updateCameraFromPose();
1323 const float yaw = mCameraPose.headingAngle();
1324 const float dist = mCameraPose.distanceFromCenterPoint();
1325 const float x = tx * dist * 0.02f;
1326 const float y = -ty * dist * 0.02f;
1329 const float t = sqrt( x * x + y * y );
1330 const float a = atan2( y, x ) -
yaw * M_PI / 180;
1331 const float dx = cos( a ) * t;
1332 const float dy = sin( a ) * t;
1335 center.
set( center.
x() + dx, center.
y() - dy, center.
z() );
1336 mCameraPose.setCenterPoint( center );
1337 updateCameraFromPose();
1342 if ( !mInputHandlersEnabled )
1345 if ( event->type() == QKeyEvent::Type::KeyRelease )
1347 if ( !event->isAutoRepeat() && mDepressedKeys.contains( event->key() ) )
1349 mDepressedKeys.remove( event->key() );
1353 else if ( event->type() == QEvent::ShortcutOverride )
1355 if ( event->modifiers() & Qt::ControlModifier )
1357 switch ( event->key() )
1359 case Qt::Key_QuoteLeft:
1362 switch ( mCameraNavigationMode )
1408 switch ( mCameraNavigationMode )
1411 return onKeyPressedFlyNavigation( event );
1414 return onKeyPressedTerrainNavigation( event );
1417 return onKeyPressedGlobeTerrainNavigation( event );
1425 mDepthBufferImage = depthImage;
1426 mDepthBufferIsReady =
true;
1427 mDepthBufferNonVoidAverage = -1;
1435 if ( mCurrentOperation == MouseOperation::ZoomWheel )
1437 handleTerrainNavigationWheelZoom();
1441bool QgsCameraController::isATranslationRotationSequence( MouseOperation newOperation )
const
1443 return std::find( mTranslateOrRotate.begin(), mTranslateOrRotate.end(), newOperation ) != std::end( mTranslateOrRotate ) && std::find( mTranslateOrRotate.begin(), mTranslateOrRotate.end(), mCurrentOperation ) != std::end( mTranslateOrRotate );
1446void QgsCameraController::setMouseParameters(
const MouseOperation &newOperation,
const QPoint &clickPoint )
1448 if ( newOperation == mCurrentOperation )
1453 if ( newOperation == MouseOperation::None )
1455 mClickPoint = QPoint();
1463 else if ( mClickPoint.isNull() || isATranslationRotationSequence( newOperation ) )
1465 mClickPoint = clickPoint;
1466 mRotationPitch = mCameraPose.pitchAngle();
1467 mRotationYaw = mCameraPose.headingAngle();
1469 mCurrentOperation = newOperation;
1470 mDepthBufferIsReady =
false;
1471 mRotationCenterCalculated =
false;
1472 mDragPointCalculated =
false;
1473 mZoomPointCalculated =
false;
1475 if ( mCurrentOperation != MouseOperation::None && mCurrentOperation != MouseOperation::RotationCamera )
1477 mMousePressViewCenter = mCameraPose.centerPoint() + mOrigin;
1487 mCameraPose.setCenterPoint( mCameraPose.centerPoint() - diff );
1490 mCameraBefore->setPosition( (
QgsVector3D( mCameraBefore->position() ) - diff ).toVector3D() );
1491 mCameraBefore->setViewCenter( (
QgsVector3D( mCameraBefore->viewCenter() ) - diff ).toVector3D() );
1492 mDragPoint = (
QgsVector3D( mDragPoint ) - diff ).toVector3D();
1493 mRotationCenter = (
QgsVector3D( mRotationCenter ) - diff ).toVector3D();
1497 updateCameraFromPose();
1500void QgsCameraController::rotateToRespectingTerrain(
float pitch,
float yaw )
1503 double elevation = 0.0;
1507 QVector3D camPos = mCamera->position();
1512 const QList<QgsRayCastHit> results = mScene->
terrainEntity()->rayIntersection( ray, context );
1514 if ( !results.isEmpty() )
1516 elevation = results.constFirst().mapCoordinates().z() - mOrigin.z();
1517 QgsDebugMsgLevel( QString(
"Computed elevation from terrain: %1" ).arg( elevation ), 2 );
1524 pos.
set( pos.
x(), pos.
y(), elevation + mScene->terrainEntity()->terrainElevationOffset() );
1531 if ( !crossSection.
isValid() )
1536 const double width = crossSection.
halfWidth();
1538 const QgsVector3D startVec { startPoint.
x(), startPoint.
y(), 0 };
1541 QgsVector linePerpVec( ( endPoint - startPoint ).x(), ( endPoint - startPoint ).y() );
1544 const QgsVector3D linePerpVec3D( linePerpVec.
x(), linePerpVec.
y(), 0 );
1545 const QgsVector3D frontStartPoint( startVec + linePerpVec3D * width );
1546 const QgsVector3D frontEndPoint( endVec + linePerpVec3D * width );
1551 mScene->elevationRange(
true ),
1552 mCamera->fieldOfView(),
VerticalAxisInversion
Vertical axis inversion options for 3D views.
@ Always
Always invert vertical axis movements.
@ Never
Never invert vertical axis movements.
@ WhenDragging
Invert vertical axis movements when dragging in first person modes.
NavigationMode
The navigation mode used by 3D cameras.
@ TerrainBased
The default navigation based on the terrain.
@ Walk
Uses WASD keys or arrows to navigate in walking (first person) manner.
@ GlobeTerrainBased
Navigation similar to TerrainBased, but for use with globe.
@ Globe
Scene is represented as a globe using a geocentric CRS.
@ Local
Local scene based on a projected CRS.
@ Reverse
Reverse/inverse transform (from destination to source).
Entity that encapsulates our 3D scene - contains all other entities (such as terrain) as children.
Qgs3DMapSettings * mapSettings() const
Returns the 3D map settings.
QgsAbstract3DEngine * engine() const
Returns the abstract 3D engine.
QgsTerrainEntity * terrainEntity()
Returns terrain entity (may be nullptr if using globe scene, terrain rendering is disabled or when te...
Qgis::SceneMode sceneMode() const
Returns mode of the 3D scene - whether it is represented as a globe (when using Geocentric CRS such a...
bool terrainRenderingEnabled() const
Returns whether the 2D terrain surface will be rendered.
static QQuaternion rotationFromPitchHeadingAngles(float pitchAngle, float headingAngle)
Returns rotation quaternion that performs rotation around X axis by pitchAngle, followed by rotation ...
static std::unique_ptr< Qt3DRender::QCamera > copyCamera(Qt3DRender::QCamera *cam)
Returns new camera object with copied properties.
static double decodeDepth(const QRgb &pixel)
Decodes the depth value from the pixel's color value The depth value is encoded from OpenGL side (the...
static QVector3D screenPointToWorldPos(const QPoint &screenPoint, double depth, const QSize &screenSize, Qt3DRender::QCamera *camera)
Converts the clicked mouse position to the corresponding 3D world coordinates.
static QgsRay3D rayFromScreenPoint(const QPoint &point, const QSize &windowSize, Qt3DRender::QCamera *camera)
Convert from clicked point on the screen to a ray in world coordinates.
static QgsCameraPose lineSegmentToCameraPose(const QgsVector3D &startPoint, const QgsVector3D &endPoint, const QgsDoubleRange &elevationRange, float fieldOfView, const QgsVector3D &worldOrigin)
Returns the camera pose for a camera looking at mid-point between startPoint and endPoint.
void navigationModeChanged(Qgis::NavigationMode mode)
Emitted when the navigation mode is changed using the hotkey ctrl + ~.
void rotateCameraToBottom()
Rotate to bottom-up view.
void setLookingAtMapPoint(const QgsVector3D &point, float distance, float pitch, float yaw)
Sets camera configuration like setLookingAtPoint(), but the point is given in map coordinates.
float pitch() const
Returns pitch angle in degrees (0 = looking from the top, 90 = looking from the side).
Qt3DRender::QCamera * camera() const
Returns camera that is being controlled.
~QgsCameraController() override
float yaw() const
Returns yaw angle in degrees.
void requestDepthBufferCapture()
Emitted to ask for the depth buffer image.
void rotateCameraToTop()
Rotate to top-down view.
void tiltUpAroundViewCenter(float deltaPitch)
Tilt up the view by deltaPitch around the view center (camera moves).
void globeMoveCenterPoint(double latDiff, double lonDiff)
Orbits camera around the globe by the specified amount given as the difference in latitude/longitude ...
void rotateCameraToEast()
Rotate to view from the east.
void setVerticalAxisInversion(Qgis::VerticalAxisInversion inversion)
Sets the vertical axis inversion behavior.
const QgsVector3D origin() const
Returns the origin of the scene in map coordinates.
void rotateCameraToHome()
Rotate to diagonal view.
void rotateCameraToNorth()
Rotate to view from the north.
float distance() const
Returns distance of the camera from the point it is looking at.
void globeUpdatePitchAngle(float angleDiff)
Updates pitch angle by the specified amount given as the angular difference in degrees.
Q_DECL_DEPRECATED void zoomCameraAroundPivot(const QVector3D &oldCameraPosition, double zoomFactor, const QVector3D &pivotPoint)
Zooms camera by given zoom factor (>1 one means zoom in) while keeping the pivot point (given in worl...
void globeZoom(float factor)
Moves camera closer or further away from the globe.
void rotateCamera(float diffPitch, float diffYaw)
Rotates the camera on itself.
void rotateCameraToWest()
Rotate to view from the west.
void setCameraNavigationMode(Qgis::NavigationMode navigationMode)
Sets the navigation mode used by the camera controller.
void cameraChanged()
Emitted when camera has been updated.
void moveCenterPoint(const QVector3D &posDiff)
Moves camera position by the given difference vector in world coordinates.
void cameraMovementSpeedChanged(double speed)
Emitted whenever the camera movement speed is changed by the controller.
void setCrossSectionSideView(const QgsCrossSection &crossSection)
Sets the cross section side view definition for the 3D map canvas.
QgsCameraController(Qgs3DMapScene *scene)
Constructs the camera controller with optional parent node that will take ownership.
void frameTriggered(float dt)
Called internally from 3D scene when a new frame is generated. Updates camera according to keyboard/m...
void resetView(float distance)
Move camera back to the initial position (looking down towards origin of world's coordinates).
void setLookingAtPoint(const QgsVector3D &point, float distance, float pitch, float yaw)
Sets the complete camera configuration: the point towards it is looking (in 3D world coordinates),...
QgsVector3D lookingAtPoint() const
Returns the point in the world coordinates towards which the camera is looking.
QgsVector3D lookingAtMapPoint() const
Returns the point in the map coordinates towards which the camera is looking.
void rotateCameraToSouth()
Rotate to view from the south.
QDomElement writeXml(QDomDocument &doc) const
Writes camera configuration to the given DOM element.
void setViewFromTop(float worldX, float worldY, float distance, float yaw=0)
Sets camera to look down towards given point in world coordinate, in given distance from plane with z...
bool keyboardEventFilter(QKeyEvent *event)
If the event is relevant, handles the event and returns true, otherwise false.
void resetGlobe(float distance, double lat=0, double lon=0)
Resets view of the globe to look at a particular location given as latitude and longitude (in degrees...
void readXml(const QDomElement &elem, QgsVector3D savedOrigin)
Reads camera configuration from the given DOM element.
void zoom(float factor)
Zoom the map by factor.
void globeUpdateHeadingAngle(float angleDiff)
Updates heading angle by the specified amount given as the angular difference in degrees.
void rotateAroundViewCenter(float deltaYaw)
Rotate clockwise the view by deltaYaw around the view center (camera moves).
void walkView(double tx, double ty, double tz)
Walks into the map by tx, ty, and tz.
void setCameraPose(const QgsCameraPose &camPose, bool force=false)
Sets camera pose.
void setOrigin(const QgsVector3D &origin)
Reacts to the shift of origin of the scene, updating camera pose and any other member variables so th...
void setCameraHeadingAngle(float angle)
Set camera heading to angle (used for rotating the view).
void setCameraMovementSpeed(double movementSpeed)
Sets the camera movement speed.
void rotateCameraAroundPivot(float newPitch, float newHeading, const QVector3D &pivotPoint)
Rotates the camera around the pivot point (in world coordinates) to the given new pitch and heading a...
void depthBufferCaptured(const QImage &depthImage)
Sets the depth buffer image used by the camera controller to calculate world position from a pixel's ...
void cameraRotationCenterChanged(QVector3D position)
Emitted when the camera rotation center changes.
void setCursorPosition(QPoint point)
Emitted when the mouse cursor position should be moved to the specified point on the map viewport.
void moveView(float tx, float ty)
Move the map by tx and ty.
Encapsulates camera pose in a 3D scene.
QgsVector3D centerPoint() const
Returns center point (towards which point the camera is looking).
void setPitchAngle(float pitch)
Sets pitch angle in degrees.
void setCenterPoint(const QgsVector3D &point)
Sets center point (towards which point the camera is looking).
void setHeadingAngle(float heading)
Sets heading (yaw) angle in degrees.
void setDistanceFromCenterPoint(float distance)
Sets distance of the camera from the center point.
Encapsulates the definition of a cross section in 3D map coordinates.
double halfWidth() const
Returns the half-width of the cross section.
QgsPoint endPoint() const
Returns the end point of the cross section.
bool isValid() const
Returns cross section validity.
QgsPoint startPoint() const
Returns the start point of the cross section.
Custom exception class for Coordinate Reference System related exceptions.
Point geometry type, with support for z-dimension and m-values.
A representation of a ray in 3D.
QVector3D origin() const
Returns the origin of the ray.
QVector3D direction() const
Returns the direction of the ray see setDirection().
Responsible for defining parameters of the ray casting operations in 3D map canvases.
void setSingleResult(bool enable)
Sets whether to fetch only the closest hit for each layer or entity type.
void setMaximumDistance(float distance)
Sets the maximum distance from ray origin to look for hits when casting a ray.
A 3D vector (similar to QVector3D) with the difference that it uses double precision instead of singl...
double y() const
Returns Y coordinate.
double z() const
Returns Z coordinate.
QVector3D toVector3D() const
Converts the current object to QVector3D.
static double dotProduct(const QgsVector3D &v1, const QgsVector3D &v2)
Returns the dot product of two vectors.
double x() const
Returns X coordinate.
void setX(double x)
Sets X coordinate.
void set(double x, double y, double z)
Sets vector coordinates.
void setY(double y)
Sets Y coordinate.
double length() const
Returns the length of the vector.
Represent a 2-dimensional vector.
double y() const
Returns the vector's y-component.
QgsVector normalized() const
Returns the vector's normalized (or "unit") vector (ie same angle but length of 1....
QgsVector perpVector() const
Returns the perpendicular vector to this vector (rotated 90 degrees counter-clockwise).
double x() const
Returns the vector's x-component.
On-screen 3D engine: it creates an OpenGL window (QWindow) and displays rendered 3D scenes there.
QWindow * window()
Returns the internal 3D window where all the rendered output is displayed.
double ANALYSIS_EXPORT angle(QgsPoint *p1, QgsPoint *p2, QgsPoint *p3, QgsPoint *p4)
Calculates the angle between two segments (in 2 dimension, z-values are ignored).
#define QgsDebugMsgLevel(str, level)
#define QgsDebugError(str)