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