32#include <QDomDocument>
34#include <QStringLiteral>
36#include <Qt3DRender/QCamera>
38#include "moc_qgscameracontroller.cpp"
43 , mCamera( scene->engine()->
camera() )
45 , mMouseHandler( new
Qt3DInput::QMouseHandler )
46 , mKeyboardHandler( new
Qt3DInput::QKeyboardHandler )
47 , mOrigin( scene->mapSettings()->
origin() )
49 mMouseHandler->setSourceDevice(
new Qt3DInput::QMouseDevice() );
50 connect( mMouseHandler, &Qt3DInput::QMouseHandler::positionChanged,
this, &QgsCameraController::onPositionChanged );
51 connect( mMouseHandler, &Qt3DInput::QMouseHandler::wheel,
this, &QgsCameraController::onWheel );
52 connect( mMouseHandler, &Qt3DInput::QMouseHandler::pressed,
this, &QgsCameraController::onMousePressed );
53 connect( mMouseHandler, &Qt3DInput::QMouseHandler::released,
this, &QgsCameraController::onMouseReleased );
54 addComponent( mMouseHandler );
57 connect(
this, &Qt3DCore::QEntity::enabledChanged, mMouseHandler, &Qt3DInput::QMouseHandler::setEnabled );
58 connect(
this, &Qt3DCore::QEntity::enabledChanged, mKeyboardHandler, &Qt3DInput::QKeyboardHandler::setEnabled );
60 mFpsNavTimer =
new QTimer(
this );
61 mFpsNavTimer->setInterval( 10 );
62 connect( mFpsNavTimer, &QTimer::timeout,
this, &QgsCameraController::applyFlyModeKeyMovements );
63 mFpsNavTimer->start();
68 mGlobeCrsToLatLon =
QgsCoordinateTransform( mScene->mapSettings()->crs(), mScene->mapSettings()->crs().toGeographicCrs(), mScene->mapSettings()->transformContext() );
74QWindow *QgsCameraController::window()
const
77 return windowEngine ? windowEngine->
window() :
nullptr;
82 if ( navigationMode == mCameraNavigationMode )
85 mCameraNavigationMode = navigationMode;
86 mIgnoreNextMouseMove =
true;
92 if ( movementSpeed == mCameraMovementSpeed )
97 mCameraMovementSpeed = std::clamp( movementSpeed, 0.05, 150.0 );
103 mVerticalAxisInversion = inversion;
108 float newPitch = mCameraPose.pitchAngle() + diffPitch;
109 float newHeading = mCameraPose.headingAngle() + diffHeading;
111 newPitch = std::clamp( newPitch, 0.f, 180.f );
113 switch ( mScene->mapSettings()->sceneMode() )
124 viewCenterLatLon = mGlobeCrsToLatLon.transform( mCameraPose.centerPoint() + mOrigin );
128 QgsDebugError( QStringLiteral(
"rotateCamera: ECEF -> lat,lon transform failed!" ) );
131 QQuaternion qLatLon = QQuaternion::fromAxisAndAngle( QVector3D( 0, 0, 1 ),
static_cast<float>( viewCenterLatLon.
x() ) )
132 * QQuaternion::fromAxisAndAngle( QVector3D( 0, -1, 0 ),
static_cast<float>( viewCenterLatLon.
y() ) );
133 QQuaternion qPitchHeading = QQuaternion::fromAxisAndAngle( QVector3D( 1, 0, 0 ), newHeading )
134 * QQuaternion::fromAxisAndAngle( QVector3D( 0, 1, 0 ), newPitch );
135 QVector3D newCameraToCenter = ( qLatLon * qPitchHeading * QVector3D( -1, 0, 0 ) ) * mCameraPose.distanceFromCenterPoint();
137 mCameraPose.setCenterPoint( mCamera->position() + newCameraToCenter );
138 mCameraPose.setPitchAngle( newPitch );
139 mCameraPose.setHeadingAngle( newHeading );
140 updateCameraFromPose();
147 QVector3D newCameraToCenter = q * QVector3D( 0, 0, -mCameraPose.distanceFromCenterPoint() );
148 mCameraPose.setCenterPoint( mCamera->position() + newCameraToCenter );
149 mCameraPose.setPitchAngle( newPitch );
150 mCameraPose.setHeadingAngle( newHeading );
151 updateCameraFromPose();
159 const float oldPitch = mCameraPose.pitchAngle();
160 const float oldHeading = mCameraPose.headingAngle();
162 newPitch = std::clamp( newPitch, 0.f, 180.f );
168 const QQuaternion q = qNew * qOld.conjugated();
170 const QVector3D newViewCenter = q * ( mCamera->viewCenter() - pivotPoint ) + pivotPoint;
172 mCameraPose.setCenterPoint( newViewCenter );
173 mCameraPose.setPitchAngle( newPitch );
174 mCameraPose.setHeadingAngle( newHeading );
175 updateCameraFromPose();
181 QVector3D newCamPosition = pivotPoint + ( oldCameraPosition - pivotPoint ) * zoomFactor;
182 double newDistance = mCameraPose.distanceFromCenterPoint() * zoomFactor;
186 QVector3D cameraToCenter = q * QVector3D( 0, 0, -newDistance );
187 QVector3D newViewCenter = newCamPosition + cameraToCenter;
189 mCameraPose.setDistanceFromCenterPoint( newDistance );
190 mCameraPose.setCenterPoint( newViewCenter );
191 updateCameraFromPose();
197 QVector3D newCamPosition = pivotPoint + ( oldCameraPosition - pivotPoint ) * zoomFactor;
198 const double newDistance = oldDistanceFromCenterPoint * zoomFactor;
202 QVector3D cameraToCenter = q * QVector3D( 0, 0, -newDistance );
203 QVector3D newViewCenter = newCamPosition + cameraToCenter;
205 mCameraPose.setDistanceFromCenterPoint( newDistance );
206 mCameraPose.setCenterPoint( newViewCenter );
207 updateCameraFromPose();
214 if ( mCameraChanged )
217 mCameraChanged =
false;
223 QgsPointXY extentCenter = mScene->mapSettings()->extent().center();
232 QgsDebugError( QStringLiteral(
"setViewFromTop() should not be used with globe!" ) );
237 QgsTerrainEntity *terrain = mScene->terrainEntity();
238 const float terrainElevationOffset = terrain ? terrain->terrainElevationOffset() : 0.0f;
244 mCamera->setNearPlane(
distance / 2 );
245 mCamera->setFarPlane(
distance * 2 );
252 return mCameraPose.centerPoint();
277 if ( camPose == mCameraPose && !force )
280 mCameraPose = camPose;
281 updateCameraFromPose();
286 QDomElement elemCamera = doc.createElement( QStringLiteral(
"camera" ) );
289 QgsVector3D centerPoint = mCameraPose.centerPoint() + mOrigin;
290 elemCamera.setAttribute( QStringLiteral(
"xMap" ), centerPoint.
x() );
291 elemCamera.setAttribute( QStringLiteral(
"yMap" ), centerPoint.
y() );
292 elemCamera.setAttribute( QStringLiteral(
"zMap" ), centerPoint.
z() );
293 elemCamera.setAttribute( QStringLiteral(
"dist" ), mCameraPose.distanceFromCenterPoint() );
294 elemCamera.setAttribute( QStringLiteral(
"pitch" ), mCameraPose.pitchAngle() );
295 elemCamera.setAttribute( QStringLiteral(
"yaw" ), mCameraPose.headingAngle() );
301 const float dist = elem.attribute( QStringLiteral(
"dist" ) ).toFloat();
302 const float pitch = elem.attribute( QStringLiteral(
"pitch" ) ).toFloat();
303 const float yaw = elem.attribute( QStringLiteral(
"yaw" ) ).toFloat();
306 if ( elem.hasAttribute(
"xMap" ) )
309 const double x = elem.attribute( QStringLiteral(
"xMap" ) ).toDouble();
310 const double y = elem.attribute( QStringLiteral(
"yMap" ) ).toDouble();
311 const double z = elem.attribute( QStringLiteral(
"zMap" ) ).toDouble();
317 const double x = elem.attribute( QStringLiteral(
"x" ) ).toDouble();
318 const double y = elem.attribute( QStringLiteral(
"y" ) ).toDouble();
319 const double elev = elem.attribute( QStringLiteral(
"elev" ) ).toDouble();
320 centerPoint =
QgsVector3D( x, elev, y ) - savedOrigin + mOrigin;
325double QgsCameraController::sampleDepthBuffer(
int px,
int py )
327 if ( !mDepthBufferIsReady )
329 QgsDebugError( QStringLiteral(
"Asked to sample depth buffer, but depth buffer not ready!" ) );
334 if ( QWindow *win = window() )
338 px =
static_cast<int>( px * win->devicePixelRatio() );
339 py =
static_cast<int>( py * win->devicePixelRatio() );
343 for (
int x = px - 3; x <= px + 3; ++x )
345 for (
int y = py - 3; y <= py + 3; ++y )
347 if ( mDepthBufferImage.valid( x, y ) )
356double QgsCameraController::depthBufferNonVoidAverage()
359 if ( mDepthBufferNonVoidAverage != -1 )
360 return mDepthBufferNonVoidAverage;
364 int samplesCount = 0;
366 Q_ASSERT( mDepthBufferImage.format() == QImage::Format_RGB32 );
367 for (
int y = 0; y < mDepthBufferImage.height(); ++y )
369 const QRgb *line =
reinterpret_cast<const QRgb *
>( mDepthBufferImage.constScanLine( y ) );
370 for (
int x = 0; x < mDepthBufferImage.width(); ++x )
382 if ( samplesCount == 0 )
385 depth /= samplesCount;
387 mDepthBufferNonVoidAverage = depth;
392QgsVector3D QgsCameraController::moveGeocentricPoint(
const QgsVector3D &point,
double latDiff,
double lonDiff )
396 QgsVector3D pointLatLon = mGlobeCrsToLatLon.transform( point );
397 pointLatLon.
setX( pointLatLon.
x() + lonDiff );
398 pointLatLon.
setY( std::clamp( pointLatLon.
y() + latDiff, -90., 90. ) );
402 catch (
const QgsCsException & )
404 QgsDebugError( QStringLiteral(
"moveGeocentricPoint: transform failed!" ) );
411 const QgsVector3D viewCenter = mCameraPose.centerPoint() + mOrigin;
412 const QgsVector3D newViewCenter = moveGeocentricPoint( viewCenter, latDiff, lonDiff );
413 mCameraPose.setCenterPoint( newViewCenter - mOrigin );
414 updateCameraFromPose();
419 mCameraPose.setDistanceFromCenterPoint( mCameraPose.distanceFromCenterPoint() * factor );
420 updateCameraFromPose();
425 mCameraPose.setPitchAngle( std::clamp( mCameraPose.pitchAngle() + angleDiff, 0.f, 90.f ) );
426 updateCameraFromPose();
431 mCameraPose.setHeadingAngle( mCameraPose.headingAngle() + angleDiff );
432 updateCameraFromPose();
444 QgsDebugError( QStringLiteral(
"resetGlobe: transform failed!" ) );
454void QgsCameraController::updateCameraFromPose()
465 viewCenterLatLon = mGlobeCrsToLatLon.
transform( viewCenter );
467 catch (
const QgsCsException & )
469 QgsDebugError( QStringLiteral(
"updateCameraFromPose: transform failed!" ) );
473 mCameraPose.updateCameraGlobe( mCamera, viewCenterLatLon.
y(), viewCenterLatLon.
x() );
477 mCameraPose.updateCamera( mCamera );
479 mCameraChanged =
true;
483void QgsCameraController::moveCameraPositionBy(
const QVector3D &posDiff )
485 mCameraPose.setCenterPoint( mCameraPose.centerPoint() + posDiff );
486 updateCameraFromPose();
489void QgsCameraController::onPositionChanged( Qt3DInput::QMouseEvent *mouse )
491 if ( !mInputHandlersEnabled )
494 QgsEventTracing::ScopedEvent traceEvent( QStringLiteral(
"3D" ), QStringLiteral(
"QgsCameraController::onPositionChanged" ) );
496 switch ( mCameraNavigationMode )
499 onPositionChangedTerrainNavigation( mouse );
503 onPositionChangedFlyNavigation( mouse );
507 onPositionChangedGlobeTerrainNavigation( mouse );
512bool QgsCameraController::screenPointToWorldPos( QPoint position,
double &depth, QVector3D &worldPosition )
514 depth = sampleDepthBuffer( position.x(), position.y() );
520 depth = depthBufferNonVoidAverage();
524 if ( !std::isfinite( worldPosition.x() ) || !std::isfinite( worldPosition.y() ) || !std::isfinite( worldPosition.z() ) )
526 QgsDebugMsgLevel( QStringLiteral(
"screenPointToWorldPos: position is NaN or Inf. This should not happen." ), 2 );
533void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseEvent *mouse )
535 if ( mIgnoreNextMouseMove )
537 mIgnoreNextMouseMove =
false;
538 mMousePos = QPoint( mouse->x(), mouse->y() );
542 const int dx = mouse->x() - mMousePos.x();
543 const int dy = mouse->y() - mMousePos.y();
545 const bool hasShift = ( mouse->modifiers() & Qt3DInput::QMouseEvent::Modifiers::ShiftModifier );
546 const bool hasCtrl = ( mouse->modifiers() & Qt3DInput::QMouseEvent::Modifiers::ControlModifier );
547 const bool hasLeftButton = ( mouse->buttons() & Qt::LeftButton );
548 const bool hasMiddleButton = ( mouse->buttons() & Qt::MiddleButton );
549 const bool hasRightButton = ( mouse->buttons() & Qt::RightButton );
551 if ( ( hasLeftButton && hasShift && !hasCtrl ) || ( hasMiddleButton && !hasShift && !hasCtrl ) )
554 setMouseParameters( MouseOperation::RotationCenter, mMousePos );
556 float scale =
static_cast<float>( std::max( mScene->engine()->size().width(), mScene->engine()->size().height() ) );
557 float pitchDiff = 180.0f *
static_cast<float>( mouse->y() - mClickPoint.y() ) / scale;
558 float yawDiff = -180.0f *
static_cast<float>( mouse->x() - mClickPoint.x() ) / scale;
560 if ( !mDepthBufferIsReady )
563 if ( !mRotationCenterCalculated )
566 QVector3D worldPosition;
567 if ( screenPointToWorldPos( mClickPoint, depth, worldPosition ) )
569 mRotationCenter = worldPosition;
570 mRotationDistanceFromCenter = ( mRotationCenter - mCameraBefore->position() ).length();
572 mRotationCenterCalculated =
true;
578 else if ( hasLeftButton && hasCtrl && !hasShift )
580 setMouseParameters( MouseOperation::RotationCamera );
582 const float diffPitch = 0.2f * dy;
583 const float diffYaw = -0.2f * dx;
586 else if ( hasLeftButton && !hasShift && !hasCtrl )
589 setMouseParameters( MouseOperation::Translation, mMousePos );
591 if ( !mDepthBufferIsReady )
594 if ( !mDragPointCalculated )
597 QVector3D worldPosition;
598 if ( screenPointToWorldPos( mClickPoint, depth, worldPosition ) )
601 mDragPoint = worldPosition;
602 mDragPointCalculated =
true;
606 QVector3D cameraBeforeDragPos = mCameraBefore->position();
609 QVector3D cameraBeforeToMoveToPos = ( moveToPosition - mCameraBefore->position() ).normalized();
610 QVector3D cameraBeforeToDragPointPos = ( mDragPoint - mCameraBefore->position() ).normalized();
613 if ( cameraBeforeToMoveToPos.z() == 0 )
615 cameraBeforeToMoveToPos.setZ( 0.01 );
616 cameraBeforeToMoveToPos = cameraBeforeToMoveToPos.normalized();
619 if ( cameraBeforeToDragPointPos.z() == 0 )
621 cameraBeforeToDragPointPos.setZ( 0.01 );
622 cameraBeforeToDragPointPos = cameraBeforeToDragPointPos.normalized();
625 double d1 = ( mDragPoint.z() - cameraBeforeDragPos.z() ) / cameraBeforeToMoveToPos.z();
626 double d2 = ( mDragPoint.z() - cameraBeforeDragPos.z() ) / cameraBeforeToDragPointPos.z();
628 QVector3D from = cameraBeforeDragPos + d1 * cameraBeforeToMoveToPos;
629 QVector3D to = cameraBeforeDragPos + d2 * cameraBeforeToDragPointPos;
631 QVector3D shiftVector = to - from;
633 mCameraPose.setCenterPoint( mCameraBefore->viewCenter() + shiftVector );
634 updateCameraFromPose();
636 else if ( hasLeftButton && hasShift && hasCtrl )
639 QgsVector3D center = mCameraPose.centerPoint();
640 double tElev = mMousePos.y() - mouse->y();
641 center.
set( center.
x(), center.
y(), center.
z() + tElev * 0.5 );
642 mCameraPose.setCenterPoint( center );
643 updateCameraFromPose();
645 else if ( hasRightButton && !hasShift && !hasCtrl )
647 setMouseParameters( MouseOperation::Zoom, mMousePos );
648 if ( !mDepthBufferIsReady )
651 if ( !mDragPointCalculated )
654 QVector3D worldPosition;
655 if ( screenPointToWorldPos( mClickPoint, depth, worldPosition ) )
657 mDragPoint = worldPosition;
658 mDragPointCalculated =
true;
662 const double oldDist = ( QgsVector3D( mCameraBefore->position() ) - QgsVector3D( mDragPoint ) ).length();
663 double newDist = oldDist;
666 int screenHeight = mScene->engine()->size().height();
667 QWindow *win = window();
670 yOffset = win->mapToGlobal( QPoint( 0, 0 ) ).y();
671 screenHeight = win->screen()->virtualSize().height();
675 if ( mMousePos.y() > mClickPoint.y() )
677 double f = ( double ) ( mMousePos.y() - mClickPoint.y() ) / ( double ) ( screenHeight - mClickPoint.y() - yOffset );
678 f = std::max( 0.0, std::min( 1.0, f ) );
679 f = 1 - ( std::expm1( -2 * f ) ) / ( std::expm1( -2 ) );
680 newDist = newDist * f;
684 double f = 1 - ( double ) ( mMousePos.y() + yOffset ) / ( double ) ( mClickPoint.y() + yOffset );
685 f = std::max( 0.0, std::min( 1.0, f ) );
686 f = ( std::expm1( 2 * f ) ) / ( std::expm1( 2 ) );
687 newDist = newDist + 2 * newDist * f;
690 const double zoomFactor = newDist / oldDist;
691 zoomCameraAroundPivot( mCameraBefore->position(), mCameraBefore->viewCenter().distanceToPoint( mCameraBefore->position() ), zoomFactor, mDragPoint );
694 mMousePos = QPoint( mouse->x(), mouse->y() );
697void QgsCameraController::onPositionChangedGlobeTerrainNavigation( Qt3DInput::QMouseEvent *mouse )
699 const bool hasShift = ( mouse->modifiers() & Qt3DInput::QMouseEvent::Modifiers::ShiftModifier );
700 const bool hasCtrl = ( mouse->modifiers() & Qt3DInput::QMouseEvent::Modifiers::ControlModifier );
701 const bool hasLeftButton = ( mouse->buttons() & Qt::LeftButton );
702 const bool hasMiddleButton = ( mouse->buttons() & Qt::MiddleButton );
704 if ( ( hasLeftButton && hasShift && !hasCtrl ) || ( hasMiddleButton && !hasShift && !hasCtrl ) )
706 setMouseParameters( MouseOperation::RotationCenter, mMousePos );
708 const float scale =
static_cast<float>( std::max( mScene->engine()->size().width(), mScene->engine()->size().height() ) );
709 const float pitchDiff = 180.0f *
static_cast<float>( mouse->y() - mClickPoint.y() ) / scale;
710 const float yawDiff = -180.0f *
static_cast<float>( mouse->x() - mClickPoint.x() ) / scale;
712 mCameraPose.setPitchAngle( mRotationPitch + pitchDiff );
713 mCameraPose.setHeadingAngle( mRotationYaw + yawDiff );
714 updateCameraFromPose();
718 if ( !( mouse->buttons() & Qt::LeftButton ) )
722 setMouseParameters( MouseOperation::Translation, mMousePos );
724 if ( !mDepthBufferIsReady )
727 if ( !mDragPointCalculated )
730 QVector3D worldPosition;
731 if ( !screenPointToWorldPos( mClickPoint, depth, worldPosition ) )
735 mDragPoint = worldPosition;
736 mDragPointCalculated =
true;
741 const QgsVector3D startPosMap = QgsVector3D( mDragPoint ) + mOrigin;
742 const double sphereRadiusMap = startPosMap.
length();
745 const QgsVector3D rayOriginMap = QgsVector3D( ray.
origin() ) + mOrigin;
749 const double quadC =
QgsVector3D::dotProduct( rayOriginMap, rayOriginMap ) - sphereRadiusMap * sphereRadiusMap;
750 const double disc = quadB * quadB - 4 * quadA * quadC;
755 const double rayDistMap = ( -quadB - sqrt( disc ) ) / ( 2 * quadA );
756 if ( rayDistMap < 0 )
758 QgsDebugError( QStringLiteral(
"Sphere intersection result negative, canceling move" ) );
761 const QgsVector3D newPosMap = rayOriginMap + QgsVector3D( ray.
direction() ) * rayDistMap;
766 QgsVector3D oldLatLon, newLatLon;
769 oldLatLon = mGlobeCrsToLatLon.transform( startPosMap );
770 newLatLon = mGlobeCrsToLatLon.transform( newPosMap );
772 catch (
const QgsCsException & )
774 QgsDebugError( QStringLiteral(
"onPositionChangedGlobeTerrainNavigation: transform failed!" ) );
778 const double latDiff = oldLatLon.
y() - newLatLon.
y();
779 const double lonDiff = oldLatLon.
x() - newLatLon.
x();
781 const QgsVector3D newVC = moveGeocentricPoint( mMousePressViewCenter, latDiff, lonDiff );
782 const QgsVector3D newVCWorld = newVC - mOrigin;
784 mCameraPose.setCenterPoint( newVCWorld );
785 updateCameraFromPose();
792 float dist = mCameraPose.distanceFromCenterPoint();
793 dist -= dist * factor * 0.01f;
794 mCameraPose.setDistanceFromCenterPoint( dist );
795 updateCameraFromPose();
798void QgsCameraController::handleTerrainNavigationWheelZoom()
800 if ( !mDepthBufferIsReady )
803 if ( !mZoomPointCalculated )
806 QVector3D worldPosition;
807 if ( screenPointToWorldPos( mMousePos, depth, worldPosition ) )
809 mZoomPoint = worldPosition;
810 mZoomPointCalculated =
true;
814 double oldDist = ( mZoomPoint - mCameraBefore->position() ).length();
816 double newDist = std::pow( 0.8, mCumulatedWheelY ) * oldDist;
818 newDist = std::max( newDist, 2.0 );
819 double zoomFactor = newDist / oldDist;
821 zoomFactor = std::clamp( zoomFactor, 0.01, 100.0 );
823 zoomCameraAroundPivot( mCameraBefore->position(), mCameraBefore->viewCenter().distanceToPoint( mCameraBefore->position() ), zoomFactor, mZoomPoint );
825 mCumulatedWheelY = 0;
826 setMouseParameters( MouseOperation::None );
829void QgsCameraController::onWheel( Qt3DInput::QWheelEvent *wheel )
831 if ( !mInputHandlersEnabled )
834 switch ( mCameraNavigationMode )
838 const float scaling = ( ( wheel->modifiers() & Qt3DInput::QWheelEvent::Modifiers::ControlModifier ) != 0 ? 0.1f : 1.0f ) / 1000.f;
839 setCameraMovementSpeed( mCameraMovementSpeed + mCameraMovementSpeed * scaling * wheel->angleDelta().y() );
847 const double scaling = ( 1.0 / 120.0 ) * ( ( wheel->modifiers() & Qt3DInput::QWheelEvent::Modifiers::ControlModifier ) != 0 ? 0.1 : 1.0 );
851 mCumulatedWheelY += scaling * wheel->angleDelta().y();
853 if ( mCurrentOperation != MouseOperation::ZoomWheel )
855 setMouseParameters( MouseOperation::ZoomWheel );
860 handleTerrainNavigationWheelZoom();
867 float wheelAmount =
static_cast<float>( wheel->angleDelta().y() );
868 float factor = abs( wheelAmount ) / 1000.f;
869 float mulFactor = wheelAmount > 0 ? ( 1 - factor ) : ( 1 + factor );
870 mCameraPose.setDistanceFromCenterPoint( mCameraPose.distanceFromCenterPoint() * mulFactor );
871 updateCameraFromPose();
877void QgsCameraController::onMousePressed( Qt3DInput::QMouseEvent *mouse )
879 if ( !mInputHandlersEnabled )
882 mKeyboardHandler->setFocus(
true );
884 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 ) )
886 mMousePos = QPoint( mouse->x(), mouse->y() );
888 if ( mCaptureFpsMouseMovements )
889 mIgnoreNextMouseMove =
true;
891 const MouseOperation operation {
892 ( mouse->modifiers() & Qt3DInput::QMouseEvent::Modifiers::ControlModifier ) != 0 && mouse->button() == Qt3DInput::QMouseEvent::LeftButton ? MouseOperation::RotationCamera : MouseOperation::RotationCenter
894 setMouseParameters( operation, mMousePos );
897 else if ( mouse->button() == Qt3DInput::QMouseEvent::LeftButton || mouse->button() == Qt3DInput::QMouseEvent::RightButton )
899 mMousePos = QPoint( mouse->x(), mouse->y() );
901 if ( mCaptureFpsMouseMovements )
902 mIgnoreNextMouseMove =
true;
904 const MouseOperation operation = ( mouse->button() == Qt3DInput::QMouseEvent::LeftButton ) ? MouseOperation::Translation : MouseOperation::Zoom;
905 setMouseParameters( operation, mMousePos );
909void QgsCameraController::onMouseReleased( Qt3DInput::QMouseEvent *mouse )
912 if ( !mInputHandlersEnabled )
916 setMouseParameters( MouseOperation::None );
919bool QgsCameraController::onKeyPressedTerrainNavigation( QKeyEvent *event )
921 const bool hasShift = (
event->modifiers() & Qt::ShiftModifier );
922 const bool hasCtrl = (
event->modifiers() & Qt::ControlModifier );
924 int tx = 0, ty = 0, tElev = 0;
925 switch ( event->key() )
941 case Qt::Key_PageDown:
953 if ( !hasShift && !hasCtrl )
957 else if ( hasShift && !hasCtrl )
963 else if ( hasCtrl && !hasShift )
966 const float diffPitch = ty;
967 const float diffYaw = -tx;
975 QgsVector3D center = mCameraPose.centerPoint();
976 center.
set( center.
x(), center.
y(), center.
z() + tElev * 10 );
977 mCameraPose.setCenterPoint( center );
984bool QgsCameraController::onKeyPressedGlobeTerrainNavigation( QKeyEvent *event )
988 constexpr float MOVE_FACTOR = 0.000001f;
989 constexpr float ZOOM_FACTOR = 0.9f;
991 const bool hasShift = (
event->modifiers() & Qt::ShiftModifier );
993 switch ( event->key() )
1019 case Qt::Key_PageDown:
1022 case Qt::Key_PageUp:
1031static const QSet<int> walkNavigationSavedKeys = {
1046bool QgsCameraController::onKeyPressedFlyNavigation( QKeyEvent *event )
1048 switch ( event->key() )
1050 case Qt::Key_QuoteLeft:
1053 mCaptureFpsMouseMovements = !mCaptureFpsMouseMovements;
1054 mIgnoreNextMouseMove =
true;
1055 if ( mCaptureFpsMouseMovements )
1057 qApp->setOverrideCursor( QCursor( Qt::BlankCursor ) );
1061 qApp->restoreOverrideCursor();
1067 case Qt::Key_Escape:
1070 if ( mCaptureFpsMouseMovements )
1072 mCaptureFpsMouseMovements =
false;
1073 mIgnoreNextMouseMove =
true;
1074 qApp->restoreOverrideCursor();
1084 if ( walkNavigationSavedKeys.contains( event->key() ) )
1086 if ( !event->isAutoRepeat() )
1088 mDepressedKeys.insert( event->key() );
1098 const QVector3D cameraUp = mCamera->upVector().normalized();
1099 const QVector3D cameraFront = ( QVector3D( mCameraPose.centerPoint().x(), mCameraPose.centerPoint().y(), mCameraPose.centerPoint().z() ) - mCamera->position() ).normalized();
1100 const QVector3D cameraLeft = QVector3D::crossProduct( cameraUp, cameraFront );
1102 QVector3D cameraPosDiff( 0.0f, 0.0f, 0.0f );
1106 cameraPosDiff +=
static_cast<float>( tx ) * cameraFront;
1110 cameraPosDiff +=
static_cast<float>( ty ) * cameraLeft;
1114 cameraPosDiff +=
static_cast<float>( tz ) * QVector3D( 0.0f, 0.0f, 1.0f );
1117 moveCameraPositionBy( cameraPosDiff );
1120void QgsCameraController::applyFlyModeKeyMovements()
1126 const bool shiftPressed = mDepressedKeys.contains( Qt::Key_Shift );
1127 const bool ctrlPressed = mDepressedKeys.contains( Qt::Key_Control );
1129 const double movementSpeed = mCameraMovementSpeed * ( shiftPressed ? 2 : 1 ) * ( ctrlPressed ? 0.1 : 1 );
1131 bool changed =
false;
1135 if ( mDepressedKeys.contains( Qt::Key_Left ) || mDepressedKeys.contains( Qt::Key_A ) )
1141 if ( mDepressedKeys.contains( Qt::Key_Right ) || mDepressedKeys.contains( Qt::Key_D ) )
1147 if ( mDepressedKeys.contains( Qt::Key_Up ) || mDepressedKeys.contains( Qt::Key_W ) )
1153 if ( mDepressedKeys.contains( Qt::Key_Down ) || mDepressedKeys.contains( Qt::Key_S ) )
1161 static constexpr double ELEVATION_MOVEMENT_SCALE = 0.5;
1162 if ( mDepressedKeys.contains( Qt::Key_PageUp ) || mDepressedKeys.contains( Qt::Key_E ) )
1165 z += ELEVATION_MOVEMENT_SCALE * movementSpeed;
1168 if ( mDepressedKeys.contains( Qt::Key_PageDown ) || mDepressedKeys.contains( Qt::Key_Q ) )
1171 z -= ELEVATION_MOVEMENT_SCALE * movementSpeed;
1178void QgsCameraController::onPositionChangedFlyNavigation( Qt3DInput::QMouseEvent *mouse )
1180 const bool hasMiddleButton = ( mouse->buttons() & Qt::MiddleButton );
1181 const bool hasRightButton = ( mouse->buttons() & Qt::RightButton );
1183 const double dx = mCaptureFpsMouseMovements ? QCursor::pos().x() - mMousePos.x() : mouse->x() - mMousePos.x();
1184 const double dy = mCaptureFpsMouseMovements ? QCursor::pos().y() - mMousePos.y() : mouse->y() - mMousePos.y();
1185 mMousePos = mCaptureFpsMouseMovements ? QCursor::pos() : QPoint( mouse->x(), mouse->y() );
1187 if ( mIgnoreNextMouseMove )
1189 mIgnoreNextMouseMove =
false;
1193 if ( hasMiddleButton )
1196 const QVector3D cameraUp = mCamera->upVector().normalized();
1197 const QVector3D cameraFront = ( QVector3D( mCameraPose.centerPoint().x(), mCameraPose.centerPoint().y(), mCameraPose.centerPoint().z() ) - mCamera->position() ).normalized();
1198 const QVector3D cameraLeft = QVector3D::crossProduct( cameraUp, cameraFront );
1199 const QVector3D cameraPosDiff = -dx * cameraLeft - dy * cameraUp;
1200 moveCameraPositionBy( mCameraMovementSpeed * cameraPosDiff / 10.0 );
1202 else if ( hasRightButton )
1205 const QVector3D cameraFront = ( QVector3D( mCameraPose.centerPoint().x(), mCameraPose.centerPoint().y(), mCameraPose.centerPoint().z() ) - mCamera->position() ).normalized();
1206 const QVector3D cameraPosDiff = dy * cameraFront;
1207 moveCameraPositionBy( mCameraMovementSpeed * cameraPosDiff / 5.0 );
1211 if ( mCaptureFpsMouseMovements )
1213 float diffPitch = -0.2f * dy;
1214 switch ( mVerticalAxisInversion )
1225 const float diffYaw = -0.2f * dx;
1228 else if ( mouse->buttons() & Qt::LeftButton )
1230 float diffPitch = -0.2f * dy;
1231 switch ( mVerticalAxisInversion )
1241 const float diffYaw = -0.2f * dx;
1246 if ( mCaptureFpsMouseMovements )
1248 mIgnoreNextMouseMove =
true;
1251 emit
setCursorPosition( QPoint( mScene->engine()->size().width() / 2, mScene->engine()->size().height() / 2 ) );
1258 float pitch = mCameraPose.pitchAngle();
1259 pitch -= deltaPitch;
1260 mCameraPose.setPitchAngle(
pitch );
1261 updateCameraFromPose();
1267 float yaw = mCameraPose.headingAngle();
1269 mCameraPose.setHeadingAngle(
yaw );
1270 updateCameraFromPose();
1275 mCameraPose.setHeadingAngle( angle );
1276 updateCameraFromPose();
1281 const float yaw = mCameraPose.headingAngle();
1282 const float dist = mCameraPose.distanceFromCenterPoint();
1283 const float x = tx * dist * 0.02f;
1284 const float y = -ty * dist * 0.02f;
1287 const float t = sqrt( x * x + y * y );
1288 const float a = atan2( y, x ) -
yaw * M_PI / 180;
1289 const float dx = cos( a ) * t;
1290 const float dy = sin( a ) * t;
1293 center.
set( center.
x() + dx, center.
y() - dy, center.
z() );
1294 mCameraPose.setCenterPoint( center );
1295 updateCameraFromPose();
1300 if ( !mInputHandlersEnabled )
1303 if ( event->type() == QKeyEvent::Type::KeyRelease )
1305 if ( !event->isAutoRepeat() && mDepressedKeys.contains( event->key() ) )
1307 mDepressedKeys.remove( event->key() );
1311 else if ( event->type() == QEvent::ShortcutOverride )
1313 if ( event->modifiers() & Qt::ControlModifier )
1315 switch ( event->key() )
1317 case Qt::Key_QuoteLeft:
1320 switch ( mCameraNavigationMode )
1366 switch ( mCameraNavigationMode )
1369 return onKeyPressedFlyNavigation( event );
1372 return onKeyPressedTerrainNavigation( event );
1375 return onKeyPressedGlobeTerrainNavigation( event );
1383 mDepthBufferImage = depthImage;
1384 mDepthBufferIsReady =
true;
1385 mDepthBufferNonVoidAverage = -1;
1393 if ( mCurrentOperation == MouseOperation::ZoomWheel )
1395 handleTerrainNavigationWheelZoom();
1399bool QgsCameraController::isATranslationRotationSequence( MouseOperation newOperation )
const
1401 return std::find( mTranslateOrRotate.begin(), mTranslateOrRotate.end(), newOperation ) != std::end( mTranslateOrRotate ) && std::find( mTranslateOrRotate.begin(), mTranslateOrRotate.end(), mCurrentOperation ) != std::end( mTranslateOrRotate );
1404void QgsCameraController::setMouseParameters(
const MouseOperation &newOperation,
const QPoint &clickPoint )
1406 if ( newOperation == mCurrentOperation )
1411 if ( newOperation == MouseOperation::None )
1413 mClickPoint = QPoint();
1421 else if ( mClickPoint.isNull() || isATranslationRotationSequence( newOperation ) )
1423 mClickPoint = clickPoint;
1424 mRotationPitch = mCameraPose.pitchAngle();
1425 mRotationYaw = mCameraPose.headingAngle();
1427 mCurrentOperation = newOperation;
1428 mDepthBufferIsReady =
false;
1429 mRotationCenterCalculated =
false;
1430 mDragPointCalculated =
false;
1431 mZoomPointCalculated =
false;
1433 if ( mCurrentOperation != MouseOperation::None && mCurrentOperation != MouseOperation::RotationCamera )
1435 mMousePressViewCenter = mCameraPose.centerPoint() + mOrigin;
1445 mCameraPose.setCenterPoint( mCameraPose.centerPoint() - diff );
1448 mCameraBefore->setPosition( (
QgsVector3D( mCameraBefore->position() ) - diff ).toVector3D() );
1449 mCameraBefore->setViewCenter( (
QgsVector3D( mCameraBefore->viewCenter() ) - diff ).toVector3D() );
1450 mDragPoint = (
QgsVector3D( mDragPoint ) - diff ).toVector3D();
1451 mRotationCenter = (
QgsVector3D( mRotationCenter ) - diff ).toVector3D();
1455 updateCameraFromPose();
1458void QgsCameraController::rotateToRespectingTerrain(
float pitch,
float yaw )
1461 double elevation = 0.0;
1465 QVector3D camPos = mCamera->position();
1470 const QList<QgsRayCastHit> results = mScene->
terrainEntity()->rayIntersection( ray, context );
1472 if ( !results.isEmpty() )
1474 elevation = results.constFirst().mapCoordinates().z() - mOrigin.z();
1475 QgsDebugMsgLevel( QString(
"Computed elevation from terrain: %1" ).arg( elevation ), 2 );
1482 pos.
set( pos.
x(), pos.
y(), elevation + mScene->terrainEntity()->terrainElevationOffset() );
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.
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 cameraMovementSpeedChanged(double speed)
Emitted whenever the camera movement speed is changed by the controller.
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.
Custom exception class for Coordinate Reference System related exceptions.
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.
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.
#define QgsDebugMsgLevel(str, level)
#define QgsDebugError(str)