QGIS API Documentation 3.41.0-Master (88383c3d16f)
Loading...
Searching...
No Matches
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
30#include "qgslogger.h"
31
33 : Qt3DCore::QEntity( scene )
34 , mScene( scene )
35 , mCamera( scene->engine()->camera() )
36 , mCameraBefore( new Qt3DRender::QCamera )
37 , mMouseHandler( new Qt3DInput::QMouseHandler )
38 , mKeyboardHandler( new Qt3DInput::QKeyboardHandler )
39 , mOrigin( scene->mapSettings()->origin() )
40{
41 mMouseHandler->setSourceDevice( new Qt3DInput::QMouseDevice() );
42 connect( mMouseHandler, &Qt3DInput::QMouseHandler::positionChanged, this, &QgsCameraController::onPositionChanged );
43 connect( mMouseHandler, &Qt3DInput::QMouseHandler::wheel, this, &QgsCameraController::onWheel );
44 connect( mMouseHandler, &Qt3DInput::QMouseHandler::pressed, this, &QgsCameraController::onMousePressed );
45 connect( mMouseHandler, &Qt3DInput::QMouseHandler::released, this, &QgsCameraController::onMouseReleased );
46 addComponent( mMouseHandler );
47
48 mKeyboardHandler->setSourceDevice( new Qt3DInput::QKeyboardDevice() );
49 connect( mKeyboardHandler, &Qt3DInput::QKeyboardHandler::pressed, this, &QgsCameraController::onKeyPressed );
50 connect( mKeyboardHandler, &Qt3DInput::QKeyboardHandler::released, this, &QgsCameraController::onKeyReleased );
51 addComponent( mKeyboardHandler );
52
53 // Disable the handlers when the entity is disabled
54 connect( this, &Qt3DCore::QEntity::enabledChanged, mMouseHandler, &Qt3DInput::QMouseHandler::setEnabled );
55 connect( this, &Qt3DCore::QEntity::enabledChanged, mKeyboardHandler, &Qt3DInput::QKeyboardHandler::setEnabled );
56
57 mFpsNavTimer = new QTimer( this );
58 mFpsNavTimer->setInterval( 10 );
59 connect( mFpsNavTimer, &QTimer::timeout, this, &QgsCameraController::applyFlyModeKeyMovements );
60 mFpsNavTimer->start();
61}
62
64
65QWindow *QgsCameraController::window() const
66{
67 QgsWindow3DEngine *windowEngine = qobject_cast<QgsWindow3DEngine *>( mScene->engine() );
68 return windowEngine ? windowEngine->window() : nullptr;
69}
70
72{
73 if ( navigationMode == mCameraNavigationMode )
74 return;
75
76 mCameraNavigationMode = navigationMode;
77 mIgnoreNextMouseMove = true;
78 emit navigationModeChanged( mCameraNavigationMode );
79}
80
82{
83 if ( movementSpeed == mCameraMovementSpeed )
84 return;
85
86 // If the speed becomes 0, navigation does not work anymore
87 // If the speed becomes too important, only one walk can move the view far from the scene.
88 mCameraMovementSpeed = std::clamp( movementSpeed, 0.05, 150.0 );
89 emit cameraMovementSpeedChanged( mCameraMovementSpeed );
90}
91
93{
94 mVerticalAxisInversion = inversion;
95}
96
97void QgsCameraController::rotateCamera( float diffPitch, float diffHeading )
98{
99 const float oldPitch = mCameraPose.pitchAngle();
100 const float oldHeading = mCameraPose.headingAngle();
101 float newPitch = oldPitch + diffPitch;
102 float newHeading = oldHeading + diffHeading;
103
104 newPitch = std::clamp( newPitch, 0.f, 180.f ); // prevent going over the head
105
106 // First undo the previously applied rotation, then apply the new rotation
107 // (We can't just apply our euler angles difference because the camera may be already rotated)
108 const QQuaternion qNew = Qgs3DUtils::rotationFromPitchHeadingAngles( newPitch, newHeading );
109 const QQuaternion qOld = Qgs3DUtils::rotationFromPitchHeadingAngles( oldPitch, oldHeading );
110 const QQuaternion q = qNew * qOld.conjugated();
111
112 // get camera's view vector, rotate it to get new view center
113 const QVector3D position = mCamera->position();
114 QVector3D viewCenter = mCamera->viewCenter();
115 const QVector3D viewVector = viewCenter - position;
116 const QVector3D cameraToCenter = q * viewVector;
117 viewCenter = position + cameraToCenter;
118
119 mCameraPose.setCenterPoint( viewCenter );
120 mCameraPose.setPitchAngle( newPitch );
121 mCameraPose.setHeadingAngle( newHeading );
122 updateCameraFromPose();
123}
124
125void QgsCameraController::rotateCameraAroundPivot( float newPitch, float newHeading, const QVector3D &pivotPoint )
126{
127 const float oldPitch = mCameraPose.pitchAngle();
128 const float oldHeading = mCameraPose.headingAngle();
129
130 newPitch = std::clamp( newPitch, 0.f, 180.f ); // prevent going over the head
131
132 // First undo the previously applied rotation, then apply the new rotation
133 // (We can't just apply our euler angles difference because the camera may be already rotated)
134 const QQuaternion qNew = Qgs3DUtils::rotationFromPitchHeadingAngles( newPitch, newHeading );
135 const QQuaternion qOld = Qgs3DUtils::rotationFromPitchHeadingAngles( oldPitch, oldHeading );
136 const QQuaternion q = qNew * qOld.conjugated();
137
138 const QVector3D newViewCenter = q * ( mCamera->viewCenter() - pivotPoint ) + pivotPoint;
139
140 mCameraPose.setCenterPoint( newViewCenter );
141 mCameraPose.setPitchAngle( newPitch );
142 mCameraPose.setHeadingAngle( newHeading );
143 updateCameraFromPose();
144}
145
146void QgsCameraController::zoomCameraAroundPivot( const QVector3D &oldCameraPosition, double zoomFactor, const QVector3D &pivotPoint )
147{
148 // step 1: move camera along the line connecting reference camera position and our pivot point
149 QVector3D newCamPosition = pivotPoint + ( oldCameraPosition - pivotPoint ) * zoomFactor;
150 double newDistance = mCameraPose.distanceFromCenterPoint() * zoomFactor;
151
152 // step 2: using the new camera position and distance from center, calculate new view center
153 QQuaternion q = Qgs3DUtils::rotationFromPitchHeadingAngles( mCameraPose.pitchAngle(), mCameraPose.headingAngle() );
154 QVector3D cameraToCenter = q * QVector3D( 0, 0, -newDistance );
155 QVector3D newViewCenter = newCamPosition + cameraToCenter;
156
157 mCameraPose.setDistanceFromCenterPoint( newDistance );
158 mCameraPose.setCenterPoint( newViewCenter );
159 updateCameraFromPose();
160}
161
163{
164 Q_UNUSED( dt )
165
166 if ( mCameraChanged )
167 {
168 emit cameraChanged();
169 mCameraChanged = false;
170 }
171}
172
173void QgsCameraController::resetView( float distance )
174{
175 setViewFromTop( 0, 0, distance );
176}
177
178void QgsCameraController::setViewFromTop( float worldX, float worldY, float distance, float yaw )
179{
180 QgsCameraPose camPose;
181 QgsTerrainEntity *terrain = mScene->terrainEntity();
182 if ( terrain )
183 camPose.setCenterPoint( QgsVector3D( worldX, worldY, terrain->terrainElevationOffset() ) );
184 else
185 camPose.setCenterPoint( QgsVector3D( worldX, worldY, 0.0f ) );
187 camPose.setHeadingAngle( yaw );
188
189 // a basic setup to make frustum depth range long enough that it does not cull everything
190 mCamera->setNearPlane( distance / 2 );
191 mCamera->setFarPlane( distance * 2 );
192
193 setCameraPose( camPose );
194}
195
197{
198 return mCameraPose.centerPoint();
199}
200
201void QgsCameraController::setLookingAtPoint( const QgsVector3D &point, float distance, float pitch, float yaw )
202{
203 QgsCameraPose camPose;
204 camPose.setCenterPoint( point );
206 camPose.setPitchAngle( pitch );
207 camPose.setHeadingAngle( yaw );
208 setCameraPose( camPose );
209}
210
212{
213 if ( camPose == mCameraPose )
214 return;
215
216 mCameraPose = camPose;
217 updateCameraFromPose();
218}
219
220QDomElement QgsCameraController::writeXml( QDomDocument &doc ) const
221{
222 QDomElement elemCamera = doc.createElement( QStringLiteral( "camera" ) );
223 elemCamera.setAttribute( QStringLiteral( "x" ), mCameraPose.centerPoint().x() );
224 elemCamera.setAttribute( QStringLiteral( "y" ), mCameraPose.centerPoint().z() );
225 elemCamera.setAttribute( QStringLiteral( "elev" ), mCameraPose.centerPoint().y() );
226 elemCamera.setAttribute( QStringLiteral( "dist" ), mCameraPose.distanceFromCenterPoint() );
227 elemCamera.setAttribute( QStringLiteral( "pitch" ), mCameraPose.pitchAngle() );
228 elemCamera.setAttribute( QStringLiteral( "yaw" ), mCameraPose.headingAngle() );
229 return elemCamera;
230}
231
232void QgsCameraController::readXml( const QDomElement &elem )
233{
234 const float x = elem.attribute( QStringLiteral( "x" ) ).toFloat();
235 const float y = elem.attribute( QStringLiteral( "y" ) ).toFloat();
236 const float elev = elem.attribute( QStringLiteral( "elev" ) ).toFloat();
237 const float dist = elem.attribute( QStringLiteral( "dist" ) ).toFloat();
238 const float pitch = elem.attribute( QStringLiteral( "pitch" ) ).toFloat();
239 const float yaw = elem.attribute( QStringLiteral( "yaw" ) ).toFloat();
240 setLookingAtPoint( QgsVector3D( x, elev, y ), dist, pitch, yaw );
241}
242
243double QgsCameraController::sampleDepthBuffer( int px, int py )
244{
245 double depth = 1;
246
247 // Sample the neighbouring pixels for the closest point to the camera
248 for ( int x = px - 3; x <= px + 3; ++x )
249 {
250 for ( int y = py - 3; y <= py + 3; ++y )
251 {
252 if ( mDepthBufferImage.valid( x, y ) )
253 {
254 depth = std::min( depth, Qgs3DUtils::decodeDepth( mDepthBufferImage.pixel( x, y ) ) );
255 }
256 }
257 }
258
259 if ( depth < 1 )
260 return depth;
261
262 // Cache the computed depth, since averaging over all pixels can be expensive
263 if ( mDepthBufferNonVoidAverage != -1 )
264 return mDepthBufferNonVoidAverage;
265
266 // Returns the average of depth values that are not 1 (void area)
267 depth = 0;
268 int samplesCount = 0;
269 // Make sure we can do the cast
270 Q_ASSERT( mDepthBufferImage.format() == QImage::Format_RGB32 );
271 for ( int y = 0; y < mDepthBufferImage.height(); ++y )
272 {
273 const QRgb *line = reinterpret_cast<const QRgb *>( mDepthBufferImage.constScanLine( y ) );
274 for ( int x = 0; x < mDepthBufferImage.width(); ++x )
275 {
276 double d = Qgs3DUtils::decodeDepth( line[x] );
277 if ( d < 1 )
278 {
279 depth += d;
280 samplesCount += 1;
281 }
282 }
283 }
284
285 // if the whole buffer is white, a depth cannot be computed
286 if ( samplesCount == 0 )
287 depth = 1.0;
288 else
289 depth /= samplesCount;
290
291 mDepthBufferNonVoidAverage = depth;
292
293 return depth;
294}
295
296void QgsCameraController::updateCameraFromPose()
297{
298 if ( mCamera )
299 mCameraPose.updateCamera( mCamera );
300
301 mCameraChanged = true;
302}
303
304void QgsCameraController::moveCameraPositionBy( const QVector3D &posDiff )
305{
306 mCameraPose.setCenterPoint( mCameraPose.centerPoint() + posDiff );
307 updateCameraFromPose();
308}
309
310void QgsCameraController::onPositionChanged( Qt3DInput::QMouseEvent *mouse )
311{
312 if ( !mInputHandlersEnabled )
313 return;
314
315 QgsEventTracing::ScopedEvent traceEvent( QStringLiteral( "3D" ), QStringLiteral( "QgsCameraController::onPositionChanged" ) );
316
317 switch ( mCameraNavigationMode )
318 {
320 onPositionChangedTerrainNavigation( mouse );
321 break;
322
324 onPositionChangedFlyNavigation( mouse );
325 break;
326 }
327}
328
329bool QgsCameraController::screenPointToWorldPos( QPoint position, Qt3DRender::QCamera *mCameraBefore, double &depth, QVector3D &worldPosition )
330{
331 depth = sampleDepthBuffer( position.x(), position.y() );
332 if ( !std::isfinite( depth ) )
333 {
334 QgsDebugMsgLevel( QStringLiteral( "screenPointToWorldPos: depth is NaN or Inf. This should not happen." ), 2 );
335 return false;
336 }
337 else
338 {
339 worldPosition = Qgs3DUtils::screenPointToWorldPos( position, depth, mScene->engine()->size(), mCameraBefore );
340 if ( !std::isfinite( worldPosition.x() ) || !std::isfinite( worldPosition.y() ) || !std::isfinite( worldPosition.z() ) )
341 {
342 QgsDebugMsgLevel( QStringLiteral( "screenPointToWorldPos: position is NaN or Inf. This should not happen." ), 2 );
343 return false;
344 }
345 else
346 {
347 return true;
348 }
349 }
350}
351
352void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseEvent *mouse )
353{
354 if ( mIgnoreNextMouseMove )
355 {
356 mIgnoreNextMouseMove = false;
357 mMousePos = QPoint( mouse->x(), mouse->y() );
358 return;
359 }
360
361 const int dx = mouse->x() - mMousePos.x();
362 const int dy = mouse->y() - mMousePos.y();
363
364 const bool hasShift = ( mouse->modifiers() & Qt::ShiftModifier );
365 const bool hasCtrl = ( mouse->modifiers() & Qt::ControlModifier );
366 const bool hasLeftButton = ( mouse->buttons() & Qt::LeftButton );
367 const bool hasMiddleButton = ( mouse->buttons() & Qt::MiddleButton );
368 const bool hasRightButton = ( mouse->buttons() & Qt::RightButton );
369
370 if ( ( hasLeftButton && hasShift && !hasCtrl ) || ( hasMiddleButton && !hasShift && !hasCtrl ) )
371 {
372 // rotate/tilt using mouse (camera moves as it rotates around the clicked point)
373 setMouseParameters( MouseOperation::RotationCenter, mMousePos );
374
375 float scale = static_cast<float>( std::max( mScene->engine()->size().width(), mScene->engine()->size().height() ) );
376 float pitchDiff = 180.0f * static_cast<float>( mouse->y() - mClickPoint.y() ) / scale;
377 float yawDiff = -180.0f * static_cast<float>( mouse->x() - mClickPoint.x() ) / scale;
378
379 if ( !mDepthBufferIsReady )
380 return;
381
382 if ( !mRotationCenterCalculated )
383 {
384 double depth;
385 QVector3D worldPosition;
386 if ( screenPointToWorldPos( mClickPoint, mCameraBefore.get(), depth, worldPosition ) )
387 {
388 mRotationCenter = worldPosition;
389 mRotationDistanceFromCenter = ( mRotationCenter - mCameraBefore->position() ).length();
390 emit cameraRotationCenterChanged( mRotationCenter );
391 mRotationCenterCalculated = true;
392 }
393 }
394
395 rotateCameraAroundPivot( mRotationPitch + pitchDiff, mRotationYaw + yawDiff, mRotationCenter );
396 }
397 else if ( hasLeftButton && hasCtrl && !hasShift )
398 {
399 setMouseParameters( MouseOperation::RotationCamera );
400 // rotate/tilt using mouse (camera stays at one position as it rotates)
401 const float diffPitch = 0.2f * dy;
402 const float diffYaw = -0.2f * dx;
403 rotateCamera( diffPitch, diffYaw );
404 }
405 else if ( hasLeftButton && !hasShift && !hasCtrl )
406 {
407 // translation works as if one grabbed a point on the 3D viewer and dragged it
408 setMouseParameters( MouseOperation::Translation, mMousePos );
409
410 if ( !mDepthBufferIsReady )
411 return;
412
413 if ( !mDragPointCalculated )
414 {
415 double depth;
416 QVector3D worldPosition;
417 if ( screenPointToWorldPos( mClickPoint, mCameraBefore.get(), depth, worldPosition ) )
418 {
419 mDragDepth = depth;
420 mDragPoint = worldPosition;
421 mDragPointCalculated = true;
422 }
423 }
424
425 QVector3D cameraBeforeDragPos = mCameraBefore->position();
426
427 QVector3D moveToPosition = Qgs3DUtils::screenPointToWorldPos( mMousePos, mDragDepth, mScene->engine()->size(), mCameraBefore.get() );
428 QVector3D cameraBeforeToMoveToPos = ( moveToPosition - mCameraBefore->position() ).normalized();
429 QVector3D cameraBeforeToDragPointPos = ( mDragPoint - mCameraBefore->position() ).normalized();
430
431 // Make sure the rays are not horizontal (add small z shift if it is)
432 if ( cameraBeforeToMoveToPos.z() == 0 )
433 {
434 cameraBeforeToMoveToPos.setZ( 0.01 );
435 cameraBeforeToMoveToPos = cameraBeforeToMoveToPos.normalized();
436 }
437
438 if ( cameraBeforeToDragPointPos.z() == 0 )
439 {
440 cameraBeforeToDragPointPos.setZ( 0.01 );
441 cameraBeforeToDragPointPos = cameraBeforeToDragPointPos.normalized();
442 }
443
444 double d1 = ( mDragPoint.z() - cameraBeforeDragPos.z() ) / cameraBeforeToMoveToPos.z();
445 double d2 = ( mDragPoint.z() - cameraBeforeDragPos.z() ) / cameraBeforeToDragPointPos.z();
446
447 QVector3D from = cameraBeforeDragPos + d1 * cameraBeforeToMoveToPos;
448 QVector3D to = cameraBeforeDragPos + d2 * cameraBeforeToDragPointPos;
449
450 QVector3D shiftVector = to - from;
451
452 mCameraPose.setCenterPoint( mCameraBefore->viewCenter() + shiftVector );
453 updateCameraFromPose();
454 }
455 else if ( hasLeftButton && hasShift && hasCtrl )
456 {
457 // change the camera elevation, similar to pageUp/pageDown
458 QgsVector3D center = mCameraPose.centerPoint();
459 double tElev = mMousePos.y() - mouse->y();
460 center.set( center.x(), center.y(), center.z() + tElev * 0.5 );
461 mCameraPose.setCenterPoint( center );
462 updateCameraFromPose();
463 }
464 else if ( hasRightButton && !hasShift && !hasCtrl )
465 {
466 setMouseParameters( MouseOperation::Zoom, mMousePos );
467 if ( !mDepthBufferIsReady )
468 return;
469
470 if ( !mDragPointCalculated )
471 {
472 double depth;
473 QVector3D worldPosition;
474 if ( screenPointToWorldPos( mClickPoint, mCameraBefore.get(), depth, worldPosition ) )
475 {
476 mDragPoint = worldPosition;
477 mDragPointCalculated = true;
478 }
479 }
480
481 float oldDist = ( mCameraBefore->position() - mDragPoint ).length();
482 float newDist = oldDist;
483
484 int yOffset = 0;
485 int screenHeight = mScene->engine()->size().height();
486 QWindow *win = window();
487 if ( win )
488 {
489 yOffset = win->mapToGlobal( QPoint( 0, 0 ) ).y();
490 screenHeight = win->screen()->size().height();
491 }
492
493 // Applies smoothing
494 if ( mMousePos.y() > mClickPoint.y() ) // zoom in
495 {
496 double f = ( double ) ( mMousePos.y() - mClickPoint.y() ) / ( double ) ( screenHeight - mClickPoint.y() - yOffset );
497 f = std::max( 0.0, std::min( 1.0, f ) );
498 f = 1 - ( std::expm1( -2 * f ) ) / ( std::expm1( -2 ) );
499 newDist = newDist * f;
500 }
501 else // zoom out
502 {
503 double f = 1 - ( double ) ( mMousePos.y() + yOffset ) / ( double ) ( mClickPoint.y() + yOffset );
504 f = std::max( 0.0, std::min( 1.0, f ) );
505 f = ( std::expm1( 2 * f ) ) / ( std::expm1( 2 ) );
506 newDist = newDist + 2 * newDist * f;
507 }
508
509 double zoomFactor = newDist / oldDist;
510 zoomCameraAroundPivot( mCameraBefore->position(), zoomFactor, mDragPoint );
511 }
512
513 mMousePos = QPoint( mouse->x(), mouse->y() );
514}
515
516void QgsCameraController::zoom( float factor )
517{
518 // zoom in/out
519 float dist = mCameraPose.distanceFromCenterPoint();
520 dist -= dist * factor * 0.01f;
521 mCameraPose.setDistanceFromCenterPoint( dist );
522 updateCameraFromPose();
523}
524
525void QgsCameraController::handleTerrainNavigationWheelZoom()
526{
527 if ( !mDepthBufferIsReady )
528 return;
529
530 if ( !mZoomPointCalculated )
531 {
532 double depth;
533 QVector3D worldPosition;
534 if ( screenPointToWorldPos( mMousePos, mCameraBefore.get(), depth, worldPosition ) )
535 {
536 mZoomPoint = worldPosition;
537 mZoomPointCalculated = true;
538 }
539 }
540
541 float f = mCumulatedWheelY / ( 120.0 * 24.0 );
542
543 double oldDist = ( mZoomPoint - mCameraBefore->position() ).length();
544 double newDist = ( 1 - f ) * oldDist;
545 double zoomFactor = newDist / oldDist;
546
547 zoomCameraAroundPivot( mCameraBefore->position(), zoomFactor, mZoomPoint );
548
549 mCumulatedWheelY = 0;
550 setMouseParameters( MouseOperation::None );
551}
552
553void QgsCameraController::onWheel( Qt3DInput::QWheelEvent *wheel )
554{
555 if ( !mInputHandlersEnabled )
556 return;
557
558 switch ( mCameraNavigationMode )
559 {
561 {
562 const float scaling = ( ( wheel->modifiers() & Qt::ControlModifier ) != 0 ? 0.1f : 1.0f ) / 1000.f;
563 setCameraMovementSpeed( mCameraMovementSpeed + mCameraMovementSpeed * scaling * wheel->angleDelta().y() );
564 break;
565 }
566
568 {
569 const float scaling = ( ( wheel->modifiers() & Qt::ControlModifier ) != 0 ? 0.5f : 5.f );
570
571 // Apparently angleDelta needs to be accumulated
572 // see: https://doc.qt.io/qt-5/qwheelevent.html#angleDelta
573 mCumulatedWheelY += scaling * wheel->angleDelta().y();
574
575 if ( mCurrentOperation != MouseOperation::ZoomWheel )
576 {
577 setMouseParameters( MouseOperation::ZoomWheel );
578 // The actual zooming will happen after we get a new depth buffer
579 }
580 else
581 {
582 handleTerrainNavigationWheelZoom();
583 }
584 break;
585 }
586 }
587}
588
589void QgsCameraController::onMousePressed( Qt3DInput::QMouseEvent *mouse )
590{
591 if ( !mInputHandlersEnabled )
592 return;
593
594 mKeyboardHandler->setFocus( true );
595
596 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 ) )
597 {
598 mMousePos = QPoint( mouse->x(), mouse->y() );
599
600 if ( mCaptureFpsMouseMovements )
601 mIgnoreNextMouseMove = true;
602
603 const MouseOperation operation {
604 ( mouse->modifiers() & Qt::ControlModifier ) != 0 && mouse->button() == Qt3DInput::QMouseEvent::LeftButton ? MouseOperation::RotationCamera : MouseOperation::RotationCenter
605 };
606 setMouseParameters( operation, mMousePos );
607 }
608
609 else if ( mouse->button() == Qt3DInput::QMouseEvent::LeftButton || mouse->button() == Qt3DInput::QMouseEvent::RightButton )
610 {
611 mMousePos = QPoint( mouse->x(), mouse->y() );
612
613 if ( mCaptureFpsMouseMovements )
614 mIgnoreNextMouseMove = true;
615
616 const MouseOperation operation = ( mouse->button() == Qt3DInput::QMouseEvent::LeftButton ) ? MouseOperation::Translation : MouseOperation::Zoom;
617 setMouseParameters( operation, mMousePos );
618 }
619}
620
621void QgsCameraController::onMouseReleased( Qt3DInput::QMouseEvent *mouse )
622{
623 Q_UNUSED( mouse )
624 if ( !mInputHandlersEnabled )
625 return;
626
627
628 setMouseParameters( MouseOperation::None );
629}
630
631void QgsCameraController::onKeyPressed( Qt3DInput::QKeyEvent *event )
632{
633 if ( !mInputHandlersEnabled )
634 return;
635
636 if ( event->modifiers() & Qt::ControlModifier && event->key() == Qt::Key_QuoteLeft )
637 {
638 // switch navigation mode
639 switch ( mCameraNavigationMode )
640 {
643 break;
646 break;
647 }
648 return;
649 }
650
651 switch ( mCameraNavigationMode )
652 {
654 {
655 onKeyPressedFlyNavigation( event );
656 break;
657 }
658
660 {
661 onKeyPressedTerrainNavigation( event );
662 break;
663 }
664 }
665}
666
667void QgsCameraController::onKeyPressedTerrainNavigation( Qt3DInput::QKeyEvent *event )
668{
669 const bool hasShift = ( event->modifiers() & Qt::ShiftModifier );
670 const bool hasCtrl = ( event->modifiers() & Qt::ControlModifier );
671
672 int tx = 0, ty = 0, tElev = 0;
673 switch ( event->key() )
674 {
675 case Qt::Key_Left:
676 tx -= 1;
677 break;
678 case Qt::Key_Right:
679 tx += 1;
680 break;
681
682 case Qt::Key_Up:
683 ty += 1;
684 break;
685 case Qt::Key_Down:
686 ty -= 1;
687 break;
688
689 case Qt::Key_PageDown:
690 tElev -= 1;
691 break;
692 case Qt::Key_PageUp:
693 tElev += 1;
694 break;
695 }
696
697 if ( tx || ty )
698 {
699 if ( !hasShift && !hasCtrl )
700 {
701 moveView( tx, ty );
702 }
703 else if ( hasShift && !hasCtrl )
704 {
705 // rotate/tilt using keyboard (camera moves as it rotates around its view center)
708 }
709 else if ( hasCtrl && !hasShift )
710 {
711 // rotate/tilt using keyboard (camera stays at one position as it rotates)
712 const float diffPitch = ty; // down key = rotating camera down
713 const float diffYaw = -tx; // right key = rotating camera to the right
714 rotateCamera( diffPitch, diffYaw );
715 }
716 }
717
718 if ( tElev )
719 {
720 QgsVector3D center = mCameraPose.centerPoint();
721 center.set( center.x(), center.y(), center.z() + tElev * 10 );
722 mCameraPose.setCenterPoint( center );
723 updateCameraFromPose();
724 }
725}
726
727void QgsCameraController::onKeyPressedFlyNavigation( Qt3DInput::QKeyEvent *event )
728{
729 switch ( event->key() )
730 {
731 case Qt::Key_QuoteLeft:
732 {
733 // toggle mouse lock mode
734 mCaptureFpsMouseMovements = !mCaptureFpsMouseMovements;
735 mIgnoreNextMouseMove = true;
736 if ( mCaptureFpsMouseMovements )
737 {
738 qApp->setOverrideCursor( QCursor( Qt::BlankCursor ) );
739 }
740 else
741 {
742 qApp->restoreOverrideCursor();
743 }
744 return;
745 }
746
747 case Qt::Key_Escape:
748 {
749 // always exit mouse lock mode
750 if ( mCaptureFpsMouseMovements )
751 {
752 mCaptureFpsMouseMovements = false;
753 mIgnoreNextMouseMove = true;
754 qApp->restoreOverrideCursor();
755 return;
756 }
757 break;
758 }
759
760 default:
761 break;
762 }
763
764 if ( event->isAutoRepeat() )
765 return;
766
767 mDepressedKeys.insert( event->key() );
768}
769
770void QgsCameraController::walkView( double tx, double ty, double tz )
771{
772 const QVector3D cameraUp = mCamera->upVector().normalized();
773 const QVector3D cameraFront = ( QVector3D( mCameraPose.centerPoint().x(), mCameraPose.centerPoint().y(), mCameraPose.centerPoint().z() ) - mCamera->position() ).normalized();
774 const QVector3D cameraLeft = QVector3D::crossProduct( cameraUp, cameraFront );
775
776 QVector3D cameraPosDiff( 0.0f, 0.0f, 0.0f );
777
778 if ( tx != 0.0 )
779 {
780 cameraPosDiff += static_cast<float>( tx ) * cameraFront;
781 }
782 if ( ty != 0.0 )
783 {
784 cameraPosDiff += static_cast<float>( ty ) * cameraLeft;
785 }
786 if ( tz != 0.0 )
787 {
788 cameraPosDiff += static_cast<float>( tz ) * QVector3D( 0.0f, 0.0f, 1.0f );
789 }
790
791 moveCameraPositionBy( cameraPosDiff );
792}
793
794void QgsCameraController::applyFlyModeKeyMovements()
795{
796 // shift = "run", ctrl = "slow walk"
797 const bool shiftPressed = mDepressedKeys.contains( Qt::Key_Shift );
798 const bool ctrlPressed = mDepressedKeys.contains( Qt::Key_Control );
799
800 const double movementSpeed = mCameraMovementSpeed * ( shiftPressed ? 2 : 1 ) * ( ctrlPressed ? 0.1 : 1 );
801
802 bool changed = false;
803 double x = 0.0;
804 double y = 0.0;
805 double z = 0.0;
806 if ( mDepressedKeys.contains( Qt::Key_Left ) || mDepressedKeys.contains( Qt::Key_A ) )
807 {
808 changed = true;
809 y += movementSpeed;
810 }
811
812 if ( mDepressedKeys.contains( Qt::Key_Right ) || mDepressedKeys.contains( Qt::Key_D ) )
813 {
814 changed = true;
815 y -= movementSpeed;
816 }
817
818 if ( mDepressedKeys.contains( Qt::Key_Up ) || mDepressedKeys.contains( Qt::Key_W ) )
819 {
820 changed = true;
821 x += movementSpeed;
822 }
823
824 if ( mDepressedKeys.contains( Qt::Key_Down ) || mDepressedKeys.contains( Qt::Key_S ) )
825 {
826 changed = true;
827 x -= movementSpeed;
828 }
829
830 // note -- vertical axis movements are slower by default then horizontal ones, as GIS projects
831 // tend to have much more limited elevation range vs ground range
832 static constexpr double ELEVATION_MOVEMENT_SCALE = 0.5;
833 if ( mDepressedKeys.contains( Qt::Key_PageUp ) || mDepressedKeys.contains( Qt::Key_E ) )
834 {
835 changed = true;
836 z += ELEVATION_MOVEMENT_SCALE * movementSpeed;
837 }
838
839 if ( mDepressedKeys.contains( Qt::Key_PageDown ) || mDepressedKeys.contains( Qt::Key_Q ) )
840 {
841 changed = true;
842 z -= ELEVATION_MOVEMENT_SCALE * movementSpeed;
843 }
844
845 if ( changed )
846 walkView( x, y, z );
847}
848
849void QgsCameraController::onPositionChangedFlyNavigation( Qt3DInput::QMouseEvent *mouse )
850{
851 const bool hasMiddleButton = ( mouse->buttons() & Qt::MiddleButton );
852 const bool hasRightButton = ( mouse->buttons() & Qt::RightButton );
853
854 const double dx = mCaptureFpsMouseMovements ? QCursor::pos().x() - mMousePos.x() : mouse->x() - mMousePos.x();
855 const double dy = mCaptureFpsMouseMovements ? QCursor::pos().y() - mMousePos.y() : mouse->y() - mMousePos.y();
856 mMousePos = mCaptureFpsMouseMovements ? QCursor::pos() : QPoint( mouse->x(), mouse->y() );
857
858 if ( mIgnoreNextMouseMove )
859 {
860 mIgnoreNextMouseMove = false;
861 return;
862 }
863
864 if ( hasMiddleButton )
865 {
866 // middle button drag = pan camera in place (strafe)
867 const QVector3D cameraUp = mCamera->upVector().normalized();
868 const QVector3D cameraFront = ( QVector3D( mCameraPose.centerPoint().x(), mCameraPose.centerPoint().y(), mCameraPose.centerPoint().z() ) - mCamera->position() ).normalized();
869 const QVector3D cameraLeft = QVector3D::crossProduct( cameraUp, cameraFront );
870 const QVector3D cameraPosDiff = -dx * cameraLeft - dy * cameraUp;
871 moveCameraPositionBy( mCameraMovementSpeed * cameraPosDiff / 10.0 );
872 }
873 else if ( hasRightButton )
874 {
875 // right button drag = camera dolly
876 const QVector3D cameraFront = ( QVector3D( mCameraPose.centerPoint().x(), mCameraPose.centerPoint().y(), mCameraPose.centerPoint().z() ) - mCamera->position() ).normalized();
877 const QVector3D cameraPosDiff = dy * cameraFront;
878 moveCameraPositionBy( mCameraMovementSpeed * cameraPosDiff / 5.0 );
879 }
880 else
881 {
882 if ( mCaptureFpsMouseMovements )
883 {
884 float diffPitch = -0.2f * dy;
885 switch ( mVerticalAxisInversion )
886 {
888 diffPitch *= -1;
889 break;
890
893 break;
894 }
895
896 const float diffYaw = -0.2f * dx;
897 rotateCamera( diffPitch, diffYaw );
898 }
899 else if ( mouse->buttons() & Qt::LeftButton )
900 {
901 float diffPitch = -0.2f * dy;
902 switch ( mVerticalAxisInversion )
903 {
906 diffPitch *= -1;
907 break;
908
910 break;
911 }
912 const float diffYaw = -0.2f * dx;
913 rotateCamera( diffPitch, diffYaw );
914 }
915 }
916
917 if ( mCaptureFpsMouseMovements )
918 {
919 mIgnoreNextMouseMove = true;
920
921 // reset cursor back to center of map widget
922 emit setCursorPosition( QPoint( mScene->engine()->size().width() / 2, mScene->engine()->size().height() / 2 ) );
923 }
924}
925
926void QgsCameraController::onKeyReleased( Qt3DInput::QKeyEvent *event )
927{
928 if ( !mInputHandlersEnabled )
929 return;
930
931 if ( event->isAutoRepeat() )
932 return;
933
934 mDepressedKeys.remove( event->key() );
935}
936
938{
939 // Tilt up the view by deltaPitch around the view center (camera moves)
940 float pitch = mCameraPose.pitchAngle();
941 pitch -= deltaPitch; // down key = moving camera toward terrain
942 mCameraPose.setPitchAngle( pitch );
943 updateCameraFromPose();
944}
945
947{
948 // Rotate clockwise the view by deltaYaw around the view center (camera moves)
949 float yaw = mCameraPose.headingAngle();
950 yaw -= deltaYaw; // right key = moving camera clockwise
951 mCameraPose.setHeadingAngle( yaw );
952 updateCameraFromPose();
953}
954
956{
957 mCameraPose.setHeadingAngle( angle );
958 updateCameraFromPose();
959}
960
961void QgsCameraController::moveView( float tx, float ty )
962{
963 const float yaw = mCameraPose.headingAngle();
964 const float dist = mCameraPose.distanceFromCenterPoint();
965 const float x = tx * dist * 0.02f;
966 const float y = -ty * dist * 0.02f;
967
968 // moving with keyboard - take into account yaw of camera
969 const float t = sqrt( x * x + y * y );
970 const float a = atan2( y, x ) - yaw * M_PI / 180;
971 const float dx = cos( a ) * t;
972 const float dy = sin( a ) * t;
973
974 QgsVector3D center = mCameraPose.centerPoint();
975 center.set( center.x() + dx, center.y() - dy, center.z() );
976 mCameraPose.setCenterPoint( center );
977 updateCameraFromPose();
978}
979
981{
982 if ( event->key() == Qt::Key_QuoteLeft )
983 return true;
984
985 switch ( mCameraNavigationMode )
986 {
988 {
989 switch ( event->key() )
990 {
991 case Qt::Key_Left:
992 case Qt::Key_A:
993 case Qt::Key_Right:
994 case Qt::Key_D:
995 case Qt::Key_Up:
996 case Qt::Key_W:
997 case Qt::Key_Down:
998 case Qt::Key_S:
999 case Qt::Key_PageUp:
1000 case Qt::Key_E:
1001 case Qt::Key_PageDown:
1002 case Qt::Key_Q:
1003 return true;
1004
1005 case Qt::Key_Escape:
1006 if ( mCaptureFpsMouseMovements )
1007 return true;
1008 break;
1009
1010 default:
1011 break;
1012 }
1013 break;
1014 }
1015
1017 {
1018 switch ( event->key() )
1019 {
1020 case Qt::Key_Left:
1021 case Qt::Key_Right:
1022 case Qt::Key_PageUp:
1023 case Qt::Key_PageDown:
1024 return true;
1025
1026 default:
1027 break;
1028 }
1029 break;
1030 }
1031 }
1032 return false;
1033}
1034
1035void QgsCameraController::depthBufferCaptured( const QImage &depthImage )
1036{
1037 mDepthBufferImage = depthImage;
1038 mDepthBufferIsReady = true;
1039 mDepthBufferNonVoidAverage = -1;
1040
1041 if ( mCurrentOperation == MouseOperation::ZoomWheel )
1042 {
1043 handleTerrainNavigationWheelZoom();
1044 }
1045}
1046
1047bool QgsCameraController::isATranslationRotationSequence( MouseOperation newOperation ) const
1048{
1049 return std::find( mTranslateOrRotate.begin(), mTranslateOrRotate.end(), newOperation ) != std::end( mTranslateOrRotate ) && std::find( mTranslateOrRotate.begin(), mTranslateOrRotate.end(), mCurrentOperation ) != std::end( mTranslateOrRotate );
1050}
1051
1052void QgsCameraController::setMouseParameters( const MouseOperation &newOperation, const QPoint &clickPoint )
1053{
1054 if ( newOperation == mCurrentOperation )
1055 {
1056 return;
1057 }
1058
1059 if ( newOperation == MouseOperation::None )
1060 {
1061 mClickPoint = QPoint();
1062 }
1063 // click point and rotation angles are updated if:
1064 // - it has never been computed
1065 // - the current and new operations are both rotation and translation
1066 // Indeed, if the sequence such as rotation -> zoom -> rotation updating mClickPoint on
1067 // the click point does not need to be updated because the relative mouse position is kept
1068 // during a zoom operation
1069 else if ( mClickPoint.isNull() || isATranslationRotationSequence( newOperation ) )
1070 {
1071 mClickPoint = clickPoint;
1072 mRotationPitch = mCameraPose.pitchAngle();
1073 mRotationYaw = mCameraPose.headingAngle();
1074 }
1075 mCurrentOperation = newOperation;
1076 mDepthBufferIsReady = false;
1077 mRotationCenterCalculated = false;
1078 mDragPointCalculated = false;
1079 mZoomPointCalculated = false;
1080
1081 if ( mCurrentOperation != MouseOperation::None && mCurrentOperation != MouseOperation::RotationCamera )
1082 {
1084
1085 mCameraBefore->setProjectionMatrix( mCamera->projectionMatrix() );
1086 mCameraBefore->setNearPlane( mCamera->nearPlane() );
1087 mCameraBefore->setFarPlane( mCamera->farPlane() );
1088 mCameraBefore->setAspectRatio( mCamera->aspectRatio() );
1089 mCameraBefore->setFieldOfView( mCamera->fieldOfView() );
1090 mCameraPose.updateCamera( mCameraBefore.get() );
1091 }
1092}
1093
1095{
1096 QgsVector3D diff = origin - mOrigin;
1097 mCameraPose.setCenterPoint( mCameraPose.centerPoint() - diff );
1098
1099 // update other members that depend on world coordinates
1100 mCameraBefore->setPosition( ( QgsVector3D( mCameraBefore->position() ) - diff ).toVector3D() );
1101 mCameraBefore->setViewCenter( ( QgsVector3D( mCameraBefore->viewCenter() ) - diff ).toVector3D() );
1102 mDragPoint = ( QgsVector3D( mDragPoint ) - diff ).toVector3D();
1103 mRotationCenter = ( QgsVector3D( mRotationCenter ) - diff ).toVector3D();
1104
1105 mOrigin = origin;
1106
1107 updateCameraFromPose();
1108}
VerticalAxisInversion
Vertical axis inversion options for 3D views.
Definition qgis.h:3967
@ 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:3955
@ TerrainBased
The default navigation based on the terrain.
@ Walk
Uses WASD keys or arrows to navigate in walking (first person) manner.
QgsAbstract3DEngine * engine() const
Returns the abstract 3D engine.
QgsTerrainEntity * terrainEntity()
Returns terrain entity (may be temporarily nullptr)
static QQuaternion rotationFromPitchHeadingAngles(float pitchAngle, float headingAngle)
Returns rotation quaternion that performs rotation around X axis by pitchAngle, followed by rotation ...
static double decodeDepth(const QRgb &pixel)
Decodes the depth value from the pixel's color value The depth value is encoded from OpenGL side (the...
Definition qgs3dutils.h:243
static QVector3D screenPointToWorldPos(const QPoint &screenPoint, double depth, const QSize &screenSize, Qt3DRender::QCamera *camera)
Converts the clicked mouse position to the corresponding 3D world coordinates.
virtual QSize size() const =0
Returns size of the engine's rendering area in pixels.
void navigationModeChanged(Qgis::NavigationMode mode)
Emitted when the navigation mode is changed using the hotkey ctrl + ~.
void 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 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...
float distance() const
Returns distance of the camera from the point it is looking at.
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 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.
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 zoom(float factor)
Zoom the map by factor.
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 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 setCameraPose(const QgsCameraPose &camPose)
Sets camera pose.
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.
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.
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.
Class for storage of 3D vectors similar to QVector3D, with the difference that it uses double precisi...
Definition qgsvector3d.h:31
double y() const
Returns Y coordinate.
Definition qgsvector3d.h:50
double z() const
Returns Z coordinate.
Definition qgsvector3d.h:52
double x() const
Returns X coordinate.
Definition qgsvector3d.h:48
void set(double x, double y, double z)
Sets vector coordinates.
Definition qgsvector3d.h:73
QWindow * window()
Returns the internal 3D window where all the rendered output is displayed.
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:41