QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
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  // object picker for terrain for correct map panning
96  if ( mTerrainEntity )
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  const 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  const float d_x = x1 - x0;
148  const float d_y = y1 - y0;
149  const 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  const QVector3D l0 = unproject( QVector3D( pt.x(), viewport.height() - pt.y(), 0 ), camera->viewMatrix(), camera->projectionMatrix(), viewport );
157  const QVector3D l1 = unproject( QVector3D( pt.x(), viewport.height() - pt.y(), 1 ), camera->viewMatrix(), camera->projectionMatrix(), viewport );
158 
159  const QVector3D p0( 0, y, 0 ); // a point on the plane
160  const QVector3D n( 0, 1, 0 ); // normal of the plane
161  const QVector3D l = l1 - l0; // vector in the direction of the line
162  const float d = QVector3D::dotProduct( p0 - l0, n ) / QVector3D::dotProduct( l, n );
163  const 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  const float pitch = mCameraPose.pitchAngle();
172  const 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  const 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  const QVector3D position = mCamera->position();
189  QVector3D viewCenter = mCamera->viewCenter();
190  const QVector3D viewVector = viewCenter - position;
191  const 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  const float x = elem.attribute( QStringLiteral( "x" ) ).toFloat();
270  const float y = elem.attribute( QStringLiteral( "y" ) ).toFloat();
271  const float elev = elem.attribute( QStringLiteral( "elev" ) ).toFloat();
272  const float dist = elem.attribute( QStringLiteral( "dist" ) ).toFloat();
273  const float pitch = elem.attribute( QStringLiteral( "pitch" ) ).toFloat();
274  const 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  const QgsRayCastingUtils::Ray3D ray = QgsRayCastingUtils::rayForCameraCenter( mCamera );
303  if ( mTerrainEntity->rayIntersection( ray, intersectionPoint ) )
304  {
305  const 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  mCameraPose.updateCamera( mCamera );
316  }
317  }
318 
319  if ( mCamera && !mTerrainEntity && centerPointChanged )
320  {
321  QgsVector3D centerPoint = mCameraPose.centerPoint();
322  centerPoint.set( centerPoint.x(), 0, centerPoint.z() );
323  mCameraPose.setCenterPoint( centerPoint );
324  mCameraPose.updateCamera( mCamera );
325  }
326 
327  emit cameraChanged();
328 }
329 
330 void QgsCameraController::moveCameraPositionBy( const QVector3D &posDiff )
331 {
332  mCameraPose.setCenterPoint( mCameraPose.centerPoint() + posDiff );
333 
334  if ( mCameraPose.pitchAngle() > 180 )
335  mCameraPose.setPitchAngle( 180 ); // prevent going over the head
336  if ( mCameraPose.pitchAngle() < 0 )
337  mCameraPose.setPitchAngle( 0 ); // prevent going over the head
338  if ( mCameraPose.distanceFromCenterPoint() < 10 )
339  mCameraPose.setDistanceFromCenterPoint( 10 );
340 
341  if ( mCamera )
342  mCameraPose.updateCamera( mCamera );
343 
344  emit cameraChanged();
345 
346 }
347 
348 void QgsCameraController::onPositionChanged( Qt3DInput::QMouseEvent *mouse )
349 {
350  switch ( mCameraNavigationMode )
351  {
353  onPositionChangedTerrainNavigation( mouse );
354  break;
355 
356  case WalkNavigation:
357  onPositionChangedFlyNavigation( mouse );
358  break;
359  }
360 }
361 
362 void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseEvent *mouse )
363 {
364  if ( mIgnoreNextMouseMove )
365  {
366  mIgnoreNextMouseMove = false;
367  mMousePos = QPoint( mouse->x(), mouse->y() );
368  return;
369  }
370 
371  const int dx = mouse->x() - mMousePos.x();
372  const int dy = mouse->y() - mMousePos.y();
373 
374  const bool hasShift = ( mouse->modifiers() & Qt::ShiftModifier );
375  const bool hasCtrl = ( mouse->modifiers() & Qt::ControlModifier );
376  const bool hasLeftButton = ( mouse->buttons() & Qt::LeftButton );
377  const bool hasMiddleButton = ( mouse->buttons() & Qt::MiddleButton );
378  const bool hasRightButton = ( mouse->buttons() & Qt::RightButton );
379 
380  if ( ( hasLeftButton && hasShift && !hasCtrl ) || ( hasMiddleButton && !hasShift && !hasCtrl ) )
381  {
382  // rotate/tilt using mouse (camera moves as it rotates around its view center)
383  float pitch = mCameraPose.pitchAngle();
384  float yaw = mCameraPose.headingAngle();
385  pitch += 0.2f * dy;
386  yaw -= 0.2f * dx;
387  mCameraPose.setPitchAngle( pitch );
388  mCameraPose.setHeadingAngle( yaw );
389  updateCameraFromPose();
390  }
391  else if ( hasLeftButton && hasCtrl && !hasShift )
392  {
393  // rotate/tilt using mouse (camera stays at one position as it rotates)
394  const float diffPitch = 0.2f * dy;
395  const float diffYaw = - 0.2f * dx;
396  rotateCamera( diffPitch, diffYaw );
397  updateCameraFromPose( true );
398  }
399  else if ( hasLeftButton && !hasShift && !hasCtrl )
400  {
401  // translation works as if one grabbed a point on the plane and dragged it
402  // i.e. find out x,z of the previous mouse point, find out x,z of the current mouse point
403  // and use the difference
404 
405  const float z = mLastPressedHeight;
406  const QPointF p1 = screen_point_to_point_on_plane( QPointF( mMousePos.x(), mMousePos.y() ), mViewport, mCamera, z );
407  const QPointF p2 = screen_point_to_point_on_plane( QPointF( mouse->x(), mouse->y() ), mViewport, mCamera, z );
408 
409  QgsVector3D center = mCameraPose.centerPoint();
410  center.set( center.x() - ( p2.x() - p1.x() ), center.y(), center.z() - ( p2.y() - p1.y() ) );
411  mCameraPose.setCenterPoint( center );
412  updateCameraFromPose( true );
413  }
414  else if ( hasRightButton && !hasShift && !hasCtrl )
415  {
416  zoom( dy );
417  }
418 
419  mMousePos = QPoint( mouse->x(), mouse->y() );
420 }
421 
422 
423 
424 void QgsCameraController::zoom( float factor )
425 {
426  // zoom in/out
427  float dist = mCameraPose.distanceFromCenterPoint();
428  dist -= dist * factor * 0.01f;
429  mCameraPose.setDistanceFromCenterPoint( dist );
430  updateCameraFromPose();
431 }
432 
433 void QgsCameraController::onWheel( Qt3DInput::QWheelEvent *wheel )
434 {
435  switch ( mCameraNavigationMode )
436  {
438  {
439  const float scaling = ( ( wheel->modifiers() & Qt::ControlModifier ) ? 0.1f : 1.0f ) / 1000.f;
440  setCameraMovementSpeed( mCameraMovementSpeed + mCameraMovementSpeed * scaling * wheel->angleDelta().y() );
441  break;
442  }
443 
445  {
446  const float scaling = ( ( wheel->modifiers() & Qt::ControlModifier ) ? 0.1f : 1.0f ) / 1000.f;
447  float dist = mCameraPose.distanceFromCenterPoint();
448  dist -= dist * scaling * wheel->angleDelta().y();
449  mCameraPose.setDistanceFromCenterPoint( dist );
450  updateCameraFromPose();
451  break;
452  }
453  }
454 }
455 
456 void QgsCameraController::onMousePressed( Qt3DInput::QMouseEvent *mouse )
457 {
458  Q_UNUSED( mouse )
459  mKeyboardHandler->setFocus( true );
460  if ( mouse->button() == Qt3DInput::QMouseEvent::LeftButton || mouse->button() == Qt3DInput::QMouseEvent::RightButton || mouse->button() == Qt3DInput::QMouseEvent::MiddleButton )
461  {
462  mMousePos = QPoint( mouse->x(), mouse->y() );
463  mPressedButton = mouse->button();
464  mMousePressed = true;
465  if ( mCaptureFpsMouseMovements )
466  mIgnoreNextMouseMove = true;
467  }
468 }
469 
470 void QgsCameraController::onMouseReleased( Qt3DInput::QMouseEvent *mouse )
471 {
472  Q_UNUSED( mouse )
473  mPressedButton = Qt3DInput::QMouseEvent::NoButton;
474  mMousePressed = false;
475 }
476 
477 void QgsCameraController::onKeyPressed( Qt3DInput::QKeyEvent *event )
478 {
479  if ( event->modifiers() & Qt::ControlModifier && event->key() == Qt::Key_QuoteLeft )
480  {
481  // switch navigation mode
482  switch ( mCameraNavigationMode )
483  {
484  case NavigationMode::WalkNavigation:
485  setCameraNavigationMode( NavigationMode::TerrainBasedNavigation );
486  break;
487  case NavigationMode::TerrainBasedNavigation:
488  setCameraNavigationMode( NavigationMode::WalkNavigation );
489  break;
490  }
491  emit navigationModeHotKeyPressed( mCameraNavigationMode );
492  return;
493  }
494 
495  switch ( mCameraNavigationMode )
496  {
497  case WalkNavigation:
498  {
499  onKeyPressedFlyNavigation( event );
500  break;
501  }
502 
504  {
505  onKeyPressedTerrainNavigation( event );
506  break;
507  }
508  }
509 }
510 
511 void QgsCameraController::onKeyPressedTerrainNavigation( Qt3DInput::QKeyEvent *event )
512 {
513  const bool hasShift = ( event->modifiers() & Qt::ShiftModifier );
514  const bool hasCtrl = ( event->modifiers() & Qt::ControlModifier );
515 
516  int tx = 0, ty = 0, tElev = 0;
517  switch ( event->key() )
518  {
519  case Qt::Key_Left:
520  tx -= 1;
521  break;
522  case Qt::Key_Right:
523  tx += 1;
524  break;
525 
526  case Qt::Key_Up:
527  ty += 1;
528  break;
529  case Qt::Key_Down:
530  ty -= 1;
531  break;
532 
533  case Qt::Key_PageDown:
534  tElev -= 1;
535  break;
536  case Qt::Key_PageUp:
537  tElev += 1;
538  break;
539  }
540 
541  if ( tx || ty )
542  {
543  if ( !hasShift && !hasCtrl )
544  {
545  moveView( tx, ty );
546  }
547  else if ( hasShift && !hasCtrl )
548  {
549  // rotate/tilt using keyboard (camera moves as it rotates around its view center)
552  }
553  else if ( hasCtrl && !hasShift )
554  {
555  // rotate/tilt using keyboard (camera stays at one position as it rotates)
556  const float diffPitch = ty; // down key = rotating camera down
557  const float diffYaw = -tx; // right key = rotating camera to the right
558  rotateCamera( diffPitch, diffYaw );
559  updateCameraFromPose( true );
560  }
561  }
562 
563  if ( tElev )
564  {
565  QgsVector3D center = mCameraPose.centerPoint();
566  center.set( center.x(), center.y() + tElev * 10, center.z() );
567  mCameraPose.setCenterPoint( center );
568  updateCameraFromPose( true );
569  }
570 }
571 
572 void QgsCameraController::onKeyPressedFlyNavigation( Qt3DInput::QKeyEvent *event )
573 {
574  switch ( event->key() )
575  {
576  case Qt::Key_QuoteLeft:
577  {
578  // toggle mouse lock mode
579  mCaptureFpsMouseMovements = !mCaptureFpsMouseMovements;
580  mIgnoreNextMouseMove = true;
581  if ( mCaptureFpsMouseMovements )
582  {
583  qApp->setOverrideCursor( QCursor( Qt::BlankCursor ) );
584  }
585  else
586  {
587  qApp->restoreOverrideCursor();
588  }
589  return;
590  }
591 
592  case Qt::Key_Escape:
593  {
594  // always exit mouse lock mode
595  if ( mCaptureFpsMouseMovements )
596  {
597  mCaptureFpsMouseMovements = false;
598  mIgnoreNextMouseMove = true;
599  qApp->restoreOverrideCursor();
600  return;
601  }
602  break;
603  }
604 
605  default:
606  break;
607  }
608 
609  if ( event->isAutoRepeat() )
610  return;
611 
612  mDepressedKeys.insert( event->key() );
613 }
614 
615 void QgsCameraController::applyFlyModeKeyMovements()
616 {
617  const QVector3D cameraUp = mCamera->upVector().normalized();
618  const QVector3D cameraFront = ( QVector3D( mCameraPose.centerPoint().x(), mCameraPose.centerPoint().y(), mCameraPose.centerPoint().z() ) - mCamera->position() ).normalized();
619  const QVector3D cameraLeft = QVector3D::crossProduct( cameraUp, cameraFront );
620 
621  QVector3D cameraPosDiff( 0.0f, 0.0f, 0.0f );
622 
623  // shift = "run", ctrl = "slow walk"
624  const bool shiftPressed = mDepressedKeys.contains( Qt::Key_Shift );
625  const bool ctrlPressed = mDepressedKeys.contains( Qt::Key_Control );
626 
627  const double movementSpeed = mCameraMovementSpeed * ( shiftPressed ? 2 : 1 ) * ( ctrlPressed ? 0.1 : 1 );
628 
629  bool changed = false;
630  if ( mDepressedKeys.contains( Qt::Key_Left ) || mDepressedKeys.contains( Qt::Key_A ) )
631  {
632  changed = true;
633  cameraPosDiff += movementSpeed * cameraLeft;
634  }
635 
636  if ( mDepressedKeys.contains( Qt::Key_Right ) || mDepressedKeys.contains( Qt::Key_D ) )
637  {
638  changed = true;
639  cameraPosDiff += - movementSpeed * cameraLeft;
640  }
641 
642  if ( mDepressedKeys.contains( Qt::Key_Up ) || mDepressedKeys.contains( Qt::Key_W ) )
643  {
644  changed = true;
645  cameraPosDiff += movementSpeed * cameraFront;
646  }
647 
648  if ( mDepressedKeys.contains( Qt::Key_Down ) || mDepressedKeys.contains( Qt::Key_S ) )
649  {
650  changed = true;
651  cameraPosDiff += - movementSpeed * cameraFront;
652  }
653 
654  // note -- vertical axis movements are slower by default then horizontal ones, as GIS projects
655  // tend to have much more limited elevation range vs ground range
656  static constexpr double ELEVATION_MOVEMENT_SCALE = 0.5;
657  if ( mDepressedKeys.contains( Qt::Key_PageUp ) || mDepressedKeys.contains( Qt::Key_E ) )
658  {
659  changed = true;
660  cameraPosDiff += ELEVATION_MOVEMENT_SCALE * movementSpeed * QVector3D( 0.0f, 1.0f, 0.0f );
661  }
662 
663  if ( mDepressedKeys.contains( Qt::Key_PageDown ) || mDepressedKeys.contains( Qt::Key_Q ) )
664  {
665  changed = true;
666  cameraPosDiff += ELEVATION_MOVEMENT_SCALE * - movementSpeed * QVector3D( 0.0f, 1.0f, 0.0f );
667  }
668 
669  if ( changed )
670  moveCameraPositionBy( cameraPosDiff );
671 }
672 
673 void QgsCameraController::onPositionChangedFlyNavigation( Qt3DInput::QMouseEvent *mouse )
674 {
675  const bool hasMiddleButton = ( mouse->buttons() & Qt::MiddleButton );
676  const bool hasRightButton = ( mouse->buttons() & Qt::RightButton );
677 
678  const double dx = mCaptureFpsMouseMovements ? QCursor::pos().x() - mMousePos.x() : mouse->x() - mMousePos.x();
679  const double dy = mCaptureFpsMouseMovements ? QCursor::pos().y() - mMousePos.y() : mouse->y() - mMousePos.y();
680  mMousePos = mCaptureFpsMouseMovements ? QCursor::pos() : QPoint( mouse->x(), mouse->y() );
681 
682  if ( mIgnoreNextMouseMove )
683  {
684  mIgnoreNextMouseMove = false;
685  return;
686  }
687 
688  if ( hasMiddleButton )
689  {
690  // middle button drag = pan camera in place (strafe)
691  const QVector3D cameraUp = mCamera->upVector().normalized();
692  const QVector3D cameraFront = ( QVector3D( mCameraPose.centerPoint().x(), mCameraPose.centerPoint().y(), mCameraPose.centerPoint().z() ) - mCamera->position() ).normalized();
693  const QVector3D cameraLeft = QVector3D::crossProduct( cameraUp, cameraFront );
694  const QVector3D cameraPosDiff = -dx * cameraLeft - dy * cameraUp;
695  moveCameraPositionBy( mCameraMovementSpeed * cameraPosDiff / 10.0 );
696  }
697  else if ( hasRightButton )
698  {
699  // right button drag = camera dolly
700  const QVector3D cameraFront = ( QVector3D( mCameraPose.centerPoint().x(), mCameraPose.centerPoint().y(), mCameraPose.centerPoint().z() ) - mCamera->position() ).normalized();
701  const QVector3D cameraPosDiff = dy * cameraFront;
702  moveCameraPositionBy( mCameraMovementSpeed * cameraPosDiff / 5.0 );
703  }
704  else
705  {
706  if ( mCaptureFpsMouseMovements )
707  {
708  float diffPitch = -0.2f * dy;
709  switch ( mVerticalAxisInversion )
710  {
711  case Always:
712  diffPitch *= -1;
713  break;
714 
715  case WhenDragging:
716  case Never:
717  break;
718  }
719 
720  const float diffYaw = - 0.2f * dx;
721  rotateCamera( diffPitch, diffYaw );
722  updateCameraFromPose( false );
723  }
724  else if ( mouse->buttons() & Qt::LeftButton )
725  {
726  float diffPitch = -0.2f * dy;
727  switch ( mVerticalAxisInversion )
728  {
729  case Always:
730  case WhenDragging:
731  diffPitch *= -1;
732  break;
733 
734  case Never:
735  break;
736  }
737  const float diffYaw = - 0.2f * dx;
738  rotateCamera( diffPitch, diffYaw );
739  updateCameraFromPose( false );
740  }
741  }
742 
743  if ( mCaptureFpsMouseMovements )
744  {
745  mIgnoreNextMouseMove = true;
746 
747  // reset cursor back to center of map widget
748  emit setCursorPosition( QPoint( mViewport.width() / 2, mViewport.height() / 2 ) );
749  }
750 }
751 
752 void QgsCameraController::onKeyReleased( Qt3DInput::QKeyEvent *event )
753 {
754  if ( event->isAutoRepeat() )
755  return;
756 
757  mDepressedKeys.remove( event->key() );
758 }
759 
760 void QgsCameraController::onPickerMousePressed( Qt3DRender::QPickEvent *pick )
761 {
762  mLastPressedHeight = pick->worldIntersection().y();
763 }
764 
765 
767 {
768  // Tilt up the view by deltaPitch around the view center (camera moves)
769  float pitch = mCameraPose.pitchAngle();
770  pitch -= deltaPitch; // down key = moving camera toward terrain
771  mCameraPose.setPitchAngle( pitch );
772  updateCameraFromPose();
773 }
774 
776 {
777  // Rotate clockwise the view by deltaYaw around the view center (camera moves)
778  float yaw = mCameraPose.headingAngle();
779  yaw -= deltaYaw; // right key = moving camera clockwise
780  mCameraPose.setHeadingAngle( yaw );
781  updateCameraFromPose();
782  qInfo() << "Delta yaw: " << deltaYaw;
783  qInfo() << "Yaw: " << yaw;
784 }
785 
787 {
788  mCameraPose.setHeadingAngle( angle );
789  updateCameraFromPose();
790 }
791 
792 void QgsCameraController::moveView( float tx, float ty )
793 {
794  const float yaw = mCameraPose.headingAngle();
795  const float dist = mCameraPose.distanceFromCenterPoint();
796  const float x = tx * dist * 0.02f;
797  const float y = -ty * dist * 0.02f;
798 
799  // moving with keyboard - take into account yaw of camera
800  const float t = sqrt( x * x + y * y );
801  const float a = atan2( y, x ) - yaw * M_PI / 180;
802  const float dx = cos( a ) * t;
803  const float dy = sin( a ) * t;
804 
805  QgsVector3D center = mCameraPose.centerPoint();
806  center.set( center.x() + dx, center.y(), center.z() + dy );
807  mCameraPose.setCenterPoint( center );
808  updateCameraFromPose( true );
809 }
810 
812 {
813  if ( event->key() == Qt::Key_QuoteLeft )
814  return true;
815 
816  switch ( mCameraNavigationMode )
817  {
818  case WalkNavigation:
819  {
820  switch ( event->key() )
821  {
822  case Qt::Key_Left:
823  case Qt::Key_A:
824  case Qt::Key_Right:
825  case Qt::Key_D:
826  case Qt::Key_Up:
827  case Qt::Key_W:
828  case Qt::Key_Down:
829  case Qt::Key_S:
830  case Qt::Key_PageUp:
831  case Qt::Key_E:
832  case Qt::Key_PageDown:
833  case Qt::Key_Q:
834  return true;
835 
836  case Qt::Key_Escape:
837  if ( mCaptureFpsMouseMovements )
838  return true;
839  break;
840 
841  default:
842  break;
843  }
844  break;
845  }
846 
848  {
849  switch ( event->key() )
850  {
851  case Qt::Key_Left:
852  case Qt::Key_Right:
853  case Qt::Key_PageUp:
854  case Qt::Key_PageDown:
855  return true;
856 
857  default:
858  break;
859  }
860  break;
861  }
862  }
863  return false;
864 }
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:1246
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)