Skip to content

Commit

Permalink
[3d] Handle tilting and rotation of camera better
Browse files Browse the repository at this point in the history
This will update camera's view center as the camera moves around.
Before the view center would be always at the zero elevation, which
means that with terrain further away from zero elevation tilting
and rotation of camera would feel weird due to the center point being
far away.

In order to update camera's view center we need to calculate intersection
of terrain with a 3D ray coming from the camera's position towards the center
of the viewport. This is done by going through the active terrain tiles
and checking whether their bounding box intersects the ray - if it does,
then we do an exact test of terrain tile's triangle mesh against the ray
to find the closest intersection point. When we have the intersection,
we update the view center to be at the terrain's surface.

Unfortunately raycasting in Qt3D is only available from 5.11 which has been
released only very recently. I have therefore ported some code from Qt3D
internals and added ray vs axis-aligned box from a different source
(Qt3D uses bounding spheres but they are not available in public API either)
  • Loading branch information
wonder-sk committed Jun 28, 2018
1 parent 97addfc commit 8a15cef
Show file tree
Hide file tree
Showing 10 changed files with 600 additions and 11 deletions.
2 changes: 2 additions & 0 deletions src/3d/CMakeLists.txt
Expand Up @@ -8,6 +8,7 @@ SET(QGIS_3D_SRCS
qgs3dutils.cpp
qgscameracontroller.cpp
qgsphongmaterialsettings.cpp
qgsraycastingutils_p.cpp
qgstessellatedpolygongeometry.cpp
qgstilingscheme.cpp
qgsvectorlayer3drenderer.cpp
Expand Down Expand Up @@ -74,6 +75,7 @@ SET(QGIS_3D_HDRS
qgs3dutils.h
qgscameracontroller.h
qgsphongmaterialsettings.h
qgsraycastingutils_p.h
qgstessellatedpolygongeometry.h
qgstilingscheme.h
qgsvectorlayer3drenderer.h
Expand Down
17 changes: 17 additions & 0 deletions src/3d/qgs3dmapscene.cpp
Expand Up @@ -34,6 +34,7 @@
#include "qgscameracontroller.h"
#include "qgschunkedentity_p.h"
#include "qgschunknode_p.h"
#include "qgsraycastingutils_p.h"
#include "qgsterrainentity_p.h"
#include "qgsterraingenerator.h"
#include "qgsvectorlayer.h"
Expand Down Expand Up @@ -253,6 +254,22 @@ void Qgs3DMapScene::onCameraChanged()
// set near/far plane - with some tolerance in front/behind expected near/far planes
camera->setFarPlane( ffar * 2 );
camera->setNearPlane( fnear / 2 );

// 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
QVector3D intersectionPoint;
QgsRayCastingUtils::Ray3D ray = QgsRayCastingUtils::rayForViewportAndCamera(
mCameraController->viewport().size(),
mCameraController->viewport().center(),
QRectF( 0.0, 0.0, 1.0, 1.0 ),
mCameraController->camera() );
if ( mTerrain->rayIntersection( ray, intersectionPoint ) )
{
float dist = ( intersectionPoint - mCameraController->camera()->position() ).length();
mCameraController->blockSignals( true );
mCameraController->setLookingAtPoint( QgsVector3D( intersectionPoint.x(), intersectionPoint.y(), intersectionPoint.z() ), dist );
mCameraController->blockSignals( false );
}
}
else
qDebug() << "no terrain - not setting near/far plane";
Expand Down
15 changes: 10 additions & 5 deletions src/3d/qgscameracontroller.cpp
Expand Up @@ -157,10 +157,11 @@ void QgsCameraController::setViewport( const QRect &viewport )
emit viewportChanged();
}

void QgsCameraController::setCameraData( float x, float y, float dist, float pitch, float yaw )
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;
Expand Down Expand Up @@ -305,7 +306,7 @@ void QgsCameraController::resetView( float distance )

void QgsCameraController::setViewFromTop( float worldX, float worldY, float distance, float yaw )
{
setCameraData( worldX, worldY, distance, 0, yaw );
setCameraData( worldX, worldY, 0, distance, 0, 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 );
Expand All @@ -318,9 +319,11 @@ QgsVector3D QgsCameraController::lookingAtPoint() const
return QgsVector3D( mCameraData.x, 0, mCameraData.y );
}

void QgsCameraController::setLookingAtPoint( const QgsVector3D &point )
void QgsCameraController::setLookingAtPoint( const QgsVector3D &point, float dist )
{
setCameraData( point.x(), point.z(), mCameraData.dist, mCameraData.pitch, mCameraData.yaw );
if ( dist < 0 )
dist = mCameraData.dist;
setCameraData( point.x(), point.z(), point.y(), dist, mCameraData.pitch, mCameraData.yaw );
emit cameraChanged();
}

Expand All @@ -329,6 +332,7 @@ 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 );
Expand All @@ -339,10 +343,11 @@ void QgsCameraController::readXml( const QDomElement &elem )
{
float x = elem.attribute( QStringLiteral( "x" ) ).toFloat();
float y = elem.attribute( QStringLiteral( "y" ) ).toFloat();
float elev = elem.attribute( QStringLiteral( "elev" ) ).toFloat();
float dist = elem.attribute( QStringLiteral( "dist" ) ).toFloat();
float pitch = elem.attribute( QStringLiteral( "pitch" ) ).toFloat();
float yaw = elem.attribute( QStringLiteral( "yaw" ) ).toFloat();
setCameraData( x, y, dist, pitch, yaw );
setCameraData( x, y, elev, dist, pitch, yaw );
}

void QgsCameraController::onPositionChanged( Qt3DInput::QMouseEvent *mouse )
Expand Down
12 changes: 6 additions & 6 deletions src/3d/qgscameracontroller.h
Expand Up @@ -67,15 +67,15 @@ 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 );
void setLookingAtPoint( const QgsVector3D &point, float distance = -1 );

//! Writes camera configuration to the given DOM element
QDomElement writeXml( QDomDocument &doc ) const;
//! Reads camera configuration from the given DOM element
void readXml( const QDomElement &elem );

private:
void setCameraData( float x, float y, float dist, float pitch = 0, float yaw = 0 );
void setCameraData( float x, float y, float elev, float dist, float pitch = 0, float yaw = 0 );

signals:
//! Emitted when camera has been updated
Expand All @@ -97,14 +97,14 @@ class _3D_EXPORT QgsCameraController : public Qt3DCore::QEntity

struct CameraData
{
float x = 0, y = 0; // ground point towards which the camera is looking
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 && dist == other.dist && pitch == other.pitch && yaw == other.yaw;
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
{
Expand All @@ -119,8 +119,8 @@ class _3D_EXPORT QgsCameraController : public Qt3DCore::QEntity
// - 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, y ) );
camera->setViewCenter( QVector3D( x, 0, y ) );
camera->setPosition( QVector3D( x, dist + elev, y ) );
camera->setViewCenter( QVector3D( x, elev, y ) );
camera->rotateAboutViewCenter( QQuaternion::fromEulerAngles( pitch, yaw, 0 ) );
}

Expand Down

0 comments on commit 8a15cef

Please sign in to comment.