QGIS API Documentation 3.43.0-Master (c67cf405802)
qgscameracontroller.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgscameracontroller.cpp
3 --------------------------------------
4 Date : July 2017
5 Copyright : (C) 2017 by Martin Dobias
6 Email : wonder dot sk at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgscameracontroller.h"
17#include "moc_qgscameracontroller.cpp"
18#include "qgseventtracing.h"
19#include "qgsvector3d.h"
20#include "qgswindow3dengine.h"
21#include "qgs3dmapscene.h"
22#include "qgsterrainentity.h"
23#include "qgis.h"
24#include "qgs3dutils.h"
25
26#include <QDomDocument>
27#include <Qt3DRender/QCamera>
28#include <Qt3DInput>
29#include <cmath>
30
31#include "qgslogger.h"
32
34 : Qt3DCore::QEntity( scene )
35 , mScene( scene )
36 , mCamera( scene->engine()->camera() )
37 , mCameraBefore( new Qt3DRender::QCamera )
38 , mMouseHandler( new Qt3DInput::QMouseHandler )
39 , mKeyboardHandler( new Qt3DInput::QKeyboardHandler )
40 , mOrigin( scene->mapSettings()->origin() )
41{
42 mMouseHandler->setSourceDevice( new Qt3DInput::QMouseDevice() );
43 connect( mMouseHandler, &Qt3DInput::QMouseHandler::positionChanged, this, &QgsCameraController::onPositionChanged );
44 connect( mMouseHandler, &Qt3DInput::QMouseHandler::wheel, this, &QgsCameraController::onWheel );
45 connect( mMouseHandler, &Qt3DInput::QMouseHandler::pressed, this, &QgsCameraController::onMousePressed );
46 connect( mMouseHandler, &Qt3DInput::QMouseHandler::released, this, &QgsCameraController::onMouseReleased );
47 addComponent( mMouseHandler );
48
49 mKeyboardHandler->setSourceDevice( new Qt3DInput::QKeyboardDevice() );
50 connect( mKeyboardHandler, &Qt3DInput::QKeyboardHandler::pressed, this, &QgsCameraController::onKeyPressed );
51 connect( mKeyboardHandler, &Qt3DInput::QKeyboardHandler::released, this, &QgsCameraController::onKeyReleased );
52 addComponent( mKeyboardHandler );
53
54 // Disable the handlers when the entity is disabled
55 connect( this, &Qt3DCore::QEntity::enabledChanged, mMouseHandler, &Qt3DInput::QMouseHandler::setEnabled );
56 connect( this, &Qt3DCore::QEntity::enabledChanged, mKeyboardHandler, &Qt3DInput::QKeyboardHandler::setEnabled );
57
58 mFpsNavTimer = new QTimer( this );
59 mFpsNavTimer->setInterval( 10 );
60 connect( mFpsNavTimer, &QTimer::timeout, this, &QgsCameraController::applyFlyModeKeyMovements );
61 mFpsNavTimer->start();
62
63 if ( mScene->mapSettings()->sceneMode() == Qgis::SceneMode::Globe )
64 {
66 mGlobeCrsToLatLon = QgsCoordinateTransform( mScene->mapSettings()->crs(), mScene->mapSettings()->crs().toGeographicCrs(), mScene->mapSettings()->transformContext() );
67 }
68}
69
71
72QWindow *QgsCameraController::window() const
73{
74 QgsWindow3DEngine *windowEngine = qobject_cast<QgsWindow3DEngine *>( mScene->engine() );
75 return windowEngine ? windowEngine->window() : nullptr;
76}
77
79{
80 if ( navigationMode == mCameraNavigationMode )
81 return;
82
83 mCameraNavigationMode = navigationMode;
84 mIgnoreNextMouseMove = true;
85 emit navigationModeChanged( mCameraNavigationMode );
86}
87
89{
90 if ( movementSpeed == mCameraMovementSpeed )
91 return;
92
93 // If the speed becomes 0, navigation does not work anymore
94 // If the speed becomes too important, only one walk can move the view far from the scene.
95 mCameraMovementSpeed = std::clamp( movementSpeed, 0.05, 150.0 );
96 emit cameraMovementSpeedChanged( mCameraMovementSpeed );
97}
98
100{
101 mVerticalAxisInversion = inversion;
102}
103
104void QgsCameraController::rotateCamera( float diffPitch, float diffHeading )
105{
106 const float oldPitch = mCameraPose.pitchAngle();
107 const float oldHeading = mCameraPose.headingAngle();
108 float newPitch = oldPitch + diffPitch;
109 float newHeading = oldHeading + diffHeading;
110
111 newPitch = std::clamp( newPitch, 0.f, 180.f ); // prevent going over the head
112
113 // First undo the previously applied rotation, then apply the new rotation
114 // (We can't just apply our euler angles difference because the camera may be already rotated)
115 const QQuaternion qNew = Qgs3DUtils::rotationFromPitchHeadingAngles( newPitch, newHeading );
116 const QQuaternion qOld = Qgs3DUtils::rotationFromPitchHeadingAngles( oldPitch, oldHeading );
117 const QQuaternion q = qNew * qOld.conjugated();
118
119 // get camera's view vector, rotate it to get new view center
120 const QVector3D position = mCamera->position();
121 QVector3D viewCenter = mCamera->viewCenter();
122 const QVector3D viewVector = viewCenter - position;
123 const QVector3D cameraToCenter = q * viewVector;
124 viewCenter = position + cameraToCenter;
125
126 mCameraPose.setCenterPoint( viewCenter );
127 mCameraPose.setPitchAngle( newPitch );
128 mCameraPose.setHeadingAngle( newHeading );
129 updateCameraFromPose();
130}
131
132void QgsCameraController::rotateCameraAroundPivot( float newPitch, float newHeading, const QVector3D &pivotPoint )
133{
134 const float oldPitch = mCameraPose.pitchAngle();
135 const float oldHeading = mCameraPose.headingAngle();
136
137 newPitch = std::clamp( newPitch, 0.f, 180.f ); // prevent going over the head
138
139 // First undo the previously applied rotation, then apply the new rotation
140 // (We can't just apply our euler angles difference because the camera may be already rotated)
141 const QQuaternion qNew = Qgs3DUtils::rotationFromPitchHeadingAngles( newPitch, newHeading );
142 const QQuaternion qOld = Qgs3DUtils::rotationFromPitchHeadingAngles( oldPitch, oldHeading );
143 const QQuaternion q = qNew * qOld.conjugated();
144
145 const QVector3D newViewCenter = q * ( mCamera->viewCenter() - pivotPoint ) + pivotPoint;
146
147 mCameraPose.setCenterPoint( newViewCenter );
148 mCameraPose.setPitchAngle( newPitch );
149 mCameraPose.setHeadingAngle( newHeading );
150 updateCameraFromPose();
151}
152
153void QgsCameraController::zoomCameraAroundPivot( const QVector3D &oldCameraPosition, double zoomFactor, const QVector3D &pivotPoint )
154{
155 // step 1: move camera along the line connecting reference camera position and our pivot point
156 QVector3D newCamPosition = pivotPoint + ( oldCameraPosition - pivotPoint ) * zoomFactor;
157 double newDistance = mCameraPose.distanceFromCenterPoint() * zoomFactor;
158
159 // step 2: using the new camera position and distance from center, calculate new view center
160 QQuaternion q = Qgs3DUtils::rotationFromPitchHeadingAngles( mCameraPose.pitchAngle(), mCameraPose.headingAngle() );
161 QVector3D cameraToCenter = q * QVector3D( 0, 0, -newDistance );
162 QVector3D newViewCenter = newCamPosition + cameraToCenter;
163
164 mCameraPose.setDistanceFromCenterPoint( newDistance );
165 mCameraPose.setCenterPoint( newViewCenter );
166 updateCameraFromPose();
167}
168
170{
171 Q_UNUSED( dt )
172
173 if ( mCameraChanged )
174 {
175 emit cameraChanged();
176 mCameraChanged = false;
177 }
178}
179
180void QgsCameraController::resetView( float distance )
181{
182 QgsPointXY extentCenter = mScene->mapSettings()->extent().center();
183 QgsVector3D origin = mScene->mapSettings()->origin();
184 setViewFromTop( extentCenter.x() - origin.x(), extentCenter.y() - origin.y(), distance );
185}
186
187void QgsCameraController::setViewFromTop( float worldX, float worldY, float distance, float yaw )
188{
189 if ( mScene->mapSettings()->sceneMode() == Qgis::SceneMode::Globe )
190 {
191 QgsDebugError( QStringLiteral( "setViewFromTop() should not be used with globe!" ) );
192 return;
193 }
194
195 QgsCameraPose camPose;
196 QgsTerrainEntity *terrain = mScene->terrainEntity();
197 const float terrainElevationOffset = terrain ? terrain->terrainElevationOffset() : 0.0f;
198 camPose.setCenterPoint( QgsVector3D( worldX, worldY, terrainElevationOffset - mScene->mapSettings()->origin().z() ) );
200 camPose.setHeadingAngle( yaw );
201
202 // a basic setup to make frustum depth range long enough that it does not cull everything
203 mCamera->setNearPlane( distance / 2 );
204 mCamera->setFarPlane( distance * 2 );
205 // we force the updateCameraNearFarPlanes() in Qgs3DMapScene to properly set the planes
206 setCameraPose( camPose, true );
207}
208
210{
211 return mCameraPose.centerPoint();
212}
213
214void QgsCameraController::setLookingAtPoint( const QgsVector3D &point, float distance, float pitch, float yaw )
215{
216 QgsCameraPose camPose;
217 camPose.setCenterPoint( point );
219 camPose.setPitchAngle( pitch );
220 camPose.setHeadingAngle( yaw );
221 setCameraPose( camPose );
222}
223
225{
226 return lookingAtPoint() + mOrigin;
227}
228
229void QgsCameraController::setLookingAtMapPoint( const QgsVector3D &point, float distance, float pitch, float yaw )
230{
231 setLookingAtPoint( point - mOrigin, distance, pitch, yaw );
232}
233
234void QgsCameraController::setCameraPose( const QgsCameraPose &camPose, bool force )
235{
236 if ( camPose == mCameraPose && !force )
237 return;
238
239 mCameraPose = camPose;
240 updateCameraFromPose();
241}
242
243QDomElement QgsCameraController::writeXml( QDomDocument &doc ) const
244{
245 QDomElement elemCamera = doc.createElement( QStringLiteral( "camera" ) );
246 elemCamera.setAttribute( QStringLiteral( "x" ), mCameraPose.centerPoint().x() );
247 elemCamera.setAttribute( QStringLiteral( "y" ), mCameraPose.centerPoint().z() );
248 elemCamera.setAttribute( QStringLiteral( "elev" ), mCameraPose.centerPoint().y() );
249 elemCamera.setAttribute( QStringLiteral( "dist" ), mCameraPose.distanceFromCenterPoint() );
250 elemCamera.setAttribute( QStringLiteral( "pitch" ), mCameraPose.pitchAngle() );
251 elemCamera.setAttribute( QStringLiteral( "yaw" ), mCameraPose.headingAngle() );
252 return elemCamera;
253}
254
255void QgsCameraController::readXml( const QDomElement &elem )
256{
257 const float x = elem.attribute( QStringLiteral( "x" ) ).toFloat();
258 const float y = elem.attribute( QStringLiteral( "y" ) ).toFloat();
259 const float elev = elem.attribute( QStringLiteral( "elev" ) ).toFloat();
260 const float dist = elem.attribute( QStringLiteral( "dist" ) ).toFloat();
261 const float pitch = elem.attribute( QStringLiteral( "pitch" ) ).toFloat();
262 const float yaw = elem.attribute( QStringLiteral( "yaw" ) ).toFloat();
263 setLookingAtPoint( QgsVector3D( x, elev, y ), dist, pitch, yaw );
264}
265
266double QgsCameraController::sampleDepthBuffer( int px, int py )
267{
268 double depth = 1;
269
270 if ( QWindow *win = window() )
271 {
272 // on high DPI screens, the mouse position is in device-independent pixels,
273 // but the depth buffer is in physical pixels...
274 px = static_cast<int>( px * win->devicePixelRatio() );
275 py = static_cast<int>( py * win->devicePixelRatio() );
276 }
277
278 // Sample the neighbouring pixels for the closest point to the camera
279 for ( int x = px - 3; x <= px + 3; ++x )
280 {
281 for ( int y = py - 3; y <= py + 3; ++y )
282 {
283 if ( mDepthBufferImage.valid( x, y ) )
284 {
285 depth = std::min( depth, Qgs3DUtils::decodeDepth( mDepthBufferImage.pixel( x, y ) ) );
286 }
287 }
288 }
289 return depth;
290}
291
292double QgsCameraController::depthBufferNonVoidAverage()
293{
294 // Cache the computed depth, since averaging over all pixels can be expensive
295 if ( mDepthBufferNonVoidAverage != -1 )
296 return mDepthBufferNonVoidAverage;
297
298 // Returns the average of depth values that are not 1 (void area)
299 double depth = 0;
300 int samplesCount = 0;
301 // Make sure we can do the cast
302 Q_ASSERT( mDepthBufferImage.format() == QImage::Format_RGB32 );
303 for ( int y = 0; y < mDepthBufferImage.height(); ++y )
304 {
305 const QRgb *line = reinterpret_cast<const QRgb *>( mDepthBufferImage.constScanLine( y ) );
306 for ( int x = 0; x < mDepthBufferImage.width(); ++x )
307 {
308 double d = Qgs3DUtils::decodeDepth( line[x] );
309 if ( d < 1 )
310 {
311 depth += d;
312 samplesCount += 1;
313 }
314 }
315 }
316
317 // if the whole buffer is white, a depth cannot be computed
318 if ( samplesCount == 0 )
319 depth = 1.0;
320 else
321 depth /= samplesCount;
322
323 mDepthBufferNonVoidAverage = depth;
324
325 return depth;
326}
327
328QgsVector3D QgsCameraController::moveGeocentricPoint( const QgsVector3D &point, double latDiff, double lonDiff )
329{
330 try
331 {
332 QgsVector3D pointLatLon = mGlobeCrsToLatLon.transform( point );
333 pointLatLon.setX( pointLatLon.x() + lonDiff );
334 pointLatLon.setY( std::clamp( pointLatLon.y() + latDiff, -90., 90. ) );
335
336 return mGlobeCrsToLatLon.transform( pointLatLon, Qgis::TransformDirection::Reverse );
337 }
338 catch ( const QgsCsException & )
339 {
340 QgsDebugError( QStringLiteral( "moveGeocentricPoint: transform failed!" ) );
341 return point;
342 }
343}
344
345void QgsCameraController::globeMoveCenterPoint( double latDiff, double lonDiff )
346{
347 const QgsVector3D viewCenter = mCameraPose.centerPoint() + mOrigin;
348 const QgsVector3D newViewCenter = moveGeocentricPoint( viewCenter, latDiff, lonDiff );
349 mCameraPose.setCenterPoint( newViewCenter - mOrigin );
350 updateCameraFromPose();
351}
352
354{
355 mCameraPose.setDistanceFromCenterPoint( mCameraPose.distanceFromCenterPoint() * factor );
356 updateCameraFromPose();
357}
358
360{
361 mCameraPose.setPitchAngle( std::clamp( mCameraPose.pitchAngle() + angleDiff, 0.f, 90.f ) );
362 updateCameraFromPose();
363}
364
366{
367 mCameraPose.setHeadingAngle( mCameraPose.headingAngle() + angleDiff );
368 updateCameraFromPose();
369}
370
371void QgsCameraController::resetGlobe( float distance, double lat, double lon )
372{
373 QgsVector3D mapPoint;
374 try
375 {
376 mapPoint = mGlobeCrsToLatLon.transform( QgsVector3D( lon, lat, 0 ), Qgis::TransformDirection::Reverse );
377 }
378 catch ( const QgsCsException & )
379 {
380 QgsDebugError( QStringLiteral( "resetGlobe: transform failed!" ) );
381 return;
382 }
383
384 QgsCameraPose cp;
385 cp.setCenterPoint( mapPoint - mOrigin );
387 setCameraPose( cp );
388}
389
390void QgsCameraController::updateCameraFromPose()
391{
392 if ( mCamera )
393 {
394 if ( mScene->mapSettings()->sceneMode() == Qgis::SceneMode::Globe )
395 {
396 const QgsVector3D viewCenter = mCameraPose.centerPoint() + mOrigin;
397
398 QgsVector3D viewCenterLatLon;
399 try
400 {
401 viewCenterLatLon = mGlobeCrsToLatLon.transform( viewCenter );
402 }
403 catch ( const QgsCsException & )
404 {
405 QgsDebugError( QStringLiteral( "updateCameraFromPose: transform failed!" ) );
406 return;
407 }
408
409 mCameraPose.updateCameraGlobe( mCamera, viewCenterLatLon.y(), viewCenterLatLon.x() );
410 }
411 else
412 {
413 mCameraPose.updateCamera( mCamera );
414 }
415 mCameraChanged = true;
416 }
417}
418
419void QgsCameraController::moveCameraPositionBy( const QVector3D &posDiff )
420{
421 mCameraPose.setCenterPoint( mCameraPose.centerPoint() + posDiff );
422 updateCameraFromPose();
423}
424
425void QgsCameraController::onPositionChanged( Qt3DInput::QMouseEvent *mouse )
426{
427 if ( !mInputHandlersEnabled )
428 return;
429
430 QgsEventTracing::ScopedEvent traceEvent( QStringLiteral( "3D" ), QStringLiteral( "QgsCameraController::onPositionChanged" ) );
431
432 switch ( mCameraNavigationMode )
433 {
435 onPositionChangedTerrainNavigation( mouse );
436 break;
437
439 onPositionChangedFlyNavigation( mouse );
440 break;
441
443 onPositionChangedGlobeTerrainNavigation( mouse );
444 break;
445 }
446}
447
448bool QgsCameraController::screenPointToWorldPos( QPoint position, Qt3DRender::QCamera *mCameraBefore, double &depth, QVector3D &worldPosition )
449{
450 depth = sampleDepthBuffer( position.x(), position.y() );
451
452 // if there's nothing around the given position, try to get just any depth
453 // from the scene as a coarse approximation...
454 if ( depth == 1 )
455 {
456 depth = depthBufferNonVoidAverage();
457 }
458
459 worldPosition = Qgs3DUtils::screenPointToWorldPos( position, depth, mScene->engine()->size(), mCameraBefore );
460 if ( !std::isfinite( worldPosition.x() ) || !std::isfinite( worldPosition.y() ) || !std::isfinite( worldPosition.z() ) )
461 {
462 QgsDebugMsgLevel( QStringLiteral( "screenPointToWorldPos: position is NaN or Inf. This should not happen." ), 2 );
463 return false;
464 }
465
466 return true;
467}
468
469void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseEvent *mouse )
470{
471 if ( mIgnoreNextMouseMove )
472 {
473 mIgnoreNextMouseMove = false;
474 mMousePos = QPoint( mouse->x(), mouse->y() );
475 return;
476 }
477
478 const int dx = mouse->x() - mMousePos.x();
479 const int dy = mouse->y() - mMousePos.y();
480
481 const bool hasShift = ( mouse->modifiers() & Qt::ShiftModifier );
482 const bool hasCtrl = ( mouse->modifiers() & Qt::ControlModifier );
483 const bool hasLeftButton = ( mouse->buttons() & Qt::LeftButton );
484 const bool hasMiddleButton = ( mouse->buttons() & Qt::MiddleButton );
485 const bool hasRightButton = ( mouse->buttons() & Qt::RightButton );
486
487 if ( ( hasLeftButton && hasShift && !hasCtrl ) || ( hasMiddleButton && !hasShift && !hasCtrl ) )
488 {
489 // rotate/tilt using mouse (camera moves as it rotates around the clicked point)
490 setMouseParameters( MouseOperation::RotationCenter, mMousePos );
491
492 float scale = static_cast<float>( std::max( mScene->engine()->size().width(), mScene->engine()->size().height() ) );
493 float pitchDiff = 180.0f * static_cast<float>( mouse->y() - mClickPoint.y() ) / scale;
494 float yawDiff = -180.0f * static_cast<float>( mouse->x() - mClickPoint.x() ) / scale;
495
496 if ( !mDepthBufferIsReady )
497 return;
498
499 if ( !mRotationCenterCalculated )
500 {
501 double depth;
502 QVector3D worldPosition;
503 if ( screenPointToWorldPos( mClickPoint, mCameraBefore.get(), depth, worldPosition ) )
504 {
505 mRotationCenter = worldPosition;
506 mRotationDistanceFromCenter = ( mRotationCenter - mCameraBefore->position() ).length();
507 emit cameraRotationCenterChanged( mRotationCenter );
508 mRotationCenterCalculated = true;
509 }
510 }
511
512 rotateCameraAroundPivot( mRotationPitch + pitchDiff, mRotationYaw + yawDiff, mRotationCenter );
513 }
514 else if ( hasLeftButton && hasCtrl && !hasShift )
515 {
516 setMouseParameters( MouseOperation::RotationCamera );
517 // rotate/tilt using mouse (camera stays at one position as it rotates)
518 const float diffPitch = 0.2f * dy;
519 const float diffYaw = -0.2f * dx;
520 rotateCamera( diffPitch, diffYaw );
521 }
522 else if ( hasLeftButton && !hasShift && !hasCtrl )
523 {
524 // translation works as if one grabbed a point on the 3D viewer and dragged it
525 setMouseParameters( MouseOperation::Translation, mMousePos );
526
527 if ( !mDepthBufferIsReady )
528 return;
529
530 if ( !mDragPointCalculated )
531 {
532 double depth;
533 QVector3D worldPosition;
534 if ( screenPointToWorldPos( mClickPoint, mCameraBefore.get(), depth, worldPosition ) )
535 {
536 mDragDepth = depth;
537 mDragPoint = worldPosition;
538 mDragPointCalculated = true;
539 }
540 }
541
542 QVector3D cameraBeforeDragPos = mCameraBefore->position();
543
544 QVector3D moveToPosition = Qgs3DUtils::screenPointToWorldPos( mMousePos, mDragDepth, mScene->engine()->size(), mCameraBefore.get() );
545 QVector3D cameraBeforeToMoveToPos = ( moveToPosition - mCameraBefore->position() ).normalized();
546 QVector3D cameraBeforeToDragPointPos = ( mDragPoint - mCameraBefore->position() ).normalized();
547
548 // Make sure the rays are not horizontal (add small z shift if it is)
549 if ( cameraBeforeToMoveToPos.z() == 0 )
550 {
551 cameraBeforeToMoveToPos.setZ( 0.01 );
552 cameraBeforeToMoveToPos = cameraBeforeToMoveToPos.normalized();
553 }
554
555 if ( cameraBeforeToDragPointPos.z() == 0 )
556 {
557 cameraBeforeToDragPointPos.setZ( 0.01 );
558 cameraBeforeToDragPointPos = cameraBeforeToDragPointPos.normalized();
559 }
560
561 double d1 = ( mDragPoint.z() - cameraBeforeDragPos.z() ) / cameraBeforeToMoveToPos.z();
562 double d2 = ( mDragPoint.z() - cameraBeforeDragPos.z() ) / cameraBeforeToDragPointPos.z();
563
564 QVector3D from = cameraBeforeDragPos + d1 * cameraBeforeToMoveToPos;
565 QVector3D to = cameraBeforeDragPos + d2 * cameraBeforeToDragPointPos;
566
567 QVector3D shiftVector = to - from;
568
569 mCameraPose.setCenterPoint( mCameraBefore->viewCenter() + shiftVector );
570 updateCameraFromPose();
571 }
572 else if ( hasLeftButton && hasShift && hasCtrl )
573 {
574 // change the camera elevation, similar to pageUp/pageDown
575 QgsVector3D center = mCameraPose.centerPoint();
576 double tElev = mMousePos.y() - mouse->y();
577 center.set( center.x(), center.y(), center.z() + tElev * 0.5 );
578 mCameraPose.setCenterPoint( center );
579 updateCameraFromPose();
580 }
581 else if ( hasRightButton && !hasShift && !hasCtrl )
582 {
583 setMouseParameters( MouseOperation::Zoom, mMousePos );
584 if ( !mDepthBufferIsReady )
585 return;
586
587 if ( !mDragPointCalculated )
588 {
589 double depth;
590 QVector3D worldPosition;
591 if ( screenPointToWorldPos( mClickPoint, mCameraBefore.get(), depth, worldPosition ) )
592 {
593 mDragPoint = worldPosition;
594 mDragPointCalculated = true;
595 }
596 }
597
598 float oldDist = ( mCameraBefore->position() - mDragPoint ).length();
599 float newDist = oldDist;
600
601 int yOffset = 0;
602 int screenHeight = mScene->engine()->size().height();
603 QWindow *win = window();
604 if ( win )
605 {
606 yOffset = win->mapToGlobal( QPoint( 0, 0 ) ).y();
607 screenHeight = win->screen()->size().height();
608 }
609
610 // Applies smoothing
611 if ( mMousePos.y() > mClickPoint.y() ) // zoom in
612 {
613 double f = ( double ) ( mMousePos.y() - mClickPoint.y() ) / ( double ) ( screenHeight - mClickPoint.y() - yOffset );
614 f = std::max( 0.0, std::min( 1.0, f ) );
615 f = 1 - ( std::expm1( -2 * f ) ) / ( std::expm1( -2 ) );
616 newDist = newDist * f;
617 }
618 else // zoom out
619 {
620 double f = 1 - ( double ) ( mMousePos.y() + yOffset ) / ( double ) ( mClickPoint.y() + yOffset );
621 f = std::max( 0.0, std::min( 1.0, f ) );
622 f = ( std::expm1( 2 * f ) ) / ( std::expm1( 2 ) );
623 newDist = newDist + 2 * newDist * f;
624 }
625
626 double zoomFactor = newDist / oldDist;
627 zoomCameraAroundPivot( mCameraBefore->position(), zoomFactor, mDragPoint );
628 }
629
630 mMousePos = QPoint( mouse->x(), mouse->y() );
631}
632
633void QgsCameraController::onPositionChangedGlobeTerrainNavigation( Qt3DInput::QMouseEvent *mouse )
634{
635 const bool hasShift = ( mouse->modifiers() & Qt::ShiftModifier );
636 const bool hasCtrl = ( mouse->modifiers() & Qt::ControlModifier );
637 const bool hasLeftButton = ( mouse->buttons() & Qt::LeftButton );
638 const bool hasMiddleButton = ( mouse->buttons() & Qt::MiddleButton );
639
640 if ( ( hasLeftButton && hasShift && !hasCtrl ) || ( hasMiddleButton && !hasShift && !hasCtrl ) )
641 {
642 setMouseParameters( MouseOperation::RotationCenter, mMousePos );
643
644 const float scale = static_cast<float>( std::max( mScene->engine()->size().width(), mScene->engine()->size().height() ) );
645 const float pitchDiff = 180.0f * static_cast<float>( mouse->y() - mClickPoint.y() ) / scale;
646 const float yawDiff = -180.0f * static_cast<float>( mouse->x() - mClickPoint.x() ) / scale;
647
648 mCameraPose.setPitchAngle( mRotationPitch + pitchDiff );
649 mCameraPose.setHeadingAngle( mRotationYaw + yawDiff );
650 updateCameraFromPose();
651 return;
652 }
653
654 if ( !( mouse->buttons() & Qt::LeftButton ) )
655 return;
656
657 // translation works as if one grabbed a point on the 3D viewer and dragged it
658 setMouseParameters( MouseOperation::Translation, mMousePos );
659
660 if ( !mDepthBufferIsReady )
661 return;
662
663 if ( !mDragPointCalculated )
664 {
665 double depth;
666 QVector3D worldPosition;
667 if ( !screenPointToWorldPos( mClickPoint, mCameraBefore.get(), depth, worldPosition ) )
668 return;
669
670 mDragDepth = depth;
671 mDragPoint = worldPosition;
672 mDragPointCalculated = true;
673 }
674
675 const QgsVector3D mapPressPos = QgsVector3D( mDragPoint ) + mOrigin;
676
677 double newDepth = sampleDepthBuffer( mouse->x(), mouse->y() );
678 if ( newDepth == 1 )
679 return; // the mouse is somewhere in the void...
680
681 const QVector3D newWorldPosition = Qgs3DUtils::screenPointToWorldPos( QPoint( mouse->x(), mouse->y() ), newDepth, mScene->engine()->size(), mCameraBefore.get() );
682 if ( !std::isfinite( newWorldPosition.x() ) || !std::isfinite( newWorldPosition.y() ) || !std::isfinite( newWorldPosition.z() ) )
683 return;
684
685 const QgsVector3D newMapPos = QgsVector3D( newWorldPosition ) + mOrigin;
686
687 // now that we have old and new mouse position in ECEF coordinates,
688 // let's figure out the difference in lat/lon angles and update the center point
689
690 QgsVector3D oldLatLon, newLatLon;
691 try
692 {
693 oldLatLon = mGlobeCrsToLatLon.transform( mapPressPos );
694 newLatLon = mGlobeCrsToLatLon.transform( newMapPos );
695 }
696 catch ( const QgsCsException & )
697 {
698 QgsDebugError( QStringLiteral( "onPositionChangedGlobeTerrainNavigation: transform failed!" ) );
699 return;
700 }
701
702 const double latDiff = oldLatLon.y() - newLatLon.y();
703 const double lonDiff = oldLatLon.x() - newLatLon.x();
704
705 const QgsVector3D newVC = moveGeocentricPoint( mMousePressViewCenter, latDiff, lonDiff );
706 const QgsVector3D newVCWorld = newVC - mOrigin;
707 mCameraPose.setCenterPoint( newVCWorld );
708 updateCameraFromPose();
709}
710
711
712void QgsCameraController::zoom( float factor )
713{
714 // zoom in/out
715 float dist = mCameraPose.distanceFromCenterPoint();
716 dist -= dist * factor * 0.01f;
717 mCameraPose.setDistanceFromCenterPoint( dist );
718 updateCameraFromPose();
719}
720
721void QgsCameraController::handleTerrainNavigationWheelZoom()
722{
723 if ( !mDepthBufferIsReady )
724 return;
725
726 if ( !mZoomPointCalculated )
727 {
728 double depth;
729 QVector3D worldPosition;
730 if ( screenPointToWorldPos( mMousePos, mCameraBefore.get(), depth, worldPosition ) )
731 {
732 mZoomPoint = worldPosition;
733 mZoomPointCalculated = true;
734 }
735 }
736
737 double oldDist = ( mZoomPoint - mCameraBefore->position() ).length();
738 // Each step of the scroll wheel decreases distance by 20%
739 double newDist = std::pow( 0.8, mCumulatedWheelY ) * oldDist;
740 // Make sure we don't clip the thing we're zooming to.
741 newDist = std::max( newDist, 2.0 );
742 double zoomFactor = newDist / oldDist;
743 // Don't change the distance too suddenly to hopefully prevent numerical instability
744 zoomFactor = std::clamp( zoomFactor, 0.01, 100.0 );
745
746 zoomCameraAroundPivot( mCameraBefore->position(), zoomFactor, mZoomPoint );
747
748 mCumulatedWheelY = 0;
749 setMouseParameters( MouseOperation::None );
750}
751
752void QgsCameraController::onWheel( Qt3DInput::QWheelEvent *wheel )
753{
754 if ( !mInputHandlersEnabled )
755 return;
756
757 switch ( mCameraNavigationMode )
758 {
760 {
761 const float scaling = ( ( wheel->modifiers() & Qt::ControlModifier ) != 0 ? 0.1f : 1.0f ) / 1000.f;
762 setCameraMovementSpeed( mCameraMovementSpeed + mCameraMovementSpeed * scaling * wheel->angleDelta().y() );
763 break;
764 }
765
767 {
768 // Scale our variable to roughly "number of normal steps", with Ctrl
769 // increasing granularity 10x
770 const double scaling = ( 1.0 / 120.0 ) * ( ( wheel->modifiers() & Qt::ControlModifier ) != 0 ? 0.1 : 1.0 );
771
772 // Apparently angleDelta needs to be accumulated
773 // see: https://doc.qt.io/qt-5/qwheelevent.html#angleDelta
774 mCumulatedWheelY += scaling * wheel->angleDelta().y();
775
776 if ( mCurrentOperation != MouseOperation::ZoomWheel )
777 {
778 setMouseParameters( MouseOperation::ZoomWheel );
779 // The actual zooming will happen after we get a new depth buffer
780 }
781 else
782 {
783 handleTerrainNavigationWheelZoom();
784 }
785 break;
786 }
787
789 {
790 float wheelAmount = static_cast<float>( wheel->angleDelta().y() );
791 float factor = abs( wheelAmount ) / 1000.f;
792 float mulFactor = wheelAmount > 0 ? ( 1 - factor ) : ( 1 + factor );
793 mCameraPose.setDistanceFromCenterPoint( mCameraPose.distanceFromCenterPoint() * mulFactor );
794 updateCameraFromPose();
795 break;
796 }
797 }
798}
799
800void QgsCameraController::onMousePressed( Qt3DInput::QMouseEvent *mouse )
801{
802 if ( !mInputHandlersEnabled )
803 return;
804
805 mKeyboardHandler->setFocus( true );
806
807 if ( mouse->button() == Qt3DInput::QMouseEvent::MiddleButton || ( ( mouse->modifiers() & Qt::ShiftModifier ) != 0 && mouse->button() == Qt3DInput::QMouseEvent::LeftButton ) || ( ( mouse->modifiers() & Qt::ControlModifier ) != 0 && mouse->button() == Qt3DInput::QMouseEvent::LeftButton ) )
808 {
809 mMousePos = QPoint( mouse->x(), mouse->y() );
810
811 if ( mCaptureFpsMouseMovements )
812 mIgnoreNextMouseMove = true;
813
814 const MouseOperation operation {
815 ( mouse->modifiers() & Qt::ControlModifier ) != 0 && mouse->button() == Qt3DInput::QMouseEvent::LeftButton ? MouseOperation::RotationCamera : MouseOperation::RotationCenter
816 };
817 setMouseParameters( operation, mMousePos );
818 }
819
820 else if ( mouse->button() == Qt3DInput::QMouseEvent::LeftButton || mouse->button() == Qt3DInput::QMouseEvent::RightButton )
821 {
822 mMousePos = QPoint( mouse->x(), mouse->y() );
823
824 if ( mCaptureFpsMouseMovements )
825 mIgnoreNextMouseMove = true;
826
827 const MouseOperation operation = ( mouse->button() == Qt3DInput::QMouseEvent::LeftButton ) ? MouseOperation::Translation : MouseOperation::Zoom;
828 setMouseParameters( operation, mMousePos );
829 }
830}
831
832void QgsCameraController::onMouseReleased( Qt3DInput::QMouseEvent *mouse )
833{
834 Q_UNUSED( mouse )
835 if ( !mInputHandlersEnabled )
836 return;
837
838
839 setMouseParameters( MouseOperation::None );
840}
841
842void QgsCameraController::onKeyPressed( Qt3DInput::QKeyEvent *event )
843{
844 if ( !mInputHandlersEnabled )
845 return;
846
847 if ( event->modifiers() & Qt::ControlModifier && event->key() == Qt::Key_QuoteLeft )
848 {
849 // switch navigation mode
850 switch ( mCameraNavigationMode )
851 {
854 break;
858 break;
859 }
860 return;
861 }
862
863 switch ( mCameraNavigationMode )
864 {
866 {
867 onKeyPressedFlyNavigation( event );
868 break;
869 }
870
872 {
873 onKeyPressedTerrainNavigation( event );
874 break;
875 }
876
878 {
879 onKeyPressedGlobeTerrainNavigation( event );
880 break;
881 }
882 }
883}
884
885void QgsCameraController::onKeyPressedTerrainNavigation( Qt3DInput::QKeyEvent *event )
886{
887 const bool hasShift = ( event->modifiers() & Qt::ShiftModifier );
888 const bool hasCtrl = ( event->modifiers() & Qt::ControlModifier );
889
890 int tx = 0, ty = 0, tElev = 0;
891 switch ( event->key() )
892 {
893 case Qt::Key_Left:
894 tx -= 1;
895 break;
896 case Qt::Key_Right:
897 tx += 1;
898 break;
899
900 case Qt::Key_Up:
901 ty += 1;
902 break;
903 case Qt::Key_Down:
904 ty -= 1;
905 break;
906
907 case Qt::Key_PageDown:
908 tElev -= 1;
909 break;
910 case Qt::Key_PageUp:
911 tElev += 1;
912 break;
913 }
914
915 if ( tx || ty )
916 {
917 if ( !hasShift && !hasCtrl )
918 {
919 moveView( tx, ty );
920 }
921 else if ( hasShift && !hasCtrl )
922 {
923 // rotate/tilt using keyboard (camera moves as it rotates around its view center)
926 }
927 else if ( hasCtrl && !hasShift )
928 {
929 // rotate/tilt using keyboard (camera stays at one position as it rotates)
930 const float diffPitch = ty; // down key = rotating camera down
931 const float diffYaw = -tx; // right key = rotating camera to the right
932 rotateCamera( diffPitch, diffYaw );
933 }
934 }
935
936 if ( tElev )
937 {
938 QgsVector3D center = mCameraPose.centerPoint();
939 center.set( center.x(), center.y(), center.z() + tElev * 10 );
940 mCameraPose.setCenterPoint( center );
941 updateCameraFromPose();
942 }
943}
944
945void QgsCameraController::onKeyPressedGlobeTerrainNavigation( Qt3DInput::QKeyEvent *event )
946{
947 // both move factor and zoom factor are just empirically picked numbers
948 // that seem to work well (providing steps that are not too big / not too small)
949 constexpr float MOVE_FACTOR = 0.000001f; // multiplied by distance to get angle
950 constexpr float ZOOM_FACTOR = 0.9f;
951
952 const bool hasShift = ( event->modifiers() & Qt::ShiftModifier );
953
954 switch ( event->key() )
955 {
956 case Qt::Key_Left:
957 if ( hasShift )
959 else
960 globeMoveCenterPoint( 0, -MOVE_FACTOR * mCameraPose.distanceFromCenterPoint() );
961 break;
962 case Qt::Key_Right:
963 if ( hasShift )
965 else
966 globeMoveCenterPoint( 0, MOVE_FACTOR * mCameraPose.distanceFromCenterPoint() );
967 break;
968 case Qt::Key_Up:
969 if ( hasShift )
971 else
972 globeMoveCenterPoint( MOVE_FACTOR * mCameraPose.distanceFromCenterPoint(), 0 );
973 break;
974 case Qt::Key_Down:
975 if ( hasShift )
977 else
978 globeMoveCenterPoint( -MOVE_FACTOR * mCameraPose.distanceFromCenterPoint(), 0 );
979 break;
980 case Qt::Key_PageDown:
981 globeZoom( ZOOM_FACTOR );
982 break;
983 case Qt::Key_PageUp:
984 globeZoom( 1 / ZOOM_FACTOR );
985 break;
986 default:
987 break;
988 }
989}
990
991void QgsCameraController::onKeyPressedFlyNavigation( Qt3DInput::QKeyEvent *event )
992{
993 switch ( event->key() )
994 {
995 case Qt::Key_QuoteLeft:
996 {
997 // toggle mouse lock mode
998 mCaptureFpsMouseMovements = !mCaptureFpsMouseMovements;
999 mIgnoreNextMouseMove = true;
1000 if ( mCaptureFpsMouseMovements )
1001 {
1002 qApp->setOverrideCursor( QCursor( Qt::BlankCursor ) );
1003 }
1004 else
1005 {
1006 qApp->restoreOverrideCursor();
1007 }
1008 return;
1009 }
1010
1011 case Qt::Key_Escape:
1012 {
1013 // always exit mouse lock mode
1014 if ( mCaptureFpsMouseMovements )
1015 {
1016 mCaptureFpsMouseMovements = false;
1017 mIgnoreNextMouseMove = true;
1018 qApp->restoreOverrideCursor();
1019 return;
1020 }
1021 break;
1022 }
1023
1024 default:
1025 break;
1026 }
1027
1028 if ( event->isAutoRepeat() )
1029 return;
1030
1031 mDepressedKeys.insert( event->key() );
1032}
1033
1034void QgsCameraController::walkView( double tx, double ty, double tz )
1035{
1036 const QVector3D cameraUp = mCamera->upVector().normalized();
1037 const QVector3D cameraFront = ( QVector3D( mCameraPose.centerPoint().x(), mCameraPose.centerPoint().y(), mCameraPose.centerPoint().z() ) - mCamera->position() ).normalized();
1038 const QVector3D cameraLeft = QVector3D::crossProduct( cameraUp, cameraFront );
1039
1040 QVector3D cameraPosDiff( 0.0f, 0.0f, 0.0f );
1041
1042 if ( tx != 0.0 )
1043 {
1044 cameraPosDiff += static_cast<float>( tx ) * cameraFront;
1045 }
1046 if ( ty != 0.0 )
1047 {
1048 cameraPosDiff += static_cast<float>( ty ) * cameraLeft;
1049 }
1050 if ( tz != 0.0 )
1051 {
1052 cameraPosDiff += static_cast<float>( tz ) * QVector3D( 0.0f, 0.0f, 1.0f );
1053 }
1054
1055 moveCameraPositionBy( cameraPosDiff );
1056}
1057
1058void QgsCameraController::applyFlyModeKeyMovements()
1059{
1060 // shift = "run", ctrl = "slow walk"
1061 const bool shiftPressed = mDepressedKeys.contains( Qt::Key_Shift );
1062 const bool ctrlPressed = mDepressedKeys.contains( Qt::Key_Control );
1063
1064 const double movementSpeed = mCameraMovementSpeed * ( shiftPressed ? 2 : 1 ) * ( ctrlPressed ? 0.1 : 1 );
1065
1066 bool changed = false;
1067 double x = 0.0;
1068 double y = 0.0;
1069 double z = 0.0;
1070 if ( mDepressedKeys.contains( Qt::Key_Left ) || mDepressedKeys.contains( Qt::Key_A ) )
1071 {
1072 changed = true;
1073 y += movementSpeed;
1074 }
1075
1076 if ( mDepressedKeys.contains( Qt::Key_Right ) || mDepressedKeys.contains( Qt::Key_D ) )
1077 {
1078 changed = true;
1079 y -= movementSpeed;
1080 }
1081
1082 if ( mDepressedKeys.contains( Qt::Key_Up ) || mDepressedKeys.contains( Qt::Key_W ) )
1083 {
1084 changed = true;
1085 x += movementSpeed;
1086 }
1087
1088 if ( mDepressedKeys.contains( Qt::Key_Down ) || mDepressedKeys.contains( Qt::Key_S ) )
1089 {
1090 changed = true;
1091 x -= movementSpeed;
1092 }
1093
1094 // note -- vertical axis movements are slower by default then horizontal ones, as GIS projects
1095 // tend to have much more limited elevation range vs ground range
1096 static constexpr double ELEVATION_MOVEMENT_SCALE = 0.5;
1097 if ( mDepressedKeys.contains( Qt::Key_PageUp ) || mDepressedKeys.contains( Qt::Key_E ) )
1098 {
1099 changed = true;
1100 z += ELEVATION_MOVEMENT_SCALE * movementSpeed;
1101 }
1102
1103 if ( mDepressedKeys.contains( Qt::Key_PageDown ) || mDepressedKeys.contains( Qt::Key_Q ) )
1104 {
1105 changed = true;
1106 z -= ELEVATION_MOVEMENT_SCALE * movementSpeed;
1107 }
1108
1109 if ( changed )
1110 walkView( x, y, z );
1111}
1112
1113void QgsCameraController::onPositionChangedFlyNavigation( Qt3DInput::QMouseEvent *mouse )
1114{
1115 const bool hasMiddleButton = ( mouse->buttons() & Qt::MiddleButton );
1116 const bool hasRightButton = ( mouse->buttons() & Qt::RightButton );
1117
1118 const double dx = mCaptureFpsMouseMovements ? QCursor::pos().x() - mMousePos.x() : mouse->x() - mMousePos.x();
1119 const double dy = mCaptureFpsMouseMovements ? QCursor::pos().y() - mMousePos.y() : mouse->y() - mMousePos.y();
1120 mMousePos = mCaptureFpsMouseMovements ? QCursor::pos() : QPoint( mouse->x(), mouse->y() );
1121
1122 if ( mIgnoreNextMouseMove )
1123 {
1124 mIgnoreNextMouseMove = false;
1125 return;
1126 }
1127
1128 if ( hasMiddleButton )
1129 {
1130 // middle button drag = pan camera in place (strafe)
1131 const QVector3D cameraUp = mCamera->upVector().normalized();
1132 const QVector3D cameraFront = ( QVector3D( mCameraPose.centerPoint().x(), mCameraPose.centerPoint().y(), mCameraPose.centerPoint().z() ) - mCamera->position() ).normalized();
1133 const QVector3D cameraLeft = QVector3D::crossProduct( cameraUp, cameraFront );
1134 const QVector3D cameraPosDiff = -dx * cameraLeft - dy * cameraUp;
1135 moveCameraPositionBy( mCameraMovementSpeed * cameraPosDiff / 10.0 );
1136 }
1137 else if ( hasRightButton )
1138 {
1139 // right button drag = camera dolly
1140 const QVector3D cameraFront = ( QVector3D( mCameraPose.centerPoint().x(), mCameraPose.centerPoint().y(), mCameraPose.centerPoint().z() ) - mCamera->position() ).normalized();
1141 const QVector3D cameraPosDiff = dy * cameraFront;
1142 moveCameraPositionBy( mCameraMovementSpeed * cameraPosDiff / 5.0 );
1143 }
1144 else
1145 {
1146 if ( mCaptureFpsMouseMovements )
1147 {
1148 float diffPitch = -0.2f * dy;
1149 switch ( mVerticalAxisInversion )
1150 {
1152 diffPitch *= -1;
1153 break;
1154
1157 break;
1158 }
1159
1160 const float diffYaw = -0.2f * dx;
1161 rotateCamera( diffPitch, diffYaw );
1162 }
1163 else if ( mouse->buttons() & Qt::LeftButton )
1164 {
1165 float diffPitch = -0.2f * dy;
1166 switch ( mVerticalAxisInversion )
1167 {
1170 diffPitch *= -1;
1171 break;
1172
1174 break;
1175 }
1176 const float diffYaw = -0.2f * dx;
1177 rotateCamera( diffPitch, diffYaw );
1178 }
1179 }
1180
1181 if ( mCaptureFpsMouseMovements )
1182 {
1183 mIgnoreNextMouseMove = true;
1184
1185 // reset cursor back to center of map widget
1186 emit setCursorPosition( QPoint( mScene->engine()->size().width() / 2, mScene->engine()->size().height() / 2 ) );
1187 }
1188}
1189
1190void QgsCameraController::onKeyReleased( Qt3DInput::QKeyEvent *event )
1191{
1192 if ( !mInputHandlersEnabled )
1193 return;
1194
1195 if ( event->isAutoRepeat() )
1196 return;
1197
1198 mDepressedKeys.remove( event->key() );
1199}
1200
1202{
1203 // Tilt up the view by deltaPitch around the view center (camera moves)
1204 float pitch = mCameraPose.pitchAngle();
1205 pitch -= deltaPitch; // down key = moving camera toward terrain
1206 mCameraPose.setPitchAngle( pitch );
1207 updateCameraFromPose();
1208}
1209
1211{
1212 // Rotate clockwise the view by deltaYaw around the view center (camera moves)
1213 float yaw = mCameraPose.headingAngle();
1214 yaw -= deltaYaw; // right key = moving camera clockwise
1215 mCameraPose.setHeadingAngle( yaw );
1216 updateCameraFromPose();
1217}
1218
1220{
1221 mCameraPose.setHeadingAngle( angle );
1222 updateCameraFromPose();
1223}
1224
1225void QgsCameraController::moveView( float tx, float ty )
1226{
1227 const float yaw = mCameraPose.headingAngle();
1228 const float dist = mCameraPose.distanceFromCenterPoint();
1229 const float x = tx * dist * 0.02f;
1230 const float y = -ty * dist * 0.02f;
1231
1232 // moving with keyboard - take into account yaw of camera
1233 const float t = sqrt( x * x + y * y );
1234 const float a = atan2( y, x ) - yaw * M_PI / 180;
1235 const float dx = cos( a ) * t;
1236 const float dy = sin( a ) * t;
1237
1238 QgsVector3D center = mCameraPose.centerPoint();
1239 center.set( center.x() + dx, center.y() - dy, center.z() );
1240 mCameraPose.setCenterPoint( center );
1241 updateCameraFromPose();
1242}
1243
1245{
1246 if ( event->key() == Qt::Key_QuoteLeft )
1247 return true;
1248
1249 switch ( mCameraNavigationMode )
1250 {
1252 {
1253 switch ( event->key() )
1254 {
1255 case Qt::Key_Left:
1256 case Qt::Key_A:
1257 case Qt::Key_Right:
1258 case Qt::Key_D:
1259 case Qt::Key_Up:
1260 case Qt::Key_W:
1261 case Qt::Key_Down:
1262 case Qt::Key_S:
1263 case Qt::Key_PageUp:
1264 case Qt::Key_E:
1265 case Qt::Key_PageDown:
1266 case Qt::Key_Q:
1267 return true;
1268
1269 case Qt::Key_Escape:
1270 if ( mCaptureFpsMouseMovements )
1271 return true;
1272 break;
1273
1274 default:
1275 break;
1276 }
1277 break;
1278 }
1279
1281 {
1282 switch ( event->key() )
1283 {
1284 case Qt::Key_Left:
1285 case Qt::Key_Right:
1286 case Qt::Key_Up:
1287 case Qt::Key_Down:
1288 case Qt::Key_PageUp:
1289 case Qt::Key_PageDown:
1290 return true;
1291
1292 default:
1293 break;
1294 }
1295 break;
1296 }
1297
1299 {
1300 switch ( event->key() )
1301 {
1302 case Qt::Key_Left:
1303 case Qt::Key_Right:
1304 case Qt::Key_Up:
1305 case Qt::Key_Down:
1306 case Qt::Key_PageUp:
1307 case Qt::Key_PageDown:
1308 return true;
1309
1310 default:
1311 break;
1312 }
1313 break;
1314 }
1315 }
1316 return false;
1317}
1318
1319void QgsCameraController::depthBufferCaptured( const QImage &depthImage )
1320{
1321 mDepthBufferImage = depthImage;
1322 mDepthBufferIsReady = true;
1323 mDepthBufferNonVoidAverage = -1;
1324
1325 if ( mCurrentOperation == MouseOperation::ZoomWheel )
1326 {
1327 handleTerrainNavigationWheelZoom();
1328 }
1329}
1330
1331bool QgsCameraController::isATranslationRotationSequence( MouseOperation newOperation ) const
1332{
1333 return std::find( mTranslateOrRotate.begin(), mTranslateOrRotate.end(), newOperation ) != std::end( mTranslateOrRotate ) && std::find( mTranslateOrRotate.begin(), mTranslateOrRotate.end(), mCurrentOperation ) != std::end( mTranslateOrRotate );
1334}
1335
1336void QgsCameraController::setMouseParameters( const MouseOperation &newOperation, const QPoint &clickPoint )
1337{
1338 if ( newOperation == mCurrentOperation )
1339 {
1340 return;
1341 }
1342
1343 if ( newOperation == MouseOperation::None )
1344 {
1345 mClickPoint = QPoint();
1346 }
1347 // click point and rotation angles are updated if:
1348 // - it has never been computed
1349 // - the current and new operations are both rotation and translation
1350 // Indeed, if the sequence such as rotation -> zoom -> rotation updating mClickPoint on
1351 // the click point does not need to be updated because the relative mouse position is kept
1352 // during a zoom operation
1353 else if ( mClickPoint.isNull() || isATranslationRotationSequence( newOperation ) )
1354 {
1355 mClickPoint = clickPoint;
1356 mRotationPitch = mCameraPose.pitchAngle();
1357 mRotationYaw = mCameraPose.headingAngle();
1358 }
1359 mCurrentOperation = newOperation;
1360 mDepthBufferIsReady = false;
1361 mRotationCenterCalculated = false;
1362 mDragPointCalculated = false;
1363 mZoomPointCalculated = false;
1364
1365 if ( mCurrentOperation != MouseOperation::None && mCurrentOperation != MouseOperation::RotationCamera )
1366 {
1367 mMousePressViewCenter = mCameraPose.centerPoint() + mOrigin;
1368
1370
1371 mCameraBefore->setPosition( mCamera->position() );
1372 mCameraBefore->setViewCenter( mCamera->viewCenter() );
1373 mCameraBefore->setUpVector( mCamera->upVector() );
1374 mCameraBefore->setProjectionMatrix( mCamera->projectionMatrix() );
1375 mCameraBefore->setNearPlane( mCamera->nearPlane() );
1376 mCameraBefore->setFarPlane( mCamera->farPlane() );
1377 mCameraBefore->setAspectRatio( mCamera->aspectRatio() );
1378 mCameraBefore->setFieldOfView( mCamera->fieldOfView() );
1379 }
1380}
1381
1383{
1384 QgsVector3D diff = origin - mOrigin;
1385 mCameraPose.setCenterPoint( mCameraPose.centerPoint() - diff );
1386
1387 // update other members that depend on world coordinates
1388 mCameraBefore->setPosition( ( QgsVector3D( mCameraBefore->position() ) - diff ).toVector3D() );
1389 mCameraBefore->setViewCenter( ( QgsVector3D( mCameraBefore->viewCenter() ) - diff ).toVector3D() );
1390 mDragPoint = ( QgsVector3D( mDragPoint ) - diff ).toVector3D();
1391 mRotationCenter = ( QgsVector3D( mRotationCenter ) - diff ).toVector3D();
1392
1393 mOrigin = origin;
1394
1395 updateCameraFromPose();
1396}
Provides global constants and enumerations for use throughout the application.
Definition qgis.h:54
VerticalAxisInversion
Vertical axis inversion options for 3D views.
Definition qgis.h:4031
@ 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.
Definition qgis.h:4006
@ 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.
@ 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::NavigationMode cameraNavigationMode() const
Returns the navigation mode used by the camera.
Qgis::SceneMode sceneMode() const
Returns mode of the 3D scene - whether it is represented as a globe (when using Geocentric CRS such a...
QgsRectangle extent() const
Returns the 3D scene's 2D extent in the 3D scene's CRS.
QgsCoordinateReferenceSystem crs() const
Returns coordinate reference system used in the 3D scene.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context, which stores various information regarding which datum tran...
QgsVector3D origin() const
Returns coordinates in map CRS at which 3D scene has origin (0,0,0).
static QQuaternion rotationFromPitchHeadingAngles(float pitchAngle, float headingAngle)
Returns rotation quaternion that performs rotation around X axis by pitchAngle, followed by rotation ...
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...
Definition qgs3dutils.h:243
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.
virtual QSize size() const =0
Returns size of the engine's rendering area in pixels.
void navigationModeChanged(Qgis::NavigationMode mode)
Emitted when the navigation mode is changed using the hotkey ctrl + ~.
void setLookingAtMapPoint(const QgsVector3D &point, float distance, float pitch, float yaw)
Sets camera configuration like setLookingAtPoint(), but the point is given in map coordinates.
void readXml(const QDomElement &elem)
Reads camera configuration from the given DOM element.
float pitch() const
Returns pitch angle in degrees (0 = looking from the top, 90 = looking from the side).
~QgsCameraController() override
float yaw() const
Returns yaw angle in degrees.
void requestDepthBufferCapture()
Emitted to ask for the depth buffer image.
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 setVerticalAxisInversion(Qgis::VerticalAxisInversion inversion)
Sets the vertical axis inversion behavior.
bool willHandleKeyEvent(QKeyEvent *event)
Returns true if the camera controller will handle the specified key event, preventing it from being i...
const QgsVector3D origin() const
Returns the origin of the scene in map coordinates.
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.
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 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.
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...
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 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.
float headingAngle() const
Returns heading (yaw) angle in degrees.
QgsVector3D centerPoint() const
Returns center point (towards which point the camera is looking)
float pitchAngle() const
Returns pitch angle in degrees.
void updateCameraGlobe(Qt3DRender::QCamera *camera, double lat, double lon)
Updates camera when using a globe scene.
float distanceFromCenterPoint() const
Returns distance of the camera from the center point.
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.
void updateCamera(Qt3DRender::QCamera *camera)
Update Qt3D camera view matrix based on the pose.
QgsCoordinateReferenceSystem toGeographicCrs() const
Returns the geographic CRS associated with this CRS object.
Handles coordinate transforms between two coordinate systems.
QgsPointXY transform(const QgsPointXY &point, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Transform the point from the source CRS to the destination CRS.
Custom exception class for Coordinate Reference System related exceptions.
Represents a 2D point.
Definition qgspointxy.h:60
double y
Definition qgspointxy.h:64
double x
Definition qgspointxy.h:63
QgsPointXY center
A 3D vector (similar to QVector3D) with the difference that it uses double precision instead of singl...
Definition qgsvector3d.h:30
double y() const
Returns Y coordinate.
Definition qgsvector3d.h:49
double z() const
Returns Z coordinate.
Definition qgsvector3d.h:51
double x() const
Returns X coordinate.
Definition qgsvector3d.h:47
void setX(double x)
Sets X coordinate.
Definition qgsvector3d.h:57
void set(double x, double y, double z)
Sets vector coordinates.
Definition qgsvector3d.h:72
void setY(double y)
Sets Y coordinate.
Definition qgsvector3d.h:63
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)
Definition qgslogger.h:41
#define QgsDebugError(str)
Definition qgslogger.h:40