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