QGIS API Documentation 3.43.0-Master (2a27c31701b)
Loading...
Searching...
No Matches
qgscameracontroller.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgscameracontroller.cpp
3 --------------------------------------
4 Date : July 2017
5 Copyright : (C) 2017 by Martin Dobias
6 Email : wonder dot sk at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgscameracontroller.h"
17#include "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
65
66QWindow *QgsCameraController::window() const
67{
68 QgsWindow3DEngine *windowEngine = qobject_cast<QgsWindow3DEngine *>( mScene->engine() );
69 return windowEngine ? windowEngine->window() : nullptr;
70}
71
73{
74 if ( navigationMode == mCameraNavigationMode )
75 return;
76
77 mCameraNavigationMode = navigationMode;
78 mIgnoreNextMouseMove = true;
79 emit navigationModeChanged( mCameraNavigationMode );
80}
81
83{
84 if ( movementSpeed == mCameraMovementSpeed )
85 return;
86
87 // If the speed becomes 0, navigation does not work anymore
88 // If the speed becomes too important, only one walk can move the view far from the scene.
89 mCameraMovementSpeed = std::clamp( movementSpeed, 0.05, 150.0 );
90 emit cameraMovementSpeedChanged( mCameraMovementSpeed );
91}
92
94{
95 mVerticalAxisInversion = inversion;
96}
97
98void QgsCameraController::rotateCamera( float diffPitch, float diffHeading )
99{
100 const float oldPitch = mCameraPose.pitchAngle();
101 const float oldHeading = mCameraPose.headingAngle();
102 float newPitch = oldPitch + diffPitch;
103 float newHeading = oldHeading + diffHeading;
104
105 newPitch = std::clamp( newPitch, 0.f, 180.f ); // prevent going over the head
106
107 // First undo the previously applied rotation, then apply the new rotation
108 // (We can't just apply our euler angles difference because the camera may be already rotated)
109 const QQuaternion qNew = Qgs3DUtils::rotationFromPitchHeadingAngles( newPitch, newHeading );
110 const QQuaternion qOld = Qgs3DUtils::rotationFromPitchHeadingAngles( oldPitch, oldHeading );
111 const QQuaternion q = qNew * qOld.conjugated();
112
113 // get camera's view vector, rotate it to get new view center
114 const QVector3D position = mCamera->position();
115 QVector3D viewCenter = mCamera->viewCenter();
116 const QVector3D viewVector = viewCenter - position;
117 const QVector3D cameraToCenter = q * viewVector;
118 viewCenter = position + cameraToCenter;
119
120 mCameraPose.setCenterPoint( viewCenter );
121 mCameraPose.setPitchAngle( newPitch );
122 mCameraPose.setHeadingAngle( newHeading );
123 updateCameraFromPose();
124}
125
126void QgsCameraController::rotateCameraAroundPivot( float newPitch, float newHeading, const QVector3D &pivotPoint )
127{
128 const float oldPitch = mCameraPose.pitchAngle();
129 const float oldHeading = mCameraPose.headingAngle();
130
131 newPitch = std::clamp( newPitch, 0.f, 180.f ); // prevent going over the head
132
133 // First undo the previously applied rotation, then apply the new rotation
134 // (We can't just apply our euler angles difference because the camera may be already rotated)
135 const QQuaternion qNew = Qgs3DUtils::rotationFromPitchHeadingAngles( newPitch, newHeading );
136 const QQuaternion qOld = Qgs3DUtils::rotationFromPitchHeadingAngles( oldPitch, oldHeading );
137 const QQuaternion q = qNew * qOld.conjugated();
138
139 const QVector3D newViewCenter = q * ( mCamera->viewCenter() - pivotPoint ) + pivotPoint;
140
141 mCameraPose.setCenterPoint( newViewCenter );
142 mCameraPose.setPitchAngle( newPitch );
143 mCameraPose.setHeadingAngle( newHeading );
144 updateCameraFromPose();
145}
146
147void QgsCameraController::zoomCameraAroundPivot( const QVector3D &oldCameraPosition, double zoomFactor, const QVector3D &pivotPoint )
148{
149 // step 1: move camera along the line connecting reference camera position and our pivot point
150 QVector3D newCamPosition = pivotPoint + ( oldCameraPosition - pivotPoint ) * zoomFactor;
151 double newDistance = mCameraPose.distanceFromCenterPoint() * zoomFactor;
152
153 // step 2: using the new camera position and distance from center, calculate new view center
154 QQuaternion q = Qgs3DUtils::rotationFromPitchHeadingAngles( mCameraPose.pitchAngle(), mCameraPose.headingAngle() );
155 QVector3D cameraToCenter = q * QVector3D( 0, 0, -newDistance );
156 QVector3D newViewCenter = newCamPosition + cameraToCenter;
157
158 mCameraPose.setDistanceFromCenterPoint( newDistance );
159 mCameraPose.setCenterPoint( newViewCenter );
160 updateCameraFromPose();
161}
162
164{
165 Q_UNUSED( dt )
166
167 if ( mCameraChanged )
168 {
169 emit cameraChanged();
170 mCameraChanged = false;
171 }
172}
173
174void QgsCameraController::resetView( float distance )
175{
176 QgsPointXY extentCenter = mScene->mapSettings()->extent().center();
177 QgsVector3D origin = mScene->mapSettings()->origin();
178 setViewFromTop( extentCenter.x() - origin.x(), extentCenter.y() - origin.y(), distance );
179}
180
181void QgsCameraController::setViewFromTop( float worldX, float worldY, float distance, float yaw )
182{
183 QgsCameraPose camPose;
184 QgsTerrainEntity *terrain = mScene->terrainEntity();
185 const float terrainElevationOffset = terrain ? terrain->terrainElevationOffset() : 0.0f;
186 camPose.setCenterPoint( QgsVector3D( worldX, worldY, terrainElevationOffset - mScene->mapSettings()->origin().z() ) );
188 camPose.setHeadingAngle( yaw );
189
190 // a basic setup to make frustum depth range long enough that it does not cull everything
191 mCamera->setNearPlane( distance / 2 );
192 mCamera->setFarPlane( distance * 2 );
193 // we force the updateCameraNearFarPlanes() in Qgs3DMapScene to properly set the planes
194 setCameraPose( camPose, true );
195}
196
198{
199 return mCameraPose.centerPoint();
200}
201
202void QgsCameraController::setLookingAtPoint( const QgsVector3D &point, float distance, float pitch, float yaw )
203{
204 QgsCameraPose camPose;
205 camPose.setCenterPoint( point );
207 camPose.setPitchAngle( pitch );
208 camPose.setHeadingAngle( yaw );
209 setCameraPose( camPose );
210}
211
213{
214 return lookingAtPoint() + mOrigin;
215}
216
217void QgsCameraController::setLookingAtMapPoint( const QgsVector3D &point, float distance, float pitch, float yaw )
218{
219 setLookingAtPoint( point - mOrigin, distance, pitch, yaw );
220}
221
222void QgsCameraController::setCameraPose( const QgsCameraPose &camPose, bool force )
223{
224 if ( camPose == mCameraPose && !force )
225 return;
226
227 mCameraPose = camPose;
228 updateCameraFromPose();
229}
230
231QDomElement QgsCameraController::writeXml( QDomDocument &doc ) const
232{
233 QDomElement elemCamera = doc.createElement( QStringLiteral( "camera" ) );
234 elemCamera.setAttribute( QStringLiteral( "x" ), mCameraPose.centerPoint().x() );
235 elemCamera.setAttribute( QStringLiteral( "y" ), mCameraPose.centerPoint().z() );
236 elemCamera.setAttribute( QStringLiteral( "elev" ), mCameraPose.centerPoint().y() );
237 elemCamera.setAttribute( QStringLiteral( "dist" ), mCameraPose.distanceFromCenterPoint() );
238 elemCamera.setAttribute( QStringLiteral( "pitch" ), mCameraPose.pitchAngle() );
239 elemCamera.setAttribute( QStringLiteral( "yaw" ), mCameraPose.headingAngle() );
240 return elemCamera;
241}
242
243void QgsCameraController::readXml( const QDomElement &elem )
244{
245 const float x = elem.attribute( QStringLiteral( "x" ) ).toFloat();
246 const float y = elem.attribute( QStringLiteral( "y" ) ).toFloat();
247 const float elev = elem.attribute( QStringLiteral( "elev" ) ).toFloat();
248 const float dist = elem.attribute( QStringLiteral( "dist" ) ).toFloat();
249 const float pitch = elem.attribute( QStringLiteral( "pitch" ) ).toFloat();
250 const float yaw = elem.attribute( QStringLiteral( "yaw" ) ).toFloat();
251 setLookingAtPoint( QgsVector3D( x, elev, y ), dist, pitch, yaw );
252}
253
254double QgsCameraController::sampleDepthBuffer( int px, int py )
255{
256 double depth = 1;
257
258 // Sample the neighbouring pixels for the closest point to the camera
259 for ( int x = px - 3; x <= px + 3; ++x )
260 {
261 for ( int y = py - 3; y <= py + 3; ++y )
262 {
263 if ( mDepthBufferImage.valid( x, y ) )
264 {
265 depth = std::min( depth, Qgs3DUtils::decodeDepth( mDepthBufferImage.pixel( x, y ) ) );
266 }
267 }
268 }
269
270 if ( depth < 1 )
271 return depth;
272
273 // Cache the computed depth, since averaging over all pixels can be expensive
274 if ( mDepthBufferNonVoidAverage != -1 )
275 return mDepthBufferNonVoidAverage;
276
277 // Returns the average of depth values that are not 1 (void area)
278 depth = 0;
279 int samplesCount = 0;
280 // Make sure we can do the cast
281 Q_ASSERT( mDepthBufferImage.format() == QImage::Format_RGB32 );
282 for ( int y = 0; y < mDepthBufferImage.height(); ++y )
283 {
284 const QRgb *line = reinterpret_cast<const QRgb *>( mDepthBufferImage.constScanLine( y ) );
285 for ( int x = 0; x < mDepthBufferImage.width(); ++x )
286 {
287 double d = Qgs3DUtils::decodeDepth( line[x] );
288 if ( d < 1 )
289 {
290 depth += d;
291 samplesCount += 1;
292 }
293 }
294 }
295
296 // if the whole buffer is white, a depth cannot be computed
297 if ( samplesCount == 0 )
298 depth = 1.0;
299 else
300 depth /= samplesCount;
301
302 mDepthBufferNonVoidAverage = depth;
303
304 return depth;
305}
306
307void QgsCameraController::updateCameraFromPose()
308{
309 if ( mCamera )
310 {
311 mCameraPose.updateCamera( mCamera );
312 mCameraChanged = true;
313 }
314}
315
316void QgsCameraController::moveCameraPositionBy( const QVector3D &posDiff )
317{
318 mCameraPose.setCenterPoint( mCameraPose.centerPoint() + posDiff );
319 updateCameraFromPose();
320}
321
322void QgsCameraController::onPositionChanged( Qt3DInput::QMouseEvent *mouse )
323{
324 if ( !mInputHandlersEnabled )
325 return;
326
327 QgsEventTracing::ScopedEvent traceEvent( QStringLiteral( "3D" ), QStringLiteral( "QgsCameraController::onPositionChanged" ) );
328
329 switch ( mCameraNavigationMode )
330 {
332 onPositionChangedTerrainNavigation( mouse );
333 break;
334
336 onPositionChangedFlyNavigation( mouse );
337 break;
338 }
339}
340
341bool QgsCameraController::screenPointToWorldPos( QPoint position, Qt3DRender::QCamera *mCameraBefore, double &depth, QVector3D &worldPosition )
342{
343 depth = sampleDepthBuffer( position.x(), position.y() );
344 if ( !std::isfinite( depth ) )
345 {
346 QgsDebugMsgLevel( QStringLiteral( "screenPointToWorldPos: depth is NaN or Inf. This should not happen." ), 2 );
347 return false;
348 }
349 else
350 {
351 worldPosition = Qgs3DUtils::screenPointToWorldPos( position, depth, mScene->engine()->size(), mCameraBefore );
352 if ( !std::isfinite( worldPosition.x() ) || !std::isfinite( worldPosition.y() ) || !std::isfinite( worldPosition.z() ) )
353 {
354 QgsDebugMsgLevel( QStringLiteral( "screenPointToWorldPos: position is NaN or Inf. This should not happen." ), 2 );
355 return false;
356 }
357 else
358 {
359 return true;
360 }
361 }
362}
363
364void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseEvent *mouse )
365{
366 if ( mIgnoreNextMouseMove )
367 {
368 mIgnoreNextMouseMove = false;
369 mMousePos = QPoint( mouse->x(), mouse->y() );
370 return;
371 }
372
373 const int dx = mouse->x() - mMousePos.x();
374 const int dy = mouse->y() - mMousePos.y();
375
376 const bool hasShift = ( mouse->modifiers() & Qt::ShiftModifier );
377 const bool hasCtrl = ( mouse->modifiers() & Qt::ControlModifier );
378 const bool hasLeftButton = ( mouse->buttons() & Qt::LeftButton );
379 const bool hasMiddleButton = ( mouse->buttons() & Qt::MiddleButton );
380 const bool hasRightButton = ( mouse->buttons() & Qt::RightButton );
381
382 if ( ( hasLeftButton && hasShift && !hasCtrl ) || ( hasMiddleButton && !hasShift && !hasCtrl ) )
383 {
384 // rotate/tilt using mouse (camera moves as it rotates around the clicked point)
385 setMouseParameters( MouseOperation::RotationCenter, mMousePos );
386
387 float scale = static_cast<float>( std::max( mScene->engine()->size().width(), mScene->engine()->size().height() ) );
388 float pitchDiff = 180.0f * static_cast<float>( mouse->y() - mClickPoint.y() ) / scale;
389 float yawDiff = -180.0f * static_cast<float>( mouse->x() - mClickPoint.x() ) / scale;
390
391 if ( !mDepthBufferIsReady )
392 return;
393
394 if ( !mRotationCenterCalculated )
395 {
396 double depth;
397 QVector3D worldPosition;
398 if ( screenPointToWorldPos( mClickPoint, mCameraBefore.get(), depth, worldPosition ) )
399 {
400 mRotationCenter = worldPosition;
401 mRotationDistanceFromCenter = ( mRotationCenter - mCameraBefore->position() ).length();
402 emit cameraRotationCenterChanged( mRotationCenter );
403 mRotationCenterCalculated = true;
404 }
405 }
406
407 rotateCameraAroundPivot( mRotationPitch + pitchDiff, mRotationYaw + yawDiff, mRotationCenter );
408 }
409 else if ( hasLeftButton && hasCtrl && !hasShift )
410 {
411 setMouseParameters( MouseOperation::RotationCamera );
412 // rotate/tilt using mouse (camera stays at one position as it rotates)
413 const float diffPitch = 0.2f * dy;
414 const float diffYaw = -0.2f * dx;
415 rotateCamera( diffPitch, diffYaw );
416 }
417 else if ( hasLeftButton && !hasShift && !hasCtrl )
418 {
419 // translation works as if one grabbed a point on the 3D viewer and dragged it
420 setMouseParameters( MouseOperation::Translation, mMousePos );
421
422 if ( !mDepthBufferIsReady )
423 return;
424
425 if ( !mDragPointCalculated )
426 {
427 double depth;
428 QVector3D worldPosition;
429 if ( screenPointToWorldPos( mClickPoint, mCameraBefore.get(), depth, worldPosition ) )
430 {
431 mDragDepth = depth;
432 mDragPoint = worldPosition;
433 mDragPointCalculated = true;
434 }
435 }
436
437 QVector3D cameraBeforeDragPos = mCameraBefore->position();
438
439 QVector3D moveToPosition = Qgs3DUtils::screenPointToWorldPos( mMousePos, mDragDepth, mScene->engine()->size(), mCameraBefore.get() );
440 QVector3D cameraBeforeToMoveToPos = ( moveToPosition - mCameraBefore->position() ).normalized();
441 QVector3D cameraBeforeToDragPointPos = ( mDragPoint - mCameraBefore->position() ).normalized();
442
443 // Make sure the rays are not horizontal (add small z shift if it is)
444 if ( cameraBeforeToMoveToPos.z() == 0 )
445 {
446 cameraBeforeToMoveToPos.setZ( 0.01 );
447 cameraBeforeToMoveToPos = cameraBeforeToMoveToPos.normalized();
448 }
449
450 if ( cameraBeforeToDragPointPos.z() == 0 )
451 {
452 cameraBeforeToDragPointPos.setZ( 0.01 );
453 cameraBeforeToDragPointPos = cameraBeforeToDragPointPos.normalized();
454 }
455
456 double d1 = ( mDragPoint.z() - cameraBeforeDragPos.z() ) / cameraBeforeToMoveToPos.z();
457 double d2 = ( mDragPoint.z() - cameraBeforeDragPos.z() ) / cameraBeforeToDragPointPos.z();
458
459 QVector3D from = cameraBeforeDragPos + d1 * cameraBeforeToMoveToPos;
460 QVector3D to = cameraBeforeDragPos + d2 * cameraBeforeToDragPointPos;
461
462 QVector3D shiftVector = to - from;
463
464 mCameraPose.setCenterPoint( mCameraBefore->viewCenter() + shiftVector );
465 updateCameraFromPose();
466 }
467 else if ( hasLeftButton && hasShift && hasCtrl )
468 {
469 // change the camera elevation, similar to pageUp/pageDown
470 QgsVector3D center = mCameraPose.centerPoint();
471 double tElev = mMousePos.y() - mouse->y();
472 center.set( center.x(), center.y(), center.z() + tElev * 0.5 );
473 mCameraPose.setCenterPoint( center );
474 updateCameraFromPose();
475 }
476 else if ( hasRightButton && !hasShift && !hasCtrl )
477 {
478 setMouseParameters( MouseOperation::Zoom, mMousePos );
479 if ( !mDepthBufferIsReady )
480 return;
481
482 if ( !mDragPointCalculated )
483 {
484 double depth;
485 QVector3D worldPosition;
486 if ( screenPointToWorldPos( mClickPoint, mCameraBefore.get(), depth, worldPosition ) )
487 {
488 mDragPoint = worldPosition;
489 mDragPointCalculated = true;
490 }
491 }
492
493 float oldDist = ( mCameraBefore->position() - mDragPoint ).length();
494 float newDist = oldDist;
495
496 int yOffset = 0;
497 int screenHeight = mScene->engine()->size().height();
498 QWindow *win = window();
499 if ( win )
500 {
501 yOffset = win->mapToGlobal( QPoint( 0, 0 ) ).y();
502 screenHeight = win->screen()->size().height();
503 }
504
505 // Applies smoothing
506 if ( mMousePos.y() > mClickPoint.y() ) // zoom in
507 {
508 double f = ( double ) ( mMousePos.y() - mClickPoint.y() ) / ( double ) ( screenHeight - mClickPoint.y() - yOffset );
509 f = std::max( 0.0, std::min( 1.0, f ) );
510 f = 1 - ( std::expm1( -2 * f ) ) / ( std::expm1( -2 ) );
511 newDist = newDist * f;
512 }
513 else // zoom out
514 {
515 double f = 1 - ( double ) ( mMousePos.y() + yOffset ) / ( double ) ( mClickPoint.y() + yOffset );
516 f = std::max( 0.0, std::min( 1.0, f ) );
517 f = ( std::expm1( 2 * f ) ) / ( std::expm1( 2 ) );
518 newDist = newDist + 2 * newDist * f;
519 }
520
521 double zoomFactor = newDist / oldDist;
522 zoomCameraAroundPivot( mCameraBefore->position(), zoomFactor, mDragPoint );
523 }
524
525 mMousePos = QPoint( mouse->x(), mouse->y() );
526}
527
528void QgsCameraController::zoom( float factor )
529{
530 // zoom in/out
531 float dist = mCameraPose.distanceFromCenterPoint();
532 dist -= dist * factor * 0.01f;
533 mCameraPose.setDistanceFromCenterPoint( dist );
534 updateCameraFromPose();
535}
536
537void QgsCameraController::handleTerrainNavigationWheelZoom()
538{
539 if ( !mDepthBufferIsReady )
540 return;
541
542 if ( !mZoomPointCalculated )
543 {
544 double depth;
545 QVector3D worldPosition;
546 if ( screenPointToWorldPos( mMousePos, mCameraBefore.get(), depth, worldPosition ) )
547 {
548 mZoomPoint = worldPosition;
549 mZoomPointCalculated = true;
550 }
551 }
552
553 double oldDist = ( mZoomPoint - mCameraBefore->position() ).length();
554 // Each step of the scroll wheel decreases distance by 20%
555 double newDist = std::pow( 0.8, mCumulatedWheelY ) * oldDist;
556 // Make sure we don't clip the thing we're zooming to.
557 newDist = std::max( newDist, 2.0 );
558 double zoomFactor = newDist / oldDist;
559 // Don't change the distance too suddenly to hopefully prevent numerical instability
560 zoomFactor = std::clamp( zoomFactor, 0.01, 100.0 );
561
562 zoomCameraAroundPivot( mCameraBefore->position(), zoomFactor, mZoomPoint );
563
564 mCumulatedWheelY = 0;
565 setMouseParameters( MouseOperation::None );
566}
567
568void QgsCameraController::onWheel( Qt3DInput::QWheelEvent *wheel )
569{
570 if ( !mInputHandlersEnabled )
571 return;
572
573 switch ( mCameraNavigationMode )
574 {
576 {
577 const float scaling = ( ( wheel->modifiers() & Qt::ControlModifier ) != 0 ? 0.1f : 1.0f ) / 1000.f;
578 setCameraMovementSpeed( mCameraMovementSpeed + mCameraMovementSpeed * scaling * wheel->angleDelta().y() );
579 break;
580 }
581
583 {
584 // Scale our variable to roughly "number of normal steps", with Ctrl
585 // increasing granularity 10x
586 const double scaling = ( 1.0 / 120.0 ) * ( ( wheel->modifiers() & Qt::ControlModifier ) != 0 ? 0.1 : 1.0 );
587
588 // Apparently angleDelta needs to be accumulated
589 // see: https://doc.qt.io/qt-5/qwheelevent.html#angleDelta
590 mCumulatedWheelY += scaling * wheel->angleDelta().y();
591
592 if ( mCurrentOperation != MouseOperation::ZoomWheel )
593 {
594 setMouseParameters( MouseOperation::ZoomWheel );
595 // The actual zooming will happen after we get a new depth buffer
596 }
597 else
598 {
599 handleTerrainNavigationWheelZoom();
600 }
601 break;
602 }
603 }
604}
605
606void QgsCameraController::onMousePressed( Qt3DInput::QMouseEvent *mouse )
607{
608 if ( !mInputHandlersEnabled )
609 return;
610
611 mKeyboardHandler->setFocus( true );
612
613 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 ) )
614 {
615 mMousePos = QPoint( mouse->x(), mouse->y() );
616
617 if ( mCaptureFpsMouseMovements )
618 mIgnoreNextMouseMove = true;
619
620 const MouseOperation operation {
621 ( mouse->modifiers() & Qt::ControlModifier ) != 0 && mouse->button() == Qt3DInput::QMouseEvent::LeftButton ? MouseOperation::RotationCamera : MouseOperation::RotationCenter
622 };
623 setMouseParameters( operation, mMousePos );
624 }
625
626 else if ( mouse->button() == Qt3DInput::QMouseEvent::LeftButton || mouse->button() == Qt3DInput::QMouseEvent::RightButton )
627 {
628 mMousePos = QPoint( mouse->x(), mouse->y() );
629
630 if ( mCaptureFpsMouseMovements )
631 mIgnoreNextMouseMove = true;
632
633 const MouseOperation operation = ( mouse->button() == Qt3DInput::QMouseEvent::LeftButton ) ? MouseOperation::Translation : MouseOperation::Zoom;
634 setMouseParameters( operation, mMousePos );
635 }
636}
637
638void QgsCameraController::onMouseReleased( Qt3DInput::QMouseEvent *mouse )
639{
640 Q_UNUSED( mouse )
641 if ( !mInputHandlersEnabled )
642 return;
643
644
645 setMouseParameters( MouseOperation::None );
646}
647
648void QgsCameraController::onKeyPressed( Qt3DInput::QKeyEvent *event )
649{
650 if ( !mInputHandlersEnabled )
651 return;
652
653 if ( event->modifiers() & Qt::ControlModifier && event->key() == Qt::Key_QuoteLeft )
654 {
655 // switch navigation mode
656 switch ( mCameraNavigationMode )
657 {
660 break;
663 break;
664 }
665 return;
666 }
667
668 switch ( mCameraNavigationMode )
669 {
671 {
672 onKeyPressedFlyNavigation( event );
673 break;
674 }
675
677 {
678 onKeyPressedTerrainNavigation( event );
679 break;
680 }
681 }
682}
683
684void QgsCameraController::onKeyPressedTerrainNavigation( Qt3DInput::QKeyEvent *event )
685{
686 const bool hasShift = ( event->modifiers() & Qt::ShiftModifier );
687 const bool hasCtrl = ( event->modifiers() & Qt::ControlModifier );
688
689 int tx = 0, ty = 0, tElev = 0;
690 switch ( event->key() )
691 {
692 case Qt::Key_Left:
693 tx -= 1;
694 break;
695 case Qt::Key_Right:
696 tx += 1;
697 break;
698
699 case Qt::Key_Up:
700 ty += 1;
701 break;
702 case Qt::Key_Down:
703 ty -= 1;
704 break;
705
706 case Qt::Key_PageDown:
707 tElev -= 1;
708 break;
709 case Qt::Key_PageUp:
710 tElev += 1;
711 break;
712 }
713
714 if ( tx || ty )
715 {
716 if ( !hasShift && !hasCtrl )
717 {
718 moveView( tx, ty );
719 }
720 else if ( hasShift && !hasCtrl )
721 {
722 // rotate/tilt using keyboard (camera moves as it rotates around its view center)
725 }
726 else if ( hasCtrl && !hasShift )
727 {
728 // rotate/tilt using keyboard (camera stays at one position as it rotates)
729 const float diffPitch = ty; // down key = rotating camera down
730 const float diffYaw = -tx; // right key = rotating camera to the right
731 rotateCamera( diffPitch, diffYaw );
732 }
733 }
734
735 if ( tElev )
736 {
737 QgsVector3D center = mCameraPose.centerPoint();
738 center.set( center.x(), center.y(), center.z() + tElev * 10 );
739 mCameraPose.setCenterPoint( center );
740 updateCameraFromPose();
741 }
742}
743
744void QgsCameraController::onKeyPressedFlyNavigation( Qt3DInput::QKeyEvent *event )
745{
746 switch ( event->key() )
747 {
748 case Qt::Key_QuoteLeft:
749 {
750 // toggle mouse lock mode
751 mCaptureFpsMouseMovements = !mCaptureFpsMouseMovements;
752 mIgnoreNextMouseMove = true;
753 if ( mCaptureFpsMouseMovements )
754 {
755 qApp->setOverrideCursor( QCursor( Qt::BlankCursor ) );
756 }
757 else
758 {
759 qApp->restoreOverrideCursor();
760 }
761 return;
762 }
763
764 case Qt::Key_Escape:
765 {
766 // always exit mouse lock mode
767 if ( mCaptureFpsMouseMovements )
768 {
769 mCaptureFpsMouseMovements = false;
770 mIgnoreNextMouseMove = true;
771 qApp->restoreOverrideCursor();
772 return;
773 }
774 break;
775 }
776
777 default:
778 break;
779 }
780
781 if ( event->isAutoRepeat() )
782 return;
783
784 mDepressedKeys.insert( event->key() );
785}
786
787void QgsCameraController::walkView( double tx, double ty, double tz )
788{
789 const QVector3D cameraUp = mCamera->upVector().normalized();
790 const QVector3D cameraFront = ( QVector3D( mCameraPose.centerPoint().x(), mCameraPose.centerPoint().y(), mCameraPose.centerPoint().z() ) - mCamera->position() ).normalized();
791 const QVector3D cameraLeft = QVector3D::crossProduct( cameraUp, cameraFront );
792
793 QVector3D cameraPosDiff( 0.0f, 0.0f, 0.0f );
794
795 if ( tx != 0.0 )
796 {
797 cameraPosDiff += static_cast<float>( tx ) * cameraFront;
798 }
799 if ( ty != 0.0 )
800 {
801 cameraPosDiff += static_cast<float>( ty ) * cameraLeft;
802 }
803 if ( tz != 0.0 )
804 {
805 cameraPosDiff += static_cast<float>( tz ) * QVector3D( 0.0f, 0.0f, 1.0f );
806 }
807
808 moveCameraPositionBy( cameraPosDiff );
809}
810
811void QgsCameraController::applyFlyModeKeyMovements()
812{
813 // shift = "run", ctrl = "slow walk"
814 const bool shiftPressed = mDepressedKeys.contains( Qt::Key_Shift );
815 const bool ctrlPressed = mDepressedKeys.contains( Qt::Key_Control );
816
817 const double movementSpeed = mCameraMovementSpeed * ( shiftPressed ? 2 : 1 ) * ( ctrlPressed ? 0.1 : 1 );
818
819 bool changed = false;
820 double x = 0.0;
821 double y = 0.0;
822 double z = 0.0;
823 if ( mDepressedKeys.contains( Qt::Key_Left ) || mDepressedKeys.contains( Qt::Key_A ) )
824 {
825 changed = true;
826 y += movementSpeed;
827 }
828
829 if ( mDepressedKeys.contains( Qt::Key_Right ) || mDepressedKeys.contains( Qt::Key_D ) )
830 {
831 changed = true;
832 y -= movementSpeed;
833 }
834
835 if ( mDepressedKeys.contains( Qt::Key_Up ) || mDepressedKeys.contains( Qt::Key_W ) )
836 {
837 changed = true;
838 x += movementSpeed;
839 }
840
841 if ( mDepressedKeys.contains( Qt::Key_Down ) || mDepressedKeys.contains( Qt::Key_S ) )
842 {
843 changed = true;
844 x -= movementSpeed;
845 }
846
847 // note -- vertical axis movements are slower by default then horizontal ones, as GIS projects
848 // tend to have much more limited elevation range vs ground range
849 static constexpr double ELEVATION_MOVEMENT_SCALE = 0.5;
850 if ( mDepressedKeys.contains( Qt::Key_PageUp ) || mDepressedKeys.contains( Qt::Key_E ) )
851 {
852 changed = true;
853 z += ELEVATION_MOVEMENT_SCALE * movementSpeed;
854 }
855
856 if ( mDepressedKeys.contains( Qt::Key_PageDown ) || mDepressedKeys.contains( Qt::Key_Q ) )
857 {
858 changed = true;
859 z -= ELEVATION_MOVEMENT_SCALE * movementSpeed;
860 }
861
862 if ( changed )
863 walkView( x, y, z );
864}
865
866void QgsCameraController::onPositionChangedFlyNavigation( Qt3DInput::QMouseEvent *mouse )
867{
868 const bool hasMiddleButton = ( mouse->buttons() & Qt::MiddleButton );
869 const bool hasRightButton = ( mouse->buttons() & Qt::RightButton );
870
871 const double dx = mCaptureFpsMouseMovements ? QCursor::pos().x() - mMousePos.x() : mouse->x() - mMousePos.x();
872 const double dy = mCaptureFpsMouseMovements ? QCursor::pos().y() - mMousePos.y() : mouse->y() - mMousePos.y();
873 mMousePos = mCaptureFpsMouseMovements ? QCursor::pos() : QPoint( mouse->x(), mouse->y() );
874
875 if ( mIgnoreNextMouseMove )
876 {
877 mIgnoreNextMouseMove = false;
878 return;
879 }
880
881 if ( hasMiddleButton )
882 {
883 // middle button drag = pan camera in place (strafe)
884 const QVector3D cameraUp = mCamera->upVector().normalized();
885 const QVector3D cameraFront = ( QVector3D( mCameraPose.centerPoint().x(), mCameraPose.centerPoint().y(), mCameraPose.centerPoint().z() ) - mCamera->position() ).normalized();
886 const QVector3D cameraLeft = QVector3D::crossProduct( cameraUp, cameraFront );
887 const QVector3D cameraPosDiff = -dx * cameraLeft - dy * cameraUp;
888 moveCameraPositionBy( mCameraMovementSpeed * cameraPosDiff / 10.0 );
889 }
890 else if ( hasRightButton )
891 {
892 // right button drag = camera dolly
893 const QVector3D cameraFront = ( QVector3D( mCameraPose.centerPoint().x(), mCameraPose.centerPoint().y(), mCameraPose.centerPoint().z() ) - mCamera->position() ).normalized();
894 const QVector3D cameraPosDiff = dy * cameraFront;
895 moveCameraPositionBy( mCameraMovementSpeed * cameraPosDiff / 5.0 );
896 }
897 else
898 {
899 if ( mCaptureFpsMouseMovements )
900 {
901 float diffPitch = -0.2f * dy;
902 switch ( mVerticalAxisInversion )
903 {
905 diffPitch *= -1;
906 break;
907
910 break;
911 }
912
913 const float diffYaw = -0.2f * dx;
914 rotateCamera( diffPitch, diffYaw );
915 }
916 else if ( mouse->buttons() & Qt::LeftButton )
917 {
918 float diffPitch = -0.2f * dy;
919 switch ( mVerticalAxisInversion )
920 {
923 diffPitch *= -1;
924 break;
925
927 break;
928 }
929 const float diffYaw = -0.2f * dx;
930 rotateCamera( diffPitch, diffYaw );
931 }
932 }
933
934 if ( mCaptureFpsMouseMovements )
935 {
936 mIgnoreNextMouseMove = true;
937
938 // reset cursor back to center of map widget
939 emit setCursorPosition( QPoint( mScene->engine()->size().width() / 2, mScene->engine()->size().height() / 2 ) );
940 }
941}
942
943void QgsCameraController::onKeyReleased( Qt3DInput::QKeyEvent *event )
944{
945 if ( !mInputHandlersEnabled )
946 return;
947
948 if ( event->isAutoRepeat() )
949 return;
950
951 mDepressedKeys.remove( event->key() );
952}
953
955{
956 // Tilt up the view by deltaPitch around the view center (camera moves)
957 float pitch = mCameraPose.pitchAngle();
958 pitch -= deltaPitch; // down key = moving camera toward terrain
959 mCameraPose.setPitchAngle( pitch );
960 updateCameraFromPose();
961}
962
964{
965 // Rotate clockwise the view by deltaYaw around the view center (camera moves)
966 float yaw = mCameraPose.headingAngle();
967 yaw -= deltaYaw; // right key = moving camera clockwise
968 mCameraPose.setHeadingAngle( yaw );
969 updateCameraFromPose();
970}
971
973{
974 mCameraPose.setHeadingAngle( angle );
975 updateCameraFromPose();
976}
977
978void QgsCameraController::moveView( float tx, float ty )
979{
980 const float yaw = mCameraPose.headingAngle();
981 const float dist = mCameraPose.distanceFromCenterPoint();
982 const float x = tx * dist * 0.02f;
983 const float y = -ty * dist * 0.02f;
984
985 // moving with keyboard - take into account yaw of camera
986 const float t = sqrt( x * x + y * y );
987 const float a = atan2( y, x ) - yaw * M_PI / 180;
988 const float dx = cos( a ) * t;
989 const float dy = sin( a ) * t;
990
991 QgsVector3D center = mCameraPose.centerPoint();
992 center.set( center.x() + dx, center.y() - dy, center.z() );
993 mCameraPose.setCenterPoint( center );
994 updateCameraFromPose();
995}
996
998{
999 if ( event->key() == Qt::Key_QuoteLeft )
1000 return true;
1001
1002 switch ( mCameraNavigationMode )
1003 {
1005 {
1006 switch ( event->key() )
1007 {
1008 case Qt::Key_Left:
1009 case Qt::Key_A:
1010 case Qt::Key_Right:
1011 case Qt::Key_D:
1012 case Qt::Key_Up:
1013 case Qt::Key_W:
1014 case Qt::Key_Down:
1015 case Qt::Key_S:
1016 case Qt::Key_PageUp:
1017 case Qt::Key_E:
1018 case Qt::Key_PageDown:
1019 case Qt::Key_Q:
1020 return true;
1021
1022 case Qt::Key_Escape:
1023 if ( mCaptureFpsMouseMovements )
1024 return true;
1025 break;
1026
1027 default:
1028 break;
1029 }
1030 break;
1031 }
1032
1034 {
1035 switch ( event->key() )
1036 {
1037 case Qt::Key_Left:
1038 case Qt::Key_Right:
1039 case Qt::Key_PageUp:
1040 case Qt::Key_PageDown:
1041 return true;
1042
1043 default:
1044 break;
1045 }
1046 break;
1047 }
1048 }
1049 return false;
1050}
1051
1052void QgsCameraController::depthBufferCaptured( const QImage &depthImage )
1053{
1054 mDepthBufferImage = depthImage;
1055 mDepthBufferIsReady = true;
1056 mDepthBufferNonVoidAverage = -1;
1057
1058 if ( mCurrentOperation == MouseOperation::ZoomWheel )
1059 {
1060 handleTerrainNavigationWheelZoom();
1061 }
1062}
1063
1064bool QgsCameraController::isATranslationRotationSequence( MouseOperation newOperation ) const
1065{
1066 return std::find( mTranslateOrRotate.begin(), mTranslateOrRotate.end(), newOperation ) != std::end( mTranslateOrRotate ) && std::find( mTranslateOrRotate.begin(), mTranslateOrRotate.end(), mCurrentOperation ) != std::end( mTranslateOrRotate );
1067}
1068
1069void QgsCameraController::setMouseParameters( const MouseOperation &newOperation, const QPoint &clickPoint )
1070{
1071 if ( newOperation == mCurrentOperation )
1072 {
1073 return;
1074 }
1075
1076 if ( newOperation == MouseOperation::None )
1077 {
1078 mClickPoint = QPoint();
1079 }
1080 // click point and rotation angles are updated if:
1081 // - it has never been computed
1082 // - the current and new operations are both rotation and translation
1083 // Indeed, if the sequence such as rotation -> zoom -> rotation updating mClickPoint on
1084 // the click point does not need to be updated because the relative mouse position is kept
1085 // during a zoom operation
1086 else if ( mClickPoint.isNull() || isATranslationRotationSequence( newOperation ) )
1087 {
1088 mClickPoint = clickPoint;
1089 mRotationPitch = mCameraPose.pitchAngle();
1090 mRotationYaw = mCameraPose.headingAngle();
1091 }
1092 mCurrentOperation = newOperation;
1093 mDepthBufferIsReady = false;
1094 mRotationCenterCalculated = false;
1095 mDragPointCalculated = false;
1096 mZoomPointCalculated = false;
1097
1098 if ( mCurrentOperation != MouseOperation::None && mCurrentOperation != MouseOperation::RotationCamera )
1099 {
1101
1102 mCameraBefore->setProjectionMatrix( mCamera->projectionMatrix() );
1103 mCameraBefore->setNearPlane( mCamera->nearPlane() );
1104 mCameraBefore->setFarPlane( mCamera->farPlane() );
1105 mCameraBefore->setAspectRatio( mCamera->aspectRatio() );
1106 mCameraBefore->setFieldOfView( mCamera->fieldOfView() );
1107 mCameraPose.updateCamera( mCameraBefore.get() );
1108 }
1109}
1110
1112{
1113 QgsVector3D diff = origin - mOrigin;
1114 mCameraPose.setCenterPoint( mCameraPose.centerPoint() - diff );
1115
1116 // update other members that depend on world coordinates
1117 mCameraBefore->setPosition( ( QgsVector3D( mCameraBefore->position() ) - diff ).toVector3D() );
1118 mCameraBefore->setViewCenter( ( QgsVector3D( mCameraBefore->viewCenter() ) - diff ).toVector3D() );
1119 mDragPoint = ( QgsVector3D( mDragPoint ) - diff ).toVector3D();
1120 mRotationCenter = ( QgsVector3D( mRotationCenter ) - diff ).toVector3D();
1121
1122 mOrigin = origin;
1123
1124 updateCameraFromPose();
1125}
VerticalAxisInversion
Vertical axis inversion options for 3D views.
Definition qgis.h:4013
@ 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.
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)
QgsRectangle extent() const
Returns the 3D scene's 2D extent in the 3D scene's CRS.
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 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 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 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 zoom(float factor)
Zoom the map by factor.
void rotateAroundViewCenter(float deltaYaw)
Rotate clockwise the view by deltaYaw around the view center (camera moves)
void walkView(double tx, double ty, double tz)
Walks into the map by tx, ty, and tz.
void 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.
float distanceFromCenterPoint() const
Returns distance of the camera from the center point.
void setPitchAngle(float pitch)
Sets pitch angle in degrees.
void setCenterPoint(const QgsVector3D &point)
Sets center point (towards which point the camera is looking)
void setHeadingAngle(float heading)
Sets heading (yaw) angle in degrees.
void setDistanceFromCenterPoint(float distance)
Sets distance of the camera from the center point.
void updateCamera(Qt3DRender::QCamera *camera)
Update Qt3D camera view matrix based on the pose.
A 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 set(double x, double y, double z)
Sets vector coordinates.
Definition qgsvector3d.h:73
QWindow * window()
Returns the internal 3D window where all the rendered output is displayed.
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:41