33#include <QDomDocument>
36#include <QStringLiteral>
38#include <Qt3DRender/QCamera>
40#include "moc_qgscameracontroller.cpp"
42using namespace Qt::StringLiterals;
47 , mCamera( scene->engine()->
camera() )
49 , mMouseHandler( new
Qt3DInput::QMouseHandler )
50 , mKeyboardHandler( new
Qt3DInput::QKeyboardHandler )
51 , mOrigin( scene->mapSettings()->
origin() )
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 );
61 connect(
this, &Qt3DCore::QEntity::enabledChanged, mMouseHandler, &Qt3DInput::QMouseHandler::setEnabled );
62 connect(
this, &Qt3DCore::QEntity::enabledChanged, mKeyboardHandler, &Qt3DInput::QKeyboardHandler::setEnabled );
64 mFpsNavTimer =
new QTimer(
this );
65 mFpsNavTimer->setInterval( 10 );
66 connect( mFpsNavTimer, &QTimer::timeout,
this, &QgsCameraController::applyFlyModeKeyMovements );
67 mFpsNavTimer->start();
72 mGlobeCrsToLatLon =
QgsCoordinateTransform( mScene->mapSettings()->crs(), mScene->mapSettings()->crs().toGeographicCrs(), mScene->mapSettings()->transformContext() );
78QWindow *QgsCameraController::window()
const
81 return windowEngine ? windowEngine->
window() :
nullptr;
86 if ( navigationMode == mCameraNavigationMode )
89 mCameraNavigationMode = navigationMode;
90 mIgnoreNextMouseMove =
true;
96 if ( movementSpeed == mCameraMovementSpeed )
101 mCameraMovementSpeed = std::clamp( movementSpeed, 0.05, 150.0 );
107 mVerticalAxisInversion = inversion;
112 float newPitch = mCameraPose.pitchAngle() + diffPitch;
113 float newHeading = mCameraPose.headingAngle() + diffHeading;
115 newPitch = std::clamp( newPitch, 0.f, 180.f );
117 switch ( mScene->mapSettings()->sceneMode() )
128 viewCenterLatLon = mGlobeCrsToLatLon.transform( mCameraPose.centerPoint() + mOrigin );
132 QgsDebugError( u
"rotateCamera: ECEF -> lat,lon transform failed!"_s );
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();
140 mCameraPose.setCenterPoint( mCamera->position() + newCameraToCenter );
141 mCameraPose.setPitchAngle( newPitch );
142 mCameraPose.setHeadingAngle( newHeading );
143 updateCameraFromPose();
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();
162 const float oldPitch = mCameraPose.pitchAngle();
163 const float oldHeading = mCameraPose.headingAngle();
165 newPitch = std::clamp( newPitch, 0.f, 180.f );
171 const QQuaternion q = qNew * qOld.conjugated();
173 const QVector3D newViewCenter = q * ( mCamera->viewCenter() - pivotPoint ) + pivotPoint;
175 mCameraPose.setCenterPoint( newViewCenter );
176 mCameraPose.setPitchAngle( newPitch );
177 mCameraPose.setHeadingAngle( newHeading );
178 updateCameraFromPose();
184 QVector3D newCamPosition = pivotPoint + ( oldCameraPosition - pivotPoint ) * zoomFactor;
185 double newDistance = mCameraPose.distanceFromCenterPoint() * zoomFactor;
189 QVector3D cameraToCenter = q * QVector3D( 0, 0, -newDistance );
190 QVector3D newViewCenter = newCamPosition + cameraToCenter;
192 mCameraPose.setDistanceFromCenterPoint( newDistance );
193 mCameraPose.setCenterPoint( newViewCenter );
194 updateCameraFromPose();
200 QVector3D newCamPosition = pivotPoint + ( oldCameraPosition - pivotPoint ) * zoomFactor;
201 const double newDistance = oldDistanceFromCenterPoint * zoomFactor;
205 QVector3D cameraToCenter = q * QVector3D( 0, 0, -newDistance );
206 QVector3D newViewCenter = newCamPosition + cameraToCenter;
208 mCameraPose.setDistanceFromCenterPoint( newDistance );
209 mCameraPose.setCenterPoint( newViewCenter );
210 updateCameraFromPose();
218 updateOrthographicProjectionPlane();
220 if ( mCameraChanged )
223 mCameraChanged =
false;
229 QgsPointXY extentCenter = mScene->mapSettings()->extent().center();
238 QgsDebugError( u
"setViewFromTop() should not be used with globe!"_s );
243 QgsTerrainEntity *terrain = mScene->terrainEntity();
244 const float terrainElevationOffset = terrain ? terrain->terrainElevationOffset() : 0.0f;
250 mCamera->setNearPlane(
distance / 2 );
251 mCamera->setFarPlane(
distance * 2 );
258 return mCameraPose.centerPoint();
283 if ( camPose == mCameraPose && !force )
286 mCameraPose = camPose;
287 updateCameraFromPose();
292 QDomElement elemCamera = doc.createElement( u
"camera"_s );
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() );
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();
312 if ( elem.hasAttribute(
"xMap" ) )
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();
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;
331double QgsCameraController::sampleDepthBuffer(
int px,
int py )
333 if ( !mDepthBufferIsReady )
335 QgsDebugError( u
"Asked to sample depth buffer, but depth buffer not ready!"_s );
340 if ( QWindow *win = window() )
344 px =
static_cast<int>( px * win->devicePixelRatio() );
345 py =
static_cast<int>( py * win->devicePixelRatio() );
349 for (
int x = px - 3; x <= px + 3; ++x )
351 for (
int y = py - 3; y <= py + 3; ++y )
353 if ( mDepthBufferImage.valid( x, y ) )
362double QgsCameraController::depthBufferNonVoidAverage()
365 if ( mDepthBufferNonVoidAverage != -1 )
366 return mDepthBufferNonVoidAverage;
370 int samplesCount = 0;
372 Q_ASSERT( mDepthBufferImage.format() == QImage::Format_RGB32 );
373 for (
int y = 0; y < mDepthBufferImage.height(); ++y )
375 const QRgb *line =
reinterpret_cast<const QRgb *
>( mDepthBufferImage.constScanLine( y ) );
376 for (
int x = 0; x < mDepthBufferImage.width(); ++x )
388 if ( samplesCount == 0 )
391 depth /= samplesCount;
393 mDepthBufferNonVoidAverage = depth;
398QgsVector3D QgsCameraController::moveGeocentricPoint(
const QgsVector3D &point,
double latDiff,
double lonDiff )
402 QgsVector3D pointLatLon = mGlobeCrsToLatLon.transform( point );
403 pointLatLon.
setX( pointLatLon.
x() + lonDiff );
404 pointLatLon.
setY( std::clamp( pointLatLon.
y() + latDiff, -90., 90. ) );
408 catch (
const QgsCsException & )
410 QgsDebugError( u
"moveGeocentricPoint: transform failed!"_s );
417 const QgsVector3D viewCenter = mCameraPose.centerPoint() + mOrigin;
418 const QgsVector3D newViewCenter = moveGeocentricPoint( viewCenter, latDiff, lonDiff );
419 mCameraPose.setCenterPoint( newViewCenter - mOrigin );
420 updateCameraFromPose();
425 mCameraPose.setDistanceFromCenterPoint( mCameraPose.distanceFromCenterPoint() * factor );
426 updateCameraFromPose();
431 mCameraPose.setPitchAngle( std::clamp( mCameraPose.pitchAngle() + angleDiff, 0.f, 90.f ) );
432 updateCameraFromPose();
437 mCameraPose.setHeadingAngle( mCameraPose.headingAngle() + angleDiff );
438 updateCameraFromPose();
460void QgsCameraController::updateOrthographicProjectionPlane()
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() );
473void QgsCameraController::updateCameraFromPose()
479 const QgsVector3D viewCenter = mCameraPose.centerPoint() + mOrigin;
481 QgsVector3D viewCenterLatLon;
484 viewCenterLatLon = mGlobeCrsToLatLon.transform( viewCenter );
486 catch (
const QgsCsException & )
488 QgsDebugError( u
"updateCameraFromPose: transform failed!"_s );
492 mCameraPose.updateCameraGlobe( mCamera, viewCenterLatLon.
y(), viewCenterLatLon.
x() );
496 mCameraPose.updateCamera( mCamera );
499 updateOrthographicProjectionPlane();
501 mCameraChanged =
true;
507 mCameraPose.setCenterPoint( mCameraPose.centerPoint() + posDiff );
508 updateCameraFromPose();
511void QgsCameraController::onPositionChanged( Qt3DInput::QMouseEvent *mouse )
513 if ( !mInputHandlersEnabled )
516 QgsEventTracing::ScopedEvent traceEvent( u
"3D"_s, u
"QgsCameraController::onPositionChanged"_s );
518 switch ( mCameraNavigationMode )
521 onPositionChangedTerrainNavigation( mouse );
525 onPositionChangedFlyNavigation( mouse );
529 onPositionChangedGlobeTerrainNavigation( mouse );
534bool QgsCameraController::screenPointToWorldPos( QPoint position,
double &depth, QVector3D &worldPosition )
536 depth = sampleDepthBuffer( position.x(), position.y() );
542 depth = depthBufferNonVoidAverage();
546 if ( !std::isfinite( worldPosition.x() ) || !std::isfinite( worldPosition.y() ) || !std::isfinite( worldPosition.z() ) )
548 QgsDebugMsgLevel( u
"screenPointToWorldPos: position is NaN or Inf. This should not happen."_s, 2 );
555void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseEvent *mouse )
557 if ( mIgnoreNextMouseMove )
559 mIgnoreNextMouseMove =
false;
560 mMousePos = QPoint( mouse->x(), mouse->y() );
564 const int dx = mouse->x() - mMousePos.x();
565 const int dy = mouse->y() - mMousePos.y();
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 );
573 if ( ( hasLeftButton && hasShift && !hasCtrl ) || ( hasMiddleButton && !hasShift && !hasCtrl ) )
576 setMouseParameters( MouseOperation::RotationCenter, mMousePos );
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;
582 if ( !mDepthBufferIsReady )
585 if ( !mRotationCenterCalculated )
588 QVector3D worldPosition;
589 if ( screenPointToWorldPos( mClickPoint, depth, worldPosition ) )
591 mRotationCenter = worldPosition;
592 mRotationDistanceFromCenter = ( mRotationCenter - mCameraBefore->position() ).length();
594 mRotationCenterCalculated =
true;
600 else if ( hasLeftButton && hasCtrl && !hasShift )
602 setMouseParameters( MouseOperation::RotationCamera );
604 const float diffPitch = 0.2f * dy;
605 const float diffYaw = -0.2f * dx;
608 else if ( hasLeftButton && !hasShift && !hasCtrl )
611 setMouseParameters( MouseOperation::Translation, mMousePos );
613 if ( !mDepthBufferIsReady )
616 if ( !mDragPointCalculated )
619 QVector3D worldPosition;
620 if ( screenPointToWorldPos( mClickPoint, depth, worldPosition ) )
623 mDragPoint = worldPosition;
624 mDragPointCalculated =
true;
628 QVector3D cameraBeforeDragPos = mCameraBefore->position();
631 QVector3D shiftVector;
634 float angle = std::fabs( std::acos( QVector3D::dotProduct( QVector3D( 0, 0, 1 ), mCameraBefore->viewVector().normalized() ) ) - M_PI / 2 );
635 bool changeAltitude =
false;
637 switch ( mScene->mapSettings()->projectionType() )
639 case Qt3DRender::QCameraLens::PerspectiveProjection:
640 changeAltitude =
angle < M_PI / 30;
642 case Qt3DRender::QCameraLens::OrthographicProjection:
643 changeAltitude =
angle < M_PI / 3;
649 if ( changeAltitude )
650 shiftVector = mDragPoint - moveToPosition;
652 switch ( mScene->mapSettings()->projectionType() )
654 case Qt3DRender::QCameraLens::OrthographicProjection:
659 shiftVector = { mDragPoint.x() - moveToPosition.x(), mDragPoint.y() - moveToPosition.y(), 0 };
662 case Qt3DRender::QCameraLens::PerspectiveProjection:
664 QVector3D cameraBeforeToMoveToPos = ( moveToPosition - mCameraBefore->position() ).normalized();
665 QVector3D cameraBeforeToDragPointPos = ( mDragPoint - mCameraBefore->position() ).normalized();
668 if ( cameraBeforeToMoveToPos.z() == 0 )
670 cameraBeforeToMoveToPos.setZ( 0.01 );
671 cameraBeforeToMoveToPos = cameraBeforeToMoveToPos.normalized();
674 if ( cameraBeforeToDragPointPos.z() == 0 )
676 cameraBeforeToDragPointPos.setZ( 0.01 );
677 cameraBeforeToDragPointPos = cameraBeforeToDragPointPos.normalized();
680 float d1 = ( mDragPoint.z() - cameraBeforeDragPos.z() ) / cameraBeforeToMoveToPos.z();
681 float d2 = ( mDragPoint.z() - cameraBeforeDragPos.z() ) / cameraBeforeToDragPointPos.z();
683 QVector3D from = cameraBeforeDragPos + d1 * cameraBeforeToMoveToPos;
684 QVector3D to = cameraBeforeDragPos + d2 * cameraBeforeToDragPointPos;
686 shiftVector = to - from;
693 mCameraPose.setCenterPoint( mCameraBefore->viewCenter() + shiftVector );
694 updateCameraFromPose();
696 else if ( hasLeftButton && hasShift && hasCtrl )
699 QgsVector3D center = mCameraPose.centerPoint();
700 double tElev = mMousePos.y() - mouse->y();
701 center.
set( center.
x(), center.
y(), center.
z() + tElev * 0.5 );
702 mCameraPose.setCenterPoint( center );
703 updateCameraFromPose();
705 else if ( hasRightButton && !hasShift && !hasCtrl )
707 setMouseParameters( MouseOperation::Zoom, mMousePos );
708 if ( !mDepthBufferIsReady )
711 if ( !mDragPointCalculated )
714 QVector3D worldPosition;
715 if ( screenPointToWorldPos( mClickPoint, depth, worldPosition ) )
717 mDragPoint = worldPosition;
718 mDragPointCalculated =
true;
722 const double oldDist = ( QgsVector3D( mCameraBefore->position() ) - QgsVector3D( mDragPoint ) ).length();
723 double newDist = oldDist;
726 int screenHeight = mScene->engine()->size().height();
727 QWindow *win = window();
730 yOffset = win->mapToGlobal( QPoint( 0, 0 ) ).y();
731 screenHeight = win->screen()->virtualSize().height();
735 if ( mMousePos.y() > mClickPoint.y() )
737 double f = ( double ) ( mMousePos.y() - mClickPoint.y() ) / ( double ) ( screenHeight - mClickPoint.y() - yOffset );
738 f = std::max( 0.0, std::min( 1.0, f ) );
739 f = 1 - ( std::expm1( -2 * f ) ) / ( std::expm1( -2 ) );
740 newDist = newDist * f;
744 double f = 1 - ( double ) ( mMousePos.y() + yOffset ) / ( double ) ( mClickPoint.y() + yOffset );
745 f = std::max( 0.0, std::min( 1.0, f ) );
746 f = ( std::expm1( 2 * f ) ) / ( std::expm1( 2 ) );
747 newDist = newDist + 2 * newDist * f;
750 const double zoomFactor = newDist / oldDist;
751 zoomCameraAroundPivot( mCameraBefore->position(), mCameraBefore->viewCenter().distanceToPoint( mCameraBefore->position() ), zoomFactor, mDragPoint );
754 mMousePos = QPoint( mouse->x(), mouse->y() );
757void QgsCameraController::onPositionChangedGlobeTerrainNavigation( Qt3DInput::QMouseEvent *mouse )
759 const bool hasShift = ( mouse->modifiers() & Qt3DInput::QMouseEvent::Modifiers::ShiftModifier );
760 const bool hasCtrl = ( mouse->modifiers() & Qt3DInput::QMouseEvent::Modifiers::ControlModifier );
761 const bool hasLeftButton = ( mouse->buttons() & Qt::LeftButton );
762 const bool hasMiddleButton = ( mouse->buttons() & Qt::MiddleButton );
764 if ( ( hasLeftButton && hasShift && !hasCtrl ) || ( hasMiddleButton && !hasShift && !hasCtrl ) )
766 setMouseParameters( MouseOperation::RotationCenter, mMousePos );
768 const float scale =
static_cast<float>( std::max( mScene->engine()->size().width(), mScene->engine()->size().height() ) );
769 const float pitchDiff = 180.0f *
static_cast<float>( mouse->y() - mClickPoint.y() ) / scale;
770 const float yawDiff = -180.0f *
static_cast<float>( mouse->x() - mClickPoint.x() ) / scale;
772 mCameraPose.setPitchAngle( mRotationPitch + pitchDiff );
773 mCameraPose.setHeadingAngle( mRotationYaw + yawDiff );
774 updateCameraFromPose();
778 if ( !( mouse->buttons() & Qt::LeftButton ) )
782 setMouseParameters( MouseOperation::Translation, mMousePos );
784 if ( !mDepthBufferIsReady )
787 if ( !mDragPointCalculated )
790 QVector3D worldPosition;
791 if ( !screenPointToWorldPos( mClickPoint, depth, worldPosition ) )
795 mDragPoint = worldPosition;
796 mDragPointCalculated =
true;
801 const QgsVector3D startPosMap = QgsVector3D( mDragPoint ) + mOrigin;
802 const double sphereRadiusMap = startPosMap.
length();
805 const QgsVector3D rayOriginMap = QgsVector3D( ray.
origin() ) + mOrigin;
809 const double quadC =
QgsVector3D::dotProduct( rayOriginMap, rayOriginMap ) - sphereRadiusMap * sphereRadiusMap;
810 const double disc = quadB * quadB - 4 * quadA * quadC;
815 const double rayDistMap = ( -quadB - sqrt( disc ) ) / ( 2 * quadA );
816 if ( rayDistMap < 0 )
818 QgsDebugError( u
"Sphere intersection result negative, canceling move"_s );
821 const QgsVector3D newPosMap = rayOriginMap + QgsVector3D( ray.
direction() ) * rayDistMap;
826 QgsVector3D oldLatLon, newLatLon;
829 oldLatLon = mGlobeCrsToLatLon.transform( startPosMap );
830 newLatLon = mGlobeCrsToLatLon.transform( newPosMap );
832 catch (
const QgsCsException & )
834 QgsDebugError( u
"onPositionChangedGlobeTerrainNavigation: transform failed!"_s );
838 const double latDiff = oldLatLon.
y() - newLatLon.
y();
839 const double lonDiff = oldLatLon.
x() - newLatLon.
x();
841 const QgsVector3D newVC = moveGeocentricPoint( mMousePressViewCenter, latDiff, lonDiff );
842 const QgsVector3D newVCWorld = newVC - mOrigin;
844 mCameraPose.setCenterPoint( newVCWorld );
845 updateCameraFromPose();
852 float dist = mCameraPose.distanceFromCenterPoint();
853 dist -= dist * factor * 0.01f;
854 mCameraPose.setDistanceFromCenterPoint( dist );
855 updateCameraFromPose();
858void QgsCameraController::handleTerrainNavigationWheelZoom()
860 if ( !mDepthBufferIsReady )
863 if ( !mZoomPointCalculated )
866 QVector3D worldPosition;
867 if ( screenPointToWorldPos( mMousePos, depth, worldPosition ) )
869 mZoomPoint = worldPosition;
870 mZoomPointCalculated =
true;
874 double oldDist = ( mZoomPoint - mCameraBefore->position() ).length();
876 double newDist = std::pow( 0.8, mCumulatedWheelY ) * oldDist;
878 newDist = std::max( newDist, 2.0 );
879 double zoomFactor = newDist / oldDist;
881 zoomFactor = std::clamp( zoomFactor, 0.01, 100.0 );
883 zoomCameraAroundPivot( mCameraBefore->position(), mCameraBefore->viewCenter().distanceToPoint( mCameraBefore->position() ), zoomFactor, mZoomPoint );
885 mCumulatedWheelY = 0;
886 setMouseParameters( MouseOperation::None );
889void QgsCameraController::onWheel( Qt3DInput::QWheelEvent *wheel )
891 if ( !mInputHandlersEnabled )
894 switch ( mCameraNavigationMode )
898 const float scaling = ( ( wheel->modifiers() & Qt3DInput::QWheelEvent::Modifiers::ControlModifier ) != 0 ? 0.1f : 1.0f ) / 1000.f;
899 setCameraMovementSpeed( mCameraMovementSpeed + mCameraMovementSpeed * scaling * wheel->angleDelta().y() );
907 const double scaling = ( 1.0 / 120.0 ) * ( ( wheel->modifiers() & Qt3DInput::QWheelEvent::Modifiers::ControlModifier ) != 0 ? 0.1 : 1.0 );
911 mCumulatedWheelY += scaling * wheel->angleDelta().y();
913 if ( mCurrentOperation != MouseOperation::ZoomWheel )
915 setMouseParameters( MouseOperation::ZoomWheel );
920 handleTerrainNavigationWheelZoom();
927 float wheelAmount =
static_cast<float>( wheel->angleDelta().y() );
928 float factor = abs( wheelAmount ) / 1000.f;
929 float mulFactor = wheelAmount > 0 ? ( 1 - factor ) : ( 1 + factor );
930 mCameraPose.setDistanceFromCenterPoint( mCameraPose.distanceFromCenterPoint() * mulFactor );
931 updateCameraFromPose();
937void QgsCameraController::onMousePressed( Qt3DInput::QMouseEvent *mouse )
939 if ( !mInputHandlersEnabled )
942 mKeyboardHandler->setFocus(
true );
944 if ( mouse->button() == Qt3DInput::QMouseEvent::MiddleButton
945 || ( ( mouse->modifiers() & Qt3DInput::QMouseEvent::Modifiers::ShiftModifier ) != 0 && mouse->button() == Qt3DInput::QMouseEvent::LeftButton )
946 || ( ( mouse->modifiers() & Qt3DInput::QMouseEvent::Modifiers::ControlModifier ) != 0 && mouse->button() == Qt3DInput::QMouseEvent::LeftButton ) )
948 mMousePos = QPoint( mouse->x(), mouse->y() );
950 if ( mCaptureFpsMouseMovements )
951 mIgnoreNextMouseMove =
true;
953 const MouseOperation operation {
954 ( mouse->modifiers() & Qt3DInput::QMouseEvent::Modifiers::ControlModifier ) != 0 && mouse->button() == Qt3DInput::QMouseEvent::LeftButton ? MouseOperation::RotationCamera
955 : MouseOperation::RotationCenter
957 setMouseParameters( operation, mMousePos );
960 else if ( mouse->button() == Qt3DInput::QMouseEvent::LeftButton || mouse->button() == Qt3DInput::QMouseEvent::RightButton )
962 mMousePos = QPoint( mouse->x(), mouse->y() );
964 if ( mCaptureFpsMouseMovements )
965 mIgnoreNextMouseMove =
true;
967 const MouseOperation operation = ( mouse->button() == Qt3DInput::QMouseEvent::LeftButton ) ? MouseOperation::Translation : MouseOperation::Zoom;
968 setMouseParameters( operation, mMousePos );
972void QgsCameraController::onMouseReleased( Qt3DInput::QMouseEvent *mouse )
975 if ( !mInputHandlersEnabled )
979 setMouseParameters( MouseOperation::None );
982bool QgsCameraController::onKeyPressedTerrainNavigation( QKeyEvent *event )
984 const bool hasShift = (
event->modifiers() & Qt::ShiftModifier );
985 const bool hasCtrl = (
event->modifiers() & Qt::ControlModifier );
987 int tx = 0, ty = 0, tElev = 0;
988 switch ( event->key() )
1004 case Qt::Key_PageDown:
1007 case Qt::Key_PageUp:
1016 if ( !hasShift && !hasCtrl )
1020 else if ( hasShift && !hasCtrl )
1026 else if ( hasCtrl && !hasShift )
1029 const float diffPitch = ty;
1030 const float diffYaw = -tx;
1038 QgsVector3D center = mCameraPose.centerPoint();
1039 center.
set( center.
x(), center.
y(), center.
z() + tElev * 10 );
1040 mCameraPose.setCenterPoint( center );
1047bool QgsCameraController::onKeyPressedGlobeTerrainNavigation( QKeyEvent *event )
1051 constexpr float MOVE_FACTOR = 0.000001f;
1052 constexpr float ZOOM_FACTOR = 0.9f;
1054 const bool hasShift = (
event->modifiers() & Qt::ShiftModifier );
1056 switch ( event->key() )
1082 case Qt::Key_PageDown:
1085 case Qt::Key_PageUp:
1094static const QSet<int> walkNavigationSavedKeys = {
1109bool QgsCameraController::onKeyPressedFlyNavigation( QKeyEvent *event )
1111 switch ( event->key() )
1113 case Qt::Key_QuoteLeft:
1116 mCaptureFpsMouseMovements = !mCaptureFpsMouseMovements;
1117 mIgnoreNextMouseMove =
true;
1118 if ( mCaptureFpsMouseMovements )
1120 qApp->setOverrideCursor( QCursor( Qt::BlankCursor ) );
1124 qApp->restoreOverrideCursor();
1130 case Qt::Key_Escape:
1133 if ( mCaptureFpsMouseMovements )
1135 mCaptureFpsMouseMovements =
false;
1136 mIgnoreNextMouseMove =
true;
1137 qApp->restoreOverrideCursor();
1147 if ( walkNavigationSavedKeys.contains( event->key() ) )
1149 if ( !event->isAutoRepeat() )
1151 mDepressedKeys.insert( event->key() );
1161 const QVector3D cameraUp = mCamera->upVector().normalized();
1162 const QVector3D cameraFront = ( QVector3D( mCameraPose.centerPoint().x(), mCameraPose.centerPoint().y(), mCameraPose.centerPoint().z() ) - mCamera->position() ).normalized();
1163 const QVector3D cameraLeft = QVector3D::crossProduct( cameraUp, cameraFront );
1165 QVector3D cameraPosDiff( 0.0f, 0.0f, 0.0f );
1169 cameraPosDiff +=
static_cast<float>( tx ) * cameraFront;
1173 cameraPosDiff +=
static_cast<float>( ty ) * cameraLeft;
1177 cameraPosDiff +=
static_cast<float>( tz ) * QVector3D( 0.0f, 0.0f, 1.0f );
1183void QgsCameraController::applyFlyModeKeyMovements()
1189 const bool shiftPressed = mDepressedKeys.contains( Qt::Key_Shift );
1190 const bool ctrlPressed = mDepressedKeys.contains( Qt::Key_Control );
1192 const double movementSpeed = mCameraMovementSpeed * ( shiftPressed ? 2 : 1 ) * ( ctrlPressed ? 0.1 : 1 );
1194 bool changed =
false;
1198 if ( mDepressedKeys.contains( Qt::Key_Left ) || mDepressedKeys.contains( Qt::Key_A ) )
1204 if ( mDepressedKeys.contains( Qt::Key_Right ) || mDepressedKeys.contains( Qt::Key_D ) )
1210 if ( mDepressedKeys.contains( Qt::Key_Up ) || mDepressedKeys.contains( Qt::Key_W ) )
1216 if ( mDepressedKeys.contains( Qt::Key_Down ) || mDepressedKeys.contains( Qt::Key_S ) )
1224 static constexpr double ELEVATION_MOVEMENT_SCALE = 0.5;
1225 if ( mDepressedKeys.contains( Qt::Key_PageUp ) || mDepressedKeys.contains( Qt::Key_E ) )
1228 z += ELEVATION_MOVEMENT_SCALE * movementSpeed;
1231 if ( mDepressedKeys.contains( Qt::Key_PageDown ) || mDepressedKeys.contains( Qt::Key_Q ) )
1234 z -= ELEVATION_MOVEMENT_SCALE * movementSpeed;
1241void QgsCameraController::onPositionChangedFlyNavigation( Qt3DInput::QMouseEvent *mouse )
1243 const bool hasMiddleButton = ( mouse->buttons() & Qt::MiddleButton );
1244 const bool hasRightButton = ( mouse->buttons() & Qt::RightButton );
1246 const double dx = mCaptureFpsMouseMovements ? QCursor::pos().x() - mMousePos.x() : mouse->x() - mMousePos.x();
1247 const double dy = mCaptureFpsMouseMovements ? QCursor::pos().y() - mMousePos.y() : mouse->y() - mMousePos.y();
1248 mMousePos = mCaptureFpsMouseMovements ? QCursor::pos() : QPoint( mouse->x(), mouse->y() );
1250 if ( mIgnoreNextMouseMove )
1252 mIgnoreNextMouseMove =
false;
1256 if ( hasMiddleButton )
1259 const QVector3D cameraUp = mCamera->upVector().normalized();
1260 const QVector3D cameraFront = ( QVector3D( mCameraPose.centerPoint().x(), mCameraPose.centerPoint().y(), mCameraPose.centerPoint().z() ) - mCamera->position() ).normalized();
1261 const QVector3D cameraLeft = QVector3D::crossProduct( cameraUp, cameraFront );
1262 const QVector3D cameraPosDiff = -dx * cameraLeft - dy * cameraUp;
1263 moveCenterPoint(
static_cast<float>( mCameraMovementSpeed ) * cameraPosDiff / 10.0 );
1265 else if ( hasRightButton )
1268 const QVector3D cameraFront = ( QVector3D( mCameraPose.centerPoint().x(), mCameraPose.centerPoint().y(), mCameraPose.centerPoint().z() ) - mCamera->position() ).normalized();
1269 const QVector3D cameraPosDiff = dy * cameraFront;
1270 moveCenterPoint(
static_cast<float>( mCameraMovementSpeed ) * cameraPosDiff / 5.0 );
1274 if ( mCaptureFpsMouseMovements )
1276 float diffPitch = -0.2f * dy;
1277 switch ( mVerticalAxisInversion )
1288 const float diffYaw = -0.2f * dx;
1291 else if ( mouse->buttons() & Qt::LeftButton )
1293 float diffPitch = -0.2f * dy;
1294 switch ( mVerticalAxisInversion )
1304 const float diffYaw = -0.2f * dx;
1309 if ( mCaptureFpsMouseMovements )
1311 mIgnoreNextMouseMove =
true;
1314 emit
setCursorPosition( QPoint( mScene->engine()->size().width() / 2, mScene->engine()->size().height() / 2 ) );
1321 float pitch = mCameraPose.pitchAngle();
1322 pitch -= deltaPitch;
1323 mCameraPose.setPitchAngle(
pitch );
1324 updateCameraFromPose();
1330 float yaw = mCameraPose.headingAngle();
1332 mCameraPose.setHeadingAngle(
yaw );
1333 updateCameraFromPose();
1338 mCameraPose.setHeadingAngle( angle );
1339 updateCameraFromPose();
1344 const float yaw = mCameraPose.headingAngle();
1345 const float dist = mCameraPose.distanceFromCenterPoint();
1346 const float x = tx * dist * 0.02f;
1347 const float y = -ty * dist * 0.02f;
1350 const float t = sqrt( x * x + y * y );
1351 const float a = atan2( y, x ) -
yaw * M_PI / 180;
1352 const float dx = cos( a ) * t;
1353 const float dy = sin( a ) * t;
1356 center.
set( center.
x() + dx, center.
y() - dy, center.
z() );
1357 mCameraPose.setCenterPoint( center );
1358 updateCameraFromPose();
1363 if ( !mInputHandlersEnabled )
1366 if ( event->type() == QKeyEvent::Type::KeyRelease )
1368 if ( !event->isAutoRepeat() && mDepressedKeys.contains( event->key() ) )
1370 mDepressedKeys.remove( event->key() );
1374 else if ( event->type() == QEvent::ShortcutOverride )
1376 if ( event->modifiers() & Qt::ControlModifier )
1378 switch ( event->key() )
1380 case Qt::Key_QuoteLeft:
1383 switch ( mCameraNavigationMode )
1431 switch ( mCameraNavigationMode )
1434 return onKeyPressedFlyNavigation( event );
1437 return onKeyPressedTerrainNavigation( event );
1440 return onKeyPressedGlobeTerrainNavigation( event );
1448 mDepthBufferImage = depthImage;
1449 mDepthBufferIsReady =
true;
1450 mDepthBufferNonVoidAverage = -1;
1458 if ( mCurrentOperation == MouseOperation::ZoomWheel )
1460 handleTerrainNavigationWheelZoom();
1464bool QgsCameraController::isATranslationRotationSequence( MouseOperation newOperation )
const
1466 return std::find( mTranslateOrRotate.begin(), mTranslateOrRotate.end(), newOperation ) != std::end( mTranslateOrRotate )
1467 && std::find( mTranslateOrRotate.begin(), mTranslateOrRotate.end(), mCurrentOperation ) != std::end( mTranslateOrRotate );
1470void QgsCameraController::setMouseParameters(
const MouseOperation &newOperation,
const QPoint &clickPoint )
1472 if ( newOperation == mCurrentOperation )
1477 if ( newOperation == MouseOperation::None )
1479 mClickPoint = QPoint();
1487 else if ( mClickPoint.isNull() || isATranslationRotationSequence( newOperation ) )
1489 mClickPoint = clickPoint;
1490 mRotationPitch = mCameraPose.pitchAngle();
1491 mRotationYaw = mCameraPose.headingAngle();
1493 mCurrentOperation = newOperation;
1494 mDepthBufferIsReady =
false;
1495 mRotationCenterCalculated =
false;
1496 mDragPointCalculated =
false;
1497 mZoomPointCalculated =
false;
1499 if ( mCurrentOperation != MouseOperation::None && mCurrentOperation != MouseOperation::RotationCamera )
1501 mMousePressViewCenter = mCameraPose.centerPoint() + mOrigin;
1511 mCameraPose.setCenterPoint( mCameraPose.centerPoint() - diff );
1514 mCameraBefore->setPosition( (
QgsVector3D( mCameraBefore->position() ) - diff ).toVector3D() );
1515 mCameraBefore->setViewCenter( (
QgsVector3D( mCameraBefore->viewCenter() ) - diff ).toVector3D() );
1516 mDragPoint = (
QgsVector3D( mDragPoint ) - diff ).toVector3D();
1517 mRotationCenter = (
QgsVector3D( mRotationCenter ) - diff ).toVector3D();
1521 updateCameraFromPose();
1524void QgsCameraController::rotateToRespectingTerrain(
float pitch,
float yaw )
1527 double elevation = 0.0;
1531 QVector3D camPos = mCamera->position();
1536 const QList<QgsRayCastHit> results = mScene->
terrainEntity()->rayIntersection( ray, context );
1538 if ( !results.isEmpty() )
1540 elevation = results.constFirst().mapCoordinates().z() - mOrigin.z();
1541 QgsDebugMsgLevel( QString(
"Computed elevation from terrain: %1" ).arg( elevation ), 2 );
1548 pos.
set( pos.
x(), pos.
y(), elevation + mScene->terrainEntity()->terrainElevationOffset() );
1555 if ( !crossSection.
isValid() )
1560 const double width = crossSection.
halfWidth();
1562 const QgsVector3D startVec { startPoint.
x(), startPoint.
y(), 0 };
1565 QgsVector linePerpVec( ( endPoint - startPoint ).x(), ( endPoint - startPoint ).y() );
1568 const QgsVector3D linePerpVec3D( linePerpVec.
x(), linePerpVec.
y(), 0 );
1569 const QgsVector3D frontStartPoint( startVec + linePerpVec3D * width );
1570 const QgsVector3D frontEndPoint( endVec + linePerpVec3D * width );
VerticalAxisInversion
Vertical axis inversion options for 3D views.
@ 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.
@ TerrainBased
The default navigation based on the terrain.
@ Walk
Uses WASD keys or arrows to navigate in walking (first person) manner.
@ GlobeTerrainBased
Navigation similar to TerrainBased, but for use with globe.
@ Globe
Scene is represented as a globe using a geocentric CRS.
@ Local
Local scene based on a projected CRS.
@ Reverse
Reverse/inverse transform (from destination to source).
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.
Qt3DRender::QCameraLens::ProjectionType 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...
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.
void setVerticalAxisInversion(Qgis::VerticalAxisInversion inversion)
Sets the vertical axis inversion behavior.
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 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.
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.
Point geometry type, with support for z-dimension and m-values.
A representation of a ray in 3D.
QVector3D origin() const
Returns the origin of the ray.
QVector3D direction() const
Returns the direction of the ray see setDirection().
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...
double y() const
Returns Y coordinate.
double z() const
Returns Z coordinate.
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.
void setX(double x)
Sets X coordinate.
void set(double x, double y, double z)
Sets vector coordinates.
void setY(double y)
Sets Y coordinate.
double length() const
Returns the length of the vector.
Represent a 2-dimensional vector.
double y() const
Returns the vector's y-component.
QgsVector normalized() const
Returns the vector's normalized (or "unit") vector (ie same angle but length of 1....
QgsVector perpVector() const
Returns the perpendicular vector to this vector (rotated 90 degrees counter-clockwise).
double x() const
Returns the vector's x-component.
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)
#define QgsDebugError(str)