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