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