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