QGIS API Documentation 4.1.0-Master (659fe69c07c)
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
18#include <cmath>
19
20#include "qgis.h"
21#include "qgs3dmapscene.h"
22#include "qgs3dutils.h"
23#include "qgscrosssection.h"
24#include "qgseventtracing.h"
25#include "qgslogger.h"
26#include "qgsray3d.h"
27#include "qgsraycastcontext.h"
28#include "qgsraycasthit.h"
29#include "qgsterrainentity.h"
30#include "qgsvector3d.h"
31#include "qgswindow3dengine.h"
32
33#include <QDomDocument>
34#include <QQuaternion>
35#include <QString>
36#include <QStringLiteral>
37#include <Qt3DInput>
38#include <Qt3DRender/QCamera>
39
40#include "moc_qgscameracontroller.cpp"
41
42using namespace Qt::StringLiterals;
43
45 : Qt3DCore::QEntity( scene )
46 , mScene( scene )
47 , mCamera( scene->engine()->camera() )
48 , mCameraBefore( new Qt3DRender::QCamera )
49 , mMouseHandler( new Qt3DInput::QMouseHandler )
50 , mKeyboardHandler( new Qt3DInput::QKeyboardHandler )
51 , mOrigin( scene->mapSettings()->origin() )
52{
53 mMouseHandler->setSourceDevice( new Qt3DInput::QMouseDevice() );
54 connect( mMouseHandler, &Qt3DInput::QMouseHandler::positionChanged, this, &QgsCameraController::onPositionChanged );
55 connect( mMouseHandler, &Qt3DInput::QMouseHandler::wheel, this, &QgsCameraController::onWheel );
56 connect( mMouseHandler, &Qt3DInput::QMouseHandler::pressed, this, &QgsCameraController::onMousePressed );
57 connect( mMouseHandler, &Qt3DInput::QMouseHandler::released, this, &QgsCameraController::onMouseReleased );
58 addComponent( mMouseHandler );
59
60 // Disable the handlers when the entity is disabled
61 connect( this, &Qt3DCore::QEntity::enabledChanged, mMouseHandler, &Qt3DInput::QMouseHandler::setEnabled );
62 connect( this, &Qt3DCore::QEntity::enabledChanged, 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 if ( mScene->mapSettings()->sceneMode() == Qgis::SceneMode::Globe )
70 {
71 setCameraNavigationMode( mScene->mapSettings()->cameraNavigationMode() );
72 mGlobeCrsToLatLon = QgsCoordinateTransform( mScene->mapSettings()->crs(), mScene->mapSettings()->crs().toGeographicCrs(), mScene->mapSettings()->transformContext() );
73 }
74}
75
77
78QWindow *QgsCameraController::window() const
79{
80 QgsWindow3DEngine *windowEngine = qobject_cast<QgsWindow3DEngine *>( mScene->engine() );
81 return windowEngine ? windowEngine->window() : nullptr;
82}
83
85{
86 if ( navigationMode == mCameraNavigationMode )
87 return;
88
89 mCameraNavigationMode = navigationMode;
90 mIgnoreNextMouseMove = true;
91 emit navigationModeChanged( mCameraNavigationMode );
92}
93
95{
96 if ( movementSpeed == mCameraMovementSpeed )
97 return;
98
99 // If the speed becomes 0, navigation does not work anymore
100 // If the speed becomes too important, only one walk can move the view far from the scene.
101 mCameraMovementSpeed = std::clamp( movementSpeed, 0.05, 150.0 );
102 emit cameraMovementSpeedChanged( mCameraMovementSpeed );
103}
104
106{
107 mVerticalAxisInversion = inversion;
108}
109
110void QgsCameraController::rotateCamera( float diffPitch, float diffHeading )
111{
112 float newPitch = mCameraPose.pitchAngle() + diffPitch;
113 float newHeading = mCameraPose.headingAngle() + diffHeading;
114
115 newPitch = std::clamp( newPitch, 0.f, 180.f ); // prevent going over the head
116
117 switch ( mScene->mapSettings()->sceneMode() )
118 {
120 {
121 // When on a globe, we need to calculate "where is up" (the normal of a tangent plane).
122 // Also it uses different axes than the standard plane-based view.
123 // See QgsCameraPose::updateCameraGlobe(), we basically want to make sure
124 // that after an update, the camera stays in the same spot.
125 QgsVector3D viewCenterLatLon;
126 try
127 {
128 viewCenterLatLon = mGlobeCrsToLatLon.transform( mCameraPose.centerPoint() + mOrigin );
129 }
130 catch ( const QgsCsException & )
131 {
132 QgsDebugError( u"rotateCamera: ECEF -> lat,lon transform failed!"_s );
133 return;
134 }
135 QQuaternion qLatLon = QQuaternion::fromAxisAndAngle( QVector3D( 0, 0, 1 ), static_cast<float>( viewCenterLatLon.x() ) )
136 * QQuaternion::fromAxisAndAngle( QVector3D( 0, -1, 0 ), static_cast<float>( viewCenterLatLon.y() ) );
137 QQuaternion qPitchHeading = QQuaternion::fromAxisAndAngle( QVector3D( 1, 0, 0 ), newHeading ) * QQuaternion::fromAxisAndAngle( QVector3D( 0, 1, 0 ), newPitch );
138 QVector3D newCameraToCenter = ( qLatLon * qPitchHeading * QVector3D( -1, 0, 0 ) ) * mCameraPose.distanceFromCenterPoint();
139
140 mCameraPose.setCenterPoint( mCamera->position() + newCameraToCenter );
141 mCameraPose.setPitchAngle( newPitch );
142 mCameraPose.setHeadingAngle( newHeading );
143 updateCameraFromPose();
144 return;
145 }
146
148 {
149 QQuaternion q = Qgs3DUtils::rotationFromPitchHeadingAngles( newPitch, newHeading );
150 QVector3D newCameraToCenter = q * QVector3D( 0, 0, -mCameraPose.distanceFromCenterPoint() );
151 mCameraPose.setCenterPoint( mCamera->position() + newCameraToCenter );
152 mCameraPose.setPitchAngle( newPitch );
153 mCameraPose.setHeadingAngle( newHeading );
154 updateCameraFromPose();
155 return;
156 }
157 }
158}
159
160void QgsCameraController::rotateCameraAroundPivot( float newPitch, float newHeading, const QVector3D &pivotPoint )
161{
162 const float oldPitch = mCameraPose.pitchAngle();
163 const float oldHeading = mCameraPose.headingAngle();
164
165 newPitch = std::clamp( newPitch, 0.f, 180.f ); // prevent going over the head
166
167 // First undo the previously applied rotation, then apply the new rotation
168 // (We can't just apply our euler angles difference because the camera may be already rotated)
169 const QQuaternion qNew = Qgs3DUtils::rotationFromPitchHeadingAngles( newPitch, newHeading );
170 const QQuaternion qOld = Qgs3DUtils::rotationFromPitchHeadingAngles( oldPitch, oldHeading );
171 const QQuaternion q = qNew * qOld.conjugated();
172
173 const QVector3D newViewCenter = q * ( mCamera->viewCenter() - pivotPoint ) + pivotPoint;
174
175 mCameraPose.setCenterPoint( newViewCenter );
176 mCameraPose.setPitchAngle( newPitch );
177 mCameraPose.setHeadingAngle( newHeading );
178 updateCameraFromPose();
179}
180
181void QgsCameraController::zoomCameraAroundPivot( const QVector3D &oldCameraPosition, double zoomFactor, const QVector3D &pivotPoint )
182{
183 // step 1: move camera along the line connecting reference camera position and our pivot point
184 QVector3D newCamPosition = pivotPoint + ( oldCameraPosition - pivotPoint ) * zoomFactor;
185 double newDistance = mCameraPose.distanceFromCenterPoint() * zoomFactor;
186
187 // step 2: using the new camera position and distance from center, calculate new view center
188 QQuaternion q = Qgs3DUtils::rotationFromPitchHeadingAngles( mCameraPose.pitchAngle(), mCameraPose.headingAngle() );
189 QVector3D cameraToCenter = q * QVector3D( 0, 0, -newDistance );
190 QVector3D newViewCenter = newCamPosition + cameraToCenter;
191
192 mCameraPose.setDistanceFromCenterPoint( newDistance );
193 mCameraPose.setCenterPoint( newViewCenter );
194 updateCameraFromPose();
195}
196
197void QgsCameraController::zoomCameraAroundPivot( const QVector3D &oldCameraPosition, double oldDistanceFromCenterPoint, double zoomFactor, const QVector3D &pivotPoint )
198{
199 // step 1: move camera along the line connecting reference camera position and our pivot point
200 QVector3D newCamPosition = pivotPoint + ( oldCameraPosition - pivotPoint ) * zoomFactor;
201 const double newDistance = oldDistanceFromCenterPoint * zoomFactor;
202
203 // step 2: using the new camera position and distance from center, calculate new view center
204 QQuaternion q = Qgs3DUtils::rotationFromPitchHeadingAngles( mCameraPose.pitchAngle(), mCameraPose.headingAngle() );
205 QVector3D cameraToCenter = q * QVector3D( 0, 0, -newDistance );
206 QVector3D newViewCenter = newCamPosition + cameraToCenter;
207
208 mCameraPose.setDistanceFromCenterPoint( newDistance );
209 mCameraPose.setCenterPoint( newViewCenter );
210 updateCameraFromPose();
211}
212
214{
215 Q_UNUSED( dt )
216
217 // In case viewport size has changed
218 updateOrthographicProjectionPlane();
219
220 if ( mCameraChanged )
221 {
222 emit cameraChanged();
223 mCameraChanged = false;
224 }
225}
226
228{
229 QgsPointXY extentCenter = mScene->mapSettings()->extent().center();
230 QgsVector3D origin = mScene->mapSettings()->origin();
231 setViewFromTop( extentCenter.x() - origin.x(), extentCenter.y() - origin.y(), distance );
232}
233
234void QgsCameraController::setViewFromTop( float worldX, float worldY, float distance, float yaw )
235{
236 if ( mScene->mapSettings()->sceneMode() == Qgis::SceneMode::Globe )
237 {
238 QgsDebugError( u"setViewFromTop() should not be used with globe!"_s );
239 return;
240 }
241
242 QgsCameraPose camPose;
243 QgsTerrainEntity *terrain = mScene->terrainEntity();
244 const float terrainElevationOffset = terrain ? terrain->terrainElevationOffset() : 0.0f;
245 camPose.setCenterPoint( QgsVector3D( worldX, worldY, terrainElevationOffset - mScene->mapSettings()->origin().z() ) );
247 camPose.setHeadingAngle( yaw );
248
249 // a basic setup to make frustum depth range long enough that it does not cull everything
250 mCamera->setNearPlane( distance / 2 );
251 mCamera->setFarPlane( distance * 2 );
252 // we force the updateCameraNearFarPlanes() in Qgs3DMapScene to properly set the planes
253 setCameraPose( camPose, true );
254}
255
257{
258 return mCameraPose.centerPoint();
259}
260
261void QgsCameraController::setLookingAtPoint( const QgsVector3D &point, float distance, float pitch, float yaw )
262{
263 QgsCameraPose camPose;
264 camPose.setCenterPoint( point );
266 camPose.setPitchAngle( pitch );
267 camPose.setHeadingAngle( yaw );
268 setCameraPose( camPose );
269}
270
272{
273 return lookingAtPoint() + mOrigin;
274}
275
277{
278 setLookingAtPoint( point - mOrigin, distance, pitch, yaw );
279}
280
281void QgsCameraController::setCameraPose( const QgsCameraPose &camPose, bool force )
282{
283 if ( camPose == mCameraPose && !force )
284 return;
285
286 mCameraPose = camPose;
287 updateCameraFromPose();
288}
289
290QDomElement QgsCameraController::writeXml( QDomDocument &doc ) const
291{
292 QDomElement elemCamera = doc.createElement( u"camera"_s );
293 // Save center point in map coordinates, since our world origin won't be
294 // the same on loading
295 QgsVector3D centerPoint = mCameraPose.centerPoint() + mOrigin;
296 elemCamera.setAttribute( u"xMap"_s, centerPoint.x() );
297 elemCamera.setAttribute( u"yMap"_s, centerPoint.y() );
298 elemCamera.setAttribute( u"zMap"_s, centerPoint.z() );
299 elemCamera.setAttribute( u"dist"_s, mCameraPose.distanceFromCenterPoint() );
300 elemCamera.setAttribute( u"pitch"_s, mCameraPose.pitchAngle() );
301 elemCamera.setAttribute( u"yaw"_s, mCameraPose.headingAngle() );
302 return elemCamera;
303}
304
305void QgsCameraController::readXml( const QDomElement &elem, QgsVector3D savedOrigin )
306{
307 const float dist = elem.attribute( u"dist"_s ).toFloat();
308 const float pitch = elem.attribute( u"pitch"_s ).toFloat();
309 const float yaw = elem.attribute( u"yaw"_s ).toFloat();
310
311 QgsVector3D centerPoint;
312 if ( elem.hasAttribute( "xMap" ) )
313 {
314 // Prefer newer point saved in map coordinates ...
315 const double x = elem.attribute( u"xMap"_s ).toDouble();
316 const double y = elem.attribute( u"yMap"_s ).toDouble();
317 const double z = elem.attribute( u"zMap"_s ).toDouble();
318 centerPoint = QgsVector3D( x, y, z ) - mOrigin;
319 }
320 else
321 {
322 // ... but allow use of older origin-relative coordinates.
323 const double x = elem.attribute( u"x"_s ).toDouble();
324 const double y = elem.attribute( u"y"_s ).toDouble();
325 const double elev = elem.attribute( u"elev"_s ).toDouble();
326 centerPoint = QgsVector3D( x, elev, y ) - savedOrigin + mOrigin;
327 }
328 setLookingAtPoint( centerPoint, dist, pitch, yaw );
329}
330
331double QgsCameraController::sampleDepthBuffer( int px, int py )
332{
333 if ( !mDepthBufferIsReady )
334 {
335 QgsDebugError( u"Asked to sample depth buffer, but depth buffer not ready!"_s );
336 }
337
338 double depth = 1;
339
340 if ( QWindow *win = window() )
341 {
342 // on high DPI screens, the mouse position is in device-independent pixels,
343 // but the depth buffer is in physical pixels...
344 px = static_cast<int>( px * win->devicePixelRatio() );
345 py = static_cast<int>( py * win->devicePixelRatio() );
346 }
347
348 // Sample the neighbouring pixels for the closest point to the camera
349 for ( int x = px - 3; x <= px + 3; ++x )
350 {
351 for ( int y = py - 3; y <= py + 3; ++y )
352 {
353 if ( mDepthBufferImage.valid( x, y ) )
354 {
355 depth = std::min( depth, Qgs3DUtils::decodeDepth( mDepthBufferImage.pixel( x, y ) ) );
356 }
357 }
358 }
359 return depth;
360}
361
362double QgsCameraController::depthBufferNonVoidAverage()
363{
364 // Cache the computed depth, since averaging over all pixels can be expensive
365 if ( mDepthBufferNonVoidAverage != -1 )
366 return mDepthBufferNonVoidAverage;
367
368 // Returns the average of depth values that are not 1 (void area)
369 double depth = 0;
370 int samplesCount = 0;
371 // Make sure we can do the cast
372 Q_ASSERT( mDepthBufferImage.format() == QImage::Format_RGB32 );
373 for ( int y = 0; y < mDepthBufferImage.height(); ++y )
374 {
375 const QRgb *line = reinterpret_cast<const QRgb *>( mDepthBufferImage.constScanLine( y ) );
376 for ( int x = 0; x < mDepthBufferImage.width(); ++x )
377 {
378 double d = Qgs3DUtils::decodeDepth( line[x] );
379 if ( d < 1 )
380 {
381 depth += d;
382 samplesCount += 1;
383 }
384 }
385 }
386
387 // if the whole buffer is white, a depth cannot be computed
388 if ( samplesCount == 0 )
389 depth = 1.0;
390 else
391 depth /= samplesCount;
392
393 mDepthBufferNonVoidAverage = depth;
394
395 return depth;
396}
397
398QgsVector3D QgsCameraController::moveGeocentricPoint( const QgsVector3D &point, double latDiff, double lonDiff )
399{
400 try
401 {
402 QgsVector3D pointLatLon = mGlobeCrsToLatLon.transform( point );
403 pointLatLon.setX( pointLatLon.x() + lonDiff );
404 pointLatLon.setY( std::clamp( pointLatLon.y() + latDiff, -90., 90. ) );
405
406 return mGlobeCrsToLatLon.transform( pointLatLon, Qgis::TransformDirection::Reverse );
407 }
408 catch ( const QgsCsException & )
409 {
410 QgsDebugError( u"moveGeocentricPoint: transform failed!"_s );
411 return point;
412 }
413}
414
415void QgsCameraController::globeMoveCenterPoint( double latDiff, double lonDiff )
416{
417 const QgsVector3D viewCenter = mCameraPose.centerPoint() + mOrigin;
418 const QgsVector3D newViewCenter = moveGeocentricPoint( viewCenter, latDiff, lonDiff );
419 mCameraPose.setCenterPoint( newViewCenter - mOrigin );
420 updateCameraFromPose();
421}
422
424{
425 mCameraPose.setDistanceFromCenterPoint( mCameraPose.distanceFromCenterPoint() * factor );
426 updateCameraFromPose();
427}
428
430{
431 mCameraPose.setPitchAngle( std::clamp( mCameraPose.pitchAngle() + angleDiff, 0.f, 90.f ) );
432 updateCameraFromPose();
433}
434
436{
437 mCameraPose.setHeadingAngle( mCameraPose.headingAngle() + angleDiff );
438 updateCameraFromPose();
439}
440
441void QgsCameraController::resetGlobe( float distance, double lat, double lon )
442{
443 QgsVector3D mapPoint;
444 try
445 {
446 mapPoint = mGlobeCrsToLatLon.transform( QgsVector3D( lon, lat, 0 ), Qgis::TransformDirection::Reverse );
447 }
448 catch ( const QgsCsException & )
449 {
450 QgsDebugError( u"resetGlobe: transform failed!"_s );
451 return;
452 }
453
454 QgsCameraPose cp;
455 cp.setCenterPoint( mapPoint - mOrigin );
457 setCameraPose( cp );
458}
459
460void QgsCameraController::updateOrthographicProjectionPlane()
461{
462 // When using orthographic projection, reuse the distance to center (which
463 // ordinarily wouldn't do anything) to set the viewport size in the world.
465 {
466 const QSize viewportRect = mScene->engine()->size();
467 const float viewWidthFromCenter = distance();
468 const float viewHeightFromCenter = distance() * static_cast<float>( viewportRect.height() ) / static_cast<float>( viewportRect.width() );
469 mCamera->lens()->setOrthographicProjection( -viewWidthFromCenter, viewWidthFromCenter, -viewHeightFromCenter, viewHeightFromCenter, mCamera->nearPlane(), mCamera->farPlane() );
470 }
471}
472
473void QgsCameraController::updateCameraFromPose()
474{
475 if ( mCamera )
476 {
477 if ( mScene->mapSettings()->sceneMode() == Qgis::SceneMode::Globe )
478 {
479 const QgsVector3D viewCenter = mCameraPose.centerPoint() + mOrigin;
480
481 QgsVector3D viewCenterLatLon;
482 try
483 {
484 viewCenterLatLon = mGlobeCrsToLatLon.transform( viewCenter );
485 }
486 catch ( const QgsCsException & )
487 {
488 QgsDebugError( u"updateCameraFromPose: transform failed!"_s );
489 return;
490 }
491
492 mCameraPose.updateCameraGlobe( mCamera, viewCenterLatLon.y(), viewCenterLatLon.x() );
493 }
494 else
495 {
496 mCameraPose.updateCamera( mCamera );
497 }
498
499 updateOrthographicProjectionPlane();
500
501 mCameraChanged = true;
502 }
503}
504
505void QgsCameraController::moveCenterPoint( const QVector3D &posDiff )
506{
507 mCameraPose.setCenterPoint( mCameraPose.centerPoint() + posDiff );
508 updateCameraFromPose();
509}
510
511void QgsCameraController::onPositionChanged( Qt3DInput::QMouseEvent *mouse )
512{
513 if ( !mInputHandlersEnabled )
514 return;
515
516 QgsScopedEvent traceEvent( u"3D"_s, u"QgsCameraController::onPositionChanged"_s );
517
518 switch ( mCameraNavigationMode )
519 {
521 onPositionChangedTerrainNavigation( mouse );
522 break;
523
525 onPositionChangedFlyNavigation( mouse );
526 break;
527
529 onPositionChangedGlobeTerrainNavigation( mouse );
530 break;
531 }
532}
533
534bool QgsCameraController::screenPointToWorldPos( QPoint position, double &depth, QVector3D &worldPosition )
535{
536 depth = sampleDepthBuffer( position.x(), position.y() );
537
538 // if there's nothing around the given position, try to get just any depth
539 // from the scene as a coarse approximation...
540 if ( depth == 1 )
541 {
542 depth = depthBufferNonVoidAverage();
543 }
544
545 worldPosition = Qgs3DUtils::screenPointToWorldPos( position, depth, mScene->engine()->size(), mDepthBufferCamera.get() );
546 if ( !std::isfinite( worldPosition.x() ) || !std::isfinite( worldPosition.y() ) || !std::isfinite( worldPosition.z() ) )
547 {
548 QgsDebugMsgLevel( u"screenPointToWorldPos: position is NaN or Inf. This should not happen."_s, 2 );
549 return false;
550 }
551
552 return true;
553}
554
555void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseEvent *mouse )
556{
557 if ( mIgnoreNextMouseMove )
558 {
559 mIgnoreNextMouseMove = false;
560 mMousePos = QPoint( mouse->x(), mouse->y() );
561 return;
562 }
563
564 const int dx = mouse->x() - mMousePos.x();
565 const int dy = mouse->y() - mMousePos.y();
566
567 const bool hasShift = ( mouse->modifiers() & Qt3DInput::QMouseEvent::Modifiers::ShiftModifier );
568 const bool hasCtrl = ( mouse->modifiers() & Qt3DInput::QMouseEvent::Modifiers::ControlModifier );
569 const bool hasLeftButton = ( mouse->buttons() & Qt::LeftButton );
570 const bool hasMiddleButton = ( mouse->buttons() & Qt::MiddleButton );
571 const bool hasRightButton = ( mouse->buttons() & Qt::RightButton );
572
573 if ( ( hasLeftButton && hasShift && !hasCtrl ) || ( hasMiddleButton && !hasShift && !hasCtrl ) )
574 {
575 // rotate/tilt using mouse (camera moves as it rotates around the clicked point)
576 setMouseParameters( MouseOperation::RotationCenter, mMousePos );
577
578 float scale = static_cast<float>( std::max( mScene->engine()->size().width(), mScene->engine()->size().height() ) );
579 float pitchDiff = -180.0f * static_cast<float>( mouse->y() - mClickPoint.y() ) / scale;
580 float yawDiff = -180.0f * static_cast<float>( mouse->x() - mClickPoint.x() ) / scale;
581
582 if ( mVerticalAxisInversion & Qgis::VerticalAxisInversion::WhenPivoting )
583 pitchDiff *= -1;
584
585 if ( !mDepthBufferIsReady )
586 return;
587
588 if ( !mRotationCenterCalculated )
589 {
590 double depth;
591 QVector3D worldPosition;
592 if ( screenPointToWorldPos( mClickPoint, depth, worldPosition ) )
593 {
594 mRotationCenter = worldPosition;
595 mRotationDistanceFromCenter = ( mRotationCenter - mCameraBefore->position() ).length();
596 emit cameraRotationCenterChanged( mRotationCenter );
597 mRotationCenterCalculated = true;
598 }
599 }
600
601 rotateCameraAroundPivot( mRotationPitch + pitchDiff, mRotationYaw + yawDiff, mRotationCenter );
602 }
603 else if ( hasLeftButton && hasCtrl && !hasShift )
604 {
605 setMouseParameters( MouseOperation::RotationCamera );
606 // rotate/tilt using mouse (camera stays at one position as it rotates)
607 float diffPitch = -0.2f * static_cast<float>( dy );
608 const float diffYaw = -0.2f * dx;
609 if ( mVerticalAxisInversion & Qgis::VerticalAxisInversion::WhenRotatingDragging )
610 diffPitch *= -1.0f;
611 rotateCamera( diffPitch, diffYaw );
612 }
613 else if ( hasLeftButton && !hasShift && !hasCtrl )
614 {
615 // translation works as if one grabbed a point on the 3D viewer and dragged it
616 setMouseParameters( MouseOperation::Translation, mMousePos );
617
618 if ( !mDepthBufferIsReady )
619 return;
620
621 if ( !mDragPointCalculated )
622 {
623 double depth;
624 QVector3D worldPosition;
625 if ( screenPointToWorldPos( mClickPoint, depth, worldPosition ) )
626 {
627 mDragDepth = depth;
628 mDragPoint = worldPosition;
629 mDragPointCalculated = true;
630 }
631 }
632
633 QVector3D cameraBeforeDragPos = mCameraBefore->position();
634 QVector3D moveToPosition = Qgs3DUtils::screenPointToWorldPos( { mouse->x(), mouse->y() }, mDragDepth, mScene->engine()->size(), mCameraBefore.get() );
635
636 QVector3D shiftVector;
637 // Compute angle of camera's view vector to ground and decide if moving
638 // the cursor up-down should change altitude or latitude/longitude.
639 float angle = std::fabs( std::acos( QVector3D::dotProduct( QVector3D( 0, 0, 1 ), mCameraBefore->viewVector().normalized() ) ) - M_PI / 2 );
640 bool changeAltitude = false;
641 // Choose threshold angle based on projection type so it "feels right".
642 switch ( mScene->mapSettings()->projectionType() )
643 {
645 changeAltitude = angle < M_PI / 30;
646 break;
648 changeAltitude = angle < M_PI / 3;
649 break;
650 default:
651 QgsDebugError( "Unhandled 3D projection type" );
652 }
653
654 if ( changeAltitude )
655 shiftVector = mDragPoint - moveToPosition;
656 else
657 switch ( mScene->mapSettings()->projectionType() )
658 {
660 {
661 // Project change to XY plane.
662 // This isn't quite accurate at higher angles, "desyncing" the mouse
663 // cursor and the dragged point.
664 shiftVector = { mDragPoint.x() - moveToPosition.x(), mDragPoint.y() - moveToPosition.y(), 0 };
665 break;
666 }
668 {
669 QVector3D cameraBeforeToMoveToPos = ( moveToPosition - mCameraBefore->position() ).normalized();
670 QVector3D cameraBeforeToDragPointPos = ( mDragPoint - mCameraBefore->position() ).normalized();
671
672 // Make sure the rays are not horizontal (add small z shift if it is)
673 if ( cameraBeforeToMoveToPos.z() == 0 )
674 {
675 cameraBeforeToMoveToPos.setZ( 0.01 );
676 cameraBeforeToMoveToPos = cameraBeforeToMoveToPos.normalized();
677 }
678
679 if ( cameraBeforeToDragPointPos.z() == 0 )
680 {
681 cameraBeforeToDragPointPos.setZ( 0.01 );
682 cameraBeforeToDragPointPos = cameraBeforeToDragPointPos.normalized();
683 }
684
685 float d1 = ( mDragPoint.z() - cameraBeforeDragPos.z() ) / cameraBeforeToMoveToPos.z();
686 float d2 = ( mDragPoint.z() - cameraBeforeDragPos.z() ) / cameraBeforeToDragPointPos.z();
687
688 QVector3D from = cameraBeforeDragPos + d1 * cameraBeforeToMoveToPos;
689 QVector3D to = cameraBeforeDragPos + d2 * cameraBeforeToDragPointPos;
690
691 shiftVector = to - from;
692 break;
693 }
694 default:
695 QgsDebugError( "Unhandled 3D projection type" );
696 }
697
698 mCameraPose.setCenterPoint( mCameraBefore->viewCenter() + shiftVector );
699 updateCameraFromPose();
700 }
701 else if ( hasLeftButton && hasShift && hasCtrl )
702 {
703 // change the camera elevation, similar to pageUp/pageDown
704 QgsVector3D center = mCameraPose.centerPoint();
705 double tElev = mMousePos.y() - mouse->y();
706 center.set( center.x(), center.y(), center.z() + tElev * 0.5 );
707 mCameraPose.setCenterPoint( center );
708 updateCameraFromPose();
709 }
710 else if ( hasRightButton && !hasShift && !hasCtrl )
711 {
712 setMouseParameters( MouseOperation::Zoom, mMousePos );
713 if ( !mDepthBufferIsReady )
714 return;
715
716 if ( !mDragPointCalculated )
717 {
718 double depth;
719 QVector3D worldPosition;
720 if ( screenPointToWorldPos( mClickPoint, depth, worldPosition ) )
721 {
722 mDragPoint = worldPosition;
723 mDragPointCalculated = true;
724 }
725 }
726
727 const double oldDist = ( QgsVector3D( mCameraBefore->position() ) - QgsVector3D( mDragPoint ) ).length();
728 double newDist = oldDist;
729
730 int yOffset = 0;
731 int screenHeight = mScene->engine()->size().height();
732 QWindow *win = window();
733 if ( win )
734 {
735 yOffset = win->mapToGlobal( QPoint( 0, 0 ) ).y();
736 screenHeight = win->screen()->virtualSize().height();
737 }
738
739 // Applies smoothing
740 if ( mMousePos.y() > mClickPoint.y() ) // zoom in
741 {
742 double f = ( double ) ( mMousePos.y() - mClickPoint.y() ) / ( double ) ( screenHeight - mClickPoint.y() - yOffset );
743 f = std::max( 0.0, std::min( 1.0, f ) );
744 f = 1 - ( std::expm1( -2 * f ) ) / ( std::expm1( -2 ) );
745 newDist = newDist * f;
746 }
747 else // zoom out
748 {
749 double f = 1 - ( double ) ( mMousePos.y() + yOffset ) / ( double ) ( mClickPoint.y() + yOffset );
750 f = std::max( 0.0, std::min( 1.0, f ) );
751 f = ( std::expm1( 2 * f ) ) / ( std::expm1( 2 ) );
752 newDist = newDist + 2 * newDist * f;
753 }
754
755 const double zoomFactor = newDist / oldDist;
756 zoomCameraAroundPivot( mCameraBefore->position(), mCameraBefore->viewCenter().distanceToPoint( mCameraBefore->position() ), zoomFactor, mDragPoint );
757 }
758
759 mMousePos = QPoint( mouse->x(), mouse->y() );
760}
761
762void QgsCameraController::onPositionChangedGlobeTerrainNavigation( Qt3DInput::QMouseEvent *mouse )
763{
764 const bool hasShift = ( mouse->modifiers() & Qt3DInput::QMouseEvent::Modifiers::ShiftModifier );
765 const bool hasCtrl = ( mouse->modifiers() & Qt3DInput::QMouseEvent::Modifiers::ControlModifier );
766 const bool hasLeftButton = ( mouse->buttons() & Qt::LeftButton );
767 const bool hasMiddleButton = ( mouse->buttons() & Qt::MiddleButton );
768
769 if ( ( hasLeftButton && hasShift && !hasCtrl ) || ( hasMiddleButton && !hasShift && !hasCtrl ) )
770 {
771 setMouseParameters( MouseOperation::RotationCenter, mMousePos );
772
773 const float scale = static_cast<float>( std::max( mScene->engine()->size().width(), mScene->engine()->size().height() ) );
774 float pitchDiff = -180.0f * static_cast<float>( mouse->y() - mClickPoint.y() ) / scale;
775 const float yawDiff = -180.0f * static_cast<float>( mouse->x() - mClickPoint.x() ) / scale;
776
777 if ( mVerticalAxisInversion & Qgis::VerticalAxisInversion::WhenPivoting )
778 pitchDiff *= -1;
779
780 mCameraPose.setPitchAngle( mRotationPitch + pitchDiff );
781 mCameraPose.setHeadingAngle( mRotationYaw + yawDiff );
782 updateCameraFromPose();
783 return;
784 }
785
786 if ( !( mouse->buttons() & Qt::LeftButton ) )
787 return;
788
789 // translation works as if one grabbed a point on the 3D viewer and dragged it
790 setMouseParameters( MouseOperation::Translation, mMousePos );
791
792 if ( !mDepthBufferIsReady )
793 return;
794
795 if ( !mDragPointCalculated )
796 {
797 double depth;
798 QVector3D worldPosition;
799 if ( !screenPointToWorldPos( mClickPoint, depth, worldPosition ) )
800 return;
801
802 mDragDepth = depth;
803 mDragPoint = worldPosition;
804 mDragPointCalculated = true;
805 }
806
807 // Approximate the globe as a sphere with a center in (0,0,0) map coords and
808 // of radius the same as at startPosMap.
809 const QgsVector3D startPosMap = QgsVector3D( mDragPoint ) + mOrigin;
810 const double sphereRadiusMap = startPosMap.length();
811 // Find the intersection of this sphere and the ray from the current clicked point.
812 const QgsRay3D ray = Qgs3DUtils::rayFromScreenPoint( QPoint( mouse->x(), mouse->y() ), mScene->engine()->size(), mCameraBefore.get() );
813 const QgsVector3D rayOriginMap = QgsVector3D( ray.origin() ) + mOrigin;
814 // From equations of ray and sphere
815 const double quadA = QVector3D::dotProduct( ray.direction(), ray.direction() );
816 const double quadB = 2 * QgsVector3D::dotProduct( ray.direction(), rayOriginMap );
817 const double quadC = QgsVector3D::dotProduct( rayOriginMap, rayOriginMap ) - sphereRadiusMap * sphereRadiusMap;
818 const double disc = quadB * quadB - 4 * quadA * quadC;
819 if ( disc < 0 )
820 // Ray misses sphere
821 return;
822 // Distance to intersection along ray (take smaller root, closer to camera)
823 const double rayDistMap = ( -quadB - sqrt( disc ) ) / ( 2 * quadA );
824 if ( rayDistMap < 0 )
825 {
826 QgsDebugError( u"Sphere intersection result negative, canceling move"_s );
827 return;
828 }
829 const QgsVector3D newPosMap = rayOriginMap + QgsVector3D( ray.direction() ) * rayDistMap;
830
831 // now that we have old and new mouse position in ECEF coordinates,
832 // let's figure out the difference in lat/lon angles and update the center point
833
834 QgsVector3D oldLatLon, newLatLon;
835 try
836 {
837 oldLatLon = mGlobeCrsToLatLon.transform( startPosMap );
838 newLatLon = mGlobeCrsToLatLon.transform( newPosMap );
839 }
840 catch ( const QgsCsException & )
841 {
842 QgsDebugError( u"onPositionChangedGlobeTerrainNavigation: transform failed!"_s );
843 return;
844 }
845
846 const double latDiff = oldLatLon.y() - newLatLon.y();
847 const double lonDiff = oldLatLon.x() - newLatLon.x();
848
849 const QgsVector3D newVC = moveGeocentricPoint( mMousePressViewCenter, latDiff, lonDiff );
850 const QgsVector3D newVCWorld = newVC - mOrigin;
851
852 mCameraPose.setCenterPoint( newVCWorld );
853 updateCameraFromPose();
854}
855
856
857void QgsCameraController::zoom( float factor )
858{
859 // zoom in/out
860 float dist = mCameraPose.distanceFromCenterPoint();
861 dist -= dist * factor * 0.01f;
862 mCameraPose.setDistanceFromCenterPoint( dist );
863 updateCameraFromPose();
864}
865
866void QgsCameraController::handleTerrainNavigationWheelZoom()
867{
868 if ( !mDepthBufferIsReady )
869 return;
870
871 if ( !mZoomPointCalculated )
872 {
873 double depth;
874 QVector3D worldPosition;
875 if ( screenPointToWorldPos( mMousePos, depth, worldPosition ) )
876 {
877 mZoomPoint = worldPosition;
878 mZoomPointCalculated = true;
879 }
880 }
881
882 double oldDist = ( mZoomPoint - mCameraBefore->position() ).length();
883 // Each step of the scroll wheel decreases distance by 20%
884 double newDist = std::pow( 0.8, mCumulatedWheelY ) * oldDist;
885 // Make sure we don't clip the thing we're zooming to.
886 newDist = std::max( newDist, 2.0 );
887 double zoomFactor = newDist / oldDist;
888 // Don't change the distance too suddenly to hopefully prevent numerical instability
889 zoomFactor = std::clamp( zoomFactor, 0.01, 100.0 );
890
891 zoomCameraAroundPivot( mCameraBefore->position(), mCameraBefore->viewCenter().distanceToPoint( mCameraBefore->position() ), zoomFactor, mZoomPoint );
892
893 mCumulatedWheelY = 0;
894 setMouseParameters( MouseOperation::None );
895}
896
897void QgsCameraController::onWheel( Qt3DInput::QWheelEvent *wheel )
898{
899 if ( !mInputHandlersEnabled )
900 return;
901
902 switch ( mCameraNavigationMode )
903 {
905 {
906 const float scaling = ( ( wheel->modifiers() & Qt3DInput::QWheelEvent::Modifiers::ControlModifier ) != 0 ? 0.1f : 1.0f ) / 1000.f;
907 setCameraMovementSpeed( mCameraMovementSpeed + mCameraMovementSpeed * scaling * wheel->angleDelta().y() );
908 break;
909 }
910
912 {
913 // Scale our variable to roughly "number of normal steps", with Ctrl
914 // increasing granularity 10x
915 const double scaling = ( 1.0 / 120.0 ) * ( ( wheel->modifiers() & Qt3DInput::QWheelEvent::Modifiers::ControlModifier ) != 0 ? 0.1 : 1.0 );
916
917 // Apparently angleDelta needs to be accumulated
918 // see: https://doc.qt.io/qt-6/qwheelevent.html#angleDelta
919 mCumulatedWheelY += scaling * wheel->angleDelta().y();
920
921 if ( mCurrentOperation != MouseOperation::ZoomWheel )
922 {
923 setMouseParameters( MouseOperation::ZoomWheel );
924 // The actual zooming will happen after we get a new depth buffer
925 }
926 else
927 {
928 handleTerrainNavigationWheelZoom();
929 }
930 break;
931 }
932
934 {
935 float wheelAmount = static_cast<float>( wheel->angleDelta().y() );
936 float factor = abs( wheelAmount ) / 1000.f;
937 float mulFactor = wheelAmount > 0 ? ( 1 - factor ) : ( 1 + factor );
938 mCameraPose.setDistanceFromCenterPoint( mCameraPose.distanceFromCenterPoint() * mulFactor );
939 updateCameraFromPose();
940 break;
941 }
942 }
943}
944
945void QgsCameraController::onMousePressed( Qt3DInput::QMouseEvent *mouse )
946{
947 if ( !mInputHandlersEnabled )
948 return;
949
950 mKeyboardHandler->setFocus( true );
951
952 if ( mouse->button() == Qt3DInput::QMouseEvent::MiddleButton
953 || ( ( mouse->modifiers() & Qt3DInput::QMouseEvent::Modifiers::ShiftModifier ) != 0 && mouse->button() == Qt3DInput::QMouseEvent::LeftButton )
954 || ( ( mouse->modifiers() & Qt3DInput::QMouseEvent::Modifiers::ControlModifier ) != 0 && mouse->button() == Qt3DInput::QMouseEvent::LeftButton ) )
955 {
956 mMousePos = QPoint( mouse->x(), mouse->y() );
957
958 if ( mCaptureFpsMouseMovements )
959 mIgnoreNextMouseMove = true;
960
961 const MouseOperation operation {
962 ( mouse->modifiers() & Qt3DInput::QMouseEvent::Modifiers::ControlModifier ) != 0 && mouse->button() == Qt3DInput::QMouseEvent::LeftButton ? MouseOperation::RotationCamera
963 : MouseOperation::RotationCenter
964 };
965 setMouseParameters( operation, mMousePos );
966 }
967
968 else if ( mouse->button() == Qt3DInput::QMouseEvent::LeftButton || mouse->button() == Qt3DInput::QMouseEvent::RightButton )
969 {
970 mMousePos = QPoint( mouse->x(), mouse->y() );
971
972 if ( mCaptureFpsMouseMovements )
973 mIgnoreNextMouseMove = true;
974
975 const MouseOperation operation = ( mouse->button() == Qt3DInput::QMouseEvent::LeftButton ) ? MouseOperation::Translation : MouseOperation::Zoom;
976 setMouseParameters( operation, mMousePos );
977 }
978}
979
980void QgsCameraController::onMouseReleased( Qt3DInput::QMouseEvent *mouse )
981{
982 Q_UNUSED( mouse )
983 if ( !mInputHandlersEnabled )
984 return;
985
986
987 setMouseParameters( MouseOperation::None );
988}
989
990bool QgsCameraController::onKeyPressedTerrainNavigation( QKeyEvent *event )
991{
992 const bool hasShift = ( event->modifiers() & Qt::ShiftModifier );
993 const bool hasCtrl = ( event->modifiers() & Qt::ControlModifier );
994
995 int tx = 0, ty = 0, tElev = 0;
996 switch ( event->key() )
997 {
998 case Qt::Key_Left:
999 tx -= 1;
1000 break;
1001 case Qt::Key_Right:
1002 tx += 1;
1003 break;
1004
1005 case Qt::Key_Up:
1006 ty += 1;
1007 break;
1008 case Qt::Key_Down:
1009 ty -= 1;
1010 break;
1011
1012 case Qt::Key_PageDown:
1013 tElev -= 1;
1014 break;
1015 case Qt::Key_PageUp:
1016 tElev += 1;
1017 break;
1018 default:
1019 break;
1020 }
1021
1022 if ( tx || ty )
1023 {
1024 if ( !hasShift && !hasCtrl )
1025 {
1026 moveView( tx, ty );
1027 }
1028 else if ( hasShift && !hasCtrl )
1029 {
1030 // rotate/tilt using keyboard (camera moves as it rotates around its view center)
1033 }
1034 else if ( hasCtrl && !hasShift )
1035 {
1036 // rotate/tilt using keyboard (camera stays at one position as it rotates)
1037 const float diffPitch = ty; // down key = rotating camera down
1038 const float diffYaw = -tx; // right key = rotating camera to the right
1039 rotateCamera( diffPitch, diffYaw );
1040 }
1041 return true;
1042 }
1043
1044 if ( tElev )
1045 {
1046 QgsVector3D center = mCameraPose.centerPoint();
1047 center.set( center.x(), center.y(), center.z() + tElev * 10 );
1048 mCameraPose.setCenterPoint( center );
1049 return true;
1050 }
1051
1052 return false;
1053}
1054
1055bool QgsCameraController::onKeyPressedGlobeTerrainNavigation( QKeyEvent *event )
1056{
1057 // both move factor and zoom factor are just empirically picked numbers
1058 // that seem to work well (providing steps that are not too big / not too small)
1059 constexpr float MOVE_FACTOR = 0.000001f; // multiplied by distance to get angle
1060 constexpr float ZOOM_FACTOR = 0.9f;
1061
1062 const bool hasShift = ( event->modifiers() & Qt::ShiftModifier );
1063
1064 switch ( event->key() )
1065 {
1066 case Qt::Key_Left:
1067 if ( hasShift )
1069 else
1070 globeMoveCenterPoint( 0, -MOVE_FACTOR * mCameraPose.distanceFromCenterPoint() );
1071 return true;
1072 case Qt::Key_Right:
1073 if ( hasShift )
1075 else
1076 globeMoveCenterPoint( 0, MOVE_FACTOR * mCameraPose.distanceFromCenterPoint() );
1077 return true;
1078 case Qt::Key_Up:
1079 if ( hasShift )
1081 else
1082 globeMoveCenterPoint( MOVE_FACTOR * mCameraPose.distanceFromCenterPoint(), 0 );
1083 return true;
1084 case Qt::Key_Down:
1085 if ( hasShift )
1087 else
1088 globeMoveCenterPoint( -MOVE_FACTOR * mCameraPose.distanceFromCenterPoint(), 0 );
1089 return true;
1090 case Qt::Key_PageDown:
1091 globeZoom( ZOOM_FACTOR );
1092 return true;
1093 case Qt::Key_PageUp:
1094 globeZoom( 1 / ZOOM_FACTOR );
1095 return true;
1096 default:
1097 break;
1098 }
1099 return false;
1100}
1101
1102static const QSet<int> walkNavigationSavedKeys = {
1103 Qt::Key_Left,
1104 Qt::Key_A,
1105 Qt::Key_Right,
1106 Qt::Key_D,
1107 Qt::Key_Up,
1108 Qt::Key_W,
1109 Qt::Key_Down,
1110 Qt::Key_S,
1111 Qt::Key_PageUp,
1112 Qt::Key_E,
1113 Qt::Key_PageDown,
1114 Qt::Key_Q,
1115};
1116
1117bool QgsCameraController::onKeyPressedFlyNavigation( QKeyEvent *event )
1118{
1119 switch ( event->key() )
1120 {
1121 case Qt::Key_QuoteLeft:
1122 {
1123 // toggle mouse lock mode
1124 mCaptureFpsMouseMovements = !mCaptureFpsMouseMovements;
1125 mIgnoreNextMouseMove = true;
1126 if ( mCaptureFpsMouseMovements )
1127 {
1128 qApp->setOverrideCursor( QCursor( Qt::BlankCursor ) );
1129 }
1130 else
1131 {
1132 qApp->restoreOverrideCursor();
1133 }
1134 event->accept();
1135 return true;
1136 }
1137
1138 case Qt::Key_Escape:
1139 {
1140 // always exit mouse lock mode
1141 if ( mCaptureFpsMouseMovements )
1142 {
1143 mCaptureFpsMouseMovements = false;
1144 mIgnoreNextMouseMove = true;
1145 qApp->restoreOverrideCursor();
1146 event->accept();
1147 return true;
1148 }
1149 break;
1150 }
1151 default:
1152 break;
1153 }
1154
1155 if ( walkNavigationSavedKeys.contains( event->key() ) )
1156 {
1157 if ( !event->isAutoRepeat() )
1158 {
1159 mDepressedKeys.insert( event->key() );
1160 }
1161 event->accept();
1162 return true;
1163 }
1164 return false;
1165}
1166
1167void QgsCameraController::walkView( double tx, double ty, double tz )
1168{
1169 const QVector3D cameraUp = mCamera->upVector().normalized();
1170 const QVector3D cameraFront = ( QVector3D( mCameraPose.centerPoint().x(), mCameraPose.centerPoint().y(), mCameraPose.centerPoint().z() ) - mCamera->position() ).normalized();
1171 const QVector3D cameraLeft = QVector3D::crossProduct( cameraUp, cameraFront );
1172
1173 QVector3D cameraPosDiff( 0.0f, 0.0f, 0.0f );
1174
1175 if ( tx != 0.0 )
1176 {
1177 cameraPosDiff += static_cast<float>( tx ) * cameraFront;
1178 }
1179 if ( ty != 0.0 )
1180 {
1181 cameraPosDiff += static_cast<float>( ty ) * cameraLeft;
1182 }
1183 if ( tz != 0.0 )
1184 {
1185 cameraPosDiff += static_cast<float>( tz ) * QVector3D( 0.0f, 0.0f, 1.0f );
1186 }
1187
1188 moveCenterPoint( cameraPosDiff );
1189}
1190
1191void QgsCameraController::applyFlyModeKeyMovements()
1192{
1193 if ( mCameraNavigationMode != Qgis::NavigationMode::Walk )
1194 return;
1195
1196 // shift = "run", ctrl = "slow walk"
1197 const bool shiftPressed = mDepressedKeys.contains( Qt::Key_Shift );
1198 const bool ctrlPressed = mDepressedKeys.contains( Qt::Key_Control );
1199
1200 const double movementSpeed = mCameraMovementSpeed * ( shiftPressed ? 2 : 1 ) * ( ctrlPressed ? 0.1 : 1 );
1201
1202 bool changed = false;
1203 double x = 0.0;
1204 double y = 0.0;
1205 double z = 0.0;
1206 if ( mDepressedKeys.contains( Qt::Key_Left ) || mDepressedKeys.contains( Qt::Key_A ) )
1207 {
1208 changed = true;
1209 y += movementSpeed;
1210 }
1211
1212 if ( mDepressedKeys.contains( Qt::Key_Right ) || mDepressedKeys.contains( Qt::Key_D ) )
1213 {
1214 changed = true;
1215 y -= movementSpeed;
1216 }
1217
1218 if ( mDepressedKeys.contains( Qt::Key_Up ) || mDepressedKeys.contains( Qt::Key_W ) )
1219 {
1220 changed = true;
1221 x += movementSpeed;
1222 }
1223
1224 if ( mDepressedKeys.contains( Qt::Key_Down ) || mDepressedKeys.contains( Qt::Key_S ) )
1225 {
1226 changed = true;
1227 x -= movementSpeed;
1228 }
1229
1230 // note -- vertical axis movements are slower by default then horizontal ones, as GIS projects
1231 // tend to have much more limited elevation range vs ground range
1232 static constexpr double ELEVATION_MOVEMENT_SCALE = 0.5;
1233 if ( mDepressedKeys.contains( Qt::Key_PageUp ) || mDepressedKeys.contains( Qt::Key_E ) )
1234 {
1235 changed = true;
1236 z += ELEVATION_MOVEMENT_SCALE * movementSpeed;
1237 }
1238
1239 if ( mDepressedKeys.contains( Qt::Key_PageDown ) || mDepressedKeys.contains( Qt::Key_Q ) )
1240 {
1241 changed = true;
1242 z -= ELEVATION_MOVEMENT_SCALE * movementSpeed;
1243 }
1244
1245 if ( changed )
1246 walkView( x, y, z );
1247}
1248
1249void QgsCameraController::onPositionChangedFlyNavigation( Qt3DInput::QMouseEvent *mouse )
1250{
1251 const bool hasMiddleButton = ( mouse->buttons() & Qt::MiddleButton );
1252 const bool hasRightButton = ( mouse->buttons() & Qt::RightButton );
1253
1254 const double dx = mCaptureFpsMouseMovements ? QCursor::pos().x() - mMousePos.x() : mouse->x() - mMousePos.x();
1255 const double dy = mCaptureFpsMouseMovements ? QCursor::pos().y() - mMousePos.y() : mouse->y() - mMousePos.y();
1256 mMousePos = mCaptureFpsMouseMovements ? QCursor::pos() : QPoint( mouse->x(), mouse->y() );
1257
1258 if ( mIgnoreNextMouseMove )
1259 {
1260 mIgnoreNextMouseMove = false;
1261 return;
1262 }
1263
1264 if ( hasMiddleButton )
1265 {
1266 // middle button drag = pan camera in place (strafe)
1267 const QVector3D cameraUp = mCamera->upVector().normalized();
1268 const QVector3D cameraFront = ( QVector3D( mCameraPose.centerPoint().x(), mCameraPose.centerPoint().y(), mCameraPose.centerPoint().z() ) - mCamera->position() ).normalized();
1269 const QVector3D cameraLeft = QVector3D::crossProduct( cameraUp, cameraFront );
1270 const QVector3D cameraPosDiff = -dx * cameraLeft - dy * cameraUp;
1271 moveCenterPoint( static_cast<float>( mCameraMovementSpeed ) * cameraPosDiff / 10.0 );
1272 }
1273 else if ( hasRightButton )
1274 {
1275 // right button drag = camera dolly
1276 const QVector3D cameraFront = ( QVector3D( mCameraPose.centerPoint().x(), mCameraPose.centerPoint().y(), mCameraPose.centerPoint().z() ) - mCamera->position() ).normalized();
1277 const QVector3D cameraPosDiff = dy * cameraFront;
1278 moveCenterPoint( static_cast<float>( mCameraMovementSpeed ) * cameraPosDiff / 5.0 );
1279 }
1280 else
1281 {
1282 if ( mCaptureFpsMouseMovements )
1283 {
1284 float diffPitch = -0.2f * dy;
1285 if ( mVerticalAxisInversion & Qgis::VerticalAxisInversion::WhenRotatingCaptured )
1286 diffPitch *= -1;
1287
1288 const float diffYaw = -0.2f * dx;
1289 rotateCamera( diffPitch, diffYaw );
1290 }
1291 else if ( mouse->buttons() & Qt::LeftButton )
1292 {
1293 float diffPitch = -0.2f * dy;
1294 if ( mVerticalAxisInversion & Qgis::VerticalAxisInversion::WhenRotatingDragging )
1295 diffPitch *= -1;
1296
1297 const float diffYaw = -0.2f * dx;
1298 rotateCamera( diffPitch, diffYaw );
1299 }
1300 }
1301
1302 if ( mCaptureFpsMouseMovements )
1303 {
1304 mIgnoreNextMouseMove = true;
1305
1306 // reset cursor back to center of map widget
1307 emit setCursorPosition( QPoint( mScene->engine()->size().width() / 2, mScene->engine()->size().height() / 2 ) );
1308 }
1309}
1310
1312{
1313 // Tilt up the view by deltaPitch around the view center (camera moves)
1314 float pitch = mCameraPose.pitchAngle();
1315 pitch -= deltaPitch; // down key = moving camera toward terrain
1316 mCameraPose.setPitchAngle( pitch );
1317 updateCameraFromPose();
1318}
1319
1321{
1322 // Rotate clockwise the view by deltaYaw around the view center (camera moves)
1323 float yaw = mCameraPose.headingAngle();
1324 yaw -= deltaYaw; // right key = moving camera clockwise
1325 mCameraPose.setHeadingAngle( yaw );
1326 updateCameraFromPose();
1327}
1328
1330{
1331 mCameraPose.setHeadingAngle( angle );
1332 updateCameraFromPose();
1333}
1334
1335void QgsCameraController::moveView( float tx, float ty )
1336{
1337 const float yaw = mCameraPose.headingAngle();
1338 const float dist = mCameraPose.distanceFromCenterPoint();
1339 const float x = tx * dist * 0.02f;
1340 const float y = -ty * dist * 0.02f;
1341
1342 // moving with keyboard - take into account yaw of camera
1343 const float t = sqrt( x * x + y * y );
1344 const float a = atan2( y, x ) - yaw * M_PI / 180;
1345 const float dx = cos( a ) * t;
1346 const float dy = sin( a ) * t;
1347
1348 QgsVector3D center = mCameraPose.centerPoint();
1349 center.set( center.x() + dx, center.y() - dy, center.z() );
1350 mCameraPose.setCenterPoint( center );
1351 updateCameraFromPose();
1352}
1353
1355{
1356 if ( !mInputHandlersEnabled )
1357 return false;
1358
1359 if ( event->type() == QKeyEvent::Type::KeyRelease )
1360 {
1361 if ( !event->isAutoRepeat() && mDepressedKeys.contains( event->key() ) )
1362 {
1363 mDepressedKeys.remove( event->key() );
1364 return true;
1365 }
1366 }
1367 else if ( event->type() == QEvent::ShortcutOverride )
1368 {
1369 if ( event->modifiers() & Qt::ControlModifier )
1370 {
1371 switch ( event->key() )
1372 {
1373 case Qt::Key_QuoteLeft:
1374 {
1375 // switch navigation mode
1376 switch ( mCameraNavigationMode )
1377 {
1379 // clang-format off
1381 mScene->mapSettings()->sceneMode() == Qgis::SceneMode::Globe
1384 );
1385 // clang-format on
1386 break;
1390 break;
1391 }
1392 event->accept();
1393 return true;
1394 }
1395
1396 // Make sure to sync the key combinations with strings in Qgs3DAxis::createMenu()!
1397 case Qt::Key_8:
1399 return true;
1400 case Qt::Key_6:
1402 return true;
1403 case Qt::Key_2:
1405 return true;
1406 case Qt::Key_4:
1408 return true;
1409 case Qt::Key_9:
1411 return true;
1412 case Qt::Key_3:
1414 return true;
1415 case Qt::Key_5:
1417 return true;
1418
1419 default:
1420 break;
1421 }
1422 }
1423
1424 switch ( mCameraNavigationMode )
1425 {
1427 return onKeyPressedFlyNavigation( event );
1428
1430 return onKeyPressedTerrainNavigation( event );
1431
1433 return onKeyPressedGlobeTerrainNavigation( event );
1434 }
1435 }
1436 return false;
1437}
1438
1439void QgsCameraController::depthBufferCaptured( const QImage &depthImage )
1440{
1441 mDepthBufferImage = depthImage;
1442 mDepthBufferIsReady = true;
1443 mDepthBufferNonVoidAverage = -1;
1444
1445 // To read distances from the captured depth buffer, we need to know the
1446 // camera parameters it was rendered with. This seems like the closest
1447 // place to save them, though I have no idea if they can't be changed
1448 // between the rendering and now anyway...
1449 mDepthBufferCamera = Qgs3DUtils::copyCamera( mCamera );
1450
1451 if ( mCurrentOperation == MouseOperation::ZoomWheel )
1452 {
1453 handleTerrainNavigationWheelZoom();
1454 }
1455}
1456
1457bool QgsCameraController::isATranslationRotationSequence( MouseOperation newOperation ) const
1458{
1459 return std::find( mTranslateOrRotate.begin(), mTranslateOrRotate.end(), newOperation ) != std::end( mTranslateOrRotate )
1460 && std::find( mTranslateOrRotate.begin(), mTranslateOrRotate.end(), mCurrentOperation ) != std::end( mTranslateOrRotate );
1461}
1462
1463void QgsCameraController::setMouseParameters( const MouseOperation &newOperation, const QPoint &clickPoint )
1464{
1465 if ( newOperation == mCurrentOperation )
1466 {
1467 return;
1468 }
1469
1470 if ( newOperation == MouseOperation::None )
1471 {
1472 mClickPoint = QPoint();
1473 }
1474 // click point and rotation angles are updated if:
1475 // - it has never been computed
1476 // - the current and new operations are both rotation and translation
1477 // Indeed, if the sequence such as rotation -> zoom -> rotation updating mClickPoint on
1478 // the click point does not need to be updated because the relative mouse position is kept
1479 // during a zoom operation
1480 else if ( mClickPoint.isNull() || isATranslationRotationSequence( newOperation ) )
1481 {
1482 mClickPoint = clickPoint;
1483 mRotationPitch = mCameraPose.pitchAngle();
1484 mRotationYaw = mCameraPose.headingAngle();
1485 }
1486 mCurrentOperation = newOperation;
1487 mDepthBufferIsReady = false;
1488 mRotationCenterCalculated = false;
1489 mDragPointCalculated = false;
1490 mZoomPointCalculated = false;
1491
1492 if ( mCurrentOperation != MouseOperation::None && mCurrentOperation != MouseOperation::RotationCamera )
1493 {
1494 mMousePressViewCenter = mCameraPose.centerPoint() + mOrigin;
1495 mCameraBefore = Qgs3DUtils::copyCamera( mCamera );
1496
1498 }
1499}
1500
1502{
1503 QgsVector3D diff = origin - mOrigin;
1504 mCameraPose.setCenterPoint( mCameraPose.centerPoint() - diff );
1505
1506 // update other members that depend on world coordinates
1507 mCameraBefore->setPosition( ( QgsVector3D( mCameraBefore->position() ) - diff ).toVector3D() );
1508 mCameraBefore->setViewCenter( ( QgsVector3D( mCameraBefore->viewCenter() ) - diff ).toVector3D() );
1509 mDragPoint = ( QgsVector3D( mDragPoint ) - diff ).toVector3D();
1510 mRotationCenter = ( QgsVector3D( mRotationCenter ) - diff ).toVector3D();
1511
1512 mOrigin = origin;
1513
1514 updateCameraFromPose();
1515}
1516
1517void QgsCameraController::rotateToRespectingTerrain( float pitch, float yaw )
1518{
1520 double elevation = 0.0;
1521 if ( mScene->mapSettings()->terrainRenderingEnabled() )
1522 {
1523 QgsDebugMsgLevel( "Checking elevation from terrain...", 2 );
1524 QVector3D camPos = mCamera->position();
1525 const QgsRay3D ray( camPos, pos.toVector3D() - camPos );
1526 QgsRayCastContext context;
1527 context.setSingleResult( true );
1528 context.setMaximumDistance( mCamera->farPlane() );
1529 const QList<QgsRayCastHit> results = mScene->terrainEntity()->rayIntersection( ray, context );
1530
1531 if ( !results.isEmpty() )
1532 {
1533 elevation = results.constFirst().mapCoordinates().z() - mOrigin.z();
1534 QgsDebugMsgLevel( QString( "Computed elevation from terrain: %1" ).arg( elevation ), 2 );
1535 }
1536 else
1537 {
1538 QgsDebugMsgLevel( "Unable to obtain elevation from terrain", 2 );
1539 }
1540 }
1541 pos.set( pos.x(), pos.y(), elevation + mScene->terrainEntity()->terrainElevationOffset() );
1542
1543 setLookingAtPoint( pos, ( mCamera->position() - pos.toVector3D() ).length(), pitch, yaw );
1544}
1545
1547{
1548 if ( !crossSection.isValid() )
1549 return;
1550
1551 const QgsPoint startPoint = crossSection.startPoint();
1552 const QgsPoint endPoint = crossSection.endPoint();
1553 const double width = crossSection.halfWidth();
1554
1555 const QgsVector3D startVec { startPoint.x(), startPoint.y(), 0 };
1556 const QgsVector3D endVec { endPoint.x(), endPoint.y(), 0 };
1557
1558 QgsVector linePerpVec( ( endPoint - startPoint ).x(), ( endPoint - startPoint ).y() );
1559 linePerpVec = -linePerpVec.normalized().perpVector();
1560
1561 const QgsVector3D linePerpVec3D( linePerpVec.x(), linePerpVec.y(), 0 );
1562 const QgsVector3D frontStartPoint( startVec + linePerpVec3D * width );
1563 const QgsVector3D frontEndPoint( endVec + linePerpVec3D * width );
1564
1565 const QgsCameraPose camPose = Qgs3DUtils::lineSegmentToCameraPose( frontStartPoint, frontEndPoint, mScene->elevationRange( true ), mCamera->fieldOfView(), mOrigin );
1566
1567 setCameraPose( camPose );
1568}
@ WhenPivoting
When pivoting camera around point in terrain.
Definition qgis.h:4505
@ WhenRotatingCaptured
When rotating camera around self with mouse button pressed.
Definition qgis.h:4504
@ WhenRotatingDragging
When rotating camera around self with mouse captured.
Definition qgis.h:4503
NavigationMode
The navigation mode used by 3D cameras.
Definition qgis.h:4477
@ TerrainBased
The default navigation based on the terrain.
Definition qgis.h:4478
@ Walk
Uses WASD keys or arrows to navigate in walking (first person) manner.
Definition qgis.h:4479
@ GlobeTerrainBased
Navigation similar to TerrainBased, but for use with globe.
Definition qgis.h:4480
QFlags< VerticalAxisInversion > VerticalAxisInversionFlags
Definition qgis.h:4514
@ Orthographic
Orthogonal projection.
Definition qgis.h:4340
@ Perspective
Perspective projection.
Definition qgis.h:4341
@ Globe
Scene is represented as a globe using a geocentric CRS.
Definition qgis.h:4492
@ Local
Local scene based on a projected CRS.
Definition qgis.h:4491
@ Reverse
Reverse/inverse transform (from destination to source).
Definition qgis.h:2834
Entity that encapsulates our 3D scene - contains all other entities (such as terrain) as children.
QgsTerrainEntity * terrainEntity() SIP_SKIP
Returns terrain entity (may be nullptr if using globe scene, terrain rendering is disabled or when te...
Qgs3DMapSettings * mapSettings() const
Returns the 3D map settings.
QgsAbstract3DEngine * engine() const SIP_SKIP
Returns the abstract 3D engine.
Qgis::Map3DProjectionType projectionType() const
Returns the camera lens' projection type.
bool terrainRenderingEnabled() const
Returns whether the 2D terrain surface will be rendered.
static QQuaternion rotationFromPitchHeadingAngles(float pitchAngle, float headingAngle)
Returns rotation quaternion that performs rotation around X axis by pitchAngle, followed by rotation ...
static std::unique_ptr< Qt3DRender::QCamera > copyCamera(Qt3DRender::QCamera *cam)
Returns new camera object with copied properties.
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:322
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.
static QgsCameraPose lineSegmentToCameraPose(const QgsVector3D &startPoint, const QgsVector3D &endPoint, const QgsDoubleRange &elevationRange, float fieldOfView, const QgsVector3D &worldOrigin)
Returns the camera pose for a camera looking at mid-point between startPoint and endPoint.
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 rotateCameraToBottom()
Rotate to bottom-up view.
void setLookingAtMapPoint(const QgsVector3D &point, float distance, float pitch, float yaw)
Sets camera configuration like setLookingAtPoint(), but the point is given in map coordinates.
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 rotateCameraToTop()
Rotate to top-down view.
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 rotateCameraToEast()
Rotate to view from the east.
const QgsVector3D origin() const
Returns the origin of the scene in map coordinates.
void rotateCameraToHome()
Rotate to diagonal view.
void rotateCameraToNorth()
Rotate to view from the north.
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.
Q_DECL_DEPRECATED 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 rotateCameraToWest()
Rotate to view from the west.
void setCameraNavigationMode(Qgis::NavigationMode navigationMode)
Sets the navigation mode used by the camera controller.
void setVerticalAxisInversion(Qgis::VerticalAxisInversionFlags inversion)
Sets the vertical axis inversion behavior.
void cameraChanged()
Emitted when camera has been updated.
void moveCenterPoint(const QVector3D &posDiff)
Moves camera position by the given difference vector in world coordinates.
void cameraMovementSpeedChanged(double speed)
Emitted whenever the camera movement speed is changed by the controller.
void setCrossSectionSideView(const QgsCrossSection &crossSection)
Sets the cross section side view definition for the 3D map canvas.
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.
void rotateCameraToSouth()
Rotate to view from the south.
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...
bool keyboardEventFilter(QKeyEvent *event)
If the event is relevant, handles the event and returns true, otherwise false.
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 readXml(const QDomElement &elem, QgsVector3D savedOrigin)
Reads camera configuration from the given DOM element.
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.
Encapsulates camera pose in a 3D scene.
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.
Handles coordinate transforms between two coordinate systems.
Encapsulates the definition of a cross section in 3D map coordinates.
double halfWidth() const
Returns the half-width of the cross section.
QgsPoint endPoint() const
Returns the end point of the cross section.
bool isValid() const
Returns cross section validity.
QgsPoint startPoint() const
Returns the start point of the cross section.
Custom exception class for Coordinate Reference System related exceptions.
Represents a 2D point.
Definition qgspointxy.h:62
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:53
double x
Definition qgspoint.h:56
double y
Definition qgspoint.h:57
A representation of a ray in 3D.
Definition qgsray3d.h:31
QVector3D origin() const
Returns the origin of the ray.
Definition qgsray3d.h:43
QVector3D direction() const
Returns the direction of the ray see setDirection().
Definition qgsray3d.h:49
Responsible for defining parameters of the ray casting operations in 3D map canvases.
void setSingleResult(bool enable)
Sets whether to fetch only the closest hit for each layer or entity type.
void setMaximumDistance(float distance)
Sets the maximum distance from ray origin to look for hits when casting a ray.
A 3D vector (similar to QVector3D) with the difference that it uses double precision instead of singl...
Definition qgsvector3d.h:33
double y() const
Returns Y coordinate.
Definition qgsvector3d.h:60
double z() const
Returns Z coordinate.
Definition qgsvector3d.h:62
QVector3D toVector3D() const
Converts the current object to QVector3D.
static double dotProduct(const QgsVector3D &v1, const QgsVector3D &v2)
Returns the dot product of two vectors.
double x() const
Returns X coordinate.
Definition qgsvector3d.h:58
void setX(double x)
Sets X coordinate.
Definition qgsvector3d.h:68
void set(double x, double y, double z)
Sets vector coordinates.
Definition qgsvector3d.h:83
void setY(double y)
Sets Y coordinate.
Definition qgsvector3d.h:74
double length() const
Returns the length of the vector.
Represent a 2-dimensional vector.
Definition qgsvector.h:34
double y() const
Returns the vector's y-component.
Definition qgsvector.h:155
QgsVector normalized() const
Returns the vector's normalized (or "unit") vector (ie same angle but length of 1....
Definition qgsvector.cpp:33
QgsVector perpVector() const
Returns the perpendicular vector to this vector (rotated 90 degrees counter-clockwise).
Definition qgsvector.h:163
double x() const
Returns the vector's x-component.
Definition qgsvector.h:146
On-screen 3D engine: it creates an OpenGL window (QWindow) and displays rendered 3D scenes there.
QWindow * window()
Returns the internal 3D window where all the rendered output is displayed.
double ANALYSIS_EXPORT angle(QgsPoint *p1, QgsPoint *p2, QgsPoint *p3, QgsPoint *p4)
Calculates the angle between two segments (in 2 dimension, z-values are ignored).
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:80
#define QgsDebugError(str)
Definition qgslogger.h:71