Refactor camera pose to a separate class outside of the camera controller

This will allow easier storage of camera configuration when it is needed outside
of the camera controller.
This commit is contained in:
Martin Dobias 2018-07-27 15:11:12 +02:00
parent 88ddd4b0de
commit c28de6d3b5
8 changed files with 244 additions and 118 deletions

View File

@ -8,6 +8,7 @@
class QgsVector3D
{
%Docstring
@ -29,6 +30,10 @@ Constructs a null vector
QgsVector3D( double x, double y, double z );
%Docstring
Constructs a vector from given coordinates
%End
QgsVector3D( const QVector3D &v );
%Docstring
Constructs a vector from single-precision QVector3D
%End
bool isNull() const;
%Docstring

View File

@ -8,6 +8,7 @@ SET(QGIS_3D_SRCS
qgs3dmapsettings.cpp
qgs3dutils.cpp
qgscameracontroller.cpp
qgscamerapose.cpp
qgsoffscreen3dengine.cpp
qgsphongmaterialsettings.cpp
qgsraycastingutils_p.cpp
@ -81,6 +82,7 @@ SET(QGIS_3D_HDRS
qgs3dmapsettings.h
qgs3dutils.h
qgscameracontroller.h
qgscamerapose.h
qgsoffscreen3dengine.h
qgsphongmaterialsettings.h
qgsraycastingutils_p.h

View File

@ -161,7 +161,7 @@ void QgsCameraController::setCamera( Qt3DRender::QCamera *camera )
return;
mCamera = camera;
mCameraData.setCamera( mCamera ); // initial setup
mCameraPose.updateCamera( mCamera ); // initial setup
// TODO: set camera's parent if not set already?
// TODO: registerDestructionHelper (?)
@ -177,21 +177,6 @@ void QgsCameraController::setViewport( const QRect &viewport )
emit viewportChanged();
}
void QgsCameraController::setCameraData( float x, float y, float elev, float dist, float pitch, float yaw )
{
mCameraData.x = x;
mCameraData.y = y;
mCameraData.elev = elev;
mCameraData.dist = dist;
mCameraData.pitch = pitch;
mCameraData.yaw = yaw;
if ( mCamera )
{
mCameraData.setCamera( mCamera );
}
}
static QVector3D unproject( const QVector3D &v, const QMatrix4x4 &modelView, const QMatrix4x4 &projection, const QRect &viewport )
{
@ -241,18 +226,21 @@ QPointF screen_point_to_point_on_plane( const QPointF &pt, const QRect &viewport
void QgsCameraController::rotateCamera( float diffPitch, float diffYaw )
{
if ( mCameraData.pitch + diffPitch > 180 )
diffPitch = 180 - mCameraData.pitch; // prevent going over the head
if ( mCameraData.pitch + diffPitch < 0 )
diffPitch = 0 - mCameraData.pitch; // prevent going over the head
float pitch = mCameraPose.pitchAngle();
float yaw = mCameraPose.headingAngle();
if ( pitch + diffPitch > 180 )
diffPitch = 180 - pitch; // prevent going over the head
if ( pitch + diffPitch < 0 )
diffPitch = 0 - pitch; // prevent going over the head
// Is it always going to be love/hate relationship with quaternions???
// This quaternion combines two rotations:
// - first it undoes the previously applied rotation so we have do not have any rotation compared to world coords
// - then it applies new rotation
// (We can't just apply our euler angles difference because the camera may be already rotated)
QQuaternion q = QQuaternion::fromEulerAngles( mCameraData.pitch + diffPitch, mCameraData.yaw + diffYaw, 0 ) *
QQuaternion::fromEulerAngles( mCameraData.pitch, mCameraData.yaw, 0 ).conjugated();
QQuaternion q = QQuaternion::fromEulerAngles( pitch + diffPitch, yaw + diffYaw, 0 ) *
QQuaternion::fromEulerAngles( pitch, yaw, 0 ).conjugated();
// get camera's view vector, rotate it to get new view center
QVector3D position = mCamera->position();
@ -261,11 +249,9 @@ void QgsCameraController::rotateCamera( float diffPitch, float diffYaw )
QVector3D cameraToCenter = q * viewVector;
viewCenter = position + cameraToCenter;
mCameraData.x = viewCenter.x();
mCameraData.y = viewCenter.z();
mCameraData.elev = viewCenter.y();
mCameraData.pitch += diffPitch;
mCameraData.yaw += diffYaw;
mCameraPose.setCenterPoint( viewCenter );
mCameraPose.setPitchAngle( pitch + diffPitch );
mCameraPose.setHeadingAngle( yaw + diffYaw );
}
@ -274,46 +260,56 @@ void QgsCameraController::frameTriggered( float dt )
if ( mCamera == nullptr )
return;
CameraData oldCamData = mCameraData;
QgsCameraPose oldCamPose = mCameraPose;
float dist = mCameraPose.distanceFromCenterPoint();
float yaw = mCameraPose.headingAngle();
float pitch = mCameraPose.pitchAngle();
QgsVector3D center = mCameraPose.centerPoint();
int dx = mMousePos.x() - mLastMousePos.x();
int dy = mMousePos.y() - mLastMousePos.y();
mLastMousePos = mMousePos;
double scaling = ( mCtrlAction->isActive() ? 0.1 : 1.0 );
mCameraData.dist -= scaling * mCameraData.dist * mWheelAxis->value() * 10 * dt;
dist -= scaling * dist * mWheelAxis->value() * 10 * dt;
if ( mRightMouseButtonAction->isActive() )
{
mCameraData.dist -= mCameraData.dist * dy * 0.01;
dist -= dist * dy * 0.01;
}
float tx = mTxAxis->value() * dt * mCameraData.dist * 1.5;
float ty = -mTyAxis->value() * dt * mCameraData.dist * 1.5;
float tx = mTxAxis->value() * dt * dist * 1.5;
float ty = -mTyAxis->value() * dt * dist * 1.5;
float telev = mTelevAxis->value() * dt * 300;
mCameraPose.setDistanceFromCenterPoint( dist );
if ( !mShiftAction->isActive() && !mCtrlAction->isActive() && ( tx || ty ) )
{
// moving with keyboard - take into account yaw of camera
float t = sqrt( tx * tx + ty * ty );
float a = atan2( ty, tx ) - mCameraData.yaw * M_PI / 180;
float a = atan2( ty, tx ) - yaw * M_PI / 180;
float dx = cos( a ) * t;
float dy = sin( a ) * t;
mCameraData.x += dx;
mCameraData.y += dy;
center.set( center.x() + dx, center.y(), center.z() + dy );
mCameraPose.setCenterPoint( center );
}
if ( ( mLeftMouseButtonAction->isActive() && mShiftAction->isActive() ) || mMiddleMouseButtonAction->isActive() )
{
// rotate/tilt using mouse (camera moves as it rotates around its view center)
mCameraData.pitch += dy;
mCameraData.yaw -= dx / 2;
pitch += dy;
yaw -= dx / 2;
mCameraPose.setPitchAngle( pitch );
mCameraPose.setHeadingAngle( yaw );
}
else if ( mShiftAction->isActive() && ( mTxAxis->value() || mTyAxis->value() ) )
{
// rotate/tilt using keyboard (camera moves as it rotates around its view center)
mCameraData.pitch -= mTyAxis->value(); // down key = moving camera toward terrain
mCameraData.yaw -= mTxAxis->value(); // right key = moving camera clockwise
pitch -= mTyAxis->value(); // down key = moving camera toward terrain
yaw -= mTxAxis->value(); // right key = moving camera clockwise
mCameraPose.setPitchAngle( pitch );
mCameraPose.setHeadingAngle( yaw );
}
else if ( mCtrlAction->isActive() && mLeftMouseButtonAction->isActive() )
{
@ -339,33 +335,36 @@ void QgsCameraController::frameTriggered( float dt )
QPointF p1 = screen_point_to_point_on_plane( QPointF( mMousePos - QPoint( dx, dy ) ), mViewport, mCamera, z );
QPointF p2 = screen_point_to_point_on_plane( QPointF( mMousePos ), mViewport, mCamera, z );
mCameraData.x -= p2.x() - p1.x();
mCameraData.y -= p2.y() - p1.y();
center.set( center.x() - ( p2.x() - p1.x() ), center.y(), center.z() - ( p2.y() - p1.y() ) );
mCameraPose.setCenterPoint( center );
}
if ( telev != 0 )
mCameraData.elev += telev;
{
center.set( center.x(), center.y() + telev, center.z() );
mCameraPose.setCenterPoint( center );
}
if ( std::isnan( mCameraData.x ) || std::isnan( mCameraData.y ) )
if ( std::isnan( mCameraPose.centerPoint().x() ) || std::isnan( mCameraPose.centerPoint().y() ) || std::isnan( mCameraPose.centerPoint().z() ) )
{
// something went horribly wrong but we need to at least try to fix it somehow
qDebug() << "camera position got NaN!";
mCameraData.x = mCameraData.y = 0;
center.set( 0, 0, 0 );
mCameraPose.setCenterPoint( center );
}
if ( mCameraData.pitch > 180 )
mCameraData.pitch = 180; // prevent going over the head
if ( mCameraData.pitch < 0 )
mCameraData.pitch = 0; // prevent going over the head
if ( mCameraData.dist < 10 )
mCameraData.dist = 10;
if ( mCameraPose.pitchAngle() > 180 )
mCameraPose.setPitchAngle( 180 ); // prevent going over the head
if ( mCameraPose.pitchAngle() < 0 )
mCameraPose.setPitchAngle( 0 ); // prevent going over the head
if ( mCameraPose.distanceFromCenterPoint() < 10 )
mCameraPose.setDistanceFromCenterPoint( 10 );
if ( mCameraData != oldCamData )
if ( mCameraPose != oldCamPose )
{
mCameraData.setCamera( mCamera );
mCameraPose.updateCamera( mCamera );
bool viewCenterChanged = ( mCameraData.x != oldCamData.x || mCameraData.y != oldCamData.y || mCameraData.elev != oldCamData.elev );
if ( mTerrainEntity && viewCenterChanged )
if ( mTerrainEntity && mCameraPose.centerPoint() != oldCamPose.centerPoint() )
{
// figure out our distance from terrain and update the camera's view center
// so that camera tilting and rotation is around a point on terrain, not an point at fixed elevation
@ -374,11 +373,9 @@ void QgsCameraController::frameTriggered( float dt )
if ( mTerrainEntity->rayIntersection( ray, intersectionPoint ) )
{
float dist = ( intersectionPoint - mCamera->position() ).length();
mCameraData.dist = dist;
mCameraData.x = intersectionPoint.x();
mCameraData.y = intersectionPoint.z();
mCameraData.elev = intersectionPoint.y();
mCameraData.setCamera( mCamera );
mCameraPose.setDistanceFromCenterPoint( dist );
mCameraPose.setCenterPoint( QgsVector3D( intersectionPoint ) );
mCameraPose.updateCamera( mCamera );
}
}
@ -393,42 +390,52 @@ void QgsCameraController::resetView( float distance )
void QgsCameraController::setViewFromTop( float worldX, float worldY, float distance, float yaw )
{
setCameraData( worldX, worldY, 0, distance, 0, yaw );
QgsCameraPose camPose;
camPose.setCenterPoint( QgsVector3D( worldX, 0, worldY ) );
camPose.setDistanceFromCenterPoint( distance );
camPose.setHeadingAngle( yaw );
// a basic setup to make frustum depth range long enough that it does not cull everything
mCamera->setNearPlane( distance / 2 );
mCamera->setFarPlane( distance * 2 );
emit cameraChanged();
setCameraPose( camPose );
}
QgsVector3D QgsCameraController::lookingAtPoint() const
{
return QgsVector3D( mCameraData.x, mCameraData.elev, mCameraData.y );
}
void QgsCameraController::setLookingAtPoint( const QgsVector3D &point, float dist )
{
if ( dist < 0 )
dist = mCameraData.dist;
setCameraData( point.x(), point.z(), point.y(), dist, mCameraData.pitch, mCameraData.yaw );
emit cameraChanged();
return mCameraPose.centerPoint();
}
void QgsCameraController::setLookingAtPoint( const QgsVector3D &point, float distance, float pitch, float yaw )
{
setCameraData( point.x(), point.z(), point.y(), distance, pitch, yaw );
QgsCameraPose camPose;
camPose.setCenterPoint( point );
camPose.setDistanceFromCenterPoint( distance );
camPose.setPitchAngle( pitch );
camPose.setHeadingAngle( yaw );
setCameraPose( camPose );
}
void QgsCameraController::setCameraPose( const QgsCameraPose &camPose )
{
mCameraPose = camPose;
if ( mCamera )
mCameraPose.updateCamera( mCamera );
emit cameraChanged();
}
QDomElement QgsCameraController::writeXml( QDomDocument &doc ) const
{
QDomElement elemCamera = doc.createElement( "camera" );
elemCamera.setAttribute( QStringLiteral( "x" ), mCameraData.x );
elemCamera.setAttribute( QStringLiteral( "y" ), mCameraData.y );
elemCamera.setAttribute( QStringLiteral( "elev" ), mCameraData.elev );
elemCamera.setAttribute( QStringLiteral( "dist" ), mCameraData.dist );
elemCamera.setAttribute( QStringLiteral( "pitch" ), mCameraData.pitch );
elemCamera.setAttribute( QStringLiteral( "yaw" ), mCameraData.yaw );
elemCamera.setAttribute( QStringLiteral( "x" ), mCameraPose.centerPoint().x() );
elemCamera.setAttribute( QStringLiteral( "y" ), mCameraPose.centerPoint().z() );
elemCamera.setAttribute( QStringLiteral( "elev" ), mCameraPose.centerPoint().y() );
elemCamera.setAttribute( QStringLiteral( "dist" ), mCameraPose.distanceFromCenterPoint() );
elemCamera.setAttribute( QStringLiteral( "pitch" ), mCameraPose.pitchAngle() );
elemCamera.setAttribute( QStringLiteral( "yaw" ), mCameraPose.headingAngle() );
return elemCamera;
}
@ -440,7 +447,7 @@ void QgsCameraController::readXml( const QDomElement &elem )
float dist = elem.attribute( QStringLiteral( "dist" ) ).toFloat();
float pitch = elem.attribute( QStringLiteral( "pitch" ) ).toFloat();
float yaw = elem.attribute( QStringLiteral( "yaw" ) ).toFloat();
setCameraData( x, y, elev, dist, pitch, yaw );
setLookingAtPoint( QgsVector3D( x, elev, y ), dist, pitch, yaw );
}
void QgsCameraController::onPositionChanged( Qt3DInput::QMouseEvent *mouse )

View File

@ -22,9 +22,12 @@
#include <Qt3DInput>
#include <Qt3DRender>
#include "qgscamerapose.h"
class QDomDocument;
class QDomElement;
class QgsCameraPose;
class QgsTerrainEntity;
class QgsVector3D;
@ -69,8 +72,6 @@ class _3D_EXPORT QgsCameraController : public Qt3DCore::QEntity
//! Returns the point in the world coordinates towards which the camera is looking
QgsVector3D lookingAtPoint() const;
//! Sets the point toward which the camera is looking - this is used when world origin changes (e.g. after terrain generator changes)
void setLookingAtPoint( const QgsVector3D &point, float distance = -1 );
/**
* Sets the complete camera configuration: the point towards it is looking (in 3D world coordinates), the distance
@ -80,25 +81,37 @@ class _3D_EXPORT QgsCameraController : public Qt3DCore::QEntity
*/
void setLookingAtPoint( const QgsVector3D &point, float distance, float pitch, float yaw );
/**
* Sets camera pose
* \since QGIS 3.4
*/
void setCameraPose( const QgsCameraPose &camPose );
/**
* Returns camera pose
* \since QGIS 3.4
*/
QgsCameraPose cameraPose() const { return mCameraPose; }
/**
* Returns distance of the camera from the point it is looking at.
* \since QGIS 3.4
*/
float distance() const { return mCameraData.dist; }
float distance() const { return mCameraPose.distanceFromCenterPoint(); }
/**
* Returns pitch angle in degrees (0 = looking from the top, 90 = looking from the side).
* The angle should range from 0 to 180.
* \since QGIS 3.4
*/
float pitch() const { return mCameraData.pitch; }
float pitch() const { return mCameraPose.pitchAngle(); }
/**
* Returns yaw angle in degrees. Yaw value of zero means the camera is pointing towards north.
* The angle should range from 0 to 360.
* \since QGIS 3.4
*/
float yaw() const { return mCameraData.yaw; }
float yaw() const { return mCameraPose.headingAngle(); }
//! Writes camera configuration to the given DOM element
QDomElement writeXml( QDomDocument &doc ) const;
@ -106,7 +119,6 @@ class _3D_EXPORT QgsCameraController : public Qt3DCore::QEntity
void readXml( const QDomElement &elem );
private:
void setCameraData( float x, float y, float elev, float dist, float pitch = 0, float yaw = 0 );
void rotateCamera( float diffPitch, float diffYaw );
signals:
@ -129,38 +141,8 @@ class _3D_EXPORT QgsCameraController : public Qt3DCore::QEntity
QPointer<QgsTerrainEntity> mTerrainEntity;
struct CameraData
{
float x = 0, y = 0, elev = 0; // ground point towards which the camera is looking
float dist = 40; // distance of camera from the point it is looking at
float pitch = 0; // aircraft nose up/down (0 = looking straight down to the plane). angle in degrees
float yaw = 0; // aircraft nose left/right. angle in degrees
bool operator==( const CameraData &other ) const
{
return x == other.x && y == other.y && elev == other.elev && dist == other.dist && pitch == other.pitch && yaw == other.yaw;
}
bool operator!=( const CameraData &other ) const
{
return !operator==( other );
}
void setCamera( Qt3DRender::QCamera *camera )
{
// basic scene setup:
// - x grows to the right
// - z grows to the bottom
// - y grows towards camera
// so a point on the plane (x',y') is transformed to (x,-z) in our 3D world
camera->setUpVector( QVector3D( 0, 0, -1 ) );
camera->setPosition( QVector3D( x, dist + elev, y ) );
camera->setViewCenter( QVector3D( x, elev, y ) );
camera->rotateAboutViewCenter( QQuaternion::fromEulerAngles( pitch, yaw, 0 ) );
}
};
CameraData mCameraData;
//! Keeps definition of the camera's position and towards where it is looking
QgsCameraPose mCameraPose;
//! Last mouse position recorded
QPoint mMousePos;

32
src/3d/qgscamerapose.cpp Normal file
View File

@ -0,0 +1,32 @@
/***************************************************************************
qgscamerapose.cpp
--------------------------------------
Date : July 2018
Copyright : (C) 2018 by Martin Dobias
Email : wonder dot sk at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgscamerapose.h"
#include <Qt3DRender/QCamera>
void QgsCameraPose::updateCamera( Qt3DRender::QCamera *camera )
{
// basic scene setup:
// - x grows to the right
// - z grows to the bottom
// - y grows towards camera
// so a point on the plane (x',y') is transformed to (x,-z) in our 3D world
camera->setUpVector( QVector3D( 0, 0, -1 ) );
camera->setPosition( QVector3D( mCenterPoint.x(), mDistanceFromCenterPoint + mCenterPoint.y(), mCenterPoint.z() ) );
camera->setViewCenter( QVector3D( mCenterPoint.x(), mCenterPoint.y(), mCenterPoint.z() ) );
camera->rotateAboutViewCenter( QQuaternion::fromEulerAngles( mPitchAngle, mHeadingAngle, 0 ) );
}

89
src/3d/qgscamerapose.h Normal file
View File

@ -0,0 +1,89 @@
/***************************************************************************
qgscamerapose.h
--------------------------------------
Date : July 2018
Copyright : (C) 2018 by Martin Dobias
Email : wonder dot sk at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSCAMERAPOSE_H
#define QGSCAMERAPOSE_H
#include "qgis_3d.h"
#include "qgsvector3d.h"
namespace Qt3DRender
{
class QCamera;
}
/**
* \ingroup 3d
* Class that encapsulates camera pose in a 3D scene. The pose is defined with the following parameters:
* - center point - towards which point the camera is looking
* - distance from the center point - how far is the camera from the point towards which it is looking
* - pitch angle - vertical rotation of the camera (0 degrees = camera looking down, 90 degrees = camera looking from the side)
* - yaw angle - horizontal rotation of the camera
*
* \since QGIS 3.4
*/
class _3D_EXPORT QgsCameraPose
{
public:
//! Returns center point (towards which point the camera is looking)
QgsVector3D centerPoint() const { return mCenterPoint; }
//! Sets center point (towards which point the camera is looking)
void setCenterPoint( const QgsVector3D &point ) { mCenterPoint = point; }
//! Returns distance of the camera from the center point
float distanceFromCenterPoint() const { return mDistanceFromCenterPoint; }
//! Sets distance of the camera from the center point
void setDistanceFromCenterPoint( float distance ) { mDistanceFromCenterPoint = distance; }
//! Returns pitch angle in degrees
float pitchAngle() const { return mPitchAngle; }
//! Sets pitch angle in degrees
void setPitchAngle( float pitch ) { mPitchAngle = pitch; }
//! Returns heading (yaw) angle in degrees
float headingAngle() const { return mHeadingAngle; }
//! Sets heading (yaw) angle in degrees
void setHeadingAngle( float heading ) { mHeadingAngle = heading; }
//! Update Qt3D camera view matrix based on the pose
void updateCamera( Qt3DRender::QCamera *camera );
bool operator==( const QgsCameraPose &other ) const
{
return mCenterPoint == other.mCenterPoint &&
mDistanceFromCenterPoint == other.mDistanceFromCenterPoint &&
mPitchAngle == other.mPitchAngle &&
mHeadingAngle == other.mHeadingAngle;
}
bool operator!=( const QgsCameraPose &other ) const
{
return !operator==( other );
}
private:
//! ground point towards which the camera is looking
QgsVector3D mCenterPoint;
//! distance of camera from the point it is looking at
float mDistanceFromCenterPoint = 1000;
//! aircraft nose up/down (0 = looking straight down to the plane). angle in degrees
float mPitchAngle = 0;
//! aircraft nose left/right. angle in degrees
float mHeadingAngle = 0;
};
#endif // QGSCAMERAPOSE_H

View File

@ -158,7 +158,8 @@ void Qgs3DMapCanvasDockWidget::configure()
QgsVector3D oldOrigin = map->origin();
QgsCoordinateReferenceSystem oldCrs = map->crs();
QgsVector3D oldLookingAt = mCanvas->cameraController()->lookingAtPoint();
QgsCameraPose oldCameraPose = mCanvas->cameraController()->cameraPose();
QgsVector3D oldLookingAt = oldCameraPose.centerPoint();
// update map
w->apply();
@ -171,7 +172,9 @@ void Qgs3DMapCanvasDockWidget::configure()
if ( p != oldLookingAt )
{
// apply() call has moved origin of the world so let's move camera so we look still at the same place
mCanvas->cameraController()->setLookingAtPoint( p );
QgsCameraPose newCameraPose = oldCameraPose;
newCameraPose.setCenterPoint( p );
mCanvas->cameraController()->setCameraPose( newCameraPose );
}
}

View File

@ -19,6 +19,8 @@
#include "qgis_core.h"
#include "qgis.h"
#include <QVector3D>
/**
* \ingroup 3d
* Class for storage of 3D vectors similar to QVector3D, with the difference that it uses double precision
@ -36,6 +38,10 @@ class CORE_EXPORT QgsVector3D
QgsVector3D( double x, double y, double z )
: mX( x ), mY( y ), mZ( z ) {}
//! Constructs a vector from single-precision QVector3D
QgsVector3D( const QVector3D &v )
: mX( v.x() ), mY( v.y() ), mZ( v.z() ) {}
//! Returns true if all three coordinates are zero
bool isNull() const { return mX == 0 && mY == 0 && mZ == 0; }