Skip to content
Permalink
Browse files

[3d] Handle tilting and rotation of camera better

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 27, 2018
1 parent 97addfc commit 8a15cef2fc26696a39687f19a8847e2967cf5d45
@@ -8,6 +8,7 @@ SET(QGIS_3D_SRCS
qgs3dutils.cpp
qgscameracontroller.cpp
qgsphongmaterialsettings.cpp
qgsraycastingutils_p.cpp
qgstessellatedpolygongeometry.cpp
qgstilingscheme.cpp
qgsvectorlayer3drenderer.cpp
@@ -74,6 +75,7 @@ SET(QGIS_3D_HDRS
qgs3dutils.h
qgscameracontroller.h
qgsphongmaterialsettings.h
qgsraycastingutils_p.h
qgstessellatedpolygongeometry.h
qgstilingscheme.h
qgsvectorlayer3drenderer.h
@@ -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"
@@ -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";
@@ -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;
@@ -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 );
@@ -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();
}

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

0 comments on commit 8a15cef

Please sign in to comment.
You can’t perform that action at this time.