20 #include "qgssettings.h"
24 #include <QDomDocument>
25 #include <Qt3DRender/QCamera>
26 #include <Qt3DRender/QObjectPicker>
27 #include <Qt3DRender/QPickEvent>
34 , mMouseDevice( new
Qt3DInput::QMouseDevice() )
35 , mKeyboardDevice( new
Qt3DInput::QKeyboardDevice() )
36 , mMouseHandler( new
Qt3DInput::QMouseHandler )
37 , mKeyboardHandler( new
Qt3DInput::QKeyboardHandler )
39 mMouseHandler->setSourceDevice( mMouseDevice );
40 connect( mMouseHandler, &Qt3DInput::QMouseHandler::positionChanged,
41 this, &QgsCameraController::onPositionChanged );
42 connect( mMouseHandler, &Qt3DInput::QMouseHandler::wheel,
43 this, &QgsCameraController::onWheel );
44 connect( mMouseHandler, &Qt3DInput::QMouseHandler::pressed,
45 this, &QgsCameraController::onMousePressed );
46 connect( mMouseHandler, &Qt3DInput::QMouseHandler::released,
47 this, &QgsCameraController::onMouseReleased );
48 addComponent( mMouseHandler );
50 mKeyboardHandler->setSourceDevice( mKeyboardDevice );
51 connect( mKeyboardHandler, &Qt3DInput::QKeyboardHandler::pressed,
52 this, &QgsCameraController::onKeyPressed );
53 connect( mKeyboardHandler, &Qt3DInput::QKeyboardHandler::released,
54 this, &QgsCameraController::onKeyReleased );
55 addComponent( mKeyboardHandler );
58 connect(
this, &Qt3DCore::QEntity::enabledChanged,
59 mMouseHandler, &Qt3DInput::QMouseHandler::setEnabled );
60 connect(
this, &Qt3DCore::QEntity::enabledChanged,
61 mKeyboardHandler, &Qt3DInput::QMouseHandler::setEnabled );
63 mFpsNavTimer =
new QTimer(
this );
64 mFpsNavTimer->setInterval( 10 );
65 connect( mFpsNavTimer, &QTimer::timeout,
this, &QgsCameraController::applyFlyModeKeyMovements );
66 mFpsNavTimer->start();
71 if ( navigationMode == mCameraNavigationMode )
74 mCameraNavigationMode = navigationMode;
75 mIgnoreNextMouseMove =
true;
80 if ( movementSpeed == mCameraMovementSpeed )
83 mCameraMovementSpeed = movementSpeed;
89 mVerticalAxisInversion = inversion;
97 connect( te->terrainPicker(), &Qt3DRender::QObjectPicker::pressed,
this, &QgsCameraController::onPickerMousePressed );
123 static QVector3D unproject( QVector3D v,
const QMatrix4x4 &modelView,
const QMatrix4x4 &projection, QRect viewport )
130 QMatrix4x4 inverse = QMatrix4x4( projection * modelView ).inverted();
132 QVector4D tmp( v, 1.0f );
133 tmp.setX( ( tmp.x() -
float( viewport.x() ) ) /
float( viewport.width() ) );
134 tmp.setY( ( tmp.y() -
float( viewport.y() ) ) /
float( viewport.height() ) );
135 tmp = tmp * 2.0f - QVector4D( 1.0f, 1.0f, 1.0f, 1.0f );
137 QVector4D obj = inverse * tmp;
141 return obj.toVector3D();
149 float k = ( y - y0 ) / d_y;
156 QVector3D l0 = unproject( QVector3D( pt.x(), viewport.height() - pt.y(), 0 ), camera->viewMatrix(), camera->projectionMatrix(), viewport );
157 QVector3D l1 = unproject( QVector3D( pt.x(), viewport.height() - pt.y(), 1 ), camera->viewMatrix(), camera->projectionMatrix(), viewport );
159 QVector3D p0( 0, y, 0 );
160 QVector3D n( 0, 1, 0 );
161 QVector3D l = l1 - l0;
162 float d = QVector3D::dotProduct( p0 - l0, n ) / QVector3D::dotProduct( l, n );
163 QVector3D p = d * l + l0;
165 return QPointF( p.x(), p.z() );
169 void QgsCameraController::rotateCamera(
float diffPitch,
float diffYaw )
174 if (
pitch + diffPitch > 180 )
175 diffPitch = 180 -
pitch;
176 if (
pitch + diffPitch < 0 )
177 diffPitch = 0 -
pitch;
184 QQuaternion q = QQuaternion::fromEulerAngles(
pitch + diffPitch,
yaw + diffYaw, 0 ) *
185 QQuaternion::fromEulerAngles(
pitch,
yaw, 0 ).conjugated();
188 QVector3D position = mCamera->position();
189 QVector3D viewCenter = mCamera->viewCenter();
190 QVector3D viewVector = viewCenter - position;
191 QVector3D cameraToCenter = q * viewVector;
192 viewCenter = position + cameraToCenter;
213 if ( mTerrainEntity )
221 mCamera->setNearPlane(
distance / 2 );
222 mCamera->setFarPlane(
distance * 2 );
244 if ( camPose == mCameraPose )
247 mCameraPose = camPose;
257 QDomElement elemCamera = doc.createElement( QStringLiteral(
"camera" ) );
258 elemCamera.setAttribute( QStringLiteral(
"x" ), mCameraPose.
centerPoint().
x() );
259 elemCamera.setAttribute( QStringLiteral(
"y" ), mCameraPose.
centerPoint().
z() );
260 elemCamera.setAttribute( QStringLiteral(
"elev" ), mCameraPose.
centerPoint().
y() );
262 elemCamera.setAttribute( QStringLiteral(
"pitch" ), mCameraPose.
pitchAngle() );
263 elemCamera.setAttribute( QStringLiteral(
"yaw" ), mCameraPose.
headingAngle() );
269 float x = elem.attribute( QStringLiteral(
"x" ) ).toFloat();
270 float y = elem.attribute( QStringLiteral(
"y" ) ).toFloat();
271 float elev = elem.attribute( QStringLiteral(
"elev" ) ).toFloat();
272 float dist = elem.attribute( QStringLiteral(
"dist" ) ).toFloat();
273 float pitch = elem.attribute( QStringLiteral(
"pitch" ) ).toFloat();
274 float yaw = elem.attribute( QStringLiteral(
"yaw" ) ).toFloat();
278 void QgsCameraController::updateCameraFromPose(
bool centerPointChanged )
283 qWarning() <<
"camera position got NaN!";
297 if ( mCamera && mTerrainEntity && centerPointChanged )
301 QVector3D intersectionPoint;
302 QgsRayCastingUtils::Ray3D ray = QgsRayCastingUtils::rayForCameraCenter( mCamera );
303 if ( mTerrainEntity->rayIntersection( ray, intersectionPoint ) )
305 float dist = ( intersectionPoint - mCamera->position() ).length();
313 centerPoint.
set( centerPoint.
x(), mTerrainEntity->terrainElevationOffset(), centerPoint.
z() );
321 void QgsCameraController::moveCameraPositionBy(
const QVector3D &posDiff )
339 void QgsCameraController::onPositionChanged( Qt3DInput::QMouseEvent *mouse )
341 switch ( mCameraNavigationMode )
344 onPositionChangedTerrainNavigation( mouse );
348 onPositionChangedFlyNavigation( mouse );
353 void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseEvent *mouse )
355 if ( mIgnoreNextMouseMove )
357 mIgnoreNextMouseMove =
false;
358 mMousePos = QPoint( mouse->x(), mouse->y() );
362 int dx = mouse->x() - mMousePos.x();
363 int dy = mouse->y() - mMousePos.y();
365 bool hasShift = ( mouse->modifiers() & Qt::ShiftModifier );
366 bool hasCtrl = ( mouse->modifiers() & Qt::ControlModifier );
367 bool hasLeftButton = ( mouse->buttons() & Qt::LeftButton );
368 bool hasMiddleButton = ( mouse->buttons() & Qt::MiddleButton );
369 bool hasRightButton = ( mouse->buttons() & Qt::RightButton );
371 if ( ( hasLeftButton && hasShift && !hasCtrl ) || ( hasMiddleButton && !hasShift && !hasCtrl ) )
380 updateCameraFromPose();
382 else if ( hasLeftButton && hasCtrl && !hasShift )
385 float diffPitch = 0.2f * dy;
386 float diffYaw = - 0.2f * dx;
387 rotateCamera( diffPitch, diffYaw );
388 updateCameraFromPose(
true );
390 else if ( hasLeftButton && !hasShift && !hasCtrl )
396 float z = mLastPressedHeight;
401 center.
set( center.
x() - ( p2.x() - p1.x() ), center.
y(), center.
z() - ( p2.y() - p1.y() ) );
403 updateCameraFromPose(
true );
405 else if ( hasRightButton && !hasShift && !hasCtrl )
410 mMousePos = QPoint( mouse->x(), mouse->y() );
419 dist -= dist * factor * 0.01f;
421 updateCameraFromPose();
424 void QgsCameraController::onWheel( Qt3DInput::QWheelEvent *wheel )
426 switch ( mCameraNavigationMode )
430 float scaling = ( ( wheel->modifiers() & Qt::ControlModifier ) ? 0.1f : 1.0f ) / 1000.f;
431 setCameraMovementSpeed( mCameraMovementSpeed + mCameraMovementSpeed * scaling * wheel->angleDelta().y() );
437 float scaling = ( ( wheel->modifiers() & Qt::ControlModifier ) ? 0.1f : 1.0f ) / 1000.f;
439 dist -= dist * scaling * wheel->angleDelta().y();
441 updateCameraFromPose();
447 void QgsCameraController::onMousePressed( Qt3DInput::QMouseEvent *mouse )
450 mKeyboardHandler->setFocus(
true );
451 if ( mouse->button() == Qt3DInput::QMouseEvent::LeftButton || mouse->button() == Qt3DInput::QMouseEvent::RightButton || mouse->button() == Qt3DInput::QMouseEvent::MiddleButton )
453 mMousePos = QPoint( mouse->x(), mouse->y() );
454 mPressedButton = mouse->button();
455 mMousePressed =
true;
456 if ( mCaptureFpsMouseMovements )
457 mIgnoreNextMouseMove =
true;
461 void QgsCameraController::onMouseReleased( Qt3DInput::QMouseEvent *mouse )
464 mPressedButton = Qt3DInput::QMouseEvent::NoButton;
465 mMousePressed =
false;
468 void QgsCameraController::onKeyPressed( Qt3DInput::QKeyEvent *event )
470 if ( event->modifiers() & Qt::ControlModifier && event->key() == Qt::Key_QuoteLeft )
473 switch ( mCameraNavigationMode )
475 case NavigationMode::WalkNavigation:
478 case NavigationMode::TerrainBasedNavigation:
486 switch ( mCameraNavigationMode )
490 onKeyPressedFlyNavigation( event );
496 onKeyPressedTerrainNavigation( event );
502 void QgsCameraController::onKeyPressedTerrainNavigation( Qt3DInput::QKeyEvent *event )
504 const bool hasShift = (
event->modifiers() & Qt::ShiftModifier );
505 const bool hasCtrl = (
event->modifiers() & Qt::ControlModifier );
507 int tx = 0, ty = 0, tElev = 0;
508 switch ( event->key() )
524 case Qt::Key_PageDown:
534 if ( !hasShift && !hasCtrl )
538 else if ( hasShift && !hasCtrl )
544 else if ( hasCtrl && !hasShift )
547 float diffPitch = ty;
549 rotateCamera( diffPitch, diffYaw );
550 updateCameraFromPose(
true );
557 center.
set( center.
x(), center.
y() + tElev * 10, center.
z() );
559 updateCameraFromPose(
true );
563 void QgsCameraController::onKeyPressedFlyNavigation( Qt3DInput::QKeyEvent *event )
565 switch ( event->key() )
567 case Qt::Key_QuoteLeft:
570 mCaptureFpsMouseMovements = !mCaptureFpsMouseMovements;
571 mIgnoreNextMouseMove =
true;
572 if ( mCaptureFpsMouseMovements )
574 qApp->setOverrideCursor( QCursor( Qt::BlankCursor ) );
578 qApp->restoreOverrideCursor();
586 if ( mCaptureFpsMouseMovements )
588 mCaptureFpsMouseMovements =
false;
589 mIgnoreNextMouseMove =
true;
590 qApp->restoreOverrideCursor();
600 if ( event->isAutoRepeat() )
603 mDepressedKeys.insert( event->key() );
606 void QgsCameraController::applyFlyModeKeyMovements()
608 QVector3D cameraUp = mCamera->upVector().normalized();
610 QVector3D cameraLeft = QVector3D::crossProduct( cameraUp, cameraFront );
612 QVector3D cameraPosDiff( 0.0f, 0.0f, 0.0f );
615 const bool shiftPressed = mDepressedKeys.contains( Qt::Key_Shift );
616 const bool ctrlPressed = mDepressedKeys.contains( Qt::Key_Control );
618 double movementSpeed = mCameraMovementSpeed * ( shiftPressed ? 2 : 1 ) * ( ctrlPressed ? 0.1 : 1 );
620 bool changed =
false;
621 if ( mDepressedKeys.contains( Qt::Key_Left ) || mDepressedKeys.contains( Qt::Key_A ) )
624 cameraPosDiff += movementSpeed * cameraLeft;
627 if ( mDepressedKeys.contains( Qt::Key_Right ) || mDepressedKeys.contains( Qt::Key_D ) )
630 cameraPosDiff += - movementSpeed * cameraLeft;
633 if ( mDepressedKeys.contains( Qt::Key_Up ) || mDepressedKeys.contains( Qt::Key_W ) )
636 cameraPosDiff += movementSpeed * cameraFront;
639 if ( mDepressedKeys.contains( Qt::Key_Down ) || mDepressedKeys.contains( Qt::Key_S ) )
642 cameraPosDiff += - movementSpeed * cameraFront;
647 static constexpr
double ELEVATION_MOVEMENT_SCALE = 0.5;
648 if ( mDepressedKeys.contains( Qt::Key_PageUp ) || mDepressedKeys.contains( Qt::Key_E ) )
651 cameraPosDiff += ELEVATION_MOVEMENT_SCALE * movementSpeed * QVector3D( 0.0f, 1.0f, 0.0f );
654 if ( mDepressedKeys.contains( Qt::Key_PageDown ) || mDepressedKeys.contains( Qt::Key_Q ) )
657 cameraPosDiff += ELEVATION_MOVEMENT_SCALE * - movementSpeed * QVector3D( 0.0f, 1.0f, 0.0f );
661 moveCameraPositionBy( cameraPosDiff );
664 void QgsCameraController::onPositionChangedFlyNavigation( Qt3DInput::QMouseEvent *mouse )
666 const bool hasMiddleButton = ( mouse->buttons() & Qt::MiddleButton );
667 const bool hasRightButton = ( mouse->buttons() & Qt::RightButton );
669 const double dx = mCaptureFpsMouseMovements ? QCursor::pos().x() - mMousePos.x() : mouse->x() - mMousePos.x();
670 const double dy = mCaptureFpsMouseMovements ? QCursor::pos().y() - mMousePos.y() : mouse->y() - mMousePos.y();
671 mMousePos = mCaptureFpsMouseMovements ? QCursor::pos() : QPoint( mouse->x(), mouse->y() );
673 if ( mIgnoreNextMouseMove )
675 mIgnoreNextMouseMove =
false;
679 if ( hasMiddleButton )
682 QVector3D cameraUp = mCamera->upVector().normalized();
684 QVector3D cameraLeft = QVector3D::crossProduct( cameraUp, cameraFront );
685 QVector3D cameraPosDiff = -dx * cameraLeft - dy * cameraUp;
686 moveCameraPositionBy( mCameraMovementSpeed * cameraPosDiff / 10.0 );
688 else if ( hasRightButton )
692 QVector3D cameraPosDiff = dy * cameraFront;
693 moveCameraPositionBy( mCameraMovementSpeed * cameraPosDiff / 5.0 );
697 if ( mCaptureFpsMouseMovements )
699 float diffPitch = -0.2f * dy;
700 switch ( mVerticalAxisInversion )
711 float diffYaw = - 0.2f * dx;
712 rotateCamera( diffPitch, diffYaw );
713 updateCameraFromPose(
false );
715 else if ( mouse->buttons() & Qt::LeftButton )
717 float diffPitch = -0.2f * dy;
718 switch ( mVerticalAxisInversion )
728 float diffYaw = - 0.2f * dx;
729 rotateCamera( diffPitch, diffYaw );
730 updateCameraFromPose(
false );
734 if ( mCaptureFpsMouseMovements )
736 mIgnoreNextMouseMove =
true;
739 emit
setCursorPosition( QPoint( mViewport.width() / 2, mViewport.height() / 2 ) );
743 void QgsCameraController::onKeyReleased( Qt3DInput::QKeyEvent *event )
745 if ( event->isAutoRepeat() )
748 mDepressedKeys.remove( event->key() );
751 void QgsCameraController::onPickerMousePressed( Qt3DRender::QPickEvent *pick )
753 mLastPressedHeight = pick->worldIntersection().y();
763 updateCameraFromPose();
772 updateCameraFromPose();
773 qInfo() <<
"Delta yaw: " << deltaYaw;
774 qInfo() <<
"Yaw: " <<
yaw;
780 updateCameraFromPose();
787 float x = tx * dist * 0.02f;
788 float y = -ty * dist * 0.02f;
791 float t = sqrt( x * x + y * y );
792 float a = atan2( y, x ) -
yaw * M_PI / 180;
793 float dx = cos( a ) * t;
794 float dy = sin( a ) * t;
797 center.
set( center.
x() + dx, center.
y(), center.
z() + dy );
799 updateCameraFromPose(
true );
804 if ( event->key() == Qt::Key_QuoteLeft )
807 switch ( mCameraNavigationMode )
811 switch ( event->key() )
823 case Qt::Key_PageDown:
828 if ( mCaptureFpsMouseMovements )
840 switch ( event->key() )
845 case Qt::Key_PageDown:
void setViewport(QRect viewport)
Sets viewport rectangle. Called internally from 3D canvas. Allows conversion of mouse 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).
float yaw() const
Returns yaw angle in degrees.
void setCameraNavigationMode(QgsCameraController::NavigationMode navigationMode)
Sets the navigation mode used by the camera controller.
void tiltUpAroundViewCenter(float deltaPitch)
Tilt up the view by deltaPitch around the view center (camera moves)
bool willHandleKeyEvent(QKeyEvent *event)
Returns true if the camera controller will handle the specified key event, preventing it from being i...
void navigationModeHotKeyPressed(QgsCameraController::NavigationMode mode)
Emitted when the navigation mode is changed using the hotkey ctrl + ~.
Qt3DRender::QCamera * camera
void setVerticalAxisInversion(QgsCameraController::VerticalAxisInversion inversion)
Sets the vertical axis inversion behavior.
float distance() const
Returns distance of the camera from the point it is looking at.
void setCamera(Qt3DRender::QCamera *camera)
Assigns camera that should be controlled by this class. Called internally from 3D scene.
VerticalAxisInversion
Vertical axis inversion options.
@ WhenDragging
Invert vertical axis movements when dragging in first person modes.
@ Always
Always invert vertical axis movements.
@ Never
Never invert vertical axis movements.
void cameraChanged()
Emitted when camera has been updated.
void cameraMovementSpeedChanged(double speed)
Emitted whenever the camera movement speed is changed by the controller.
void frameTriggered(float dt)
Called internally from 3D scene when a new frame is generated. Updates camera according to keyboard/m...
void resetView(float distance)
Move camera back to the initial position (looking down towards origin of world's coordinates)
void setLookingAtPoint(const QgsVector3D &point, float distance, float pitch, float yaw)
Sets the complete camera configuration: the point towards it is looking (in 3D world coordinates),...
QgsVector3D lookingAtPoint() const
Returns the point in the world coordinates towards which the camera is looking.
QDomElement writeXml(QDomDocument &doc) const
Writes camera configuration to the given DOM element.
void setViewFromTop(float worldX, float worldY, float distance, float yaw=0)
Sets camera to look down towards given point in world coordinate, in given distance from plane with z...
NavigationMode
The navigation mode used by the camera.
@ WalkNavigation
Uses WASD keys or arrows to navigate in walking (first person) manner.
@ TerrainBasedNavigation
The default navigation based on the terrain.
void zoom(float factor)
Zoom the map by factor.
void setTerrainEntity(QgsTerrainEntity *te)
Connects to object picker attached to terrain entity.
void rotateAroundViewCenter(float deltaYaw)
Rotate clockwise the view by deltaYaw around the view center (camera moves)
QgsCameraController(Qt3DCore::QNode *parent=nullptr)
Constructs the camera controller with optional parent node that will take ownership.
void setCameraHeadingAngle(float angle)
Set camera heading to angle (used for rotating the view)
void setCameraMovementSpeed(double movementSpeed)
Sets the camera movement speed.
void setCameraPose(const QgsCameraPose &camPose)
Sets camera pose.
void viewportChanged()
Emitted when viewport rectangle has been updated.
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.
double y() const
Returns Y coordinate.
double z() const
Returns Z coordinate.
double x() const
Returns X coordinate.
void set(double x, double y, double z)
Sets vector coordinates.
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)
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
QPointF screen_point_to_point_on_plane(QPointF pt, QRect viewport, Qt3DRender::QCamera *camera, float y)
float find_x_on_line(float x0, float y0, float x1, float y1, float y)