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