QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
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 "qgsraycastingutils_p.h"
18 #include "qgsterrainentity_p.h"
19 #include "qgsvector3d.h"
20 #include "qgssettings.h"
21 
22 #include "qgis.h"
23 
24 #include <QDomDocument>
25 #include <Qt3DRender/QCamera>
26 #include <Qt3DRender/QObjectPicker>
27 #include <Qt3DRender/QPickEvent>
28 #include <Qt3DInput>
29 
30 #include "qgslogger.h"
31 
32 QgsCameraController::QgsCameraController( Qt3DCore::QNode *parent )
33  : Qt3DCore::QEntity( parent )
34  , mMouseDevice( new Qt3DInput::QMouseDevice() )
35  , mKeyboardDevice( new Qt3DInput::QKeyboardDevice() )
36  , mMouseHandler( new Qt3DInput::QMouseHandler )
37  , mKeyboardHandler( new Qt3DInput::QKeyboardHandler )
38 {
39  mMouseHandler->setSourceDevice( mMouseDevice );
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( mKeyboardDevice );
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::QMouseHandler::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 {
71  if ( navigationMode == mCameraNavigationMode )
72  return;
73 
74  mCameraNavigationMode = navigationMode;
75  mIgnoreNextMouseMove = true;
76 }
77 
78 void QgsCameraController::setCameraMovementSpeed( double movementSpeed )
79 {
80  if ( movementSpeed == mCameraMovementSpeed )
81  return;
82 
83  mCameraMovementSpeed = movementSpeed;
84  emit cameraMovementSpeedChanged( mCameraMovementSpeed );
85 }
86 
88 {
89  mVerticalAxisInversion = inversion;
90 }
91 
92 void QgsCameraController::setTerrainEntity( QgsTerrainEntity *te )
93 {
94  mTerrainEntity = te;
95 
96  // object picker for terrain for correct map panning
97  connect( te->terrainPicker(), &Qt3DRender::QObjectPicker::pressed, this, &QgsCameraController::onPickerMousePressed );
98 }
99 
100 void QgsCameraController::setCamera( Qt3DRender::QCamera *camera )
101 {
102  if ( mCamera == camera )
103  return;
104  mCamera = camera;
105 
106  mCameraPose.updateCamera( mCamera ); // initial setup
107 
108  // TODO: set camera's parent if not set already?
109  // TODO: registerDestructionHelper (?)
110  emit cameraChanged();
111 }
112 
113 void QgsCameraController::setViewport( QRect viewport )
114 {
115  if ( mViewport == viewport )
116  return;
117 
118  mViewport = viewport;
119  emit viewportChanged();
120 }
121 
122 
123 static QVector3D unproject( QVector3D v, const QMatrix4x4 &modelView, const QMatrix4x4 &projection, QRect viewport )
124 {
125  // Reimplementation of QVector3D::unproject() - see qtbase/src/gui/math3d/qvector3d.cpp
126  // The only difference is that the original implementation uses tolerance 1e-5
127  // (see qFuzzyIsNull()) as a protection against division by zero. For us it is however
128  // common to get lower values (e.g. as low as 1e-8 when zoomed out to the whole Earth with web mercator).
129 
130  QMatrix4x4 inverse = QMatrix4x4( projection * modelView ).inverted();
131 
132  QVector4D tmp( v, 1.0f );
133  tmp.setX( ( tmp.x() - float( viewport.x() ) ) / float( viewport.width() ) );
134  tmp.setY( ( tmp.y() - float( viewport.y() ) ) / float( viewport.height() ) );
135  tmp = tmp * 2.0f - QVector4D( 1.0f, 1.0f, 1.0f, 1.0f );
136 
137  QVector4D obj = inverse * tmp;
138  if ( qgsDoubleNear( obj.w(), 0, 1e-10 ) )
139  obj.setW( 1.0f );
140  obj /= obj.w();
141  return obj.toVector3D();
142 }
143 
144 
145 float find_x_on_line( float x0, float y0, float x1, float y1, float y )
146 {
147  float d_x = x1 - x0;
148  float d_y = y1 - y0;
149  float k = ( y - y0 ) / d_y; // TODO: can we have d_y == 0 ?
150  return x0 + k * d_x;
151 }
152 
153 QPointF screen_point_to_point_on_plane( QPointF pt, QRect viewport, Qt3DRender::QCamera *camera, float y )
154 {
155  // get two points of the ray
156  QVector3D l0 = unproject( QVector3D( pt.x(), viewport.height() - pt.y(), 0 ), camera->viewMatrix(), camera->projectionMatrix(), viewport );
157  QVector3D l1 = unproject( QVector3D( pt.x(), viewport.height() - pt.y(), 1 ), camera->viewMatrix(), camera->projectionMatrix(), viewport );
158 
159  QVector3D p0( 0, y, 0 ); // a point on the plane
160  QVector3D n( 0, 1, 0 ); // normal of the plane
161  QVector3D l = l1 - l0; // vector in the direction of the line
162  float d = QVector3D::dotProduct( p0 - l0, n ) / QVector3D::dotProduct( l, n );
163  QVector3D p = d * l + l0;
164 
165  return QPointF( p.x(), p.z() );
166 }
167 
168 
169 void QgsCameraController::rotateCamera( float diffPitch, float diffYaw )
170 {
171  float pitch = mCameraPose.pitchAngle();
172  float yaw = mCameraPose.headingAngle();
173 
174  if ( pitch + diffPitch > 180 )
175  diffPitch = 180 - pitch; // prevent going over the head
176  if ( pitch + diffPitch < 0 )
177  diffPitch = 0 - pitch; // prevent going over the head
178 
179  // Is it always going to be love/hate relationship with quaternions???
180  // This quaternion combines two rotations:
181  // - first it undoes the previously applied rotation so we have do not have any rotation compared to world coords
182  // - then it applies new rotation
183  // (We can't just apply our euler angles difference because the camera may be already rotated)
184  QQuaternion q = QQuaternion::fromEulerAngles( pitch + diffPitch, yaw + diffYaw, 0 ) *
185  QQuaternion::fromEulerAngles( pitch, yaw, 0 ).conjugated();
186 
187  // get camera's view vector, rotate it to get new view center
188  QVector3D position = mCamera->position();
189  QVector3D viewCenter = mCamera->viewCenter();
190  QVector3D viewVector = viewCenter - position;
191  QVector3D cameraToCenter = q * viewVector;
192  viewCenter = position + cameraToCenter;
193 
194  mCameraPose.setCenterPoint( viewCenter );
195  mCameraPose.setPitchAngle( pitch + diffPitch );
196  mCameraPose.setHeadingAngle( yaw + diffYaw );
197 }
198 
199 
201 {
202  Q_UNUSED( dt )
203 }
204 
205 void QgsCameraController::resetView( float distance )
206 {
207  setViewFromTop( 0, 0, distance );
208 }
209 
210 void QgsCameraController::setViewFromTop( float worldX, float worldY, float distance, float yaw )
211 {
212  QgsCameraPose camPose;
213  if ( mTerrainEntity )
214  camPose.setCenterPoint( QgsVector3D( worldX, mTerrainEntity->terrainElevationOffset(), worldY ) );
215  else
216  camPose.setCenterPoint( QgsVector3D( worldX, 0.0f, worldY ) );
218  camPose.setHeadingAngle( yaw );
219 
220  // a basic setup to make frustum depth range long enough that it does not cull everything
221  mCamera->setNearPlane( distance / 2 );
222  mCamera->setFarPlane( distance * 2 );
223 
224  setCameraPose( camPose );
225 }
226 
228 {
229  return mCameraPose.centerPoint();
230 }
231 
232 void QgsCameraController::setLookingAtPoint( const QgsVector3D &point, float distance, float pitch, float yaw )
233 {
234  QgsCameraPose camPose;
235  camPose.setCenterPoint( point );
237  camPose.setPitchAngle( pitch );
238  camPose.setHeadingAngle( yaw );
239  setCameraPose( camPose );
240 }
241 
243 {
244  if ( camPose == mCameraPose )
245  return;
246 
247  mCameraPose = camPose;
248 
249  if ( mCamera )
250  mCameraPose.updateCamera( mCamera );
251 
252  emit cameraChanged();
253 }
254 
255 QDomElement QgsCameraController::writeXml( QDomDocument &doc ) const
256 {
257  QDomElement elemCamera = doc.createElement( QStringLiteral( "camera" ) );
258  elemCamera.setAttribute( QStringLiteral( "x" ), mCameraPose.centerPoint().x() );
259  elemCamera.setAttribute( QStringLiteral( "y" ), mCameraPose.centerPoint().z() );
260  elemCamera.setAttribute( QStringLiteral( "elev" ), mCameraPose.centerPoint().y() );
261  elemCamera.setAttribute( QStringLiteral( "dist" ), mCameraPose.distanceFromCenterPoint() );
262  elemCamera.setAttribute( QStringLiteral( "pitch" ), mCameraPose.pitchAngle() );
263  elemCamera.setAttribute( QStringLiteral( "yaw" ), mCameraPose.headingAngle() );
264  return elemCamera;
265 }
266 
267 void QgsCameraController::readXml( const QDomElement &elem )
268 {
269  float x = elem.attribute( QStringLiteral( "x" ) ).toFloat();
270  float y = elem.attribute( QStringLiteral( "y" ) ).toFloat();
271  float elev = elem.attribute( QStringLiteral( "elev" ) ).toFloat();
272  float dist = elem.attribute( QStringLiteral( "dist" ) ).toFloat();
273  float pitch = elem.attribute( QStringLiteral( "pitch" ) ).toFloat();
274  float yaw = elem.attribute( QStringLiteral( "yaw" ) ).toFloat();
275  setLookingAtPoint( QgsVector3D( x, elev, y ), dist, pitch, yaw );
276 }
277 
278 void QgsCameraController::updateCameraFromPose( bool centerPointChanged )
279 {
280  if ( std::isnan( mCameraPose.centerPoint().x() ) || std::isnan( mCameraPose.centerPoint().y() ) || std::isnan( mCameraPose.centerPoint().z() ) )
281  {
282  // something went horribly wrong but we need to at least try to fix it somehow
283  qWarning() << "camera position got NaN!";
284  mCameraPose.setCenterPoint( QgsVector3D( 0, 0, 0 ) );
285  }
286 
287  if ( mCameraPose.pitchAngle() > 180 )
288  mCameraPose.setPitchAngle( 180 ); // prevent going over the head
289  if ( mCameraPose.pitchAngle() < 0 )
290  mCameraPose.setPitchAngle( 0 ); // prevent going over the head
291  if ( mCameraPose.distanceFromCenterPoint() < 10 )
292  mCameraPose.setDistanceFromCenterPoint( 10 );
293 
294  if ( mCamera )
295  mCameraPose.updateCamera( mCamera );
296 
297  if ( mCamera && mTerrainEntity && centerPointChanged )
298  {
299  // figure out our distance from terrain and update the camera's view center
300  // so that camera tilting and rotation is around a point on terrain, not an point at fixed elevation
301  QVector3D intersectionPoint;
302  QgsRayCastingUtils::Ray3D ray = QgsRayCastingUtils::rayForCameraCenter( mCamera );
303  if ( mTerrainEntity->rayIntersection( ray, intersectionPoint ) )
304  {
305  float dist = ( intersectionPoint - mCamera->position() ).length();
306  mCameraPose.setDistanceFromCenterPoint( dist );
307  mCameraPose.setCenterPoint( QgsVector3D( intersectionPoint ) );
308  mCameraPose.updateCamera( mCamera );
309  }
310  else
311  {
312  QgsVector3D centerPoint = mCameraPose.centerPoint();
313  centerPoint.set( centerPoint.x(), mTerrainEntity->terrainElevationOffset(), centerPoint.z() );
314  mCameraPose.setCenterPoint( centerPoint );
315  }
316  }
317 
318  emit cameraChanged();
319 }
320 
321 void QgsCameraController::moveCameraPositionBy( const QVector3D &posDiff )
322 {
323  mCameraPose.setCenterPoint( mCameraPose.centerPoint() + posDiff );
324 
325  if ( mCameraPose.pitchAngle() > 180 )
326  mCameraPose.setPitchAngle( 180 ); // prevent going over the head
327  if ( mCameraPose.pitchAngle() < 0 )
328  mCameraPose.setPitchAngle( 0 ); // prevent going over the head
329  if ( mCameraPose.distanceFromCenterPoint() < 10 )
330  mCameraPose.setDistanceFromCenterPoint( 10 );
331 
332  if ( mCamera )
333  mCameraPose.updateCamera( mCamera );
334 
335  emit cameraChanged();
336 
337 }
338 
339 void QgsCameraController::onPositionChanged( Qt3DInput::QMouseEvent *mouse )
340 {
341  switch ( mCameraNavigationMode )
342  {
344  onPositionChangedTerrainNavigation( mouse );
345  break;
346 
347  case WalkNavigation:
348  onPositionChangedFlyNavigation( mouse );
349  break;
350  }
351 }
352 
353 void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseEvent *mouse )
354 {
355  if ( mIgnoreNextMouseMove )
356  {
357  mIgnoreNextMouseMove = false;
358  mMousePos = QPoint( mouse->x(), mouse->y() );
359  return;
360  }
361 
362  int dx = mouse->x() - mMousePos.x();
363  int dy = mouse->y() - mMousePos.y();
364 
365  bool hasShift = ( mouse->modifiers() & Qt::ShiftModifier );
366  bool hasCtrl = ( mouse->modifiers() & Qt::ControlModifier );
367  bool hasLeftButton = ( mouse->buttons() & Qt::LeftButton );
368  bool hasMiddleButton = ( mouse->buttons() & Qt::MiddleButton );
369  bool hasRightButton = ( mouse->buttons() & Qt::RightButton );
370 
371  if ( ( hasLeftButton && hasShift && !hasCtrl ) || ( hasMiddleButton && !hasShift && !hasCtrl ) )
372  {
373  // rotate/tilt using mouse (camera moves as it rotates around its view center)
374  float pitch = mCameraPose.pitchAngle();
375  float yaw = mCameraPose.headingAngle();
376  pitch += 0.2f * dy;
377  yaw -= 0.2f * dx;
378  mCameraPose.setPitchAngle( pitch );
379  mCameraPose.setHeadingAngle( yaw );
380  updateCameraFromPose();
381  }
382  else if ( hasLeftButton && hasCtrl && !hasShift )
383  {
384  // rotate/tilt using mouse (camera stays at one position as it rotates)
385  float diffPitch = 0.2f * dy;
386  float diffYaw = - 0.2f * dx;
387  rotateCamera( diffPitch, diffYaw );
388  updateCameraFromPose( true );
389  }
390  else if ( hasLeftButton && !hasShift && !hasCtrl )
391  {
392  // translation works as if one grabbed a point on the plane and dragged it
393  // i.e. find out x,z of the previous mouse point, find out x,z of the current mouse point
394  // and use the difference
395 
396  float z = mLastPressedHeight;
397  QPointF p1 = screen_point_to_point_on_plane( QPointF( mMousePos.x(), mMousePos.y() ), mViewport, mCamera, z );
398  QPointF p2 = screen_point_to_point_on_plane( QPointF( mouse->x(), mouse->y() ), mViewport, mCamera, z );
399 
400  QgsVector3D center = mCameraPose.centerPoint();
401  center.set( center.x() - ( p2.x() - p1.x() ), center.y(), center.z() - ( p2.y() - p1.y() ) );
402  mCameraPose.setCenterPoint( center );
403  updateCameraFromPose( true );
404  }
405  else if ( hasRightButton && !hasShift && !hasCtrl )
406  {
407  zoom( dy );
408  }
409 
410  mMousePos = QPoint( mouse->x(), mouse->y() );
411 }
412 
413 
414 
415 void QgsCameraController::zoom( float factor )
416 {
417  // zoom in/out
418  float dist = mCameraPose.distanceFromCenterPoint();
419  dist -= dist * factor * 0.01f;
420  mCameraPose.setDistanceFromCenterPoint( dist );
421  updateCameraFromPose();
422 }
423 
424 void QgsCameraController::onWheel( Qt3DInput::QWheelEvent *wheel )
425 {
426  switch ( mCameraNavigationMode )
427  {
429  {
430  float scaling = ( ( wheel->modifiers() & Qt::ControlModifier ) ? 0.1f : 1.0f ) / 1000.f;
431  setCameraMovementSpeed( mCameraMovementSpeed + mCameraMovementSpeed * scaling * wheel->angleDelta().y() );
432  break;
433  }
434 
436  {
437  float scaling = ( ( wheel->modifiers() & Qt::ControlModifier ) ? 0.1f : 1.0f ) / 1000.f;
438  float dist = mCameraPose.distanceFromCenterPoint();
439  dist -= dist * scaling * wheel->angleDelta().y();
440  mCameraPose.setDistanceFromCenterPoint( dist );
441  updateCameraFromPose();
442  break;
443  }
444  }
445 }
446 
447 void QgsCameraController::onMousePressed( Qt3DInput::QMouseEvent *mouse )
448 {
449  Q_UNUSED( mouse )
450  mKeyboardHandler->setFocus( true );
451  if ( mouse->button() == Qt3DInput::QMouseEvent::LeftButton || mouse->button() == Qt3DInput::QMouseEvent::RightButton || mouse->button() == Qt3DInput::QMouseEvent::MiddleButton )
452  {
453  mMousePos = QPoint( mouse->x(), mouse->y() );
454  mPressedButton = mouse->button();
455  mMousePressed = true;
456  if ( mCaptureFpsMouseMovements )
457  mIgnoreNextMouseMove = true;
458  }
459 }
460 
461 void QgsCameraController::onMouseReleased( Qt3DInput::QMouseEvent *mouse )
462 {
463  Q_UNUSED( mouse )
464  mPressedButton = Qt3DInput::QMouseEvent::NoButton;
465  mMousePressed = false;
466 }
467 
468 void QgsCameraController::onKeyPressed( Qt3DInput::QKeyEvent *event )
469 {
470  if ( event->modifiers() & Qt::ControlModifier && event->key() == Qt::Key_QuoteLeft )
471  {
472  // switch navigation mode
473  switch ( mCameraNavigationMode )
474  {
475  case NavigationMode::WalkNavigation:
476  setCameraNavigationMode( NavigationMode::TerrainBasedNavigation );
477  break;
478  case NavigationMode::TerrainBasedNavigation:
479  setCameraNavigationMode( NavigationMode::WalkNavigation );
480  break;
481  }
482  emit navigationModeHotKeyPressed( mCameraNavigationMode );
483  return;
484  }
485 
486  switch ( mCameraNavigationMode )
487  {
488  case WalkNavigation:
489  {
490  onKeyPressedFlyNavigation( event );
491  break;
492  }
493 
495  {
496  onKeyPressedTerrainNavigation( event );
497  break;
498  }
499  }
500 }
501 
502 void QgsCameraController::onKeyPressedTerrainNavigation( Qt3DInput::QKeyEvent *event )
503 {
504  const bool hasShift = ( event->modifiers() & Qt::ShiftModifier );
505  const bool hasCtrl = ( event->modifiers() & Qt::ControlModifier );
506 
507  int tx = 0, ty = 0, tElev = 0;
508  switch ( event->key() )
509  {
510  case Qt::Key_Left:
511  tx -= 1;
512  break;
513  case Qt::Key_Right:
514  tx += 1;
515  break;
516 
517  case Qt::Key_Up:
518  ty += 1;
519  break;
520  case Qt::Key_Down:
521  ty -= 1;
522  break;
523 
524  case Qt::Key_PageDown:
525  tElev -= 1;
526  break;
527  case Qt::Key_PageUp:
528  tElev += 1;
529  break;
530  }
531 
532  if ( tx || ty )
533  {
534  if ( !hasShift && !hasCtrl )
535  {
536  moveView( tx, ty );
537  }
538  else if ( hasShift && !hasCtrl )
539  {
540  // rotate/tilt using keyboard (camera moves as it rotates around its view center)
543  }
544  else if ( hasCtrl && !hasShift )
545  {
546  // rotate/tilt using keyboard (camera stays at one position as it rotates)
547  float diffPitch = ty; // down key = rotating camera down
548  float diffYaw = -tx; // right key = rotating camera to the right
549  rotateCamera( diffPitch, diffYaw );
550  updateCameraFromPose( true );
551  }
552  }
553 
554  if ( tElev )
555  {
556  QgsVector3D center = mCameraPose.centerPoint();
557  center.set( center.x(), center.y() + tElev * 10, center.z() );
558  mCameraPose.setCenterPoint( center );
559  updateCameraFromPose( true );
560  }
561 }
562 
563 void QgsCameraController::onKeyPressedFlyNavigation( Qt3DInput::QKeyEvent *event )
564 {
565  switch ( event->key() )
566  {
567  case Qt::Key_QuoteLeft:
568  {
569  // toggle mouse lock mode
570  mCaptureFpsMouseMovements = !mCaptureFpsMouseMovements;
571  mIgnoreNextMouseMove = true;
572  if ( mCaptureFpsMouseMovements )
573  {
574  qApp->setOverrideCursor( QCursor( Qt::BlankCursor ) );
575  }
576  else
577  {
578  qApp->restoreOverrideCursor();
579  }
580  return;
581  }
582 
583  case Qt::Key_Escape:
584  {
585  // always exit mouse lock mode
586  if ( mCaptureFpsMouseMovements )
587  {
588  mCaptureFpsMouseMovements = false;
589  mIgnoreNextMouseMove = true;
590  qApp->restoreOverrideCursor();
591  return;
592  }
593  break;
594  }
595 
596  default:
597  break;
598  }
599 
600  if ( event->isAutoRepeat() )
601  return;
602 
603  mDepressedKeys.insert( event->key() );
604 }
605 
606 void QgsCameraController::applyFlyModeKeyMovements()
607 {
608  QVector3D cameraUp = mCamera->upVector().normalized();
609  QVector3D cameraFront = ( QVector3D( mCameraPose.centerPoint().x(), mCameraPose.centerPoint().y(), mCameraPose.centerPoint().z() ) - mCamera->position() ).normalized();
610  QVector3D cameraLeft = QVector3D::crossProduct( cameraUp, cameraFront );
611 
612  QVector3D cameraPosDiff( 0.0f, 0.0f, 0.0f );
613 
614  // shift = "run", ctrl = "slow walk"
615  const bool shiftPressed = mDepressedKeys.contains( Qt::Key_Shift );
616  const bool ctrlPressed = mDepressedKeys.contains( Qt::Key_Control );
617 
618  double movementSpeed = mCameraMovementSpeed * ( shiftPressed ? 2 : 1 ) * ( ctrlPressed ? 0.1 : 1 );
619 
620  bool changed = false;
621  if ( mDepressedKeys.contains( Qt::Key_Left ) || mDepressedKeys.contains( Qt::Key_A ) )
622  {
623  changed = true;
624  cameraPosDiff += movementSpeed * cameraLeft;
625  }
626 
627  if ( mDepressedKeys.contains( Qt::Key_Right ) || mDepressedKeys.contains( Qt::Key_D ) )
628  {
629  changed = true;
630  cameraPosDiff += - movementSpeed * cameraLeft;
631  }
632 
633  if ( mDepressedKeys.contains( Qt::Key_Up ) || mDepressedKeys.contains( Qt::Key_W ) )
634  {
635  changed = true;
636  cameraPosDiff += movementSpeed * cameraFront;
637  }
638 
639  if ( mDepressedKeys.contains( Qt::Key_Down ) || mDepressedKeys.contains( Qt::Key_S ) )
640  {
641  changed = true;
642  cameraPosDiff += - movementSpeed * cameraFront;
643  }
644 
645  // note -- vertical axis movements are slower by default then horizontal ones, as GIS projects
646  // tend to have much more limited elevation range vs ground range
647  static constexpr double ELEVATION_MOVEMENT_SCALE = 0.5;
648  if ( mDepressedKeys.contains( Qt::Key_PageUp ) || mDepressedKeys.contains( Qt::Key_E ) )
649  {
650  changed = true;
651  cameraPosDiff += ELEVATION_MOVEMENT_SCALE * movementSpeed * QVector3D( 0.0f, 1.0f, 0.0f );
652  }
653 
654  if ( mDepressedKeys.contains( Qt::Key_PageDown ) || mDepressedKeys.contains( Qt::Key_Q ) )
655  {
656  changed = true;
657  cameraPosDiff += ELEVATION_MOVEMENT_SCALE * - movementSpeed * QVector3D( 0.0f, 1.0f, 0.0f );
658  }
659 
660  if ( changed )
661  moveCameraPositionBy( cameraPosDiff );
662 }
663 
664 void QgsCameraController::onPositionChangedFlyNavigation( Qt3DInput::QMouseEvent *mouse )
665 {
666  const bool hasMiddleButton = ( mouse->buttons() & Qt::MiddleButton );
667  const bool hasRightButton = ( mouse->buttons() & Qt::RightButton );
668 
669  const double dx = mCaptureFpsMouseMovements ? QCursor::pos().x() - mMousePos.x() : mouse->x() - mMousePos.x();
670  const double dy = mCaptureFpsMouseMovements ? QCursor::pos().y() - mMousePos.y() : mouse->y() - mMousePos.y();
671  mMousePos = mCaptureFpsMouseMovements ? QCursor::pos() : QPoint( mouse->x(), mouse->y() );
672 
673  if ( mIgnoreNextMouseMove )
674  {
675  mIgnoreNextMouseMove = false;
676  return;
677  }
678 
679  if ( hasMiddleButton )
680  {
681  // middle button drag = pan camera in place (strafe)
682  QVector3D cameraUp = mCamera->upVector().normalized();
683  QVector3D cameraFront = ( QVector3D( mCameraPose.centerPoint().x(), mCameraPose.centerPoint().y(), mCameraPose.centerPoint().z() ) - mCamera->position() ).normalized();
684  QVector3D cameraLeft = QVector3D::crossProduct( cameraUp, cameraFront );
685  QVector3D cameraPosDiff = -dx * cameraLeft - dy * cameraUp;
686  moveCameraPositionBy( mCameraMovementSpeed * cameraPosDiff / 10.0 );
687  }
688  else if ( hasRightButton )
689  {
690  // right button drag = camera dolly
691  QVector3D cameraFront = ( QVector3D( mCameraPose.centerPoint().x(), mCameraPose.centerPoint().y(), mCameraPose.centerPoint().z() ) - mCamera->position() ).normalized();
692  QVector3D cameraPosDiff = dy * cameraFront;
693  moveCameraPositionBy( mCameraMovementSpeed * cameraPosDiff / 5.0 );
694  }
695  else
696  {
697  if ( mCaptureFpsMouseMovements )
698  {
699  float diffPitch = -0.2f * dy;
700  switch ( mVerticalAxisInversion )
701  {
702  case Always:
703  diffPitch *= -1;
704  break;
705 
706  case WhenDragging:
707  case Never:
708  break;
709  }
710 
711  float diffYaw = - 0.2f * dx;
712  rotateCamera( diffPitch, diffYaw );
713  updateCameraFromPose( false );
714  }
715  else if ( mouse->buttons() & Qt::LeftButton )
716  {
717  float diffPitch = -0.2f * dy;
718  switch ( mVerticalAxisInversion )
719  {
720  case Always:
721  case WhenDragging:
722  diffPitch *= -1;
723  break;
724 
725  case Never:
726  break;
727  }
728  float diffYaw = - 0.2f * dx;
729  rotateCamera( diffPitch, diffYaw );
730  updateCameraFromPose( false );
731  }
732  }
733 
734  if ( mCaptureFpsMouseMovements )
735  {
736  mIgnoreNextMouseMove = true;
737 
738  // reset cursor back to center of map widget
739  emit setCursorPosition( QPoint( mViewport.width() / 2, mViewport.height() / 2 ) );
740  }
741 }
742 
743 void QgsCameraController::onKeyReleased( Qt3DInput::QKeyEvent *event )
744 {
745  if ( event->isAutoRepeat() )
746  return;
747 
748  mDepressedKeys.remove( event->key() );
749 }
750 
751 void QgsCameraController::onPickerMousePressed( Qt3DRender::QPickEvent *pick )
752 {
753  mLastPressedHeight = pick->worldIntersection().y();
754 }
755 
756 
758 {
759  // Tilt up the view by deltaPitch around the view center (camera moves)
760  float pitch = mCameraPose.pitchAngle();
761  pitch -= deltaPitch; // down key = moving camera toward terrain
762  mCameraPose.setPitchAngle( pitch );
763  updateCameraFromPose();
764 }
765 
767 {
768  // Rotate clockwise the view by deltaYaw around the view center (camera moves)
769  float yaw = mCameraPose.headingAngle();
770  yaw -= deltaYaw; // right key = moving camera clockwise
771  mCameraPose.setHeadingAngle( yaw );
772  updateCameraFromPose();
773  qInfo() << "Delta yaw: " << deltaYaw;
774  qInfo() << "Yaw: " << yaw;
775 }
776 
778 {
779  mCameraPose.setHeadingAngle( angle );
780  updateCameraFromPose();
781 }
782 
783 void QgsCameraController::moveView( float tx, float ty )
784 {
785  float yaw = mCameraPose.headingAngle();
786  float dist = mCameraPose.distanceFromCenterPoint();
787  float x = tx * dist * 0.02f;
788  float y = -ty * dist * 0.02f;
789 
790  // moving with keyboard - take into account yaw of camera
791  float t = sqrt( x * x + y * y );
792  float a = atan2( y, x ) - yaw * M_PI / 180;
793  float dx = cos( a ) * t;
794  float dy = sin( a ) * t;
795 
796  QgsVector3D center = mCameraPose.centerPoint();
797  center.set( center.x() + dx, center.y(), center.z() + dy );
798  mCameraPose.setCenterPoint( center );
799  updateCameraFromPose( true );
800 }
801 
803 {
804  if ( event->key() == Qt::Key_QuoteLeft )
805  return true;
806 
807  switch ( mCameraNavigationMode )
808  {
809  case WalkNavigation:
810  {
811  switch ( event->key() )
812  {
813  case Qt::Key_Left:
814  case Qt::Key_A:
815  case Qt::Key_Right:
816  case Qt::Key_D:
817  case Qt::Key_Up:
818  case Qt::Key_W:
819  case Qt::Key_Down:
820  case Qt::Key_S:
821  case Qt::Key_PageUp:
822  case Qt::Key_E:
823  case Qt::Key_PageDown:
824  case Qt::Key_Q:
825  return true;
826 
827  case Qt::Key_Escape:
828  if ( mCaptureFpsMouseMovements )
829  return true;
830  break;
831 
832  default:
833  break;
834  }
835  break;
836  }
837 
839  {
840  switch ( event->key() )
841  {
842  case Qt::Key_Left:
843  case Qt::Key_Right:
844  case Qt::Key_PageUp:
845  case Qt::Key_PageDown:
846  return true;
847 
848  default:
849  break;
850  }
851  break;
852  }
853  }
854  return false;
855 }
void setViewport(QRect viewport)
Sets viewport rectangle. Called internally from 3D canvas. Allows conversion of mouse coordinates.
void readXml(const QDomElement &elem)
Reads camera configuration from the given DOM element.
float pitch() const
Returns pitch angle in degrees (0 = looking from the top, 90 = looking from the side).
float yaw() const
Returns yaw angle in degrees.
void setCameraNavigationMode(QgsCameraController::NavigationMode navigationMode)
Sets the navigation mode used by the camera controller.
void tiltUpAroundViewCenter(float deltaPitch)
Tilt up the view by deltaPitch around the view center (camera moves)
bool willHandleKeyEvent(QKeyEvent *event)
Returns true if the camera controller will handle the specified key event, preventing it from being i...
void navigationModeHotKeyPressed(QgsCameraController::NavigationMode mode)
Emitted when the navigation mode is changed using the hotkey ctrl + ~.
Qt3DRender::QCamera * camera
void setVerticalAxisInversion(QgsCameraController::VerticalAxisInversion inversion)
Sets the vertical axis inversion behavior.
float distance() const
Returns distance of the camera from the point it is looking at.
void setCamera(Qt3DRender::QCamera *camera)
Assigns camera that should be controlled by this class. Called internally from 3D scene.
VerticalAxisInversion
Vertical axis inversion options.
@ WhenDragging
Invert vertical axis movements when dragging in first person modes.
@ Always
Always invert vertical axis movements.
@ Never
Never invert vertical axis movements.
void cameraChanged()
Emitted when camera has been updated.
void cameraMovementSpeedChanged(double speed)
Emitted whenever the camera movement speed is changed by the controller.
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...
NavigationMode
The navigation mode used by the camera.
@ WalkNavigation
Uses WASD keys or arrows to navigate in walking (first person) manner.
@ TerrainBasedNavigation
The default navigation based on the terrain.
void zoom(float factor)
Zoom the map by factor.
void setTerrainEntity(QgsTerrainEntity *te)
Connects to object picker attached to terrain entity.
void rotateAroundViewCenter(float deltaYaw)
Rotate clockwise the view by deltaYaw around the view center (camera moves)
QgsCameraController(Qt3DCore::QNode *parent=nullptr)
Constructs the camera controller with optional parent node that will take ownership.
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 viewportChanged()
Emitted when viewport rectangle has been updated.
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.
Definition: qgscamerapose.h:63
void setCenterPoint(const QgsVector3D &point)
Sets center point (towards which point the camera is looking)
Definition: qgscamerapose.h:53
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.
Definition: qgscamerapose.h:58
void updateCamera(Qt3DRender::QCamera *camera)
Update Qt3D camera view matrix based on the pose.
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
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
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:316
QPointF screen_point_to_point_on_plane(QPointF pt, QRect viewport, Qt3DRender::QCamera *camera, float y)
float find_x_on_line(float x0, float y0, float x1, float y1, float y)