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