QGIS API Documentation 3.43.0-Master (3ee7834ace6)
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 "moc_qgscameracontroller.cpp"
18#include "qgseventtracing.h"
19#include "qgsvector3d.h"
20#include "qgswindow3dengine.h"
21#include "qgs3dmapscene.h"
22#include "qgsterrainentity.h"
23#include "qgis.h"
24#include "qgs3dutils.h"
25
26#include <QDomDocument>
27#include <Qt3DRender/QCamera>
28#include <Qt3DInput>
29#include <cmath>
30
31#include "qgslogger.h"
32
34 : Qt3DCore::QEntity( scene )
35 , mScene( scene )
36 , mCamera( scene->engine()->camera() )
37 , mCameraBefore( new Qt3DRender::QCamera )
38 , mMouseHandler( new Qt3DInput::QMouseHandler )
39 , mKeyboardHandler( new Qt3DInput::QKeyboardHandler )
40 , mOrigin( scene->mapSettings()->origin() )
41{
42 mMouseHandler->setSourceDevice( new Qt3DInput::QMouseDevice() );
43 connect( mMouseHandler, &Qt3DInput::QMouseHandler::positionChanged, this, &QgsCameraController::onPositionChanged );
44 connect( mMouseHandler, &Qt3DInput::QMouseHandler::wheel, this, &QgsCameraController::onWheel );
45 connect( mMouseHandler, &Qt3DInput::QMouseHandler::pressed, this, &QgsCameraController::onMousePressed );
46 connect( mMouseHandler, &Qt3DInput::QMouseHandler::released, this, &QgsCameraController::onMouseReleased );
47 addComponent( mMouseHandler );
48
49 mKeyboardHandler->setSourceDevice( new Qt3DInput::QKeyboardDevice() );
50 connect( mKeyboardHandler, &Qt3DInput::QKeyboardHandler::pressed, this, &QgsCameraController::onKeyPressed );
51 connect( mKeyboardHandler, &Qt3DInput::QKeyboardHandler::released, this, &QgsCameraController::onKeyReleased );
52 addComponent( mKeyboardHandler );
53
54 // Disable the handlers when the entity is disabled
55 connect( this, &Qt3DCore::QEntity::enabledChanged, mMouseHandler, &Qt3DInput::QMouseHandler::setEnabled );
56 connect( this, &Qt3DCore::QEntity::enabledChanged, mKeyboardHandler, &Qt3DInput::QKeyboardHandler::setEnabled );
57
58 mFpsNavTimer = new QTimer( this );
59 mFpsNavTimer->setInterval( 10 );
60 connect( mFpsNavTimer, &QTimer::timeout, this, &QgsCameraController::applyFlyModeKeyMovements );
61 mFpsNavTimer->start();
62
63 if ( mScene->mapSettings()->sceneMode() == Qgis::SceneMode::Globe )
64 {
65 mGlobeCrsToLatLon = QgsCoordinateTransform( mScene->mapSettings()->crs(), mScene->mapSettings()->crs().toGeographicCrs(), mScene->mapSettings()->transformContext() );
66 }
67}
68
70
71QWindow *QgsCameraController::window() const
72{
73 QgsWindow3DEngine *windowEngine = qobject_cast<QgsWindow3DEngine *>( mScene->engine() );
74 return windowEngine ? windowEngine->window() : nullptr;
75}
76
78{
79 if ( navigationMode == mCameraNavigationMode )
80 return;
81
82 mCameraNavigationMode = navigationMode;
83 mIgnoreNextMouseMove = true;
84 emit navigationModeChanged( mCameraNavigationMode );
85}
86
88{
89 if ( movementSpeed == mCameraMovementSpeed )
90 return;
91
92 // If the speed becomes 0, navigation does not work anymore
93 // If the speed becomes too important, only one walk can move the view far from the scene.
94 mCameraMovementSpeed = std::clamp( movementSpeed, 0.05, 150.0 );
95 emit cameraMovementSpeedChanged( mCameraMovementSpeed );
96}
97
99{
100 mVerticalAxisInversion = inversion;
101}
102
103void QgsCameraController::rotateCamera( float diffPitch, float diffHeading )
104{
105 const float oldPitch = mCameraPose.pitchAngle();
106 const float oldHeading = mCameraPose.headingAngle();
107 float newPitch = oldPitch + diffPitch;
108 float newHeading = oldHeading + diffHeading;
109
110 newPitch = std::clamp( newPitch, 0.f, 180.f ); // prevent going over the head
111
112 // First undo the previously applied rotation, then apply the new rotation
113 // (We can't just apply our euler angles difference because the camera may be already rotated)
114 const QQuaternion qNew = Qgs3DUtils::rotationFromPitchHeadingAngles( newPitch, newHeading );
115 const QQuaternion qOld = Qgs3DUtils::rotationFromPitchHeadingAngles( oldPitch, oldHeading );
116 const QQuaternion q = qNew * qOld.conjugated();
117
118 // get camera's view vector, rotate it to get new view center
119 const QVector3D position = mCamera->position();
120 QVector3D viewCenter = mCamera->viewCenter();
121 const QVector3D viewVector = viewCenter - position;
122 const QVector3D cameraToCenter = q * viewVector;
123 viewCenter = position + cameraToCenter;
124
125 mCameraPose.setCenterPoint( viewCenter );
126 mCameraPose.setPitchAngle( newPitch );
127 mCameraPose.setHeadingAngle( newHeading );
128 updateCameraFromPose();
129}
130
131void QgsCameraController::rotateCameraAroundPivot( float newPitch, float newHeading, const QVector3D &pivotPoint )
132{
133 const float oldPitch = mCameraPose.pitchAngle();
134 const float oldHeading = mCameraPose.headingAngle();
135
136 newPitch = std::clamp( newPitch, 0.f, 180.f ); // prevent going over the head
137
138 // First undo the previously applied rotation, then apply the new rotation
139 // (We can't just apply our euler angles difference because the camera may be already rotated)
140 const QQuaternion qNew = Qgs3DUtils::rotationFromPitchHeadingAngles( newPitch, newHeading );
141 const QQuaternion qOld = Qgs3DUtils::rotationFromPitchHeadingAngles( oldPitch, oldHeading );
142 const QQuaternion q = qNew * qOld.conjugated();
143
144 const QVector3D newViewCenter = q * ( mCamera->viewCenter() - pivotPoint ) + pivotPoint;
145
146 mCameraPose.setCenterPoint( newViewCenter );
147 mCameraPose.setPitchAngle( newPitch );
148 mCameraPose.setHeadingAngle( newHeading );
149 updateCameraFromPose();
150}
151
152void QgsCameraController::zoomCameraAroundPivot( const QVector3D &oldCameraPosition, double zoomFactor, const QVector3D &pivotPoint )
153{
154 // step 1: move camera along the line connecting reference camera position and our pivot point
155 QVector3D newCamPosition = pivotPoint + ( oldCameraPosition - pivotPoint ) * zoomFactor;
156 double newDistance = mCameraPose.distanceFromCenterPoint() * zoomFactor;
157
158 // step 2: using the new camera position and distance from center, calculate new view center
159 QQuaternion q = Qgs3DUtils::rotationFromPitchHeadingAngles( mCameraPose.pitchAngle(), mCameraPose.headingAngle() );
160 QVector3D cameraToCenter = q * QVector3D( 0, 0, -newDistance );
161 QVector3D newViewCenter = newCamPosition + cameraToCenter;
162
163 mCameraPose.setDistanceFromCenterPoint( newDistance );
164 mCameraPose.setCenterPoint( newViewCenter );
165 updateCameraFromPose();
166}
167
169{
170 Q_UNUSED( dt )
171
172 if ( mCameraChanged )
173 {
174 emit cameraChanged();
175 mCameraChanged = false;
176 }
177}
178
179void QgsCameraController::resetView( float distance )
180{
181 QgsPointXY extentCenter = mScene->mapSettings()->extent().center();
182 QgsVector3D origin = mScene->mapSettings()->origin();
183 setViewFromTop( extentCenter.x() - origin.x(), extentCenter.y() - origin.y(), distance );
184}
185
186void QgsCameraController::setViewFromTop( float worldX, float worldY, float distance, float yaw )
187{
188 QgsCameraPose camPose;
189 QgsTerrainEntity *terrain = mScene->terrainEntity();
190 const float terrainElevationOffset = terrain ? terrain->terrainElevationOffset() : 0.0f;
191 camPose.setCenterPoint( QgsVector3D( worldX, worldY, terrainElevationOffset - mScene->mapSettings()->origin().z() ) );
193 camPose.setHeadingAngle( yaw );
194
195 // a basic setup to make frustum depth range long enough that it does not cull everything
196 mCamera->setNearPlane( distance / 2 );
197 mCamera->setFarPlane( distance * 2 );
198 // we force the updateCameraNearFarPlanes() in Qgs3DMapScene to properly set the planes
199 setCameraPose( camPose, true );
200}
201
203{
204 return mCameraPose.centerPoint();
205}
206
207void QgsCameraController::setLookingAtPoint( const QgsVector3D &point, float distance, float pitch, float yaw )
208{
209 QgsCameraPose camPose;
210 camPose.setCenterPoint( point );
212 camPose.setPitchAngle( pitch );
213 camPose.setHeadingAngle( yaw );
214 setCameraPose( camPose );
215}
216
218{
219 return lookingAtPoint() + mOrigin;
220}
221
222void QgsCameraController::setLookingAtMapPoint( const QgsVector3D &point, float distance, float pitch, float yaw )
223{
224 setLookingAtPoint( point - mOrigin, distance, pitch, yaw );
225}
226
227void QgsCameraController::setCameraPose( const QgsCameraPose &camPose, bool force )
228{
229 if ( camPose == mCameraPose && !force )
230 return;
231
232 mCameraPose = camPose;
233 updateCameraFromPose();
234}
235
236QDomElement QgsCameraController::writeXml( QDomDocument &doc ) const
237{
238 QDomElement elemCamera = doc.createElement( QStringLiteral( "camera" ) );
239 elemCamera.setAttribute( QStringLiteral( "x" ), mCameraPose.centerPoint().x() );
240 elemCamera.setAttribute( QStringLiteral( "y" ), mCameraPose.centerPoint().z() );
241 elemCamera.setAttribute( QStringLiteral( "elev" ), mCameraPose.centerPoint().y() );
242 elemCamera.setAttribute( QStringLiteral( "dist" ), mCameraPose.distanceFromCenterPoint() );
243 elemCamera.setAttribute( QStringLiteral( "pitch" ), mCameraPose.pitchAngle() );
244 elemCamera.setAttribute( QStringLiteral( "yaw" ), mCameraPose.headingAngle() );
245 return elemCamera;
246}
247
248void QgsCameraController::readXml( const QDomElement &elem )
249{
250 const float x = elem.attribute( QStringLiteral( "x" ) ).toFloat();
251 const float y = elem.attribute( QStringLiteral( "y" ) ).toFloat();
252 const float elev = elem.attribute( QStringLiteral( "elev" ) ).toFloat();
253 const float dist = elem.attribute( QStringLiteral( "dist" ) ).toFloat();
254 const float pitch = elem.attribute( QStringLiteral( "pitch" ) ).toFloat();
255 const float yaw = elem.attribute( QStringLiteral( "yaw" ) ).toFloat();
256 setLookingAtPoint( QgsVector3D( x, elev, y ), dist, pitch, yaw );
257}
258
259double QgsCameraController::sampleDepthBuffer( int px, int py )
260{
261 double depth = 1;
262
263 // Sample the neighbouring pixels for the closest point to the camera
264 for ( int x = px - 3; x <= px + 3; ++x )
265 {
266 for ( int y = py - 3; y <= py + 3; ++y )
267 {
268 if ( mDepthBufferImage.valid( x, y ) )
269 {
270 depth = std::min( depth, Qgs3DUtils::decodeDepth( mDepthBufferImage.pixel( x, y ) ) );
271 }
272 }
273 }
274 return depth;
275}
276
277double QgsCameraController::depthBufferNonVoidAverage()
278{
279 // Cache the computed depth, since averaging over all pixels can be expensive
280 if ( mDepthBufferNonVoidAverage != -1 )
281 return mDepthBufferNonVoidAverage;
282
283 // Returns the average of depth values that are not 1 (void area)
284 double depth = 0;
285 int samplesCount = 0;
286 // Make sure we can do the cast
287 Q_ASSERT( mDepthBufferImage.format() == QImage::Format_RGB32 );
288 for ( int y = 0; y < mDepthBufferImage.height(); ++y )
289 {
290 const QRgb *line = reinterpret_cast<const QRgb *>( mDepthBufferImage.constScanLine( y ) );
291 for ( int x = 0; x < mDepthBufferImage.width(); ++x )
292 {
293 double d = Qgs3DUtils::decodeDepth( line[x] );
294 if ( d < 1 )
295 {
296 depth += d;
297 samplesCount += 1;
298 }
299 }
300 }
301
302 // if the whole buffer is white, a depth cannot be computed
303 if ( samplesCount == 0 )
304 depth = 1.0;
305 else
306 depth /= samplesCount;
307
308 mDepthBufferNonVoidAverage = depth;
309
310 return depth;
311}
312
313QgsVector3D QgsCameraController::moveGeocentricPoint( const QgsVector3D &point, double latDiff, double lonDiff )
314{
315 try
316 {
317 QgsVector3D pointLatLon = mGlobeCrsToLatLon.transform( point );
318 pointLatLon.setX( pointLatLon.x() + lonDiff );
319 pointLatLon.setY( std::clamp( pointLatLon.y() + latDiff, -90., 90. ) );
320
321 return mGlobeCrsToLatLon.transform( pointLatLon, Qgis::TransformDirection::Reverse );
322 }
323 catch ( const QgsCsException & )
324 {
325 QgsDebugError( QStringLiteral( "moveGeocentricPoint: transform failed!" ) );
326 return point;
327 }
328}
329
330void QgsCameraController::globeMoveCenterPoint( double latDiff, double lonDiff )
331{
332 const QgsVector3D viewCenter = mCameraPose.centerPoint() + mOrigin;
333 const QgsVector3D newViewCenter = moveGeocentricPoint( viewCenter, latDiff, lonDiff );
334 mCameraPose.setCenterPoint( newViewCenter - mOrigin );
335 updateCameraFromPose();
336}
337
339{
340 mCameraPose.setDistanceFromCenterPoint( mCameraPose.distanceFromCenterPoint() * factor );
341 updateCameraFromPose();
342}
343
345{
346 mCameraPose.setPitchAngle( std::clamp( mCameraPose.pitchAngle() + angleDiff, 0.f, 90.f ) );
347 updateCameraFromPose();
348}
349
351{
352 mCameraPose.setHeadingAngle( mCameraPose.headingAngle() + angleDiff );
353 updateCameraFromPose();
354}
355
356void QgsCameraController::resetGlobe( float distance, double lat, double lon )
357{
358 QgsVector3D mapPoint;
359 try
360 {
361 mapPoint = mGlobeCrsToLatLon.transform( QgsVector3D( lon, lat, 0 ), Qgis::TransformDirection::Reverse );
362 }
363 catch ( const QgsCsException & )
364 {
365 QgsDebugError( QStringLiteral( "resetGlobe: transform failed!" ) );
366 return;
367 }
368
369 QgsCameraPose cp;
370 cp.setCenterPoint( mapPoint - mOrigin );
372 setCameraPose( cp );
373}
374
375void QgsCameraController::updateCameraFromPose()
376{
377 if ( mCamera )
378 {
379 if ( mScene->mapSettings()->sceneMode() == Qgis::SceneMode::Globe )
380 {
381 const QgsVector3D viewCenter = mCameraPose.centerPoint() + mOrigin;
382
383 QgsVector3D viewCenterLatLon;
384 try
385 {
386 viewCenterLatLon = mGlobeCrsToLatLon.transform( viewCenter );
387 }
388 catch ( const QgsCsException & )
389 {
390 QgsDebugError( QStringLiteral( "updateCameraFromPose: transform failed!" ) );
391 return;
392 }
393
394 mCameraPose.updateCameraGlobe( mCamera, viewCenterLatLon.y(), viewCenterLatLon.x() );
395 }
396 else
397 {
398 mCameraPose.updateCamera( mCamera );
399 }
400 mCameraChanged = true;
401 }
402}
403
404void QgsCameraController::moveCameraPositionBy( const QVector3D &posDiff )
405{
406 mCameraPose.setCenterPoint( mCameraPose.centerPoint() + posDiff );
407 updateCameraFromPose();
408}
409
410void QgsCameraController::onPositionChanged( Qt3DInput::QMouseEvent *mouse )
411{
412 if ( !mInputHandlersEnabled )
413 return;
414
415 QgsEventTracing::ScopedEvent traceEvent( QStringLiteral( "3D" ), QStringLiteral( "QgsCameraController::onPositionChanged" ) );
416
417 switch ( mCameraNavigationMode )
418 {
420 onPositionChangedTerrainNavigation( mouse );
421 break;
422
424 onPositionChangedFlyNavigation( mouse );
425 break;
426
428 onPositionChangedGlobeTerrainNavigation( mouse );
429 break;
430 }
431}
432
433bool QgsCameraController::screenPointToWorldPos( QPoint position, Qt3DRender::QCamera *mCameraBefore, double &depth, QVector3D &worldPosition )
434{
435 depth = sampleDepthBuffer( position.x(), position.y() );
436
437 // if there's nothing around the given position, try to get just any depth
438 // from the scene as a coarse approximation...
439 if ( depth == 1 )
440 {
441 depth = depthBufferNonVoidAverage();
442 }
443
444 worldPosition = Qgs3DUtils::screenPointToWorldPos( position, depth, mScene->engine()->size(), mCameraBefore );
445 if ( !std::isfinite( worldPosition.x() ) || !std::isfinite( worldPosition.y() ) || !std::isfinite( worldPosition.z() ) )
446 {
447 QgsDebugMsgLevel( QStringLiteral( "screenPointToWorldPos: position is NaN or Inf. This should not happen." ), 2 );
448 return false;
449 }
450
451 return true;
452}
453
454void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseEvent *mouse )
455{
456 if ( mIgnoreNextMouseMove )
457 {
458 mIgnoreNextMouseMove = false;
459 mMousePos = QPoint( mouse->x(), mouse->y() );
460 return;
461 }
462
463 const int dx = mouse->x() - mMousePos.x();
464 const int dy = mouse->y() - mMousePos.y();
465
466 const bool hasShift = ( mouse->modifiers() & Qt::ShiftModifier );
467 const bool hasCtrl = ( mouse->modifiers() & Qt::ControlModifier );
468 const bool hasLeftButton = ( mouse->buttons() & Qt::LeftButton );
469 const bool hasMiddleButton = ( mouse->buttons() & Qt::MiddleButton );
470 const bool hasRightButton = ( mouse->buttons() & Qt::RightButton );
471
472 if ( ( hasLeftButton && hasShift && !hasCtrl ) || ( hasMiddleButton && !hasShift && !hasCtrl ) )
473 {
474 // rotate/tilt using mouse (camera moves as it rotates around the clicked point)
475 setMouseParameters( MouseOperation::RotationCenter, mMousePos );
476
477 float scale = static_cast<float>( std::max( mScene->engine()->size().width(), mScene->engine()->size().height() ) );
478 float pitchDiff = 180.0f * static_cast<float>( mouse->y() - mClickPoint.y() ) / scale;
479 float yawDiff = -180.0f * static_cast<float>( mouse->x() - mClickPoint.x() ) / scale;
480
481 if ( !mDepthBufferIsReady )
482 return;
483
484 if ( !mRotationCenterCalculated )
485 {
486 double depth;
487 QVector3D worldPosition;
488 if ( screenPointToWorldPos( mClickPoint, mCameraBefore.get(), depth, worldPosition ) )
489 {
490 mRotationCenter = worldPosition;
491 mRotationDistanceFromCenter = ( mRotationCenter - mCameraBefore->position() ).length();
492 emit cameraRotationCenterChanged( mRotationCenter );
493 mRotationCenterCalculated = true;
494 }
495 }
496
497 rotateCameraAroundPivot( mRotationPitch + pitchDiff, mRotationYaw + yawDiff, mRotationCenter );
498 }
499 else if ( hasLeftButton && hasCtrl && !hasShift )
500 {
501 setMouseParameters( MouseOperation::RotationCamera );
502 // rotate/tilt using mouse (camera stays at one position as it rotates)
503 const float diffPitch = 0.2f * dy;
504 const float diffYaw = -0.2f * dx;
505 rotateCamera( diffPitch, diffYaw );
506 }
507 else if ( hasLeftButton && !hasShift && !hasCtrl )
508 {
509 // translation works as if one grabbed a point on the 3D viewer and dragged it
510 setMouseParameters( MouseOperation::Translation, mMousePos );
511
512 if ( !mDepthBufferIsReady )
513 return;
514
515 if ( !mDragPointCalculated )
516 {
517 double depth;
518 QVector3D worldPosition;
519 if ( screenPointToWorldPos( mClickPoint, mCameraBefore.get(), depth, worldPosition ) )
520 {
521 mDragDepth = depth;
522 mDragPoint = worldPosition;
523 mDragPointCalculated = true;
524 }
525 }
526
527 QVector3D cameraBeforeDragPos = mCameraBefore->position();
528
529 QVector3D moveToPosition = Qgs3DUtils::screenPointToWorldPos( mMousePos, mDragDepth, mScene->engine()->size(), mCameraBefore.get() );
530 QVector3D cameraBeforeToMoveToPos = ( moveToPosition - mCameraBefore->position() ).normalized();
531 QVector3D cameraBeforeToDragPointPos = ( mDragPoint - mCameraBefore->position() ).normalized();
532
533 // Make sure the rays are not horizontal (add small z shift if it is)
534 if ( cameraBeforeToMoveToPos.z() == 0 )
535 {
536 cameraBeforeToMoveToPos.setZ( 0.01 );
537 cameraBeforeToMoveToPos = cameraBeforeToMoveToPos.normalized();
538 }
539
540 if ( cameraBeforeToDragPointPos.z() == 0 )
541 {
542 cameraBeforeToDragPointPos.setZ( 0.01 );
543 cameraBeforeToDragPointPos = cameraBeforeToDragPointPos.normalized();
544 }
545
546 double d1 = ( mDragPoint.z() - cameraBeforeDragPos.z() ) / cameraBeforeToMoveToPos.z();
547 double d2 = ( mDragPoint.z() - cameraBeforeDragPos.z() ) / cameraBeforeToDragPointPos.z();
548
549 QVector3D from = cameraBeforeDragPos + d1 * cameraBeforeToMoveToPos;
550 QVector3D to = cameraBeforeDragPos + d2 * cameraBeforeToDragPointPos;
551
552 QVector3D shiftVector = to - from;
553
554 mCameraPose.setCenterPoint( mCameraBefore->viewCenter() + shiftVector );
555 updateCameraFromPose();
556 }
557 else if ( hasLeftButton && hasShift && hasCtrl )
558 {
559 // change the camera elevation, similar to pageUp/pageDown
560 QgsVector3D center = mCameraPose.centerPoint();
561 double tElev = mMousePos.y() - mouse->y();
562 center.set( center.x(), center.y(), center.z() + tElev * 0.5 );
563 mCameraPose.setCenterPoint( center );
564 updateCameraFromPose();
565 }
566 else if ( hasRightButton && !hasShift && !hasCtrl )
567 {
568 setMouseParameters( MouseOperation::Zoom, mMousePos );
569 if ( !mDepthBufferIsReady )
570 return;
571
572 if ( !mDragPointCalculated )
573 {
574 double depth;
575 QVector3D worldPosition;
576 if ( screenPointToWorldPos( mClickPoint, mCameraBefore.get(), depth, worldPosition ) )
577 {
578 mDragPoint = worldPosition;
579 mDragPointCalculated = true;
580 }
581 }
582
583 float oldDist = ( mCameraBefore->position() - mDragPoint ).length();
584 float newDist = oldDist;
585
586 int yOffset = 0;
587 int screenHeight = mScene->engine()->size().height();
588 QWindow *win = window();
589 if ( win )
590 {
591 yOffset = win->mapToGlobal( QPoint( 0, 0 ) ).y();
592 screenHeight = win->screen()->size().height();
593 }
594
595 // Applies smoothing
596 if ( mMousePos.y() > mClickPoint.y() ) // zoom in
597 {
598 double f = ( double ) ( mMousePos.y() - mClickPoint.y() ) / ( double ) ( screenHeight - mClickPoint.y() - yOffset );
599 f = std::max( 0.0, std::min( 1.0, f ) );
600 f = 1 - ( std::expm1( -2 * f ) ) / ( std::expm1( -2 ) );
601 newDist = newDist * f;
602 }
603 else // zoom out
604 {
605 double f = 1 - ( double ) ( mMousePos.y() + yOffset ) / ( double ) ( mClickPoint.y() + yOffset );
606 f = std::max( 0.0, std::min( 1.0, f ) );
607 f = ( std::expm1( 2 * f ) ) / ( std::expm1( 2 ) );
608 newDist = newDist + 2 * newDist * f;
609 }
610
611 double zoomFactor = newDist / oldDist;
612 zoomCameraAroundPivot( mCameraBefore->position(), zoomFactor, mDragPoint );
613 }
614
615 mMousePos = QPoint( mouse->x(), mouse->y() );
616}
617
618void QgsCameraController::onPositionChangedGlobeTerrainNavigation( Qt3DInput::QMouseEvent *mouse )
619{
620 if ( !( mouse->buttons() & Qt::LeftButton ) )
621 return;
622
623 // translation works as if one grabbed a point on the 3D viewer and dragged it
624 setMouseParameters( MouseOperation::Translation, mMousePos );
625
626 if ( !mDepthBufferIsReady )
627 return;
628
629 if ( !mDragPointCalculated )
630 {
631 double depth;
632 QVector3D worldPosition;
633 if ( !screenPointToWorldPos( mClickPoint, mCameraBefore.get(), depth, worldPosition ) )
634 return;
635
636 mDragDepth = depth;
637 mDragPoint = worldPosition;
638 mDragPointCalculated = true;
639 }
640
641 const QgsVector3D mapPressPos = QgsVector3D( mDragPoint ) + mOrigin;
642
643 double newDepth = sampleDepthBuffer( mouse->x(), mouse->y() );
644 if ( newDepth == 1 )
645 return; // the mouse is somewhere in the void...
646
647 const QVector3D newWorldPosition = Qgs3DUtils::screenPointToWorldPos( QPoint( mouse->x(), mouse->y() ), newDepth, mScene->engine()->size(), mCameraBefore.get() );
648 if ( !std::isfinite( newWorldPosition.x() ) || !std::isfinite( newWorldPosition.y() ) || !std::isfinite( newWorldPosition.z() ) )
649 return;
650
651 const QgsVector3D newMapPos = QgsVector3D( newWorldPosition ) + mOrigin;
652
653 // now that we have old and new mouse position in ECEF coordinates,
654 // let's figure out the difference in lat/lon angles and update the center point
655
656 QgsVector3D oldLatLon, newLatLon;
657 try
658 {
659 oldLatLon = mGlobeCrsToLatLon.transform( mapPressPos );
660 newLatLon = mGlobeCrsToLatLon.transform( newMapPos );
661 }
662 catch ( const QgsCsException & )
663 {
664 QgsDebugError( QStringLiteral( "onPositionChangedGlobeTerrainNavigation: transform failed!" ) );
665 return;
666 }
667
668 const double latDiff = oldLatLon.y() - newLatLon.y();
669 const double lonDiff = oldLatLon.x() - newLatLon.x();
670
671 const QgsVector3D newVC = moveGeocentricPoint( mMousePressViewCenter, latDiff, lonDiff );
672 const QgsVector3D newVCWorld = newVC - mOrigin;
673 mCameraPose.setCenterPoint( newVCWorld );
674 updateCameraFromPose();
675}
676
677
678void QgsCameraController::zoom( float factor )
679{
680 // zoom in/out
681 float dist = mCameraPose.distanceFromCenterPoint();
682 dist -= dist * factor * 0.01f;
683 mCameraPose.setDistanceFromCenterPoint( dist );
684 updateCameraFromPose();
685}
686
687void QgsCameraController::handleTerrainNavigationWheelZoom()
688{
689 if ( !mDepthBufferIsReady )
690 return;
691
692 if ( !mZoomPointCalculated )
693 {
694 double depth;
695 QVector3D worldPosition;
696 if ( screenPointToWorldPos( mMousePos, mCameraBefore.get(), depth, worldPosition ) )
697 {
698 mZoomPoint = worldPosition;
699 mZoomPointCalculated = true;
700 }
701 }
702
703 double oldDist = ( mZoomPoint - mCameraBefore->position() ).length();
704 // Each step of the scroll wheel decreases distance by 20%
705 double newDist = std::pow( 0.8, mCumulatedWheelY ) * oldDist;
706 // Make sure we don't clip the thing we're zooming to.
707 newDist = std::max( newDist, 2.0 );
708 double zoomFactor = newDist / oldDist;
709 // Don't change the distance too suddenly to hopefully prevent numerical instability
710 zoomFactor = std::clamp( zoomFactor, 0.01, 100.0 );
711
712 zoomCameraAroundPivot( mCameraBefore->position(), zoomFactor, mZoomPoint );
713
714 mCumulatedWheelY = 0;
715 setMouseParameters( MouseOperation::None );
716}
717
718void QgsCameraController::onWheel( Qt3DInput::QWheelEvent *wheel )
719{
720 if ( !mInputHandlersEnabled )
721 return;
722
723 switch ( mCameraNavigationMode )
724 {
726 {
727 const float scaling = ( ( wheel->modifiers() & Qt::ControlModifier ) != 0 ? 0.1f : 1.0f ) / 1000.f;
728 setCameraMovementSpeed( mCameraMovementSpeed + mCameraMovementSpeed * scaling * wheel->angleDelta().y() );
729 break;
730 }
731
733 {
734 // Scale our variable to roughly "number of normal steps", with Ctrl
735 // increasing granularity 10x
736 const double scaling = ( 1.0 / 120.0 ) * ( ( wheel->modifiers() & Qt::ControlModifier ) != 0 ? 0.1 : 1.0 );
737
738 // Apparently angleDelta needs to be accumulated
739 // see: https://doc.qt.io/qt-5/qwheelevent.html#angleDelta
740 mCumulatedWheelY += scaling * wheel->angleDelta().y();
741
742 if ( mCurrentOperation != MouseOperation::ZoomWheel )
743 {
744 setMouseParameters( MouseOperation::ZoomWheel );
745 // The actual zooming will happen after we get a new depth buffer
746 }
747 else
748 {
749 handleTerrainNavigationWheelZoom();
750 }
751 break;
752 }
753
755 {
756 float wheelAmount = static_cast<float>( wheel->angleDelta().y() );
757 float factor = abs( wheelAmount ) / 1000.f;
758 float mulFactor = wheelAmount > 0 ? ( 1 - factor ) : ( 1 + factor );
759 mCameraPose.setDistanceFromCenterPoint( mCameraPose.distanceFromCenterPoint() * mulFactor );
760 updateCameraFromPose();
761 break;
762 }
763 }
764}
765
766void QgsCameraController::onMousePressed( Qt3DInput::QMouseEvent *mouse )
767{
768 if ( !mInputHandlersEnabled )
769 return;
770
771 mKeyboardHandler->setFocus( true );
772
773 if ( mouse->button() == Qt3DInput::QMouseEvent::MiddleButton || ( ( mouse->modifiers() & Qt::ShiftModifier ) != 0 && mouse->button() == Qt3DInput::QMouseEvent::LeftButton ) || ( ( mouse->modifiers() & Qt::ControlModifier ) != 0 && mouse->button() == Qt3DInput::QMouseEvent::LeftButton ) )
774 {
775 mMousePos = QPoint( mouse->x(), mouse->y() );
776
777 if ( mCaptureFpsMouseMovements )
778 mIgnoreNextMouseMove = true;
779
780 const MouseOperation operation {
781 ( mouse->modifiers() & Qt::ControlModifier ) != 0 && mouse->button() == Qt3DInput::QMouseEvent::LeftButton ? MouseOperation::RotationCamera : MouseOperation::RotationCenter
782 };
783 setMouseParameters( operation, mMousePos );
784 }
785
786 else if ( mouse->button() == Qt3DInput::QMouseEvent::LeftButton || mouse->button() == Qt3DInput::QMouseEvent::RightButton )
787 {
788 mMousePos = QPoint( mouse->x(), mouse->y() );
789
790 if ( mCaptureFpsMouseMovements )
791 mIgnoreNextMouseMove = true;
792
793 const MouseOperation operation = ( mouse->button() == Qt3DInput::QMouseEvent::LeftButton ) ? MouseOperation::Translation : MouseOperation::Zoom;
794 setMouseParameters( operation, mMousePos );
795 }
796}
797
798void QgsCameraController::onMouseReleased( Qt3DInput::QMouseEvent *mouse )
799{
800 Q_UNUSED( mouse )
801 if ( !mInputHandlersEnabled )
802 return;
803
804
805 setMouseParameters( MouseOperation::None );
806}
807
808void QgsCameraController::onKeyPressed( Qt3DInput::QKeyEvent *event )
809{
810 if ( !mInputHandlersEnabled )
811 return;
812
813 if ( event->modifiers() & Qt::ControlModifier && event->key() == Qt::Key_QuoteLeft )
814 {
815 // switch navigation mode
816 switch ( mCameraNavigationMode )
817 {
820 break;
824 break;
825 }
826 return;
827 }
828
829 switch ( mCameraNavigationMode )
830 {
832 {
833 onKeyPressedFlyNavigation( event );
834 break;
835 }
836
838 {
839 onKeyPressedTerrainNavigation( event );
840 break;
841 }
842
844 {
845 onKeyPressedGlobeTerrainNavigation( event );
846 break;
847 }
848 }
849}
850
851void QgsCameraController::onKeyPressedTerrainNavigation( Qt3DInput::QKeyEvent *event )
852{
853 const bool hasShift = ( event->modifiers() & Qt::ShiftModifier );
854 const bool hasCtrl = ( event->modifiers() & Qt::ControlModifier );
855
856 int tx = 0, ty = 0, tElev = 0;
857 switch ( event->key() )
858 {
859 case Qt::Key_Left:
860 tx -= 1;
861 break;
862 case Qt::Key_Right:
863 tx += 1;
864 break;
865
866 case Qt::Key_Up:
867 ty += 1;
868 break;
869 case Qt::Key_Down:
870 ty -= 1;
871 break;
872
873 case Qt::Key_PageDown:
874 tElev -= 1;
875 break;
876 case Qt::Key_PageUp:
877 tElev += 1;
878 break;
879 }
880
881 if ( tx || ty )
882 {
883 if ( !hasShift && !hasCtrl )
884 {
885 moveView( tx, ty );
886 }
887 else if ( hasShift && !hasCtrl )
888 {
889 // rotate/tilt using keyboard (camera moves as it rotates around its view center)
892 }
893 else if ( hasCtrl && !hasShift )
894 {
895 // rotate/tilt using keyboard (camera stays at one position as it rotates)
896 const float diffPitch = ty; // down key = rotating camera down
897 const float diffYaw = -tx; // right key = rotating camera to the right
898 rotateCamera( diffPitch, diffYaw );
899 }
900 }
901
902 if ( tElev )
903 {
904 QgsVector3D center = mCameraPose.centerPoint();
905 center.set( center.x(), center.y(), center.z() + tElev * 10 );
906 mCameraPose.setCenterPoint( center );
907 updateCameraFromPose();
908 }
909}
910
911void QgsCameraController::onKeyPressedGlobeTerrainNavigation( Qt3DInput::QKeyEvent *event )
912{
913 // both move factor and zoom factor are just empirically picked numbers
914 // that seem to work well (providing steps that are not too big / not too small)
915 constexpr float MOVE_FACTOR = 0.000001f; // multiplied by distance to get angle
916 constexpr float ZOOM_FACTOR = 0.9f;
917
918 const bool hasShift = ( event->modifiers() & Qt::ShiftModifier );
919
920 switch ( event->key() )
921 {
922 case Qt::Key_Left:
923 if ( hasShift )
925 else
926 globeMoveCenterPoint( 0, -MOVE_FACTOR * mCameraPose.distanceFromCenterPoint() );
927 break;
928 case Qt::Key_Right:
929 if ( hasShift )
931 else
932 globeMoveCenterPoint( 0, MOVE_FACTOR * mCameraPose.distanceFromCenterPoint() );
933 break;
934 case Qt::Key_Up:
935 if ( hasShift )
937 else
938 globeMoveCenterPoint( MOVE_FACTOR * mCameraPose.distanceFromCenterPoint(), 0 );
939 break;
940 case Qt::Key_Down:
941 if ( hasShift )
943 else
944 globeMoveCenterPoint( -MOVE_FACTOR * mCameraPose.distanceFromCenterPoint(), 0 );
945 break;
946 case Qt::Key_PageDown:
947 globeZoom( ZOOM_FACTOR );
948 break;
949 case Qt::Key_PageUp:
950 globeZoom( 1 / ZOOM_FACTOR );
951 break;
952 default:
953 break;
954 }
955}
956
957void QgsCameraController::onKeyPressedFlyNavigation( Qt3DInput::QKeyEvent *event )
958{
959 switch ( event->key() )
960 {
961 case Qt::Key_QuoteLeft:
962 {
963 // toggle mouse lock mode
964 mCaptureFpsMouseMovements = !mCaptureFpsMouseMovements;
965 mIgnoreNextMouseMove = true;
966 if ( mCaptureFpsMouseMovements )
967 {
968 qApp->setOverrideCursor( QCursor( Qt::BlankCursor ) );
969 }
970 else
971 {
972 qApp->restoreOverrideCursor();
973 }
974 return;
975 }
976
977 case Qt::Key_Escape:
978 {
979 // always exit mouse lock mode
980 if ( mCaptureFpsMouseMovements )
981 {
982 mCaptureFpsMouseMovements = false;
983 mIgnoreNextMouseMove = true;
984 qApp->restoreOverrideCursor();
985 return;
986 }
987 break;
988 }
989
990 default:
991 break;
992 }
993
994 if ( event->isAutoRepeat() )
995 return;
996
997 mDepressedKeys.insert( event->key() );
998}
999
1000void QgsCameraController::walkView( double tx, double ty, double tz )
1001{
1002 const QVector3D cameraUp = mCamera->upVector().normalized();
1003 const QVector3D cameraFront = ( QVector3D( mCameraPose.centerPoint().x(), mCameraPose.centerPoint().y(), mCameraPose.centerPoint().z() ) - mCamera->position() ).normalized();
1004 const QVector3D cameraLeft = QVector3D::crossProduct( cameraUp, cameraFront );
1005
1006 QVector3D cameraPosDiff( 0.0f, 0.0f, 0.0f );
1007
1008 if ( tx != 0.0 )
1009 {
1010 cameraPosDiff += static_cast<float>( tx ) * cameraFront;
1011 }
1012 if ( ty != 0.0 )
1013 {
1014 cameraPosDiff += static_cast<float>( ty ) * cameraLeft;
1015 }
1016 if ( tz != 0.0 )
1017 {
1018 cameraPosDiff += static_cast<float>( tz ) * QVector3D( 0.0f, 0.0f, 1.0f );
1019 }
1020
1021 moveCameraPositionBy( cameraPosDiff );
1022}
1023
1024void QgsCameraController::applyFlyModeKeyMovements()
1025{
1026 // shift = "run", ctrl = "slow walk"
1027 const bool shiftPressed = mDepressedKeys.contains( Qt::Key_Shift );
1028 const bool ctrlPressed = mDepressedKeys.contains( Qt::Key_Control );
1029
1030 const double movementSpeed = mCameraMovementSpeed * ( shiftPressed ? 2 : 1 ) * ( ctrlPressed ? 0.1 : 1 );
1031
1032 bool changed = false;
1033 double x = 0.0;
1034 double y = 0.0;
1035 double z = 0.0;
1036 if ( mDepressedKeys.contains( Qt::Key_Left ) || mDepressedKeys.contains( Qt::Key_A ) )
1037 {
1038 changed = true;
1039 y += movementSpeed;
1040 }
1041
1042 if ( mDepressedKeys.contains( Qt::Key_Right ) || mDepressedKeys.contains( Qt::Key_D ) )
1043 {
1044 changed = true;
1045 y -= movementSpeed;
1046 }
1047
1048 if ( mDepressedKeys.contains( Qt::Key_Up ) || mDepressedKeys.contains( Qt::Key_W ) )
1049 {
1050 changed = true;
1051 x += movementSpeed;
1052 }
1053
1054 if ( mDepressedKeys.contains( Qt::Key_Down ) || mDepressedKeys.contains( Qt::Key_S ) )
1055 {
1056 changed = true;
1057 x -= movementSpeed;
1058 }
1059
1060 // note -- vertical axis movements are slower by default then horizontal ones, as GIS projects
1061 // tend to have much more limited elevation range vs ground range
1062 static constexpr double ELEVATION_MOVEMENT_SCALE = 0.5;
1063 if ( mDepressedKeys.contains( Qt::Key_PageUp ) || mDepressedKeys.contains( Qt::Key_E ) )
1064 {
1065 changed = true;
1066 z += ELEVATION_MOVEMENT_SCALE * movementSpeed;
1067 }
1068
1069 if ( mDepressedKeys.contains( Qt::Key_PageDown ) || mDepressedKeys.contains( Qt::Key_Q ) )
1070 {
1071 changed = true;
1072 z -= ELEVATION_MOVEMENT_SCALE * movementSpeed;
1073 }
1074
1075 if ( changed )
1076 walkView( x, y, z );
1077}
1078
1079void QgsCameraController::onPositionChangedFlyNavigation( Qt3DInput::QMouseEvent *mouse )
1080{
1081 const bool hasMiddleButton = ( mouse->buttons() & Qt::MiddleButton );
1082 const bool hasRightButton = ( mouse->buttons() & Qt::RightButton );
1083
1084 const double dx = mCaptureFpsMouseMovements ? QCursor::pos().x() - mMousePos.x() : mouse->x() - mMousePos.x();
1085 const double dy = mCaptureFpsMouseMovements ? QCursor::pos().y() - mMousePos.y() : mouse->y() - mMousePos.y();
1086 mMousePos = mCaptureFpsMouseMovements ? QCursor::pos() : QPoint( mouse->x(), mouse->y() );
1087
1088 if ( mIgnoreNextMouseMove )
1089 {
1090 mIgnoreNextMouseMove = false;
1091 return;
1092 }
1093
1094 if ( hasMiddleButton )
1095 {
1096 // middle button drag = pan camera in place (strafe)
1097 const QVector3D cameraUp = mCamera->upVector().normalized();
1098 const QVector3D cameraFront = ( QVector3D( mCameraPose.centerPoint().x(), mCameraPose.centerPoint().y(), mCameraPose.centerPoint().z() ) - mCamera->position() ).normalized();
1099 const QVector3D cameraLeft = QVector3D::crossProduct( cameraUp, cameraFront );
1100 const QVector3D cameraPosDiff = -dx * cameraLeft - dy * cameraUp;
1101 moveCameraPositionBy( mCameraMovementSpeed * cameraPosDiff / 10.0 );
1102 }
1103 else if ( hasRightButton )
1104 {
1105 // right button drag = camera dolly
1106 const QVector3D cameraFront = ( QVector3D( mCameraPose.centerPoint().x(), mCameraPose.centerPoint().y(), mCameraPose.centerPoint().z() ) - mCamera->position() ).normalized();
1107 const QVector3D cameraPosDiff = dy * cameraFront;
1108 moveCameraPositionBy( mCameraMovementSpeed * cameraPosDiff / 5.0 );
1109 }
1110 else
1111 {
1112 if ( mCaptureFpsMouseMovements )
1113 {
1114 float diffPitch = -0.2f * dy;
1115 switch ( mVerticalAxisInversion )
1116 {
1118 diffPitch *= -1;
1119 break;
1120
1123 break;
1124 }
1125
1126 const float diffYaw = -0.2f * dx;
1127 rotateCamera( diffPitch, diffYaw );
1128 }
1129 else if ( mouse->buttons() & Qt::LeftButton )
1130 {
1131 float diffPitch = -0.2f * dy;
1132 switch ( mVerticalAxisInversion )
1133 {
1136 diffPitch *= -1;
1137 break;
1138
1140 break;
1141 }
1142 const float diffYaw = -0.2f * dx;
1143 rotateCamera( diffPitch, diffYaw );
1144 }
1145 }
1146
1147 if ( mCaptureFpsMouseMovements )
1148 {
1149 mIgnoreNextMouseMove = true;
1150
1151 // reset cursor back to center of map widget
1152 emit setCursorPosition( QPoint( mScene->engine()->size().width() / 2, mScene->engine()->size().height() / 2 ) );
1153 }
1154}
1155
1156void QgsCameraController::onKeyReleased( Qt3DInput::QKeyEvent *event )
1157{
1158 if ( !mInputHandlersEnabled )
1159 return;
1160
1161 if ( event->isAutoRepeat() )
1162 return;
1163
1164 mDepressedKeys.remove( event->key() );
1165}
1166
1168{
1169 // Tilt up the view by deltaPitch around the view center (camera moves)
1170 float pitch = mCameraPose.pitchAngle();
1171 pitch -= deltaPitch; // down key = moving camera toward terrain
1172 mCameraPose.setPitchAngle( pitch );
1173 updateCameraFromPose();
1174}
1175
1177{
1178 // Rotate clockwise the view by deltaYaw around the view center (camera moves)
1179 float yaw = mCameraPose.headingAngle();
1180 yaw -= deltaYaw; // right key = moving camera clockwise
1181 mCameraPose.setHeadingAngle( yaw );
1182 updateCameraFromPose();
1183}
1184
1186{
1187 mCameraPose.setHeadingAngle( angle );
1188 updateCameraFromPose();
1189}
1190
1191void QgsCameraController::moveView( float tx, float ty )
1192{
1193 const float yaw = mCameraPose.headingAngle();
1194 const float dist = mCameraPose.distanceFromCenterPoint();
1195 const float x = tx * dist * 0.02f;
1196 const float y = -ty * dist * 0.02f;
1197
1198 // moving with keyboard - take into account yaw of camera
1199 const float t = sqrt( x * x + y * y );
1200 const float a = atan2( y, x ) - yaw * M_PI / 180;
1201 const float dx = cos( a ) * t;
1202 const float dy = sin( a ) * t;
1203
1204 QgsVector3D center = mCameraPose.centerPoint();
1205 center.set( center.x() + dx, center.y() - dy, center.z() );
1206 mCameraPose.setCenterPoint( center );
1207 updateCameraFromPose();
1208}
1209
1211{
1212 if ( event->key() == Qt::Key_QuoteLeft )
1213 return true;
1214
1215 switch ( mCameraNavigationMode )
1216 {
1218 {
1219 switch ( event->key() )
1220 {
1221 case Qt::Key_Left:
1222 case Qt::Key_A:
1223 case Qt::Key_Right:
1224 case Qt::Key_D:
1225 case Qt::Key_Up:
1226 case Qt::Key_W:
1227 case Qt::Key_Down:
1228 case Qt::Key_S:
1229 case Qt::Key_PageUp:
1230 case Qt::Key_E:
1231 case Qt::Key_PageDown:
1232 case Qt::Key_Q:
1233 return true;
1234
1235 case Qt::Key_Escape:
1236 if ( mCaptureFpsMouseMovements )
1237 return true;
1238 break;
1239
1240 default:
1241 break;
1242 }
1243 break;
1244 }
1245
1247 {
1248 switch ( event->key() )
1249 {
1250 case Qt::Key_Left:
1251 case Qt::Key_Right:
1252 case Qt::Key_Up:
1253 case Qt::Key_Down:
1254 case Qt::Key_PageUp:
1255 case Qt::Key_PageDown:
1256 return true;
1257
1258 default:
1259 break;
1260 }
1261 break;
1262 }
1263
1265 {
1266 switch ( event->key() )
1267 {
1268 case Qt::Key_Left:
1269 case Qt::Key_Right:
1270 case Qt::Key_Up:
1271 case Qt::Key_Down:
1272 case Qt::Key_PageUp:
1273 case Qt::Key_PageDown:
1274 return true;
1275
1276 default:
1277 break;
1278 }
1279 break;
1280 }
1281 }
1282 return false;
1283}
1284
1285void QgsCameraController::depthBufferCaptured( const QImage &depthImage )
1286{
1287 mDepthBufferImage = depthImage;
1288 mDepthBufferIsReady = true;
1289 mDepthBufferNonVoidAverage = -1;
1290
1291 if ( mCurrentOperation == MouseOperation::ZoomWheel )
1292 {
1293 handleTerrainNavigationWheelZoom();
1294 }
1295}
1296
1297bool QgsCameraController::isATranslationRotationSequence( MouseOperation newOperation ) const
1298{
1299 return std::find( mTranslateOrRotate.begin(), mTranslateOrRotate.end(), newOperation ) != std::end( mTranslateOrRotate ) && std::find( mTranslateOrRotate.begin(), mTranslateOrRotate.end(), mCurrentOperation ) != std::end( mTranslateOrRotate );
1300}
1301
1302void QgsCameraController::setMouseParameters( const MouseOperation &newOperation, const QPoint &clickPoint )
1303{
1304 if ( newOperation == mCurrentOperation )
1305 {
1306 return;
1307 }
1308
1309 if ( newOperation == MouseOperation::None )
1310 {
1311 mClickPoint = QPoint();
1312 }
1313 // click point and rotation angles are updated if:
1314 // - it has never been computed
1315 // - the current and new operations are both rotation and translation
1316 // Indeed, if the sequence such as rotation -> zoom -> rotation updating mClickPoint on
1317 // the click point does not need to be updated because the relative mouse position is kept
1318 // during a zoom operation
1319 else if ( mClickPoint.isNull() || isATranslationRotationSequence( newOperation ) )
1320 {
1321 mClickPoint = clickPoint;
1322 mRotationPitch = mCameraPose.pitchAngle();
1323 mRotationYaw = mCameraPose.headingAngle();
1324 }
1325 mCurrentOperation = newOperation;
1326 mDepthBufferIsReady = false;
1327 mRotationCenterCalculated = false;
1328 mDragPointCalculated = false;
1329 mZoomPointCalculated = false;
1330
1331 if ( mCurrentOperation != MouseOperation::None && mCurrentOperation != MouseOperation::RotationCamera )
1332 {
1333 mMousePressViewCenter = mCameraPose.centerPoint() + mOrigin;
1334
1336
1337 mCameraBefore->setPosition( mCamera->position() );
1338 mCameraBefore->setViewCenter( mCamera->viewCenter() );
1339 mCameraBefore->setUpVector( mCamera->upVector() );
1340 mCameraBefore->setProjectionMatrix( mCamera->projectionMatrix() );
1341 mCameraBefore->setNearPlane( mCamera->nearPlane() );
1342 mCameraBefore->setFarPlane( mCamera->farPlane() );
1343 mCameraBefore->setAspectRatio( mCamera->aspectRatio() );
1344 mCameraBefore->setFieldOfView( mCamera->fieldOfView() );
1345 }
1346}
1347
1349{
1350 QgsVector3D diff = origin - mOrigin;
1351 mCameraPose.setCenterPoint( mCameraPose.centerPoint() - diff );
1352
1353 // update other members that depend on world coordinates
1354 mCameraBefore->setPosition( ( QgsVector3D( mCameraBefore->position() ) - diff ).toVector3D() );
1355 mCameraBefore->setViewCenter( ( QgsVector3D( mCameraBefore->viewCenter() ) - diff ).toVector3D() );
1356 mDragPoint = ( QgsVector3D( mDragPoint ) - diff ).toVector3D();
1357 mRotationCenter = ( QgsVector3D( mRotationCenter ) - diff ).toVector3D();
1358
1359 mOrigin = origin;
1360
1361 updateCameraFromPose();
1362}
The Qgis class provides global constants for use throughout the application.
Definition qgis.h:54
VerticalAxisInversion
Vertical axis inversion options for 3D views.
Definition qgis.h:4026
@ 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:4001
@ TerrainBased
The default navigation based on the terrain.
@ Walk
Uses WASD keys or arrows to navigate in walking (first person) manner.
@ GlobeTerrainBased
Navigation similar to TerrainBased, but for use with globe.
@ Globe
Scene is represented as a globe using a geocentric CRS.
@ Reverse
Reverse/inverse transform (from destination to source)
Qgs3DMapSettings * mapSettings() const
Returns the 3D map settings.
QgsAbstract3DEngine * engine() const
Returns the abstract 3D engine.
QgsTerrainEntity * terrainEntity()
Returns terrain entity (may be temporarily nullptr)
Qgis::SceneMode sceneMode() const
Returns mode of the 3D scene - whether it is represented as a globe (when using Geocentric CRS such a...
QgsRectangle extent() const
Returns the 3D scene's 2D extent in the 3D scene's CRS.
QgsCoordinateReferenceSystem crs() const
Returns coordinate reference system used in the 3D scene.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context, which stores various information regarding which datum tran...
QgsVector3D origin() const
Returns coordinates in map CRS at which 3D scene has origin (0,0,0).
static QQuaternion rotationFromPitchHeadingAngles(float pitchAngle, float headingAngle)
Returns rotation quaternion that performs rotation around X axis by pitchAngle, followed by rotation ...
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:241
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.
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 setLookingAtMapPoint(const QgsVector3D &point, float distance, float pitch, float yaw)
Sets camera configuration like setLookingAtPoint(), but the point is given in map 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).
~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 globeMoveCenterPoint(double latDiff, double lonDiff)
Orbits camera around the globe by the specified amount given as the difference in latitude/longitude ...
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 globeUpdatePitchAngle(float angleDiff)
Updates pitch angle by the specified amount given as the angular difference in degrees.
void zoomCameraAroundPivot(const QVector3D &oldCameraPosition, double zoomFactor, const QVector3D &pivotPoint)
Zooms camera by given zoom factor (>1 one means zoom in) while keeping the pivot point (given in worl...
void globeZoom(float factor)
Moves camera closer or further away from the globe.
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.
QgsVector3D lookingAtMapPoint() const
Returns the point in the map 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 resetGlobe(float distance, double lat=0, double lon=0)
Resets view of the globe to look at a particular location given as latitude and longitude (in degrees...
void zoom(float factor)
Zoom the map by factor.
void globeUpdateHeadingAngle(float angleDiff)
Updates heading angle by the specified amount given as the angular difference in degrees.
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 setCameraPose(const QgsCameraPose &camPose, bool force=false)
Sets camera pose.
void setOrigin(const QgsVector3D &origin)
Reacts to the shift of origin of the scene, updating camera pose and any other member variables so th...
void setCameraHeadingAngle(float angle)
Set camera heading to angle (used for rotating the view)
void setCameraMovementSpeed(double movementSpeed)
Sets the camera movement speed.
void rotateCameraAroundPivot(float newPitch, float newHeading, const QVector3D &pivotPoint)
Rotates the camera around the pivot point (in world coordinates) to the given new pitch and heading a...
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.
void updateCameraGlobe(Qt3DRender::QCamera *camera, double lat, double lon)
Updates camera when using a globe scene.
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.
QgsCoordinateReferenceSystem toGeographicCrs() const
Returns the geographic CRS associated with this CRS object.
Class for doing transforms between two map coordinate systems.
QgsPointXY transform(const QgsPointXY &point, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Transform the point from the source CRS to the destination CRS.
Custom exception class for Coordinate Reference System related exceptions.
A class to represent a 2D point.
Definition qgspointxy.h:60
double y
Definition qgspointxy.h:64
double x
Definition qgspointxy.h:63
QgsPointXY center
Class for storage of 3D vectors similar to QVector3D, with the difference that it uses double precisi...
Definition qgsvector3d.h:31
double y() const
Returns Y coordinate.
Definition qgsvector3d.h:50
double z() const
Returns Z coordinate.
Definition qgsvector3d.h:52
double x() const
Returns X coordinate.
Definition qgsvector3d.h:48
void setX(double x)
Sets X coordinate.
Definition qgsvector3d.h:58
void set(double x, double y, double z)
Sets vector coordinates.
Definition qgsvector3d.h:73
void setY(double y)
Sets Y coordinate.
Definition qgsvector3d.h:64
QWindow * window()
Returns the internal 3D window where all the rendered output is displayed.
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:41
#define QgsDebugError(str)
Definition qgslogger.h:40