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 ) * QQuaternion::fromAxisAndAngle( QVector3D( 0, 1, 0 ), newPitch );
138 QVector3D newCameraToCenter = ( qLatLon * qPitchHeading * QVector3D( -1, 0, 0 ) ) * mCameraPose.distanceFromCenterPoint();
140 mCameraPose.setCenterPoint( mCamera->position() + newCameraToCenter );
141 mCameraPose.setPitchAngle( newPitch );
142 mCameraPose.setHeadingAngle( newHeading );
143 updateCameraFromPose();
150 QVector3D newCameraToCenter = q * QVector3D( 0, 0, -mCameraPose.distanceFromCenterPoint() );
151 mCameraPose.setCenterPoint( mCamera->position() + newCameraToCenter );
152 mCameraPose.setPitchAngle( newPitch );
153 mCameraPose.setHeadingAngle( newHeading );
154 updateCameraFromPose();
162 const float oldPitch = mCameraPose.pitchAngle();
163 const float oldHeading = mCameraPose.headingAngle();
165 newPitch = std::clamp( newPitch, 0.f, 180.f );
171 const QQuaternion q = qNew * qOld.conjugated();
173 const QVector3D newViewCenter = q * ( mCamera->viewCenter() - pivotPoint ) + pivotPoint;
175 mCameraPose.setCenterPoint( newViewCenter );
176 mCameraPose.setPitchAngle( newPitch );
177 mCameraPose.setHeadingAngle( newHeading );
178 updateCameraFromPose();
184 QVector3D newCamPosition = pivotPoint + ( oldCameraPosition - pivotPoint ) * zoomFactor;
185 double newDistance = mCameraPose.distanceFromCenterPoint() * zoomFactor;
189 QVector3D cameraToCenter = q * QVector3D( 0, 0, -newDistance );
190 QVector3D newViewCenter = newCamPosition + cameraToCenter;
192 mCameraPose.setDistanceFromCenterPoint( newDistance );
193 mCameraPose.setCenterPoint( newViewCenter );
194 updateCameraFromPose();
200 QVector3D newCamPosition = pivotPoint + ( oldCameraPosition - pivotPoint ) * zoomFactor;
201 const double newDistance = oldDistanceFromCenterPoint * zoomFactor;
205 QVector3D cameraToCenter = q * QVector3D( 0, 0, -newDistance );
206 QVector3D newViewCenter = newCamPosition + cameraToCenter;
208 mCameraPose.setDistanceFromCenterPoint( newDistance );
209 mCameraPose.setCenterPoint( newViewCenter );
210 updateCameraFromPose();
217 if ( mCameraChanged )
220 mCameraChanged =
false;
226 QgsPointXY extentCenter = mScene->mapSettings()->extent().center();
235 QgsDebugError( u
"setViewFromTop() should not be used with globe!"_s );
240 QgsTerrainEntity *terrain = mScene->terrainEntity();
241 const float terrainElevationOffset = terrain ? terrain->terrainElevationOffset() : 0.0f;
247 mCamera->setNearPlane(
distance / 2 );
248 mCamera->setFarPlane(
distance * 2 );
255 return mCameraPose.centerPoint();
280 if ( camPose == mCameraPose && !force )
283 mCameraPose = camPose;
284 updateCameraFromPose();
289 QDomElement elemCamera = doc.createElement( u
"camera"_s );
292 QgsVector3D centerPoint = mCameraPose.centerPoint() + mOrigin;
293 elemCamera.setAttribute( u
"xMap"_s, centerPoint.
x() );
294 elemCamera.setAttribute( u
"yMap"_s, centerPoint.
y() );
295 elemCamera.setAttribute( u
"zMap"_s, centerPoint.
z() );
296 elemCamera.setAttribute( u
"dist"_s, mCameraPose.distanceFromCenterPoint() );
297 elemCamera.setAttribute( u
"pitch"_s, mCameraPose.pitchAngle() );
298 elemCamera.setAttribute( u
"yaw"_s, mCameraPose.headingAngle() );
304 const float dist = elem.attribute( u
"dist"_s ).toFloat();
305 const float pitch = elem.attribute( u
"pitch"_s ).toFloat();
306 const float yaw = elem.attribute( u
"yaw"_s ).toFloat();
309 if ( elem.hasAttribute(
"xMap" ) )
312 const double x = elem.attribute( u
"xMap"_s ).toDouble();
313 const double y = elem.attribute( u
"yMap"_s ).toDouble();
314 const double z = elem.attribute( u
"zMap"_s ).toDouble();
320 const double x = elem.attribute( u
"x"_s ).toDouble();
321 const double y = elem.attribute( u
"y"_s ).toDouble();
322 const double elev = elem.attribute( u
"elev"_s ).toDouble();
323 centerPoint =
QgsVector3D( x, elev, y ) - savedOrigin + mOrigin;
328double QgsCameraController::sampleDepthBuffer(
int px,
int py )
330 if ( !mDepthBufferIsReady )
332 QgsDebugError( u
"Asked to sample depth buffer, but depth buffer not ready!"_s );
337 if ( QWindow *win = window() )
341 px =
static_cast<int>( px * win->devicePixelRatio() );
342 py =
static_cast<int>( py * win->devicePixelRatio() );
346 for (
int x = px - 3; x <= px + 3; ++x )
348 for (
int y = py - 3; y <= py + 3; ++y )
350 if ( mDepthBufferImage.valid( x, y ) )
359double QgsCameraController::depthBufferNonVoidAverage()
362 if ( mDepthBufferNonVoidAverage != -1 )
363 return mDepthBufferNonVoidAverage;
367 int samplesCount = 0;
369 Q_ASSERT( mDepthBufferImage.format() == QImage::Format_RGB32 );
370 for (
int y = 0; y < mDepthBufferImage.height(); ++y )
372 const QRgb *line =
reinterpret_cast<const QRgb *
>( mDepthBufferImage.constScanLine( y ) );
373 for (
int x = 0; x < mDepthBufferImage.width(); ++x )
385 if ( samplesCount == 0 )
388 depth /= samplesCount;
390 mDepthBufferNonVoidAverage = depth;
395QgsVector3D QgsCameraController::moveGeocentricPoint(
const QgsVector3D &point,
double latDiff,
double lonDiff )
399 QgsVector3D pointLatLon = mGlobeCrsToLatLon.transform( point );
400 pointLatLon.
setX( pointLatLon.
x() + lonDiff );
401 pointLatLon.
setY( std::clamp( pointLatLon.
y() + latDiff, -90., 90. ) );
405 catch (
const QgsCsException & )
407 QgsDebugError( u
"moveGeocentricPoint: transform failed!"_s );
414 const QgsVector3D viewCenter = mCameraPose.centerPoint() + mOrigin;
415 const QgsVector3D newViewCenter = moveGeocentricPoint( viewCenter, latDiff, lonDiff );
416 mCameraPose.setCenterPoint( newViewCenter - mOrigin );
417 updateCameraFromPose();
422 mCameraPose.setDistanceFromCenterPoint( mCameraPose.distanceFromCenterPoint() * factor );
423 updateCameraFromPose();
428 mCameraPose.setPitchAngle( std::clamp( mCameraPose.pitchAngle() + angleDiff, 0.f, 90.f ) );
429 updateCameraFromPose();
434 mCameraPose.setHeadingAngle( mCameraPose.headingAngle() + angleDiff );
435 updateCameraFromPose();
457void QgsCameraController::updateCameraFromPose()
468 viewCenterLatLon = mGlobeCrsToLatLon.
transform( viewCenter );
470 catch (
const QgsCsException & )
472 QgsDebugError( u
"updateCameraFromPose: transform failed!"_s );
476 mCameraPose.updateCameraGlobe( mCamera, viewCenterLatLon.
y(), viewCenterLatLon.
x() );
480 mCameraPose.updateCamera( mCamera );
482 mCameraChanged =
true;
488 mCameraPose.setCenterPoint( mCameraPose.centerPoint() + posDiff );
489 updateCameraFromPose();
492void QgsCameraController::onPositionChanged( Qt3DInput::QMouseEvent *mouse )
494 if ( !mInputHandlersEnabled )
497 QgsEventTracing::ScopedEvent traceEvent( u
"3D"_s, u
"QgsCameraController::onPositionChanged"_s );
499 switch ( mCameraNavigationMode )
502 onPositionChangedTerrainNavigation( mouse );
506 onPositionChangedFlyNavigation( mouse );
510 onPositionChangedGlobeTerrainNavigation( mouse );
515bool QgsCameraController::screenPointToWorldPos( QPoint position,
double &depth, QVector3D &worldPosition )
517 depth = sampleDepthBuffer( position.x(), position.y() );
523 depth = depthBufferNonVoidAverage();
527 if ( !std::isfinite( worldPosition.x() ) || !std::isfinite( worldPosition.y() ) || !std::isfinite( worldPosition.z() ) )
529 QgsDebugMsgLevel( u
"screenPointToWorldPos: position is NaN or Inf. This should not happen."_s, 2 );
536void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseEvent *mouse )
538 if ( mIgnoreNextMouseMove )
540 mIgnoreNextMouseMove =
false;
541 mMousePos = QPoint( mouse->x(), mouse->y() );
545 const int dx = mouse->x() - mMousePos.x();
546 const int dy = mouse->y() - mMousePos.y();
548 const bool hasShift = ( mouse->modifiers() & Qt3DInput::QMouseEvent::Modifiers::ShiftModifier );
549 const bool hasCtrl = ( mouse->modifiers() & Qt3DInput::QMouseEvent::Modifiers::ControlModifier );
550 const bool hasLeftButton = ( mouse->buttons() & Qt::LeftButton );
551 const bool hasMiddleButton = ( mouse->buttons() & Qt::MiddleButton );
552 const bool hasRightButton = ( mouse->buttons() & Qt::RightButton );
554 if ( ( hasLeftButton && hasShift && !hasCtrl ) || ( hasMiddleButton && !hasShift && !hasCtrl ) )
557 setMouseParameters( MouseOperation::RotationCenter, mMousePos );
559 float scale =
static_cast<float>( std::max( mScene->engine()->size().width(), mScene->engine()->size().height() ) );
560 float pitchDiff = 180.0f *
static_cast<float>( mouse->y() - mClickPoint.y() ) / scale;
561 float yawDiff = -180.0f *
static_cast<float>( mouse->x() - mClickPoint.x() ) / scale;
563 if ( !mDepthBufferIsReady )
566 if ( !mRotationCenterCalculated )
569 QVector3D worldPosition;
570 if ( screenPointToWorldPos( mClickPoint, depth, worldPosition ) )
572 mRotationCenter = worldPosition;
573 mRotationDistanceFromCenter = ( mRotationCenter - mCameraBefore->position() ).length();
575 mRotationCenterCalculated =
true;
581 else if ( hasLeftButton && hasCtrl && !hasShift )
583 setMouseParameters( MouseOperation::RotationCamera );
585 const float diffPitch = 0.2f * dy;
586 const float diffYaw = -0.2f * dx;
589 else if ( hasLeftButton && !hasShift && !hasCtrl )
592 setMouseParameters( MouseOperation::Translation, mMousePos );
594 if ( !mDepthBufferIsReady )
597 if ( !mDragPointCalculated )
600 QVector3D worldPosition;
601 if ( screenPointToWorldPos( mClickPoint, depth, worldPosition ) )
604 mDragPoint = worldPosition;
605 mDragPointCalculated =
true;
609 QVector3D cameraBeforeDragPos = mCameraBefore->position();
612 QVector3D shiftVector;
615 float angle = std::fabs( std::acos( QVector3D::dotProduct( QVector3D( 0, 0, 1 ), mCameraBefore->viewVector().normalized() ) ) - M_PI / 2 );
616 bool changeAltitude =
false;
618 switch ( mScene->mapSettings()->projectionType() )
620 case Qt3DRender::QCameraLens::PerspectiveProjection:
621 changeAltitude =
angle < M_PI / 30;
623 case Qt3DRender::QCameraLens::OrthographicProjection:
624 changeAltitude =
angle < M_PI / 3;
630 if ( changeAltitude )
631 shiftVector = mDragPoint - moveToPosition;
633 switch ( mScene->mapSettings()->projectionType() )
635 case Qt3DRender::QCameraLens::OrthographicProjection:
640 shiftVector = { mDragPoint.x() - moveToPosition.x(), mDragPoint.y() - moveToPosition.y(), 0 };
643 case Qt3DRender::QCameraLens::PerspectiveProjection:
645 QVector3D cameraBeforeToMoveToPos = ( moveToPosition - mCameraBefore->position() ).normalized();
646 QVector3D cameraBeforeToDragPointPos = ( mDragPoint - mCameraBefore->position() ).normalized();
649 if ( cameraBeforeToMoveToPos.z() == 0 )
651 cameraBeforeToMoveToPos.setZ( 0.01 );
652 cameraBeforeToMoveToPos = cameraBeforeToMoveToPos.normalized();
655 if ( cameraBeforeToDragPointPos.z() == 0 )
657 cameraBeforeToDragPointPos.setZ( 0.01 );
658 cameraBeforeToDragPointPos = cameraBeforeToDragPointPos.normalized();
661 float d1 = ( mDragPoint.z() - cameraBeforeDragPos.z() ) / cameraBeforeToMoveToPos.z();
662 float d2 = ( mDragPoint.z() - cameraBeforeDragPos.z() ) / cameraBeforeToDragPointPos.z();
664 QVector3D from = cameraBeforeDragPos + d1 * cameraBeforeToMoveToPos;
665 QVector3D to = cameraBeforeDragPos + d2 * cameraBeforeToDragPointPos;
667 shiftVector = to - from;
674 mCameraPose.setCenterPoint( mCameraBefore->viewCenter() + shiftVector );
675 updateCameraFromPose();
677 else if ( hasLeftButton && hasShift && hasCtrl )
680 QgsVector3D center = mCameraPose.centerPoint();
681 double tElev = mMousePos.y() - mouse->y();
682 center.
set( center.
x(), center.
y(), center.
z() + tElev * 0.5 );
683 mCameraPose.setCenterPoint( center );
684 updateCameraFromPose();
686 else if ( hasRightButton && !hasShift && !hasCtrl )
688 setMouseParameters( MouseOperation::Zoom, mMousePos );
689 if ( !mDepthBufferIsReady )
692 if ( !mDragPointCalculated )
695 QVector3D worldPosition;
696 if ( screenPointToWorldPos( mClickPoint, depth, worldPosition ) )
698 mDragPoint = worldPosition;
699 mDragPointCalculated =
true;
703 const double oldDist = ( QgsVector3D( mCameraBefore->position() ) - QgsVector3D( mDragPoint ) ).length();
704 double newDist = oldDist;
707 int screenHeight = mScene->engine()->size().height();
708 QWindow *win = window();
711 yOffset = win->mapToGlobal( QPoint( 0, 0 ) ).y();
712 screenHeight = win->screen()->virtualSize().height();
716 if ( mMousePos.y() > mClickPoint.y() )
718 double f = ( double ) ( mMousePos.y() - mClickPoint.y() ) / ( double ) ( screenHeight - mClickPoint.y() - yOffset );
719 f = std::max( 0.0, std::min( 1.0, f ) );
720 f = 1 - ( std::expm1( -2 * f ) ) / ( std::expm1( -2 ) );
721 newDist = newDist * f;
725 double f = 1 - ( double ) ( mMousePos.y() + yOffset ) / ( double ) ( mClickPoint.y() + yOffset );
726 f = std::max( 0.0, std::min( 1.0, f ) );
727 f = ( std::expm1( 2 * f ) ) / ( std::expm1( 2 ) );
728 newDist = newDist + 2 * newDist * f;
731 const double zoomFactor = newDist / oldDist;
732 zoomCameraAroundPivot( mCameraBefore->position(), mCameraBefore->viewCenter().distanceToPoint( mCameraBefore->position() ), zoomFactor, mDragPoint );
735 mMousePos = QPoint( mouse->x(), mouse->y() );
738void QgsCameraController::onPositionChangedGlobeTerrainNavigation( Qt3DInput::QMouseEvent *mouse )
740 const bool hasShift = ( mouse->modifiers() & Qt3DInput::QMouseEvent::Modifiers::ShiftModifier );
741 const bool hasCtrl = ( mouse->modifiers() & Qt3DInput::QMouseEvent::Modifiers::ControlModifier );
742 const bool hasLeftButton = ( mouse->buttons() & Qt::LeftButton );
743 const bool hasMiddleButton = ( mouse->buttons() & Qt::MiddleButton );
745 if ( ( hasLeftButton && hasShift && !hasCtrl ) || ( hasMiddleButton && !hasShift && !hasCtrl ) )
747 setMouseParameters( MouseOperation::RotationCenter, mMousePos );
749 const float scale =
static_cast<float>( std::max( mScene->engine()->size().width(), mScene->engine()->size().height() ) );
750 const float pitchDiff = 180.0f *
static_cast<float>( mouse->y() - mClickPoint.y() ) / scale;
751 const float yawDiff = -180.0f *
static_cast<float>( mouse->x() - mClickPoint.x() ) / scale;
753 mCameraPose.setPitchAngle( mRotationPitch + pitchDiff );
754 mCameraPose.setHeadingAngle( mRotationYaw + yawDiff );
755 updateCameraFromPose();
759 if ( !( mouse->buttons() & Qt::LeftButton ) )
763 setMouseParameters( MouseOperation::Translation, mMousePos );
765 if ( !mDepthBufferIsReady )
768 if ( !mDragPointCalculated )
771 QVector3D worldPosition;
772 if ( !screenPointToWorldPos( mClickPoint, depth, worldPosition ) )
776 mDragPoint = worldPosition;
777 mDragPointCalculated =
true;
782 const QgsVector3D startPosMap = QgsVector3D( mDragPoint ) + mOrigin;
783 const double sphereRadiusMap = startPosMap.
length();
786 const QgsVector3D rayOriginMap = QgsVector3D( ray.
origin() ) + mOrigin;
790 const double quadC =
QgsVector3D::dotProduct( rayOriginMap, rayOriginMap ) - sphereRadiusMap * sphereRadiusMap;
791 const double disc = quadB * quadB - 4 * quadA * quadC;
796 const double rayDistMap = ( -quadB - sqrt( disc ) ) / ( 2 * quadA );
797 if ( rayDistMap < 0 )
799 QgsDebugError( u
"Sphere intersection result negative, canceling move"_s );
802 const QgsVector3D newPosMap = rayOriginMap + QgsVector3D( ray.
direction() ) * rayDistMap;
807 QgsVector3D oldLatLon, newLatLon;
810 oldLatLon = mGlobeCrsToLatLon.transform( startPosMap );
811 newLatLon = mGlobeCrsToLatLon.transform( newPosMap );
813 catch (
const QgsCsException & )
815 QgsDebugError( u
"onPositionChangedGlobeTerrainNavigation: transform failed!"_s );
819 const double latDiff = oldLatLon.
y() - newLatLon.
y();
820 const double lonDiff = oldLatLon.
x() - newLatLon.
x();
822 const QgsVector3D newVC = moveGeocentricPoint( mMousePressViewCenter, latDiff, lonDiff );
823 const QgsVector3D newVCWorld = newVC - mOrigin;
825 mCameraPose.setCenterPoint( newVCWorld );
826 updateCameraFromPose();
833 float dist = mCameraPose.distanceFromCenterPoint();
834 dist -= dist * factor * 0.01f;
835 mCameraPose.setDistanceFromCenterPoint( dist );
836 updateCameraFromPose();
839void QgsCameraController::handleTerrainNavigationWheelZoom()
841 if ( !mDepthBufferIsReady )
844 if ( !mZoomPointCalculated )
847 QVector3D worldPosition;
848 if ( screenPointToWorldPos( mMousePos, depth, worldPosition ) )
850 mZoomPoint = worldPosition;
851 mZoomPointCalculated =
true;
855 double oldDist = ( mZoomPoint - mCameraBefore->position() ).length();
857 double newDist = std::pow( 0.8, mCumulatedWheelY ) * oldDist;
859 newDist = std::max( newDist, 2.0 );
860 double zoomFactor = newDist / oldDist;
862 zoomFactor = std::clamp( zoomFactor, 0.01, 100.0 );
864 zoomCameraAroundPivot( mCameraBefore->position(), mCameraBefore->viewCenter().distanceToPoint( mCameraBefore->position() ), zoomFactor, mZoomPoint );
866 mCumulatedWheelY = 0;
867 setMouseParameters( MouseOperation::None );
870void QgsCameraController::onWheel( Qt3DInput::QWheelEvent *wheel )
872 if ( !mInputHandlersEnabled )
875 switch ( mCameraNavigationMode )
879 const float scaling = ( ( wheel->modifiers() & Qt3DInput::QWheelEvent::Modifiers::ControlModifier ) != 0 ? 0.1f : 1.0f ) / 1000.f;
880 setCameraMovementSpeed( mCameraMovementSpeed + mCameraMovementSpeed * scaling * wheel->angleDelta().y() );
888 const double scaling = ( 1.0 / 120.0 ) * ( ( wheel->modifiers() & Qt3DInput::QWheelEvent::Modifiers::ControlModifier ) != 0 ? 0.1 : 1.0 );
892 mCumulatedWheelY += scaling * wheel->angleDelta().y();
894 if ( mCurrentOperation != MouseOperation::ZoomWheel )
896 setMouseParameters( MouseOperation::ZoomWheel );
901 handleTerrainNavigationWheelZoom();
908 float wheelAmount =
static_cast<float>( wheel->angleDelta().y() );
909 float factor = abs( wheelAmount ) / 1000.f;
910 float mulFactor = wheelAmount > 0 ? ( 1 - factor ) : ( 1 + factor );
911 mCameraPose.setDistanceFromCenterPoint( mCameraPose.distanceFromCenterPoint() * mulFactor );
912 updateCameraFromPose();
918void QgsCameraController::onMousePressed( Qt3DInput::QMouseEvent *mouse )
920 if ( !mInputHandlersEnabled )
923 mKeyboardHandler->setFocus(
true );
925 if ( mouse->button() == Qt3DInput::QMouseEvent::MiddleButton
926 || ( ( mouse->modifiers() & Qt3DInput::QMouseEvent::Modifiers::ShiftModifier ) != 0 && mouse->button() == Qt3DInput::QMouseEvent::LeftButton )
927 || ( ( mouse->modifiers() & Qt3DInput::QMouseEvent::Modifiers::ControlModifier ) != 0 && mouse->button() == Qt3DInput::QMouseEvent::LeftButton ) )
929 mMousePos = QPoint( mouse->x(), mouse->y() );
931 if ( mCaptureFpsMouseMovements )
932 mIgnoreNextMouseMove =
true;
934 const MouseOperation operation {
935 ( mouse->modifiers() & Qt3DInput::QMouseEvent::Modifiers::ControlModifier ) != 0 && mouse->button() == Qt3DInput::QMouseEvent::LeftButton ? MouseOperation::RotationCamera
936 : MouseOperation::RotationCenter
938 setMouseParameters( operation, mMousePos );
941 else if ( mouse->button() == Qt3DInput::QMouseEvent::LeftButton || mouse->button() == Qt3DInput::QMouseEvent::RightButton )
943 mMousePos = QPoint( mouse->x(), mouse->y() );
945 if ( mCaptureFpsMouseMovements )
946 mIgnoreNextMouseMove =
true;
948 const MouseOperation operation = ( mouse->button() == Qt3DInput::QMouseEvent::LeftButton ) ? MouseOperation::Translation : MouseOperation::Zoom;
949 setMouseParameters( operation, mMousePos );
953void QgsCameraController::onMouseReleased( Qt3DInput::QMouseEvent *mouse )
956 if ( !mInputHandlersEnabled )
960 setMouseParameters( MouseOperation::None );
963bool QgsCameraController::onKeyPressedTerrainNavigation( QKeyEvent *event )
965 const bool hasShift = (
event->modifiers() & Qt::ShiftModifier );
966 const bool hasCtrl = (
event->modifiers() & Qt::ControlModifier );
968 int tx = 0, ty = 0, tElev = 0;
969 switch ( event->key() )
985 case Qt::Key_PageDown:
997 if ( !hasShift && !hasCtrl )
1001 else if ( hasShift && !hasCtrl )
1007 else if ( hasCtrl && !hasShift )
1010 const float diffPitch = ty;
1011 const float diffYaw = -tx;
1019 QgsVector3D center = mCameraPose.centerPoint();
1020 center.
set( center.
x(), center.
y(), center.
z() + tElev * 10 );
1021 mCameraPose.setCenterPoint( center );
1028bool QgsCameraController::onKeyPressedGlobeTerrainNavigation( QKeyEvent *event )
1032 constexpr float MOVE_FACTOR = 0.000001f;
1033 constexpr float ZOOM_FACTOR = 0.9f;
1035 const bool hasShift = (
event->modifiers() & Qt::ShiftModifier );
1037 switch ( event->key() )
1063 case Qt::Key_PageDown:
1066 case Qt::Key_PageUp:
1075static const QSet<int> walkNavigationSavedKeys = {
1090bool QgsCameraController::onKeyPressedFlyNavigation( QKeyEvent *event )
1092 switch ( event->key() )
1094 case Qt::Key_QuoteLeft:
1097 mCaptureFpsMouseMovements = !mCaptureFpsMouseMovements;
1098 mIgnoreNextMouseMove =
true;
1099 if ( mCaptureFpsMouseMovements )
1101 qApp->setOverrideCursor( QCursor( Qt::BlankCursor ) );
1105 qApp->restoreOverrideCursor();
1111 case Qt::Key_Escape:
1114 if ( mCaptureFpsMouseMovements )
1116 mCaptureFpsMouseMovements =
false;
1117 mIgnoreNextMouseMove =
true;
1118 qApp->restoreOverrideCursor();
1128 if ( walkNavigationSavedKeys.contains( event->key() ) )
1130 if ( !event->isAutoRepeat() )
1132 mDepressedKeys.insert( event->key() );
1142 const QVector3D cameraUp = mCamera->upVector().normalized();
1143 const QVector3D cameraFront = ( QVector3D( mCameraPose.centerPoint().x(), mCameraPose.centerPoint().y(), mCameraPose.centerPoint().z() ) - mCamera->position() ).normalized();
1144 const QVector3D cameraLeft = QVector3D::crossProduct( cameraUp, cameraFront );
1146 QVector3D cameraPosDiff( 0.0f, 0.0f, 0.0f );
1150 cameraPosDiff +=
static_cast<float>( tx ) * cameraFront;
1154 cameraPosDiff +=
static_cast<float>( ty ) * cameraLeft;
1158 cameraPosDiff +=
static_cast<float>( tz ) * QVector3D( 0.0f, 0.0f, 1.0f );
1164void QgsCameraController::applyFlyModeKeyMovements()
1170 const bool shiftPressed = mDepressedKeys.contains( Qt::Key_Shift );
1171 const bool ctrlPressed = mDepressedKeys.contains( Qt::Key_Control );
1173 const double movementSpeed = mCameraMovementSpeed * ( shiftPressed ? 2 : 1 ) * ( ctrlPressed ? 0.1 : 1 );
1175 bool changed =
false;
1179 if ( mDepressedKeys.contains( Qt::Key_Left ) || mDepressedKeys.contains( Qt::Key_A ) )
1185 if ( mDepressedKeys.contains( Qt::Key_Right ) || mDepressedKeys.contains( Qt::Key_D ) )
1191 if ( mDepressedKeys.contains( Qt::Key_Up ) || mDepressedKeys.contains( Qt::Key_W ) )
1197 if ( mDepressedKeys.contains( Qt::Key_Down ) || mDepressedKeys.contains( Qt::Key_S ) )
1205 static constexpr double ELEVATION_MOVEMENT_SCALE = 0.5;
1206 if ( mDepressedKeys.contains( Qt::Key_PageUp ) || mDepressedKeys.contains( Qt::Key_E ) )
1209 z += ELEVATION_MOVEMENT_SCALE * movementSpeed;
1212 if ( mDepressedKeys.contains( Qt::Key_PageDown ) || mDepressedKeys.contains( Qt::Key_Q ) )
1215 z -= ELEVATION_MOVEMENT_SCALE * movementSpeed;
1222void QgsCameraController::onPositionChangedFlyNavigation( Qt3DInput::QMouseEvent *mouse )
1224 const bool hasMiddleButton = ( mouse->buttons() & Qt::MiddleButton );
1225 const bool hasRightButton = ( mouse->buttons() & Qt::RightButton );
1227 const double dx = mCaptureFpsMouseMovements ? QCursor::pos().x() - mMousePos.x() : mouse->x() - mMousePos.x();
1228 const double dy = mCaptureFpsMouseMovements ? QCursor::pos().y() - mMousePos.y() : mouse->y() - mMousePos.y();
1229 mMousePos = mCaptureFpsMouseMovements ? QCursor::pos() : QPoint( mouse->x(), mouse->y() );
1231 if ( mIgnoreNextMouseMove )
1233 mIgnoreNextMouseMove =
false;
1237 if ( hasMiddleButton )
1240 const QVector3D cameraUp = mCamera->upVector().normalized();
1241 const QVector3D cameraFront = ( QVector3D( mCameraPose.centerPoint().x(), mCameraPose.centerPoint().y(), mCameraPose.centerPoint().z() ) - mCamera->position() ).normalized();
1242 const QVector3D cameraLeft = QVector3D::crossProduct( cameraUp, cameraFront );
1243 const QVector3D cameraPosDiff = -dx * cameraLeft - dy * cameraUp;
1244 moveCenterPoint(
static_cast<float>( mCameraMovementSpeed ) * cameraPosDiff / 10.0 );
1246 else if ( hasRightButton )
1249 const QVector3D cameraFront = ( QVector3D( mCameraPose.centerPoint().x(), mCameraPose.centerPoint().y(), mCameraPose.centerPoint().z() ) - mCamera->position() ).normalized();
1250 const QVector3D cameraPosDiff = dy * cameraFront;
1251 moveCenterPoint(
static_cast<float>( mCameraMovementSpeed ) * cameraPosDiff / 5.0 );
1255 if ( mCaptureFpsMouseMovements )
1257 float diffPitch = -0.2f * dy;
1258 switch ( mVerticalAxisInversion )
1269 const float diffYaw = -0.2f * dx;
1272 else if ( mouse->buttons() & Qt::LeftButton )
1274 float diffPitch = -0.2f * dy;
1275 switch ( mVerticalAxisInversion )
1285 const float diffYaw = -0.2f * dx;
1290 if ( mCaptureFpsMouseMovements )
1292 mIgnoreNextMouseMove =
true;
1295 emit
setCursorPosition( QPoint( mScene->engine()->size().width() / 2, mScene->engine()->size().height() / 2 ) );
1302 float pitch = mCameraPose.pitchAngle();
1303 pitch -= deltaPitch;
1304 mCameraPose.setPitchAngle(
pitch );
1305 updateCameraFromPose();
1311 float yaw = mCameraPose.headingAngle();
1313 mCameraPose.setHeadingAngle(
yaw );
1314 updateCameraFromPose();
1319 mCameraPose.setHeadingAngle( angle );
1320 updateCameraFromPose();
1325 const float yaw = mCameraPose.headingAngle();
1326 const float dist = mCameraPose.distanceFromCenterPoint();
1327 const float x = tx * dist * 0.02f;
1328 const float y = -ty * dist * 0.02f;
1331 const float t = sqrt( x * x + y * y );
1332 const float a = atan2( y, x ) -
yaw * M_PI / 180;
1333 const float dx = cos( a ) * t;
1334 const float dy = sin( a ) * t;
1337 center.
set( center.
x() + dx, center.
y() - dy, center.
z() );
1338 mCameraPose.setCenterPoint( center );
1339 updateCameraFromPose();
1344 if ( !mInputHandlersEnabled )
1347 if ( event->type() == QKeyEvent::Type::KeyRelease )
1349 if ( !event->isAutoRepeat() && mDepressedKeys.contains( event->key() ) )
1351 mDepressedKeys.remove( event->key() );
1355 else if ( event->type() == QEvent::ShortcutOverride )
1357 if ( event->modifiers() & Qt::ControlModifier )
1359 switch ( event->key() )
1361 case Qt::Key_QuoteLeft:
1364 switch ( mCameraNavigationMode )
1412 switch ( mCameraNavigationMode )
1415 return onKeyPressedFlyNavigation( event );
1418 return onKeyPressedTerrainNavigation( event );
1421 return onKeyPressedGlobeTerrainNavigation( event );
1429 mDepthBufferImage = depthImage;
1430 mDepthBufferIsReady =
true;
1431 mDepthBufferNonVoidAverage = -1;
1439 if ( mCurrentOperation == MouseOperation::ZoomWheel )
1441 handleTerrainNavigationWheelZoom();
1445bool QgsCameraController::isATranslationRotationSequence( MouseOperation newOperation )
const
1447 return std::find( mTranslateOrRotate.begin(), mTranslateOrRotate.end(), newOperation ) != std::end( mTranslateOrRotate )
1448 && std::find( mTranslateOrRotate.begin(), mTranslateOrRotate.end(), mCurrentOperation ) != std::end( mTranslateOrRotate );
1451void QgsCameraController::setMouseParameters(
const MouseOperation &newOperation,
const QPoint &clickPoint )
1453 if ( newOperation == mCurrentOperation )
1458 if ( newOperation == MouseOperation::None )
1460 mClickPoint = QPoint();
1468 else if ( mClickPoint.isNull() || isATranslationRotationSequence( newOperation ) )
1470 mClickPoint = clickPoint;
1471 mRotationPitch = mCameraPose.pitchAngle();
1472 mRotationYaw = mCameraPose.headingAngle();
1474 mCurrentOperation = newOperation;
1475 mDepthBufferIsReady =
false;
1476 mRotationCenterCalculated =
false;
1477 mDragPointCalculated =
false;
1478 mZoomPointCalculated =
false;
1480 if ( mCurrentOperation != MouseOperation::None && mCurrentOperation != MouseOperation::RotationCamera )
1482 mMousePressViewCenter = mCameraPose.centerPoint() + mOrigin;
1492 mCameraPose.setCenterPoint( mCameraPose.centerPoint() - diff );
1495 mCameraBefore->setPosition( (
QgsVector3D( mCameraBefore->position() ) - diff ).toVector3D() );
1496 mCameraBefore->setViewCenter( (
QgsVector3D( mCameraBefore->viewCenter() ) - diff ).toVector3D() );
1497 mDragPoint = (
QgsVector3D( mDragPoint ) - diff ).toVector3D();
1498 mRotationCenter = (
QgsVector3D( mRotationCenter ) - diff ).toVector3D();
1502 updateCameraFromPose();
1505void QgsCameraController::rotateToRespectingTerrain(
float pitch,
float yaw )
1508 double elevation = 0.0;
1512 QVector3D camPos = mCamera->position();
1517 const QList<QgsRayCastHit> results = mScene->
terrainEntity()->rayIntersection( ray, context );
1519 if ( !results.isEmpty() )
1521 elevation = results.constFirst().mapCoordinates().z() - mOrigin.z();
1522 QgsDebugMsgLevel( QString(
"Computed elevation from terrain: %1" ).arg( elevation ), 2 );
1529 pos.
set( pos.
x(), pos.
y(), elevation + mScene->terrainEntity()->terrainElevationOffset() );
1536 if ( !crossSection.
isValid() )
1541 const double width = crossSection.
halfWidth();
1543 const QgsVector3D startVec { startPoint.
x(), startPoint.
y(), 0 };
1546 QgsVector linePerpVec( ( endPoint - startPoint ).x(), ( endPoint - startPoint ).y() );
1549 const QgsVector3D linePerpVec3D( linePerpVec.
x(), linePerpVec.
y(), 0 );
1550 const QgsVector3D frontStartPoint( startVec + linePerpVec3D * width );
1551 const QgsVector3D frontEndPoint( endVec + linePerpVec3D * width );
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.
QgsTerrainEntity * terrainEntity() SIP_SKIP
Returns terrain entity (may be nullptr if using globe scene, terrain rendering is disabled or when te...
Qgs3DMapSettings * mapSettings() const
Returns the 3D map settings.
QgsAbstract3DEngine * engine() const SIP_SKIP
Returns the abstract 3D engine.
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)