Skip to content

Commit

Permalink
Refactor camera pose to a separate class outside of the camera contro…
Browse files Browse the repository at this point in the history
…ller

This will allow easier storage of camera configuration when it is needed outside
of the camera controller.
  • Loading branch information
wonder-sk committed Jul 28, 2018
1 parent 88ddd4b commit c28de6d
Show file tree
Hide file tree
Showing 8 changed files with 242 additions and 116 deletions.
5 changes: 5 additions & 0 deletions python/core/auto_generated/qgsvector3d.sip.in
Expand Up @@ -8,6 +8,7 @@




class QgsVector3D
{
%Docstring
Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/3d/CMakeLists.txt
Expand Up @@ -8,6 +8,7 @@ SET(QGIS_3D_SRCS
qgs3dmapsettings.cpp
qgs3dutils.cpp
qgscameracontroller.cpp
qgscamerapose.cpp
qgsoffscreen3dengine.cpp
qgsphongmaterialsettings.cpp
qgsraycastingutils_p.cpp
Expand Down Expand Up @@ -81,6 +82,7 @@ SET(QGIS_3D_HDRS
qgs3dmapsettings.h
qgs3dutils.h
qgscameracontroller.h
qgscamerapose.h
qgsoffscreen3dengine.h
qgsphongmaterialsettings.h
qgsraycastingutils_p.h
Expand Down
159 changes: 83 additions & 76 deletions src/3d/qgscameracontroller.cpp
Expand Up @@ -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 (?)
Expand All @@ -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 )
{
Expand Down Expand Up @@ -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();
Expand All @@ -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 );
}


Expand All @@ -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() )
{
Expand All @@ -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
Expand All @@ -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 );
}
}

Expand All @@ -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 );
return mCameraPose.centerPoint();
}

void QgsCameraController::setLookingAtPoint( const QgsVector3D &point, float dist )
void QgsCameraController::setLookingAtPoint( const QgsVector3D &point, float distance, float pitch, float yaw )
{
if ( dist < 0 )
dist = mCameraData.dist;
setCameraData( point.x(), point.z(), point.y(), dist, mCameraData.pitch, mCameraData.yaw );
emit cameraChanged();
QgsCameraPose camPose;
camPose.setCenterPoint( point );
camPose.setDistanceFromCenterPoint( distance );
camPose.setPitchAngle( pitch );
camPose.setHeadingAngle( yaw );
setCameraPose( camPose );
}

void QgsCameraController::setLookingAtPoint( const QgsVector3D &point, float distance, float pitch, float yaw )
void QgsCameraController::setCameraPose( const QgsCameraPose &camPose )
{
setCameraData( point.x(), point.z(), point.y(), distance, pitch, yaw );
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;
}

Expand All @@ -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 )
Expand Down

0 comments on commit c28de6d

Please sign in to comment.