Camera navigation improvements (#45979)

Fixes two issues in camera navigation:
- Unintuitive camera rotation
- Wrong center point when zooming in/out and rotating

See https://github.com/qgis/QGIS-Enhancement-Proposals/issues/215

This PR employs the following changes:
- The zoom in functionality will zoom in towards the real 3D position of an object in the scene instead the camera view center point used previously.
- The rotation will use the real clicked 3D position of a pixel as well instead of the camera view center point.
- The press and drag behaviour is improved to shift the map in real 3D coordinates instead of some arbitrary measurement (you can see the clicked pixel following the cursor instead of drifting away).
This commit is contained in:
Nedjima Belgacem 2022-01-05 12:13:25 +01:00 committed by GitHub
parent df64116867
commit b88b080777
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 1078 additions and 253 deletions

View File

@ -348,6 +348,20 @@ Sets whether to show camera's view center as a sphere (for debugging)
Returns whether to show camera's view center as a sphere (for debugging)
.. versionadded:: 3.4
%End
void setShowCameraRotationCenter( bool enabled );
%Docstring
Sets whether to show camera's rotation center as a sphere (for debugging)
.. versionadded:: 3.24
%End
bool showCameraRotationCenter() const;
%Docstring
Returns whether to show camera's rotation center as a sphere (for debugging)
.. versionadded:: 3.24
%End
void setShowLightSourceOrigins( bool enabled );
@ -671,6 +685,13 @@ Emitted when the flag whether terrain's tile info is shown has changed
Emitted when the flag whether camera's view center is shown has changed
.. versionadded:: 3.4
%End
void showCameraRotationCenterChanged();
%Docstring
Emitted when the flag whether camera's rotation center is shown has changed
.. versionadded:: 3.24
%End
void showLightSourceOriginsChanged();

View File

@ -103,6 +103,17 @@ Returns the perpendicular point of vector ``vp`` from [``v1`` - ``v2``]
%Docstring
Returns a string representation of the 3D vector.
Members will be truncated to the specified ``precision``.
%End
QVector3D toVector3D() const;
%Docstring
Converts the current object to QVector3D
.. warning::
the conversion may decrease the accuracy (double to float values conversion)
.. versionadded:: 3.24
%End
SIP_PYOBJECT __repr__();

View File

@ -118,6 +118,7 @@ Qgs3DMapScene::Qgs3DMapScene( const Qgs3DMapSettings &map, QgsAbstract3DEngine *
mCameraController->resetView( 1000 );
addCameraViewCenterEntity( mEngine->camera() );
addCameraRotationCenterEntity( mCameraController );
updateLights();
// create terrain entity
@ -410,6 +411,7 @@ static void _updateNearFarPlane( const QList<QgsChunkNode *> &activeNodes, const
QVector4D pc = viewMatrix * p;
float dst = -pc.z(); // in camera coordinates, x grows right, y grows down, z grows to the back
fnear = std::min( fnear, dst );
ffar = std::max( ffar, dst );
@ -1144,3 +1146,31 @@ QgsRectangle Qgs3DMapScene::sceneExtent()
return extent;
}
void Qgs3DMapScene::addCameraRotationCenterEntity( QgsCameraController *controller )
{
mEntityRotationCenter = new Qt3DCore::QEntity;
Qt3DCore::QTransform *trCameraViewCenter = new Qt3DCore::QTransform;
mEntityRotationCenter->addComponent( trCameraViewCenter );
Qt3DExtras::QPhongMaterial *materialCameraViewCenter = new Qt3DExtras::QPhongMaterial;
materialCameraViewCenter->setAmbient( Qt::blue );
mEntityRotationCenter->addComponent( materialCameraViewCenter );
Qt3DExtras::QSphereMesh *rendererCameraViewCenter = new Qt3DExtras::QSphereMesh;
rendererCameraViewCenter->setRadius( 10 );
mEntityRotationCenter->addComponent( rendererCameraViewCenter );
mEntityRotationCenter->setEnabled( true );
mEntityRotationCenter->setParent( this );
connect( controller, &QgsCameraController::cameraRotationCenterChanged, this, [trCameraViewCenter]( QVector3D center )
{
trCameraViewCenter->setTranslation( center );
} );
mEntityRotationCenter->setEnabled( mMap.showCameraRotationCenter() );
connect( &mMap, &Qgs3DMapSettings::showCameraRotationCenterChanged, this, [this]
{
mEntityRotationCenter->setEnabled( mMap.showCameraRotationCenter() );
} );
}

View File

@ -175,6 +175,7 @@ class _3D_EXPORT Qgs3DMapScene : public Qt3DCore::QEntity
void addLayerEntity( QgsMapLayer *layer );
void removeLayerEntity( QgsMapLayer *layer );
void addCameraViewCenterEntity( Qt3DRender::QCamera *camera );
void addCameraRotationCenterEntity( QgsCameraController *controller );
void setSceneState( SceneState state );
void updateSceneState();
void updateScene();
@ -204,6 +205,8 @@ class _3D_EXPORT Qgs3DMapScene : public Qt3DCore::QEntity
QList<Qt3DCore::QEntity *> mLightOriginEntities;
QList<QgsMapLayer *> mModelVectorLayers;
QgsSkyboxEntity *mSkybox = nullptr;
//! Entity that shows rotation center = useful for debugging camera issues
Qt3DCore::QEntity *mEntityRotationCenter = nullptr;
};
#endif // QGS3DMAPSCENE_H

View File

@ -49,6 +49,7 @@ Qgs3DMapSettings::Qgs3DMapSettings( const Qgs3DMapSettings &other )
, mShowTerrainBoundingBoxes( other.mShowTerrainBoundingBoxes )
, mShowTerrainTileInfo( other.mShowTerrainTileInfo )
, mShowCameraViewCenter( other.mShowCameraViewCenter )
, mShowCameraRotationCenter( other.mShowCameraRotationCenter )
, mShowLightSources( other.mShowLightSources )
, mShowLabels( other.mShowLabels )
, mPointLights( other.mPointLights )
@ -263,6 +264,7 @@ void Qgs3DMapSettings::readXml( const QDomElement &elem, const QgsReadWriteConte
mShowTerrainBoundingBoxes = elemDebug.attribute( QStringLiteral( "bounding-boxes" ), QStringLiteral( "0" ) ).toInt();
mShowTerrainTileInfo = elemDebug.attribute( QStringLiteral( "terrain-tile-info" ), QStringLiteral( "0" ) ).toInt();
mShowCameraViewCenter = elemDebug.attribute( QStringLiteral( "camera-view-center" ), QStringLiteral( "0" ) ).toInt();
mShowCameraRotationCenter = elemDebug.attribute( QStringLiteral( "camera-rotation-center" ), QStringLiteral( "0" ) ).toInt();
mShowLightSources = elemDebug.attribute( QStringLiteral( "show-light-sources" ), QStringLiteral( "0" ) ).toInt();
mIsFpsCounterEnabled = elemDebug.attribute( QStringLiteral( "show-fps-counter" ), QStringLiteral( "0" ) ).toInt();
@ -375,6 +377,7 @@ QDomElement Qgs3DMapSettings::writeXml( QDomDocument &doc, const QgsReadWriteCon
elemDebug.setAttribute( QStringLiteral( "bounding-boxes" ), mShowTerrainBoundingBoxes ? 1 : 0 );
elemDebug.setAttribute( QStringLiteral( "terrain-tile-info" ), mShowTerrainTileInfo ? 1 : 0 );
elemDebug.setAttribute( QStringLiteral( "camera-view-center" ), mShowCameraViewCenter ? 1 : 0 );
elemDebug.setAttribute( QStringLiteral( "camera-rotation-center" ), mShowCameraRotationCenter ? 1 : 0 );
elemDebug.setAttribute( QStringLiteral( "show-light-sources" ), mShowLightSources ? 1 : 0 );
elemDebug.setAttribute( QStringLiteral( "show-fps-counter" ), mIsFpsCounterEnabled ? 1 : 0 );
elem.appendChild( elemDebug );
@ -642,6 +645,16 @@ void Qgs3DMapSettings::setShowCameraViewCenter( bool enabled )
emit showCameraViewCenterChanged();
}
void Qgs3DMapSettings::setShowCameraRotationCenter( bool enabled )
{
if ( mShowCameraRotationCenter == enabled )
return;
mShowCameraRotationCenter = enabled;
emit showCameraRotationCenterChanged();
}
void Qgs3DMapSettings::setShowLightSourceOrigins( bool enabled )
{
if ( mShowLightSources == enabled )

View File

@ -331,6 +331,18 @@ class _3D_EXPORT Qgs3DMapSettings : public QObject, public QgsTemporalRangeObjec
*/
bool showCameraViewCenter() const { return mShowCameraViewCenter; }
/**
* Sets whether to show camera's rotation center as a sphere (for debugging)
* \since QGIS 3.24
*/
void setShowCameraRotationCenter( bool enabled );
/**
* Returns whether to show camera's rotation center as a sphere (for debugging)
* \since QGIS 3.24
*/
bool showCameraRotationCenter() const { return mShowCameraRotationCenter; }
/**
* Sets whether to show light source origins as a sphere (for debugging)
* \since QGIS 3.16
@ -617,6 +629,12 @@ class _3D_EXPORT Qgs3DMapSettings : public QObject, public QgsTemporalRangeObjec
*/
void showCameraViewCenterChanged();
/**
* Emitted when the flag whether camera's rotation center is shown has changed
* \since QGIS 3.24
*/
void showCameraRotationCenterChanged();
/**
* Emitted when the flag whether light source origins are shown has changed.
* \since QGIS 3.15
@ -733,6 +751,7 @@ class _3D_EXPORT Qgs3DMapSettings : public QObject, public QgsTemporalRangeObjec
bool mShowTerrainBoundingBoxes = false; //!< Whether to show bounding boxes of entities - useful for debugging
bool mShowTerrainTileInfo = false; //!< Whether to draw extra information about terrain tiles to the textures - useful for debugging
bool mShowCameraViewCenter = false; //!< Whether to show camera view center as a sphere - useful for debugging
bool mShowCameraRotationCenter = false; //!< Whether to show camera rotation center as a sphere - useful for debugging
bool mShowLightSources = false; //!< Whether to show the origin of light sources
bool mShowLabels = false; //!< Whether to display labels on terrain tiles
QList<QgsPointLightSettings> mPointLights; //!< List of point lights defined for the scene

View File

@ -37,6 +37,7 @@
#include "qgspoint3dsymbol.h"
#include "qgspolygon3dsymbol.h"
#include <QtMath>
#include <Qt3DExtras/QPhongMaterial>
#include <Qt3DRender/QRenderSettings>
@ -85,6 +86,51 @@ QImage Qgs3DUtils::captureSceneImage( QgsAbstract3DEngine &engine, Qgs3DMapScene
return resImage;
}
QImage Qgs3DUtils::captureSceneDepthBuffer( QgsAbstract3DEngine &engine, Qgs3DMapScene *scene )
{
QImage resImage;
QEventLoop evLoop;
// We need to change render policy to RenderPolicy::Always, since otherwise render capture node won't work
engine.renderSettings()->setRenderPolicy( Qt3DRender::QRenderSettings::RenderPolicy::Always );
auto requestImageFcn = [&engine, scene]
{
if ( scene->sceneState() == Qgs3DMapScene::Ready )
{
engine.requestCaptureImage();
}
};
auto saveImageFcn = [&evLoop, &resImage]( const QImage & img )
{
resImage = img;
evLoop.quit();
};
QMetaObject::Connection conn1 = QObject::connect( &engine, &QgsAbstract3DEngine::imageCaptured, saveImageFcn );
QMetaObject::Connection conn2;
if ( scene->sceneState() == Qgs3DMapScene::Ready )
{
requestImageFcn();
}
else
{
// first wait until scene is loaded
conn2 = QObject::connect( scene, &Qgs3DMapScene::sceneStateChanged, requestImageFcn );
}
evLoop.exec();
QObject::disconnect( conn1 );
if ( conn2 )
QObject::disconnect( conn2 );
engine.renderSettings()->setRenderPolicy( Qt3DRender::QRenderSettings::RenderPolicy::OnDemand );
return resImage;
}
bool Qgs3DUtils::exportAnimation( const Qgs3DAnimationSettings &animationSettings,
const Qgs3DMapSettings &mapSettings,
int framesPerSecond,
@ -596,3 +642,39 @@ QgsRay3D Qgs3DUtils::rayFromScreenPoint( const QPoint &point, const QSize &windo
return QgsRay3D( QVector3D( rayOriginWorld ), rayDirWorld );
}
QVector3D Qgs3DUtils::screenPointToWorldPos( const QPoint &screenPoint, double depth, const QSize &screenSize, Qt3DRender::QCamera *camera )
{
double near = camera->nearPlane();
double far = camera->farPlane();
double distance = ( 2.0 * near * far ) / ( far + near - ( depth * 2 - 1 ) * ( far - near ) );
QgsRay3D ray = Qgs3DUtils::rayFromScreenPoint( screenPoint, screenSize, camera );
double dot = QVector3D::dotProduct( ray.direction(), camera->viewVector().normalized() );
distance /= dot;
return ray.origin() + distance * ray.direction();
}
void Qgs3DUtils::pitchAndYawFromViewVector( QVector3D vect, double &pitch, double &yaw )
{
vect.normalize();
pitch = qRadiansToDegrees( qAcos( vect.y() ) );
yaw = qRadiansToDegrees( qAtan2( -vect.z(), vect.x() ) ) + 90;
}
QVector2D Qgs3DUtils::screenToTextureCoordinates( QVector2D screenXY, QSize winSize )
{
return QVector2D( screenXY.x() / winSize.width(), 1 - screenXY.y() / winSize.width() );
}
QVector2D Qgs3DUtils::textureToScreenCoordinates( QVector2D textureXY, QSize winSize )
{
return QVector2D( textureXY.x() * winSize.width(), ( 1 - textureXY.y() ) * winSize.height() );
}
double Qgs3DUtils::decodeDepth( const QColor &pixel )
{
return pixel.redF() / 255.0 / 255.0 + pixel.greenF() / 255.0 + pixel.blueF();
}

View File

@ -60,6 +60,16 @@ class _3D_EXPORT Qgs3DUtils
*/
static QImage captureSceneImage( QgsAbstract3DEngine &engine, Qgs3DMapScene *scene );
/**
* Captures the depth buffer of the current 3D scene of a 3D engine. The function waits
* until the scene is not fully loaded/updated before capturing the image.
*
* \note In order to get more precision, the depth values are encoded into RGB colors,
* use Qgs3DUtils::decodeDepth() to get the correct depth value.
* \since QGIS 3.24
*/
static QImage captureSceneDepthBuffer( QgsAbstract3DEngine &engine, Qgs3DMapScene *scene );
/**
* Captures 3D animation frames to the selected folder
*
@ -180,6 +190,42 @@ class _3D_EXPORT Qgs3DUtils
//! Convert from clicked point on the screen to a ray in world coordinates
static QgsRay3D rayFromScreenPoint( const QPoint &point, const QSize &windowSize, Qt3DRender::QCamera *camera );
/**
* Converts the clicked mouse position to the corresponding 3D world coordinates
* \since QGIS 3.24
*/
static QVector3D screenPointToWorldPos( const QPoint &screenPoint, double depth, const QSize &screenSize, Qt3DRender::QCamera *camera );
/**
* Function used to extract the pitch and yaw (also known as heading) angles in degrees from the view vector of the camera [cameraViewCenter - cameraPosition]
* \since QGIS 3.24
*/
static void pitchAndYawFromViewVector( QVector3D vect, double &pitch, double &yaw );
/**
* Converts from screen coordinates to texture coordinates
* \note Expected return values are in [0, 1] range
* \see textureToScreenCoordinates()
* \since QGIS 3.24
*/
static QVector2D screenToTextureCoordinates( QVector2D screenXY, QSize winSize );
/**
* Converts from texture coordinates coordinates to screen coordinates
* \note Expected return values are in [0, winSize.width], [0, winSize.height] range
* \see screenToTextureCoordinates()
* \since QGIS 3.24
*/
static QVector2D textureToScreenCoordinates( QVector2D textureXY, QSize winSize );
/**
* Decodes the depth value from the pixel's color value
* The depth value is encoded from OpenGL side (the depth render pass) into the 3 RGB channels to preserve precision.
*
* \since QGIS 3.24
*/
static double decodeDepth( const QColor &pixel );
};
#endif // QGS3DUTILS_H

View File

@ -40,6 +40,20 @@ void QgsAbstract3DEngine::requestCaptureImage()
} );
}
void QgsAbstract3DEngine::requestDepthBufferCapture()
{
Qt3DRender::QRenderCaptureReply *captureReply;
captureReply = mFrameGraph->depthRenderCapture()->requestCapture();
// We need to change render policy to RenderPolicy::Always, since otherwise render capture node won't work
this->renderSettings()->setRenderPolicy( Qt3DRender::QRenderSettings::RenderPolicy::Always );
connect( captureReply, &Qt3DRender::QRenderCaptureReply::completed, this, [ = ]
{
emit depthBufferCaptured( captureReply->image() );
this->renderSettings()->setRenderPolicy( Qt3DRender::QRenderSettings::RenderPolicy::OnDemand );
captureReply->deleteLater();
} );
}
void QgsAbstract3DEngine::setRenderCaptureEnabled( bool enabled )
{
mFrameGraph->setRenderCaptureEnabled( enabled );

View File

@ -19,6 +19,7 @@
#include "qgis_3d.h"
#include <QObject>
#include <QElapsedTimer>
#define SIP_NO_FILE
@ -82,6 +83,13 @@ class _3D_EXPORT QgsAbstract3DEngine : public QObject
//! Sets the size of the rendering area (in pixels)
virtual void setSize( QSize s ) = 0;
/**
* Starts a request for an image containing the depth buffer data of the engine.
* The function does not block - when the depth buffer image is captured, it is returned in depthBufferCaptured() signal.
* Only one image request can be active at a time.
*/
void requestDepthBufferCapture();
/**
* Starts a request for an image rendered by the engine.
* The function does not block - when the rendered image is captured, it is returned in imageCaptured() signal.
@ -121,6 +129,12 @@ class _3D_EXPORT QgsAbstract3DEngine : public QObject
//! Emitted after a call to requestCaptureImage() to return the captured image.
void imageCaptured( const QImage &image );
/**
* Emitted after a call to requestDepthBufferCapture() to return the captured depth buffer.
* \note The depth buffer values are encoded into RGB channels and should be decoded with Qgs3DUtils::decodeDepth()
* \since QGIS 3.24
*/
void depthBufferCaptured( const QImage &image );
protected:
QgsShadowRenderingFrameGraph *mFrameGraph = nullptr;
};

View File

@ -18,6 +18,7 @@
#include "qgsterrainentity_p.h"
#include "qgsvector3d.h"
#include "qgssettings.h"
#include "qgs3dutils.h"
#include "qgis.h"
@ -31,6 +32,9 @@
QgsCameraController::QgsCameraController( Qt3DCore::QNode *parent )
: Qt3DCore::QEntity( parent )
, mCameraBeforeRotation( new Qt3DRender::QCamera )
, mCameraBeforeDrag( new Qt3DRender::QCamera )
, mCameraBeforeZoom( new Qt3DRender::QCamera )
, mMouseDevice( new Qt3DInput::QMouseDevice() )
, mKeyboardDevice( new Qt3DInput::QKeyboardDevice() )
, mMouseHandler( new Qt3DInput::QMouseHandler )
@ -275,8 +279,38 @@ void QgsCameraController::readXml( const QDomElement &elem )
setLookingAtPoint( QgsVector3D( x, elev, y ), dist, pitch, yaw );
}
void QgsCameraController::updateCameraFromPose( bool centerPointChanged )
double QgsCameraController::cameraCenterElevation()
{
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
qWarning() << "camera position got NaN!";
return 0;
}
double res = 0.0;
if ( mCamera && mTerrainEntity )
{
// 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::rayForCameraCenter( mCamera );
if ( mTerrainEntity->rayIntersection( ray, intersectionPoint ) )
res = intersectionPoint.y();
else
res = mTerrainEntity->terrainElevationOffset();
}
if ( mCamera && !mTerrainEntity )
res = 0.0;
return res;
}
void QgsCameraController::updateCameraFromPose()
{
// Some changes to be inserted
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
@ -293,37 +327,6 @@ void QgsCameraController::updateCameraFromPose( bool centerPointChanged )
if ( mCamera )
mCameraPose.updateCamera( mCamera );
if ( mCamera && mTerrainEntity && centerPointChanged )
{
// 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;
const QgsRayCastingUtils::Ray3D ray = QgsRayCastingUtils::rayForCameraCenter( mCamera );
if ( mTerrainEntity->rayIntersection( ray, intersectionPoint ) )
{
const float dist = ( intersectionPoint - mCamera->position() ).length();
mCameraPose.setDistanceFromCenterPoint( dist );
mCameraPose.setCenterPoint( QgsVector3D( intersectionPoint ) );
mCameraPose.updateCamera( mCamera );
}
else
{
QgsVector3D centerPoint = mCameraPose.centerPoint();
centerPoint.set( centerPoint.x(), mTerrainEntity->terrainElevationOffset(), centerPoint.z() );
mCameraPose.setCenterPoint( centerPoint );
mCameraPose.updateCamera( mCamera );
}
}
if ( mCamera && !mTerrainEntity && centerPointChanged )
{
QgsVector3D centerPoint = mCameraPose.centerPoint();
centerPoint.set( centerPoint.x(), 0, centerPoint.z() );
mCameraPose.setCenterPoint( centerPoint );
mCameraPose.updateCamera( mCamera );
}
emit cameraChanged();
}
@ -347,6 +350,8 @@ void QgsCameraController::moveCameraPositionBy( const QVector3D &posDiff )
void QgsCameraController::onPositionChanged( Qt3DInput::QMouseEvent *mouse )
{
mIsInZoomInState = false;
mCumulatedWheelY = 0;
switch ( mCameraNavigationMode )
{
case TerrainBasedNavigation:
@ -379,14 +384,57 @@ void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseE
if ( ( hasLeftButton && hasShift && !hasCtrl ) || ( hasMiddleButton && !hasShift && !hasCtrl ) )
{
// rotate/tilt using mouse (camera moves as it rotates around its view center)
float pitch = mCameraPose.pitchAngle();
float yaw = mCameraPose.headingAngle();
pitch += 0.2f * dy;
yaw -= 0.2f * dx;
mCameraPose.setPitchAngle( pitch );
mCameraPose.setHeadingAngle( yaw );
updateCameraFromPose();
// rotate/tilt using mouse (camera moves as it rotates around the clicked point)
double scale = std::max( mViewport.width(), mViewport.height() );
float pitchDiff = 180 * ( mouse->y() - mMiddleButtonClickPos.y() ) / scale;
float yawDiff = -180 * ( mouse->x() - mMiddleButtonClickPos.x() ) / scale;
if ( !mDepthBufferIsReady )
return;
if ( !mRotationCenterCalculated )
{
double depth = Qgs3DUtils::decodeDepth( mDepthBufferImage.pixelColor( mMiddleButtonClickPos.x(), mMiddleButtonClickPos.y() ) );
mRotationCenter = Qgs3DUtils::screenPointToWorldPos( mMiddleButtonClickPos, depth, mViewport.size(), mCameraBeforeRotation.get() );
mRotationDistanceFromCenter = ( mRotationCenter - mCameraBeforeRotation->position() ).length();
emit cameraRotationCenterChanged( mRotationCenter );
mRotationCenterCalculated = true;
}
// First transformation : Shift camera position and view center and rotate the camera
{
QVector3D shiftVector = mRotationCenter - mCamera->viewCenter();
QVector3D newViewCenterWorld = camera()->viewCenter() + shiftVector;
QVector3D newCameraPosition = camera()->position() + shiftVector;
mCameraPose.setDistanceFromCenterPoint( ( newViewCenterWorld - newCameraPosition ).length() );
mCameraPose.setCenterPoint( newViewCenterWorld );
mCameraPose.setPitchAngle( mRotationPitch + pitchDiff );
mCameraPose.setHeadingAngle( mRotationYaw + yawDiff );
updateCameraFromPose();
}
// Second transformation : Shift camera position back
{
QgsRay3D ray = Qgs3DUtils::rayFromScreenPoint( QPoint( mMiddleButtonClickPos.x(), mMiddleButtonClickPos.y() ), mViewport.size(), mCamera );
QVector3D clickedPositionWorld = ray.origin() + mRotationDistanceFromCenter * ray.direction();
QVector3D shiftVector = clickedPositionWorld - mCamera->viewCenter();
QVector3D newViewCenterWorld = camera()->viewCenter() - shiftVector;
QVector3D newCameraPosition = camera()->position() - shiftVector;
mCameraPose.setDistanceFromCenterPoint( ( newViewCenterWorld - newCameraPosition ).length() );
mCameraPose.setCenterPoint( newViewCenterWorld );
updateCameraFromPose();
}
}
else if ( hasLeftButton && hasCtrl && !hasShift )
{
@ -394,33 +442,112 @@ void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseE
const float diffPitch = 0.2f * dy;
const float diffYaw = - 0.2f * dx;
rotateCamera( diffPitch, diffYaw );
updateCameraFromPose( true );
}
else if ( hasLeftButton && !hasShift && !hasCtrl )
{
// translation works as if one grabbed a point on the plane and dragged it
// i.e. find out x,z of the previous mouse point, find out x,z of the current mouse point
// and use the difference
// translation works as if one grabbed a point on the 3D viewer and dragged it
const float z = mLastPressedHeight;
const QPointF p1 = screen_point_to_point_on_plane( QPointF( mMousePos.x(), mMousePos.y() ), mViewport, mCamera, z );
const QPointF p2 = screen_point_to_point_on_plane( QPointF( mouse->x(), mouse->y() ), mViewport, mCamera, z );
if ( !mDepthBufferIsReady )
return;
QgsVector3D center = mCameraPose.centerPoint();
center.set( center.x() - ( p2.x() - p1.x() ), center.y(), center.z() - ( p2.y() - p1.y() ) );
mCameraPose.setCenterPoint( center );
updateCameraFromPose( true );
if ( !mDragPointCalculated )
{
mDragDepth = Qgs3DUtils::decodeDepth( mDepthBufferImage.pixelColor( mDragButtonClickPos.x(), mDragButtonClickPos.y() ) );
mDragPoint = Qgs3DUtils::screenPointToWorldPos( mDragButtonClickPos, mDragDepth, mViewport.size(), mCameraBeforeDrag.get() );
mDragPointCalculated = true;
}
QVector3D cameraBeforeDragPos = mCameraBeforeDrag->position();
QVector3D moveToPosition = Qgs3DUtils::screenPointToWorldPos( mMousePos, mDragDepth, mViewport.size(), mCameraBeforeDrag.get() );
QVector3D cameraBeforeToMoveToPos = ( moveToPosition - mCameraBeforeDrag->position() ).normalized();
QVector3D cameraBeforeToDragPointPos = ( mDragPoint - mCameraBeforeDrag->position() ).normalized();
// Make sure the rays are not horizontal (add small y shift if it is)
if ( cameraBeforeToMoveToPos.y() == 0 )
{
cameraBeforeToMoveToPos.setY( 0.01 );
cameraBeforeToMoveToPos = cameraBeforeToMoveToPos.normalized();
}
if ( cameraBeforeToDragPointPos.y() == 0 )
{
cameraBeforeToDragPointPos.setY( 0.01 );
cameraBeforeToDragPointPos = cameraBeforeToDragPointPos.normalized();
}
double d1 = ( mDragPoint.y() - cameraBeforeDragPos.y() ) / cameraBeforeToMoveToPos.y();
double d2 = ( mDragPoint.y() - cameraBeforeDragPos.y() ) / cameraBeforeToDragPointPos.y();
QVector3D from = cameraBeforeDragPos + d1 * cameraBeforeToMoveToPos;
QVector3D to = cameraBeforeDragPos + d2 * cameraBeforeToDragPointPos;
QVector3D shiftVector = to - from;
mCameraPose.setCenterPoint( mCameraBeforeDrag->viewCenter() + shiftVector );
updateCameraFromPose();
}
else if ( hasRightButton && !hasShift && !hasCtrl )
{
zoom( dy );
if ( !mDepthBufferIsReady )
return;
if ( !mDragPointCalculated )
{
double depth = Qgs3DUtils::decodeDepth( mDepthBufferImage.pixelColor( mDragButtonClickPos.x(), mDragButtonClickPos.y() ) );
mDragPoint = Qgs3DUtils::screenPointToWorldPos( mDragButtonClickPos, depth, mViewport.size(), mCameraBeforeDrag.get() );
mDragPointCalculated = true;
}
float dist = ( mCameraBeforeDrag->position() - mDragPoint ).length();
// Applies smoothing
if ( mMousePos.y() > mDragButtonClickPos.y() ) // zoom in
{
double f = ( double )( mMousePos.y() - mDragButtonClickPos.y() ) / ( double )( mViewport.height() - mDragButtonClickPos.y() );
f = std::max( 0.0, std::min( 1.0, f ) );
f = 1 - ( std::exp( -2 * f ) - 1 ) / ( std::exp( -2 ) - 1 );
dist = dist * f;
}
else // zoom out
{
double f = 1 - ( double )( mMousePos.y() ) / ( double )( mDragButtonClickPos.y() );
f = std::max( 0.0, std::min( 1.0, f ) );
f = ( std::exp( 2 * f ) - 1 ) / ( std::exp( 2 ) - 1 );
dist = dist + 2 * dist * f;
}
// First transformation : Shift camera position and view center and rotate the camera
{
QVector3D shiftVector = mDragPoint - mCamera->viewCenter();
QVector3D newViewCenterWorld = camera()->viewCenter() + shiftVector;
mCameraPose.setDistanceFromCenterPoint( dist );
mCameraPose.setCenterPoint( newViewCenterWorld );
updateCameraFromPose();
}
// Second transformation : Shift camera position back
{
QgsRay3D ray = Qgs3DUtils::rayFromScreenPoint( QPoint( mDragButtonClickPos.x(), mDragButtonClickPos.y() ), mViewport.size(), mCamera );
QVector3D clickedPositionWorld = ray.origin() + dist * ray.direction();
QVector3D shiftVector = clickedPositionWorld - mCamera->viewCenter();
QVector3D newViewCenterWorld = camera()->viewCenter() - shiftVector;
QVector3D newCameraPosition = camera()->position() - shiftVector;
mCameraPose.setDistanceFromCenterPoint( ( newViewCenterWorld - newCameraPosition ).length() );
mCameraPose.setCenterPoint( newViewCenterWorld );
updateCameraFromPose();
}
}
mMousePos = QPoint( mouse->x(), mouse->y() );
updateCameraFromPose();
}
void QgsCameraController::zoom( float factor )
{
// zoom in/out
@ -430,24 +557,88 @@ void QgsCameraController::zoom( float factor )
updateCameraFromPose();
}
void QgsCameraController::handleTerrainNavigationWheelZoom()
{
if ( !mDepthBufferIsReady )
return;
if ( !mZoomPointCalculated )
{
double depth = Qgs3DUtils::decodeDepth( mDepthBufferImage.pixelColor( mMousePos.x(), mMousePos.y() ) );
mZoomPoint = Qgs3DUtils::screenPointToWorldPos( mMousePos, depth, mViewport.size(), mCameraBeforeZoom.get() );
mZoomPointCalculated = true;
}
float f = mCumulatedWheelY / ( 120.0 * 24.0 );
double dist = ( mZoomPoint - mCameraBeforeZoom->position() ).length();
dist -= dist * f;
// First transformation : Shift camera position and view center and rotate the camera
{
QVector3D shiftVector = mZoomPoint - mCamera->viewCenter();
QVector3D newViewCenterWorld = camera()->viewCenter() + shiftVector;
mCameraPose.setDistanceFromCenterPoint( dist );
mCameraPose.setCenterPoint( newViewCenterWorld );
updateCameraFromPose();
}
// Second transformation : Shift camera position back
{
QgsRay3D ray = Qgs3DUtils::rayFromScreenPoint( QPoint( mMousePos.x(), mMousePos.y() ), mViewport.size(), mCamera );
QVector3D clickedPositionWorld = ray.origin() + dist * ray.direction();
QVector3D shiftVector = clickedPositionWorld - mCamera->viewCenter();
QVector3D newViewCenterWorld = camera()->viewCenter() - shiftVector;
QVector3D newCameraPosition = camera()->position() - shiftVector;
mCameraPose.setDistanceFromCenterPoint( ( newViewCenterWorld - newCameraPosition ).length() );
mCameraPose.setCenterPoint( newViewCenterWorld );
updateCameraFromPose();
}
}
void QgsCameraController::onWheel( Qt3DInput::QWheelEvent *wheel )
{
switch ( mCameraNavigationMode )
{
case QgsCameraController::WalkNavigation:
{
const float scaling = ( ( wheel->modifiers() & Qt::ControlModifier ) ? 0.1f : 1.0f ) / 1000.f;
const float scaling = ( ( wheel->modifiers() & Qt::ControlModifier ) != 0 ? 0.1f : 1.0f ) / 1000.f;
setCameraMovementSpeed( mCameraMovementSpeed + mCameraMovementSpeed * scaling * wheel->angleDelta().y() );
break;
}
case TerrainBasedNavigation:
{
const float scaling = ( ( wheel->modifiers() & Qt::ControlModifier ) ? 0.1f : 1.0f ) / 1000.f;
float dist = mCameraPose.distanceFromCenterPoint();
dist -= dist * scaling * wheel->angleDelta().y();
mCameraPose.setDistanceFromCenterPoint( dist );
updateCameraFromPose();
const float scaling = ( ( wheel->modifiers() & Qt::ControlModifier ) != 0 ? 0.5f : 5.f );
// Apparently angleDelta needs to be accumulated
// see: https://doc.qt.io/qt-5/qwheelevent.html#angleDelta
mCumulatedWheelY += scaling * wheel->angleDelta().y();
if ( !mIsInZoomInState )
{
mCameraPose.updateCamera( mCameraBeforeZoom.get() );
mCameraBeforeZoom->setProjectionMatrix( mCamera->projectionMatrix() );
mCameraBeforeZoom->setNearPlane( mCamera->nearPlane() );
mCameraBeforeZoom->setFarPlane( mCamera->farPlane() );
mCameraBeforeZoom->setAspectRatio( mCamera->aspectRatio() );
mCameraBeforeZoom->setFieldOfView( mCamera->fieldOfView() );
mZoomPointCalculated = false;
mIsInZoomInState = true;
mDepthBufferIsReady = false;
emit requestDepthBufferCapture();
}
else
handleTerrainNavigationWheelZoom();
break;
}
}
@ -455,15 +646,54 @@ void QgsCameraController::onWheel( Qt3DInput::QWheelEvent *wheel )
void QgsCameraController::onMousePressed( Qt3DInput::QMouseEvent *mouse )
{
Q_UNUSED( mouse )
mKeyboardHandler->setFocus( true );
if ( mouse->button() == Qt3DInput::QMouseEvent::LeftButton || mouse->button() == Qt3DInput::QMouseEvent::RightButton || mouse->button() == Qt3DInput::QMouseEvent::MiddleButton )
if ( mouse->button() == Qt3DInput::QMouseEvent::LeftButton || mouse->button() == Qt3DInput::QMouseEvent::RightButton )
{
mMousePos = QPoint( mouse->x(), mouse->y() );
mDragButtonClickPos = QPoint( mouse->x(), mouse->y() );
mPressedButton = mouse->button();
mMousePressed = true;
if ( mCaptureFpsMouseMovements )
mIgnoreNextMouseMove = true;
mCameraPose.updateCamera( mCameraBeforeDrag.get() );
mCameraBeforeDrag->setProjectionMatrix( mCamera->projectionMatrix() );
mCameraBeforeDrag->setNearPlane( mCamera->nearPlane() );
mCameraBeforeDrag->setFarPlane( mCamera->farPlane() );
mCameraBeforeDrag->setAspectRatio( mCamera->aspectRatio() );
mCameraBeforeDrag->setFieldOfView( mCamera->fieldOfView() );
mDepthBufferIsReady = false;
mDragPointCalculated = false;
emit requestDepthBufferCapture();
}
if ( mouse->button() == Qt3DInput::QMouseEvent::MiddleButton || ( ( mouse->modifiers() & Qt::ShiftModifier ) != 0 && mouse->button() == Qt3DInput::QMouseEvent::LeftButton ) )
{
mMousePos = QPoint( mouse->x(), mouse->y() );
mMiddleButtonClickPos = QPoint( mouse->x(), mouse->y() );
mPressedButton = mouse->button();
mMousePressed = true;
if ( mCaptureFpsMouseMovements )
mIgnoreNextMouseMove = true;
mDepthBufferIsReady = false;
mRotationCenterCalculated = false;
mRotationPitch = mCameraPose.pitchAngle();
mRotationYaw = mCameraPose.headingAngle();
mCameraPose.updateCamera( mCameraBeforeRotation.get() );
mCameraBeforeRotation->setProjectionMatrix( mCamera->projectionMatrix() );
mCameraBeforeRotation->setNearPlane( mCamera->nearPlane() );
mCameraBeforeRotation->setFarPlane( mCamera->farPlane() );
mCameraBeforeRotation->setAspectRatio( mCamera->aspectRatio() );
mCameraBeforeRotation->setFieldOfView( mCamera->fieldOfView() );
emit requestDepthBufferCapture();
}
}
@ -472,6 +702,9 @@ void QgsCameraController::onMouseReleased( Qt3DInput::QMouseEvent *mouse )
Q_UNUSED( mouse )
mPressedButton = Qt3DInput::QMouseEvent::NoButton;
mMousePressed = false;
mDragPointCalculated = false;
mRotationCenterCalculated = false;
}
void QgsCameraController::onKeyPressed( Qt3DInput::QKeyEvent *event )
@ -556,7 +789,7 @@ void QgsCameraController::onKeyPressedTerrainNavigation( Qt3DInput::QKeyEvent *e
const float diffPitch = ty; // down key = rotating camera down
const float diffYaw = -tx; // right key = rotating camera to the right
rotateCamera( diffPitch, diffYaw );
updateCameraFromPose( true );
updateCameraFromPose();
}
}
@ -565,7 +798,7 @@ void QgsCameraController::onKeyPressedTerrainNavigation( Qt3DInput::QKeyEvent *e
QgsVector3D center = mCameraPose.centerPoint();
center.set( center.x(), center.y() + tElev * 10, center.z() );
mCameraPose.setCenterPoint( center );
updateCameraFromPose( true );
updateCameraFromPose();
}
}
@ -719,7 +952,7 @@ void QgsCameraController::onPositionChangedFlyNavigation( Qt3DInput::QMouseEvent
const float diffYaw = - 0.2f * dx;
rotateCamera( diffPitch, diffYaw );
updateCameraFromPose( false );
updateCameraFromPose();
}
else if ( mouse->buttons() & Qt::LeftButton )
{
@ -736,7 +969,7 @@ void QgsCameraController::onPositionChangedFlyNavigation( Qt3DInput::QMouseEvent
}
const float diffYaw = - 0.2f * dx;
rotateCamera( diffPitch, diffYaw );
updateCameraFromPose( false );
updateCameraFromPose();
}
}
@ -779,8 +1012,6 @@ void QgsCameraController::rotateAroundViewCenter( float deltaYaw )
yaw -= deltaYaw; // right key = moving camera clockwise
mCameraPose.setHeadingAngle( yaw );
updateCameraFromPose();
qInfo() << "Delta yaw: " << deltaYaw;
qInfo() << "Yaw: " << yaw;
}
void QgsCameraController::setCameraHeadingAngle( float angle )
@ -805,7 +1036,7 @@ void QgsCameraController::moveView( float tx, float ty )
QgsVector3D center = mCameraPose.centerPoint();
center.set( center.x() + dx, center.y(), center.z() + dy );
mCameraPose.setCenterPoint( center );
updateCameraFromPose( true );
updateCameraFromPose();
}
bool QgsCameraController::willHandleKeyEvent( QKeyEvent *event )
@ -862,3 +1093,12 @@ bool QgsCameraController::willHandleKeyEvent( QKeyEvent *event )
}
return false;
}
void QgsCameraController::depthBufferCaptured( const QImage &depthImage )
{
mDepthBufferImage = depthImage;
mDepthBufferIsReady = true;
if ( mIsInZoomInState )
handleTerrainNavigationWheelZoom();
}

View File

@ -22,6 +22,7 @@
#include <QRect>
#include <Qt3DCore/QEntity>
#include <Qt3DInput/QMouseEvent>
#include <QImage>
namespace Qt3DInput
{
@ -214,9 +215,15 @@ class _3D_EXPORT QgsCameraController : public Qt3DCore::QEntity
*/
void setCameraNavigationMode( QgsCameraController::NavigationMode navigationMode );
/**
* Sets the depth buffer image used by the camera controller to calculate world position from a pixel's coordinates and depth
* \since QGIS 3.24
*/
void depthBufferCaptured( const QImage &depthImage );
private:
void rotateCamera( float diffPitch, float diffYaw );
void updateCameraFromPose( bool centerPointChanged = false );
void updateCameraFromPose();
void moveCameraPositionBy( const QVector3D &posDiff );
signals:
@ -238,6 +245,18 @@ class _3D_EXPORT QgsCameraController : public Qt3DCore::QEntity
*/
void setCursorPosition( QPoint point );
/**
* Emitted to ask for the depth buffer image
* \since QGIS 3.24
*/
void requestDepthBufferCapture();
/**
* Emitted when the camera rotation center changes
* \since QGIS 3.24
*/
void cameraRotationCenterChanged( QVector3D position );
private slots:
void onPositionChanged( Qt3DInput::QMouseEvent *mouse );
void onWheel( Qt3DInput::QWheelEvent *wheel );
@ -254,6 +273,10 @@ class _3D_EXPORT QgsCameraController : public Qt3DCore::QEntity
void onPositionChangedFlyNavigation( Qt3DInput::QMouseEvent *mouse );
void onPositionChangedTerrainNavigation( Qt3DInput::QMouseEvent *mouse );
void handleTerrainNavigationWheelZoom();
double cameraCenterElevation();
private:
//! Camera that is being controlled
Qt3DRender::QCamera *mCamera = nullptr;
@ -272,6 +295,28 @@ class _3D_EXPORT QgsCameraController : public Qt3DCore::QEntity
bool mMousePressed = false;
Qt3DInput::QMouseEvent::Buttons mPressedButton = Qt3DInput::QMouseEvent::Buttons::NoButton;
bool mDepthBufferIsReady = false;
QImage mDepthBufferImage;
QPoint mMiddleButtonClickPos;
bool mRotationCenterCalculated = false;
QVector3D mRotationCenter;
double mRotationDistanceFromCenter;
double mRotationPitch = 0;
double mRotationYaw = 0;
std::unique_ptr< Qt3DRender::QCamera > mCameraBeforeRotation;
QPoint mDragButtonClickPos;
std::unique_ptr< Qt3DRender::QCamera > mCameraBeforeDrag;
bool mDragPointCalculated = false;
QVector3D mDragPoint;
double mDragDepth;
bool mIsInZoomInState = false;
std::unique_ptr< Qt3DRender::QCamera > mCameraBeforeZoom;
bool mZoomPointCalculated = false;
QVector3D mZoomPoint;
//! Delegates mouse events to the attached MouseHandler objects
Qt3DInput::QMouseDevice *mMouseDevice = nullptr;
Qt3DInput::QKeyboardDevice *mKeyboardDevice = nullptr;
@ -286,6 +331,9 @@ class _3D_EXPORT QgsCameraController : public Qt3DCore::QEntity
bool mCaptureFpsMouseMovements = false;
bool mIgnoreNextMouseMove = false;
QTimer *mFpsNavTimer = nullptr;
double mCumulatedWheelY = 0;
};
#endif // QGSCAMERACONTROLLER_H

View File

@ -24,9 +24,10 @@
#include <Qt3DRender/QShaderProgram>
#include <QMatrix4x4>
#include <QUrl>
#include <QVector2D>
QgsPreviewQuad::QgsPreviewQuad( Qt3DRender::QAbstractTexture *texture,
const QPointF &centerNDC, const QSizeF &size,
const QPointF &centerTexCoords, const QSizeF &sizeTexCoords,
QVector<Qt3DRender::QParameter *> additionalShaderParameters,
Qt3DCore::QEntity *parent )
: Qt3DCore::QEntity( parent )
@ -58,31 +59,27 @@ QgsPreviewQuad::QgsPreviewQuad( Qt3DRender::QAbstractTexture *texture,
addComponent( renderer );
QMatrix4x4 modelMatrix;
modelMatrix.setToIdentity();
modelMatrix.translate( centerNDC.x(), centerNDC.y() );
modelMatrix.scale( size.width(), size.height() );
mMaterial = new QgsPreviewQuadMaterial( texture, modelMatrix, additionalShaderParameters );
mMaterial = new QgsPreviewQuadMaterial( texture, additionalShaderParameters );
addComponent( mMaterial );
setViewPort( centerTexCoords, sizeTexCoords );
}
void QgsPreviewQuad::setViewPort( const QPointF &centerNDC, const QSizeF &size )
void QgsPreviewQuad::setViewPort( const QPointF &centerTexCoords, const QSizeF &sizeTexCoords )
{
QMatrix4x4 modelMatrix;
modelMatrix.setToIdentity();
modelMatrix.translate( centerNDC.x(), centerNDC.y() );
modelMatrix.scale( size.width(), size.height() );
mMaterial->setModelMatrix( modelMatrix );
mMaterial->setViewPort( QVector2D( centerTexCoords.x(), centerTexCoords.y() ), QVector2D( sizeTexCoords.width(), sizeTexCoords.height() ) );
}
QgsPreviewQuadMaterial::QgsPreviewQuadMaterial( Qt3DRender::QAbstractTexture *texture, const QMatrix4x4 &modelMatrix, QVector<Qt3DRender::QParameter *> additionalShaderParameters, QNode *parent )
QgsPreviewQuadMaterial::QgsPreviewQuadMaterial( Qt3DRender::QAbstractTexture *texture, QVector<Qt3DRender::QParameter *> additionalShaderParameters, QNode *parent )
: Qt3DRender::QMaterial( parent )
{
mTextureParameter = new Qt3DRender::QParameter( "previewTexture", texture );
mTextureTransformParameter = new Qt3DRender::QParameter( "modelMatrix", QVariant::fromValue( modelMatrix ) );
mCenterTextureCoords = new Qt3DRender::QParameter( "centerTexCoords", QVector2D( 0, 0 ) );
mSizeTextureCoords = new Qt3DRender::QParameter( "sizeTexCoords", QVector2D( 1, 1 ) );
addParameter( mTextureParameter );
addParameter( mTextureTransformParameter );
addParameter( mCenterTextureCoords );
addParameter( mSizeTextureCoords );
for ( Qt3DRender::QParameter *parameter : additionalShaderParameters ) addParameter( parameter );
mEffect = new Qt3DRender::QEffect;
@ -108,7 +105,8 @@ QgsPreviewQuadMaterial::QgsPreviewQuadMaterial( Qt3DRender::QAbstractTexture *te
setEffect( mEffect );
}
void QgsPreviewQuadMaterial::setModelMatrix( const QMatrix4x4 &modelMatrix )
void QgsPreviewQuadMaterial::setViewPort( QVector2D centerTexCoords, QVector2D sizeTexCoords )
{
mTextureTransformParameter->setValue( modelMatrix );
mCenterTextureCoords->setValue( centerTexCoords );
mSizeTextureCoords->setValue( sizeTexCoords );
}

View File

@ -34,14 +34,16 @@ class QgsPreviewQuadMaterial : public Qt3DRender::QMaterial
{
public:
//! Constructor
QgsPreviewQuadMaterial( Qt3DRender::QAbstractTexture *texture, const QMatrix4x4 &modelMatrix, QVector<Qt3DRender::QParameter *> additionalShaderParameters = QVector<Qt3DRender::QParameter *>(), QNode *parent = nullptr );
QgsPreviewQuadMaterial( Qt3DRender::QAbstractTexture *texture, QVector<Qt3DRender::QParameter *> additionalShaderParameters = QVector<Qt3DRender::QParameter *>(), QNode *parent = nullptr );
//! Sets the model matrix of the quad
void setModelMatrix( const QMatrix4x4 &modelMatrix );
//! Sets the view port of the quad
void setViewPort( QVector2D centerTexCoords, QVector2D sizeTexCoords );
private:
Qt3DRender::QEffect *mEffect = nullptr;
Qt3DRender::QParameter *mTextureParameter = nullptr;
Qt3DRender::QParameter *mTextureTransformParameter = nullptr;
Qt3DRender::QParameter *mCenterTextureCoords = nullptr;
Qt3DRender::QParameter *mSizeTextureCoords = nullptr;
};
/**

View File

@ -20,6 +20,13 @@
#include "qgsrectangle.h"
#include "qgspostprocessingentity.h"
#include "qgspreviewquad.h"
#include "qgs3dutils.h"
#include <Qt3DRender/QAttribute>
#include <Qt3DRender/QBuffer>
#include <Qt3DRender/QTechnique>
#include <Qt3DRender/QGraphicsApiFilter>
Qt3DRender::QFrameGraphNode *QgsShadowRenderingFrameGraph::constructTexturesPreviewPass()
{
@ -39,13 +46,16 @@ Qt3DRender::QFrameGraphNode *QgsShadowRenderingFrameGraph::constructTexturesPrev
Qt3DRender::QFrameGraphNode *QgsShadowRenderingFrameGraph::constructForwardRenderPass()
{
mForwardRenderLayerFilter = new Qt3DRender::QLayerFilter;
mMainCameraSelector = new Qt3DRender::QCameraSelector;
mMainCameraSelector->setCamera( mMainCamera );
mForwardRenderLayerFilter = new Qt3DRender::QLayerFilter( mMainCameraSelector );
mForwardRenderLayerFilter->addLayer( mForwardRenderLayer );
mForwardColorTexture = new Qt3DRender::QTexture2D;
mForwardColorTexture->setWidth( mSize.width() );
mForwardColorTexture->setHeight( mSize.height() );
mForwardColorTexture->setFormat( Qt3DRender::QAbstractTexture::RGBA16F );
mForwardColorTexture->setFormat( Qt3DRender::QAbstractTexture::RGB8_UNorm );
mForwardColorTexture->setGenerateMipMaps( false );
mForwardColorTexture->setMagnificationFilter( Qt3DRender::QTexture2D::Linear );
mForwardColorTexture->setMinificationFilter( Qt3DRender::QTexture2D::Linear );
@ -62,75 +72,84 @@ Qt3DRender::QFrameGraphNode *QgsShadowRenderingFrameGraph::constructForwardRende
mForwardDepthTexture->wrapMode()->setX( Qt3DRender::QTextureWrapMode::ClampToEdge );
mForwardDepthTexture->wrapMode()->setY( Qt3DRender::QTextureWrapMode::ClampToEdge );
mForwardRenderTarget = new Qt3DRender::QRenderTarget;
mForwardRenderTargetDepthOutput = new Qt3DRender::QRenderTargetOutput;
mForwardRenderTargetDepthOutput->setAttachmentPoint( Qt3DRender::QRenderTargetOutput::Depth );
mForwardRenderTargetDepthOutput->setTexture( mForwardDepthTexture );
mForwardRenderTarget->addOutput( mForwardRenderTargetDepthOutput );
mForwardRenderTargetColorOutput = new Qt3DRender::QRenderTargetOutput;
mForwardRenderTargetColorOutput->setAttachmentPoint( Qt3DRender::QRenderTargetOutput::Color0 );
mForwardRenderTargetColorOutput->setTexture( mForwardColorTexture );
mForwardRenderTarget->addOutput( mForwardRenderTargetColorOutput );
Qt3DRender::QRenderTarget *forwardRenderTarget = new Qt3DRender::QRenderTarget;
Qt3DRender::QRenderTargetOutput *forwardRenderTargetDepthOutput = new Qt3DRender::QRenderTargetOutput;
forwardRenderTargetDepthOutput->setAttachmentPoint( Qt3DRender::QRenderTargetOutput::Depth );
forwardRenderTargetDepthOutput->setTexture( mForwardDepthTexture );
forwardRenderTarget->addOutput( forwardRenderTargetDepthOutput );
Qt3DRender::QRenderTargetOutput *forwardRenderTargetColorOutput = new Qt3DRender::QRenderTargetOutput;
forwardRenderTargetColorOutput->setAttachmentPoint( Qt3DRender::QRenderTargetOutput::Color0 );
forwardRenderTargetColorOutput->setTexture( mForwardColorTexture );
forwardRenderTarget->addOutput( forwardRenderTargetColorOutput );
mForwardRenderTargetSelector = new Qt3DRender::QRenderTargetSelector( mForwardRenderLayerFilter );
mForwardRenderTargetSelector->setTarget( mForwardRenderTarget );
mForwardRenderTargetSelector->setTarget( forwardRenderTarget );
mForwardClearBuffers = new Qt3DRender::QClearBuffers( mForwardRenderTargetSelector );
mForwardClearBuffers->setClearColor( QColor::fromRgbF( 0.0, 1.0, 0.0, 1.0 ) );
mForwardClearBuffers->setClearColor( QColor::fromRgbF( 0.0, 0.0, 1.0, 1.0 ) );
mForwardClearBuffers->setBuffers( Qt3DRender::QClearBuffers::ColorDepthBuffer );
mForwardClearBuffers->setClearDepthValue( 1.0f );
mFrustumCulling = new Qt3DRender::QFrustumCulling( mForwardClearBuffers );
return mForwardRenderLayerFilter;
return mMainCameraSelector;
}
Qt3DRender::QFrameGraphNode *QgsShadowRenderingFrameGraph::constructShadowRenderPass()
{
mShadowSceneEntitiesFilter = new Qt3DRender::QLayerFilter;
mLightCameraSelectorShadowPass = new Qt3DRender::QCameraSelector;
mLightCameraSelectorShadowPass->setCamera( mLightCamera );
mShadowSceneEntitiesFilter = new Qt3DRender::QLayerFilter( mLightCameraSelectorShadowPass );
mShadowSceneEntitiesFilter->addLayer( mCastShadowsLayer );
mShadowMapTexture = new Qt3DRender::QTexture2D;
mShadowMapTexture->setWidth( mShadowMapResolution );
mShadowMapTexture->setHeight( mShadowMapResolution );
mShadowMapTexture->setFormat( Qt3DRender::QTexture2D::TextureFormat::D32F );
mShadowMapTexture->setFormat( Qt3DRender::QTexture2D::TextureFormat::DepthFormat );
mShadowMapTexture->setGenerateMipMaps( false );
mShadowMapTexture->setMagnificationFilter( Qt3DRender::QTexture2D::Linear );
mShadowMapTexture->setMinificationFilter( Qt3DRender::QTexture2D::Linear );
mShadowMapTexture->wrapMode()->setX( Qt3DRender::QTextureWrapMode::ClampToEdge );
mShadowMapTexture->wrapMode()->setY( Qt3DRender::QTextureWrapMode::ClampToEdge );
mShadowRenderTarget = new Qt3DRender::QRenderTarget;
mShadowRenderTargetOutput = new Qt3DRender::QRenderTargetOutput;
mShadowRenderTargetOutput->setAttachmentPoint( Qt3DRender::QRenderTargetOutput::Depth );
mShadowRenderTargetOutput->setTexture( mShadowMapTexture );
mShadowRenderTarget->addOutput( mShadowRenderTargetOutput );
Qt3DRender::QRenderTarget *shadowRenderTarget = new Qt3DRender::QRenderTarget;
Qt3DRender::QRenderTargetOutput *shadowRenderTargetOutput = new Qt3DRender::QRenderTargetOutput;
shadowRenderTargetOutput->setAttachmentPoint( Qt3DRender::QRenderTargetOutput::Depth );
shadowRenderTargetOutput->setTexture( mShadowMapTexture );
shadowRenderTarget->addOutput( shadowRenderTargetOutput );
mShadowRenderTargetSelector = new Qt3DRender::QRenderTargetSelector( mShadowSceneEntitiesFilter );
mShadowRenderTargetSelector->setTarget( mShadowRenderTarget );
mShadowRenderTargetSelector->setTarget( shadowRenderTarget );
mShadowClearBuffers = new Qt3DRender::QClearBuffers( mShadowRenderTargetSelector );
mShadowClearBuffers ->setBuffers( Qt3DRender::QClearBuffers::BufferType::ColorDepthBuffer );
mShadowClearBuffers->setBuffers( Qt3DRender::QClearBuffers::BufferType::ColorDepthBuffer );
mShadowClearBuffers->setClearColor( QColor::fromRgbF( 0.0f, 1.0f, 0.0f ) );
mShadowRenderStateSet = new Qt3DRender::QRenderStateSet( mShadowClearBuffers );
mShadowDepthTest = new Qt3DRender::QDepthTest;
mShadowDepthTest->setDepthFunction( Qt3DRender::QDepthTest::Less );
mShadowRenderStateSet->addRenderState( mShadowDepthTest );
mShadowCullFace = new Qt3DRender::QCullFace;
mShadowCullFace->setMode( Qt3DRender::QCullFace::NoCulling );
mShadowRenderStateSet->addRenderState( mShadowCullFace );
return mShadowSceneEntitiesFilter;
Qt3DRender::QDepthTest *shadowDepthTest = new Qt3DRender::QDepthTest;
shadowDepthTest->setDepthFunction( Qt3DRender::QDepthTest::Less );
mShadowRenderStateSet->addRenderState( shadowDepthTest );
Qt3DRender::QCullFace *shadowCullFace = new Qt3DRender::QCullFace;
shadowCullFace->setMode( Qt3DRender::QCullFace::NoCulling );
mShadowRenderStateSet->addRenderState( shadowCullFace );
return mLightCameraSelectorShadowPass;
}
Qt3DRender::QFrameGraphNode *QgsShadowRenderingFrameGraph::constructPostprocessingPass()
{
mPostprocessPassLayerFilter = new Qt3DRender::QLayerFilter;
mPostProcessingCameraSelector = new Qt3DRender::QCameraSelector;
mPostProcessingCameraSelector->setCamera( mMainCamera );
mPostprocessPassLayerFilter = new Qt3DRender::QLayerFilter( mPostProcessingCameraSelector );
mPostprocessPassLayerFilter->addLayer( mPostprocessPassLayer );
mPostprocessClearBuffers = new Qt3DRender::QClearBuffers( mPostprocessPassLayerFilter );
mPostprocessClearBuffers->setClearColor( QColor::fromRgbF( 0.0f, 0.0f, 0.0f ) );
mRenderCaptureTargetSelector = new Qt3DRender::QRenderTargetSelector( mPostprocessPassLayerFilter );
mRenderCaptureTargetSelector = new Qt3DRender::QRenderTargetSelector( mPostprocessClearBuffers );
Qt3DRender::QRenderTarget *renderTarget = new Qt3DRender::QRenderTarget( mRenderCaptureTargetSelector );
@ -170,7 +189,139 @@ Qt3DRender::QFrameGraphNode *QgsShadowRenderingFrameGraph::constructPostprocessi
mRenderCapture = new Qt3DRender::QRenderCapture( mRenderCaptureTargetSelector );
return mPostprocessPassLayerFilter;
return mPostProcessingCameraSelector;
}
Qt3DRender::QFrameGraphNode *QgsShadowRenderingFrameGraph::constructDepthRenderPass()
{
// depth buffer render to copy pass
mDepthRenderCameraSelector = new Qt3DRender::QCameraSelector;
mDepthRenderCameraSelector->setCamera( mMainCamera );
mDepthRenderStateSet = new Qt3DRender::QRenderStateSet( mDepthRenderCameraSelector );
Qt3DRender::QDepthTest *depthRenderDepthTest = new Qt3DRender::QDepthTest;
depthRenderDepthTest->setDepthFunction( Qt3DRender::QDepthTest::Always );;
Qt3DRender::QCullFace *depthRenderCullFace = new Qt3DRender::QCullFace;
depthRenderCullFace->setMode( Qt3DRender::QCullFace::NoCulling );
mDepthRenderStateSet->addRenderState( depthRenderDepthTest );
mDepthRenderStateSet->addRenderState( depthRenderCullFace );
mDepthRenderLayerFilter = new Qt3DRender::QLayerFilter( mDepthRenderStateSet );
mDepthRenderLayerFilter->addLayer( mDepthRenderPassLayer );
mDepthRenderCaptureTargetSelector = new Qt3DRender::QRenderTargetSelector( mDepthRenderLayerFilter );
Qt3DRender::QRenderTarget *depthRenderTarget = new Qt3DRender::QRenderTarget( mDepthRenderCaptureTargetSelector );
// The lifetime of the objects created here is managed
// automatically, as they become children of this object.
// Create a render target output for rendering color.
Qt3DRender::QRenderTargetOutput *colorOutput = new Qt3DRender::QRenderTargetOutput( depthRenderTarget );
colorOutput->setAttachmentPoint( Qt3DRender::QRenderTargetOutput::Color0 );
// Create a texture to render into.
mDepthRenderCaptureColorTexture = new Qt3DRender::QTexture2D( colorOutput );
mDepthRenderCaptureColorTexture->setSize( mSize.width(), mSize.height() );
mDepthRenderCaptureColorTexture->setFormat( Qt3DRender::QAbstractTexture::RGB8_UNorm );
mDepthRenderCaptureColorTexture->setMinificationFilter( Qt3DRender::QAbstractTexture::Linear );
mDepthRenderCaptureColorTexture->setMagnificationFilter( Qt3DRender::QAbstractTexture::Linear );
// Hook the texture up to our output, and the output up to this object.
colorOutput->setTexture( mDepthRenderCaptureColorTexture );
depthRenderTarget->addOutput( colorOutput );
Qt3DRender::QRenderTargetOutput *depthOutput = new Qt3DRender::QRenderTargetOutput( depthRenderTarget );
depthOutput->setAttachmentPoint( Qt3DRender::QRenderTargetOutput::Depth );
mDepthRenderCaptureDepthTexture = new Qt3DRender::QTexture2D( depthOutput );
mDepthRenderCaptureDepthTexture->setSize( mSize.width(), mSize.height() );
mDepthRenderCaptureDepthTexture->setFormat( Qt3DRender::QAbstractTexture::DepthFormat );
mDepthRenderCaptureDepthTexture->setMinificationFilter( Qt3DRender::QAbstractTexture::Linear );
mDepthRenderCaptureDepthTexture->setMagnificationFilter( Qt3DRender::QAbstractTexture::Linear );
mDepthRenderCaptureDepthTexture->setComparisonFunction( Qt3DRender::QAbstractTexture::CompareLessEqual );
mDepthRenderCaptureDepthTexture->setComparisonMode( Qt3DRender::QAbstractTexture::CompareRefToTexture );
depthOutput->setTexture( mDepthRenderCaptureDepthTexture );
depthRenderTarget->addOutput( depthOutput );
mDepthRenderCaptureTargetSelector->setTarget( depthRenderTarget );
// Note: We do not a clear buffers node since we are drawing a quad that will override the buffer's content anyway
mDepthRenderCapture = new Qt3DRender::QRenderCapture( mDepthRenderCaptureTargetSelector );
return mDepthRenderCameraSelector;
}
Qt3DCore::QEntity *QgsShadowRenderingFrameGraph::constructDepthRenderQuad()
{
Qt3DCore::QEntity *quad = new Qt3DCore::QEntity;
quad->setObjectName( "depthRenderQuad" );
Qt3DRender::QGeometry *geom = new Qt3DRender::QGeometry;
Qt3DRender::QAttribute *positionAttribute = new Qt3DRender::QAttribute;
const QVector<float> vert = { -1.0f, -1.0f, 1.0f, /**/ 1.0f, -1.0f, 1.0f, /**/ -1.0f, 1.0f, 1.0f, /**/ -1.0f, 1.0f, 1.0f, /**/ 1.0f, -1.0f, 1.0f, /**/ 1.0f, 1.0f, 1.0f };
const QByteArray vertexArr( ( const char * ) vert.constData(), vert.size() * sizeof( float ) );
Qt3DRender::QBuffer *vertexBuffer = nullptr;
vertexBuffer = new Qt3DRender::QBuffer( this );
vertexBuffer->setData( vertexArr );
positionAttribute->setName( Qt3DRender::QAttribute::defaultPositionAttributeName() );
positionAttribute->setVertexBaseType( Qt3DRender::QAttribute::Float );
positionAttribute->setVertexSize( 3 );
positionAttribute->setAttributeType( Qt3DRender::QAttribute::VertexAttribute );
positionAttribute->setBuffer( vertexBuffer );
positionAttribute->setByteOffset( 0 );
positionAttribute->setByteStride( 3 * sizeof( float ) );
positionAttribute->setCount( 6 );
geom->addAttribute( positionAttribute );
Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer;
renderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::PrimitiveType::Triangles );
renderer->setGeometry( geom );
quad->addComponent( renderer );
QMatrix4x4 modelMatrix;
modelMatrix.setToIdentity();
// construct material
Qt3DRender::QMaterial *material = new Qt3DRender::QMaterial;
Qt3DRender::QParameter *textureParameter = new Qt3DRender::QParameter( "depthTexture", mForwardDepthTexture );
Qt3DRender::QParameter *textureTransformParameter = new Qt3DRender::QParameter( "modelMatrix", QVariant::fromValue( modelMatrix ) );
material->addParameter( textureParameter );
material->addParameter( textureTransformParameter );
Qt3DRender::QEffect *effect = new Qt3DRender::QEffect;
Qt3DRender::QTechnique *technique = new Qt3DRender::QTechnique;
Qt3DRender::QGraphicsApiFilter *graphicsApiFilter = technique->graphicsApiFilter();
graphicsApiFilter->setApi( Qt3DRender::QGraphicsApiFilter::Api::OpenGL );
graphicsApiFilter->setProfile( Qt3DRender::QGraphicsApiFilter::OpenGLProfile::CoreProfile );
graphicsApiFilter->setMajorVersion( 1 );
graphicsApiFilter->setMinorVersion( 5 );
Qt3DRender::QRenderPass *renderPass = new Qt3DRender::QRenderPass;
Qt3DRender::QShaderProgram *shader = new Qt3DRender::QShaderProgram;
shader->setVertexShaderCode( Qt3DRender::QShaderProgram::loadSource( QUrl( "qrc:/shaders/depth_render.vert" ) ) );
shader->setFragmentShaderCode( Qt3DRender::QShaderProgram::loadSource( QUrl( "qrc:/shaders/depth_render.frag" ) ) );
renderPass->setShaderProgram( shader );
technique->addRenderPass( renderPass );
effect->addTechnique( technique );
material->setEffect( effect );
quad->addComponent( material );
return quad;
}
QgsShadowRenderingFrameGraph::QgsShadowRenderingFrameGraph( QSurface *surface, QSize s, Qt3DRender::QCamera *mainCamera, Qt3DCore::QEntity *root )
@ -185,11 +336,14 @@ QgsShadowRenderingFrameGraph::QgsShadowRenderingFrameGraph( QSurface *surface, Q
mPreviewLayer = new Qt3DRender::QLayer;
mCastShadowsLayer = new Qt3DRender::QLayer;
mForwardRenderLayer = new Qt3DRender::QLayer;
mDepthRenderPassLayer = new Qt3DRender::QLayer;
mPostprocessPassLayer->setRecursive( true );
mPreviewLayer->setRecursive( true );
mCastShadowsLayer->setRecursive( true );
mForwardRenderLayer->setRecursive( true );
mDepthRenderPassLayer->setRecursive( true );
mRenderSurfaceSelector = new Qt3DRender::QRenderSurfaceSelector;
@ -202,70 +356,58 @@ QgsShadowRenderingFrameGraph::QgsShadowRenderingFrameGraph( QSurface *surface, Q
mMainViewPort = new Qt3DRender::QViewport( mRenderSurfaceSelector );
mMainViewPort->setNormalizedRect( QRectF( 0.0f, 0.0f, 1.0f, 1.0f ) );
mMainCameraSelector = new Qt3DRender::QCameraSelector( mMainViewPort );
mMainCameraSelector->setCamera( mMainCamera );
// Forward render
Qt3DRender::QFrameGraphNode *forwardRenderPass = constructForwardRenderPass();
forwardRenderPass->setParent( mMainCameraSelector );
forwardRenderPass->setParent( mMainViewPort );
// shadow rendering pass
mLightCameraSelector = new Qt3DRender::QCameraSelector( mMainViewPort );
mLightCameraSelector->setCamera( mLightCamera );
Qt3DRender::QFrameGraphNode *shadowRenderPass = constructShadowRenderPass();
shadowRenderPass->setParent( mLightCameraSelector );
shadowRenderPass->setParent( mMainViewPort );
// depth buffer processing
Qt3DRender::QFrameGraphNode *depthBufferProcessingPass = constructDepthRenderPass();
depthBufferProcessingPass->setParent( mMainViewPort );
// post process
Qt3DRender::QFrameGraphNode *postprocessingPass = constructPostprocessingPass();
postprocessingPass->setParent( mLightCameraSelector );
postprocessingPass->setParent( mMainViewPort );
// textures preview pass
Qt3DRender::QFrameGraphNode *previewPass = constructTexturesPreviewPass();
previewPass->setParent( mMainViewPort );
mPostprocessingEntity = new QgsPostprocessingEntity( this, mRootEntity );
mPostprocessingEntity->addComponent( mPostprocessPassLayer );
// textures preview pass
Qt3DRender::QFrameGraphNode *previewPass = constructTexturesPreviewPass();
previewPass->setParent( mRenderSurfaceSelector );
Qt3DRender::QParameter *depthMapIsDepthParam = new Qt3DRender::QParameter( "isDepth", true );
Qt3DRender::QParameter *shadowMapIsDepthParam = new Qt3DRender::QParameter( "isDepth", true );
mDebugDepthMapPreviewQuad = this->addTexturePreviewOverlay( mForwardDepthTexture, QPointF( 0.8f, 0.8f ), QSizeF( 0.2f, 0.2f ) );
mDebugShadowMapPreviewQuad = this->addTexturePreviewOverlay( mShadowMapTexture, QPointF( -0.8f, -0.8f ), QSizeF( 0.2f, 0.2f ) );
mDebugDepthMapPreviewQuad = this->addTexturePreviewOverlay( mForwardDepthTexture, QPointF( 0.9f, 0.9f ), QSizeF( 0.1, 0.1 ), QVector<Qt3DRender::QParameter *> { depthMapIsDepthParam } );
mDebugShadowMapPreviewQuad = this->addTexturePreviewOverlay( mShadowMapTexture, QPointF( 0.9f, 0.9f ), QSizeF( 0.1, 0.1 ), QVector<Qt3DRender::QParameter *> { shadowMapIsDepthParam } );
mDebugDepthMapPreviewQuad->setEnabled( false );
mDebugShadowMapPreviewQuad->setEnabled( false );
mDepthRenderQuad = constructDepthRenderQuad();
mDepthRenderQuad->addComponent( mDepthRenderPassLayer );
mDepthRenderQuad->setParent( mRootEntity );
}
QgsPreviewQuad *QgsShadowRenderingFrameGraph::addTexturePreviewOverlay( Qt3DRender::QTexture2D *texture, const QPointF &centerNDC, const QSizeF &size, QVector<Qt3DRender::QParameter *> additionalShaderParameters )
QgsPreviewQuad *QgsShadowRenderingFrameGraph::addTexturePreviewOverlay( Qt3DRender::QTexture2D *texture, const QPointF &centerTexCoords, const QSizeF &sizeTexCoords, QVector<Qt3DRender::QParameter *> additionalShaderParameters )
{
QgsPreviewQuad *previewQuad = new QgsPreviewQuad( texture, centerNDC, size, additionalShaderParameters );
QgsPreviewQuad *previewQuad = new QgsPreviewQuad( texture, centerTexCoords, sizeTexCoords, additionalShaderParameters );
previewQuad->addComponent( mPreviewLayer );
previewQuad->setParent( mRootEntity );
mPreviewQuads.push_back( previewQuad );
return previewQuad;
}
QVector3D WorldPosFromDepth( QMatrix4x4 projMatrixInv, QMatrix4x4 viewMatrixInv, float texCoordX, float texCoordY, float depth )
{
const float z = depth * 2.0 - 1.0;
const QVector4D clipSpacePosition( texCoordX * 2.0 - 1.0, texCoordY * 2.0 - 1.0, z, 1.0 );
QVector4D viewSpacePosition = projMatrixInv * clipSpacePosition;
// Perspective division
viewSpacePosition /= viewSpacePosition.w();
QVector4D worldSpacePosition = viewMatrixInv * viewSpacePosition;
worldSpacePosition /= worldSpacePosition.w();
return QVector3D( worldSpacePosition.x(), worldSpacePosition.y(), worldSpacePosition.z() );
}
// computes the portion of the Y=y plane the camera is looking at
void calculateViewExtent( Qt3DRender::QCamera *camera, float shadowRenderingDistance, float y, float &minX, float &maxX, float &minY, float &maxY, float &minZ, float &maxZ )
{
const QVector3D cameraPos = camera->position();
const QMatrix4x4 projectionMatrix = camera->projectionMatrix();
const QMatrix4x4 viewMatrix = camera->viewMatrix();
const QMatrix4x4 projectionMatrixInv = projectionMatrix.inverted();
const QMatrix4x4 viewMatrixInv = viewMatrix.inverted();
float depth = 1.0f;
QVector4D viewCenter = viewMatrix * QVector4D( camera->viewCenter(), 1.0f );
viewCenter /= viewCenter.w();
@ -288,9 +430,7 @@ void calculateViewExtent( Qt3DRender::QCamera *camera, float shadowRenderingDist
for ( int i = 0; i < viewFrustumPoints.size(); ++i )
{
// convert from view port space to world space
viewFrustumPoints[i] = WorldPosFromDepth(
projectionMatrixInv, viewMatrixInv,
viewFrustumPoints[i].x(), viewFrustumPoints[i].y(), viewFrustumPoints[i].z() );
viewFrustumPoints[i] = viewFrustumPoints[i].unproject( viewMatrix, projectionMatrix, QRect( 0, 0, 1, 1 ) );
minX = std::min( minX, viewFrustumPoints[i].x() );
maxX = std::max( maxX, viewFrustumPoints[i].x() );
minY = std::min( minY, viewFrustumPoints[i].y() );
@ -402,16 +542,16 @@ void QgsShadowRenderingFrameGraph::setupShadowMapDebugging( bool enabled, Qt::Co
switch ( corner )
{
case Qt::Corner::TopRightCorner:
mDebugShadowMapPreviewQuad->setViewPort( QPointF( 1.0f - size, 1.0f - size ), QSizeF( size, size ) );
mDebugShadowMapPreviewQuad->setViewPort( QPointF( 1.0f - size / 2, 0.0f + size / 2 ), 0.5 * QSizeF( size, size ) );
break;
case Qt::Corner::TopLeftCorner:
mDebugShadowMapPreviewQuad->setViewPort( QPointF( -1.0f + size, 1.0f - size ), QSizeF( size, size ) );
mDebugShadowMapPreviewQuad->setViewPort( QPointF( 0.0f + size / 2, 0.0f + size / 2 ), 0.5 * QSizeF( size, size ) );
break;
case Qt::Corner::BottomRightCorner:
mDebugShadowMapPreviewQuad->setViewPort( QPointF( 1.0f - size, -1.0f + size ), QSizeF( size, size ) );
mDebugShadowMapPreviewQuad->setViewPort( QPointF( 1.0f - size / 2, 1.0f - size / 2 ), 0.5 * QSizeF( size, size ) );
break;
case Qt::Corner::BottomLeftCorner:
mDebugShadowMapPreviewQuad->setViewPort( QPointF( -1.0f + size, -1.0f + size ), QSizeF( size, size ) );
mDebugShadowMapPreviewQuad->setViewPort( QPointF( 0.0f + size / 2, 1.0f - size / 2 ), 0.5 * QSizeF( size, size ) );
break;
}
}
@ -426,16 +566,16 @@ void QgsShadowRenderingFrameGraph::setupDepthMapDebugging( bool enabled, Qt::Cor
switch ( corner )
{
case Qt::Corner::TopRightCorner:
mDebugDepthMapPreviewQuad->setViewPort( QPointF( 1.0f - size, 1.0f - size ), QSizeF( size, size ) );
mDebugDepthMapPreviewQuad->setViewPort( QPointF( 1.0f - size / 2, 0.0f + size / 2 ), 0.5 * QSizeF( size, size ) );
break;
case Qt::Corner::TopLeftCorner:
mDebugDepthMapPreviewQuad->setViewPort( QPointF( -1.0f + size, 1.0f - size ), QSizeF( size, size ) );
mDebugDepthMapPreviewQuad->setViewPort( QPointF( 0.0f + size / 2, 0.0f + size / 2 ), 0.5 * QSizeF( size, size ) );
break;
case Qt::Corner::BottomRightCorner:
mDebugDepthMapPreviewQuad->setViewPort( QPointF( 1.0f - size, -1.0f + size ), QSizeF( size, size ) );
mDebugDepthMapPreviewQuad->setViewPort( QPointF( 1.0f - size / 2, 1.0f - size / 2 ), 0.5 * QSizeF( size, size ) );
break;
case Qt::Corner::BottomLeftCorner:
mDebugDepthMapPreviewQuad->setViewPort( QPointF( -1.0f + size, -1.0f + size ), QSizeF( size, size ) );
mDebugDepthMapPreviewQuad->setViewPort( QPointF( 0.0f + size / 2, 1.0f - size / 2 ), 0.5 * QSizeF( size, size ) );
break;
}
}
@ -448,6 +588,8 @@ void QgsShadowRenderingFrameGraph::setSize( QSize s )
mForwardDepthTexture->setSize( mSize.width(), mSize.height() );
mRenderCaptureColorTexture->setSize( mSize.width(), mSize.height() );
mRenderCaptureDepthTexture->setSize( mSize.width(), mSize.height() );
mDepthRenderCaptureDepthTexture->setSize( mSize.width(), mSize.height() );
mDepthRenderCaptureColorTexture->setSize( mSize.width(), mSize.height() );
mRenderSurfaceSelector->setExternalRenderTargetSize( mSize );
}

View File

@ -90,6 +90,10 @@ class QgsShadowRenderingFrameGraph : public Qt3DCore::QEntity
//! Returns the render capture object used to take an image of the scene
Qt3DRender::QRenderCapture *renderCapture() { return mRenderCapture; }
//! Returns the render capture object used to take an image of the depth buffer of the scene
Qt3DRender::QRenderCapture *depthRenderCapture() { return mDepthRenderCapture; }
//! Returns whether frustum culling is enabled
bool frustumCullingEnabled() const { return mFrustumCullingEnabled; }
//! Sets whether frustum culling is enabled
@ -139,41 +143,59 @@ class QgsShadowRenderingFrameGraph : public Qt3DCore::QEntity
private:
Qt3DRender::QRenderSurfaceSelector *mRenderSurfaceSelector = nullptr;
Qt3DRender::QViewport *mMainViewPort = nullptr;
Qt3DRender::QCameraSelector *mMainCameraSelector = nullptr;
Qt3DRender::QLayerFilter *mForwardRenderLayerFilter = nullptr;
Qt3DRender::QRenderTargetSelector *mForwardRenderTargetSelector = nullptr;
Qt3DRender::QRenderTarget *mForwardRenderTarget = nullptr;
Qt3DRender::QRenderTargetOutput *mForwardRenderTargetColorOutput = nullptr;
Qt3DRender::QRenderTargetOutput *mForwardRenderTargetDepthOutput = nullptr;
Qt3DRender::QTexture2D *mForwardColorTexture = nullptr;
Qt3DRender::QTexture2D *mForwardDepthTexture = nullptr;
Qt3DRender::QClearBuffers *mForwardClearBuffers = nullptr;
Qt3DRender::QFrustumCulling *mFrustumCulling = nullptr;
bool mFrustumCullingEnabled = true;
Qt3DRender::QCamera *mMainCamera = nullptr;
Qt3DRender::QCamera *mLightCamera = nullptr;
// Forward rendering pass branch nodes:
Qt3DRender::QCameraSelector *mMainCameraSelector = nullptr;
Qt3DRender::QLayerFilter *mForwardRenderLayerFilter = nullptr;
Qt3DRender::QRenderTargetSelector *mForwardRenderTargetSelector = nullptr;
Qt3DRender::QClearBuffers *mForwardClearBuffers = nullptr;
Qt3DRender::QFrustumCulling *mFrustumCulling = nullptr;
// Forward rendering pass texture related objects:
Qt3DRender::QTexture2D *mForwardColorTexture = nullptr;
Qt3DRender::QTexture2D *mForwardDepthTexture = nullptr;
// Shadow rendering pass branch nodes:
Qt3DRender::QCameraSelector *mLightCameraSelectorShadowPass = nullptr;
Qt3DRender::QLayerFilter *mShadowSceneEntitiesFilter = nullptr;
Qt3DRender::QRenderTargetSelector *mShadowRenderTargetSelector = nullptr;
Qt3DRender::QClearBuffers *mShadowClearBuffers = nullptr;
Qt3DRender::QRenderStateSet *mShadowRenderStateSet = nullptr;
// Shadow rendering pass texture related objects:
Qt3DRender::QTexture2D *mShadowMapTexture = nullptr;
// - The depth buffer render pass is made to copy the depth buffer into
// an RGB texture that can be captured into a QImage and sent to the CPU for
// calculating real 3D points from mouse coordinates (for zoom, rotation, drag..)
// Depth buffer render pass branch nodes:
Qt3DRender::QCameraSelector *mDepthRenderCameraSelector = nullptr;
Qt3DRender::QRenderStateSet *mDepthRenderStateSet = nullptr;;
Qt3DRender::QLayerFilter *mDepthRenderLayerFilter = nullptr;
Qt3DRender::QRenderTargetSelector *mDepthRenderCaptureTargetSelector = nullptr;
Qt3DRender::QRenderCapture *mDepthRenderCapture = nullptr;
// Depth buffer processing pass texture related objects:
Qt3DRender::QTexture2D *mDepthRenderCaptureDepthTexture = nullptr;
Qt3DRender::QTexture2D *mDepthRenderCaptureColorTexture = nullptr;
// Post processing pass branch nodes:
Qt3DRender::QCameraSelector *mPostProcessingCameraSelector = nullptr;
Qt3DRender::QLayerFilter *mPostprocessPassLayerFilter = nullptr;
Qt3DRender::QClearBuffers *mPostprocessClearBuffers = nullptr;
Qt3DRender::QRenderTargetSelector *mRenderCaptureTargetSelector = nullptr;
Qt3DRender::QRenderCapture *mRenderCapture = nullptr;
// Post processing pass texture related objects:
Qt3DRender::QTexture2D *mRenderCaptureColorTexture = nullptr;
Qt3DRender::QTexture2D *mRenderCaptureDepthTexture = nullptr;
// texture preview
// Texture preview:
Qt3DRender::QLayerFilter *mPreviewLayerFilter = nullptr;
Qt3DRender::QRenderStateSet *mPreviewRenderStateSet = nullptr;
Qt3DRender::QDepthTest *mPreviewDepthTest = nullptr;
Qt3DRender::QCullFace *mPreviewCullFace = nullptr;
// shadow rendering pass
Qt3DRender::QRenderTargetSelector *mShadowRenderTargetSelector = nullptr;
Qt3DRender::QRenderTarget *mShadowRenderTarget = nullptr;
Qt3DRender::QRenderTargetOutput *mShadowRenderTargetOutput = nullptr;
Qt3DRender::QTexture2D *mShadowMapTexture = nullptr;
Qt3DRender::QClearBuffers *mShadowClearBuffers = nullptr;
Qt3DRender::QCamera *mLightCamera = nullptr;
Qt3DRender::QCameraSelector *mLightCameraSelector = nullptr;
Qt3DRender::QRenderTargetSelector *mRenderCaptureTargetSelector = nullptr;
Qt3DRender::QTexture2D *mRenderCaptureColorTexture = nullptr;
Qt3DRender::QTexture2D *mRenderCaptureDepthTexture = nullptr;
bool mShadowRenderingEnabled = false;
float mShadowBias = 0.00001f;
int mShadowMapResolution = 2048;
@ -187,12 +209,7 @@ class QgsShadowRenderingFrameGraph : public Qt3DCore::QEntity
QgsPreviewQuad *mDebugShadowMapPreviewQuad = nullptr;
QgsPreviewQuad *mDebugDepthMapPreviewQuad = nullptr;
Qt3DRender::QLayerFilter *mShadowSceneEntitiesFilter = nullptr;
Qt3DRender::QRenderStateSet *mShadowRenderStateSet = nullptr;
Qt3DRender::QCullFace *mShadowCullFace = nullptr;
Qt3DRender::QDepthTest *mShadowDepthTest = nullptr;
Qt3DRender::QRenderCapture *mRenderCapture = nullptr;
QEntity *mDepthRenderQuad = nullptr;
QVector3D mLightDirection = QVector3D( 0.0, -1.0f, 0.0f );
@ -202,6 +219,7 @@ class QgsShadowRenderingFrameGraph : public Qt3DCore::QEntity
Qt3DRender::QLayer *mPreviewLayer = nullptr;
Qt3DRender::QLayer *mForwardRenderLayer = nullptr;
Qt3DRender::QLayer *mCastShadowsLayer = nullptr;
Qt3DRender::QLayer *mDepthRenderPassLayer = nullptr;
QgsPostprocessingEntity *mPostprocessingEntity = nullptr;
@ -211,6 +229,9 @@ class QgsShadowRenderingFrameGraph : public Qt3DCore::QEntity
Qt3DRender::QFrameGraphNode *constructForwardRenderPass();
Qt3DRender::QFrameGraphNode *constructTexturesPreviewPass();
Qt3DRender::QFrameGraphNode *constructPostprocessingPass();
Qt3DRender::QFrameGraphNode *constructDepthRenderPass();
Qt3DCore::QEntity *constructDepthRenderQuad();
bool mRenderCaptureEnabled = true;

View File

@ -25,5 +25,7 @@
<file>shaders/phongDataDefined.frag</file>
<file>shaders/goochDataDefined.frag</file>
<file>shaders/goochDataDefined.vert</file>
<file>shaders/depth_render.frag</file>
<file>shaders/depth_render.vert</file>
</qresource>
</RCC>

View File

@ -0,0 +1,20 @@
#version 330 core
uniform sampler2D depthTexture;
in vec3 position;
in vec2 texCoord;
out vec4 fragColor;
void main()
{
float z = texture2D( depthTexture, texCoord ).r;
fragColor.b = float( int(z * 255) ) / 255.0;
z = z * 255.0 - fragColor.b * 255.0;
fragColor.g = float( int(z * 255) ) / 255.0;
z = z * 255.0 - fragColor.g * 255.0;
fragColor.r = float( int(z * 255) ) / 255.0;
z = z * 255.0 - fragColor.r * 255.0;
fragColor.a = 1;
}

View File

@ -0,0 +1,14 @@
#version 150 core
in vec3 vertexPosition;
out vec3 position;
out vec2 texCoord;
uniform mat4 modelMatrix;
void main()
{
texCoord = (vertexPosition.xy + vec2(1.0f, 1.0f)) / 2.0f;
gl_Position = modelMatrix * vec4(vertexPosition.xy, 1.0f, 1.0f);
}

View File

@ -3,7 +3,6 @@
uniform sampler2D previewTexture;
uniform bool isDepth;
in vec3 position;
in vec2 texCoord;
out vec4 fragColor;

View File

@ -1,14 +1,18 @@
#version 150 core
#version 330
in vec3 vertexPosition;
out vec3 position;
out vec2 texCoord;
uniform mat4 modelMatrix;
uniform vec2 sizeTexCoords;
uniform vec2 centerTexCoords;
void main()
{
texCoord = (vertexPosition.xy + vec2(1.0f, 1.0f)) / 2.0f;
gl_Position = modelMatrix * vec4(vertexPosition.xy, 1.0f, 1.0f);
texCoord = vec2( (vertexPosition.x + 1) / 2, 1 - (vertexPosition.y + 1) / 2 );
vec2 vertexTexCoord = centerTexCoords + vec2( vertexPosition.x * sizeTexCoords.x, vertexPosition.y * sizeTexCoords.y );
gl_Position = vec4( 2 * vertexTexCoord.x - 1, 1 - 2 * vertexTexCoord.y, 1.0f, 1.0f);
}

View File

@ -70,7 +70,24 @@ Qgs3DMapCanvas::Qgs3DMapCanvas( QWidget *parent )
mEngine->window()->setCursor( Qt::OpenHandCursor );
mEngine->window()->installEventFilter( this );
mEngine->setSize( mContainer->size() );
connect( mSplitter, &QSplitter::splitterMoved, [&]( int, int )
{
QRect viewportRect( QPoint( 0, 0 ), mContainer->size() );
mScene->cameraController()->setViewport( viewportRect );
mEngine->setSize( viewportRect.size() );
} );
connect( mNavigationWidget, &Qgs3DNavigationWidget::sizeChanged, [&]( const QSize & newSize )
{
QSize widgetSize = size();
QRect viewportRect( QPoint( 0, 0 ), QSize( widgetSize.width() - newSize.width(), widgetSize.height() ) );
if ( mScene && mScene->cameraController() )
mScene->cameraController()->setViewport( viewportRect );
mEngine->setSize( viewportRect.size() );
} );
QRect viewportRect( QPoint( 0, 0 ), mContainer->size() );
mEngine->setSize( viewportRect.size() );
}
Qgs3DMapCanvas::~Qgs3DMapCanvas()
@ -91,7 +108,7 @@ void Qgs3DMapCanvas::resizeEvent( QResizeEvent *ev )
if ( !mScene )
return;
const QRect viewportRect( QPoint( 0, 0 ), size() );
QRect viewportRect( QPoint( 0, 0 ), mContainer->size() );
mScene->cameraController()->setViewport( viewportRect );
mEngine->setSize( viewportRect.size() );
@ -103,7 +120,7 @@ void Qgs3DMapCanvas::setMap( Qgs3DMapSettings *map )
Q_ASSERT( !mMap );
Q_ASSERT( !mScene );
const QRect viewportRect( QPoint( 0, 0 ), size() );
QRect viewportRect( QPoint( 0, 0 ), mContainer->size() );
Qgs3DMapScene *newScene = new Qgs3DMapScene( *map, mEngine );
mEngine->setSize( viewportRect.size() );
@ -120,6 +137,8 @@ void Qgs3DMapCanvas::setMap( Qgs3DMapSettings *map )
delete mMap;
mMap = map;
mScene->cameraController()->setViewport( viewportRect );
resetView();
// Connect the camera to the navigation widget.
@ -131,6 +150,9 @@ void Qgs3DMapCanvas::setMap( Qgs3DMapSettings *map )
connect( cameraController(), &QgsCameraController::cameraMovementSpeedChanged, mMap, &Qgs3DMapSettings::setCameraMovementSpeed );
connect( cameraController(), &QgsCameraController::cameraMovementSpeedChanged, this, &Qgs3DMapCanvas::cameraNavigationSpeedChanged );
connect( cameraController(), &QgsCameraController::navigationModeHotKeyPressed, this, &Qgs3DMapCanvas::onNavigationModeHotKeyPressed );
connect( cameraController(), &QgsCameraController::requestDepthBufferCapture, this, &Qgs3DMapCanvas::captureDepthBuffer );
connect( mEngine, &QgsAbstract3DEngine::depthBufferCaptured, cameraController(), &QgsCameraController::depthBufferCaptured );
emit mapSettingsChanged();
}
@ -216,6 +238,20 @@ void Qgs3DMapCanvas::saveAsImage( const QString fileName, const QString fileForm
}
}
void Qgs3DMapCanvas::captureDepthBuffer()
{
// Setup a frame action that is used to wait until next frame
Qt3DLogic::QFrameAction *screenCaptureFrameAction = new Qt3DLogic::QFrameAction;
mScene->addComponent( screenCaptureFrameAction );
// Wait to have the render capture enabled in the next frame
connect( screenCaptureFrameAction, &Qt3DLogic::QFrameAction::triggered, [ = ]( float )
{
mEngine->requestDepthBufferCapture();
mScene->removeComponent( screenCaptureFrameAction );
screenCaptureFrameAction->deleteLater();
} );
}
void Qgs3DMapCanvas::setMapTool( Qgs3DMapTool *tool )
{
if ( tool == mMapTool )

View File

@ -117,6 +117,8 @@ class Qgs3DMapCanvas : public QWidget
* \since QGIS 3.18
*/
void cameraNavigationSpeedChanged( double speed );
public slots:
void captureDepthBuffer();
private slots:
void updateTemporalRange( const QgsDateTimeRange &timeRange );

View File

@ -152,6 +152,7 @@ Qgs3DMapConfigWidget::Qgs3DMapConfigWidget( Qgs3DMapSettings *map, QgsMapCanvas
chkShowTileInfo->setChecked( mMap->showTerrainTilesInfo() );
chkShowBoundingBoxes->setChecked( mMap->showTerrainBoundingBoxes() );
chkShowCameraViewCenter->setChecked( mMap->showCameraViewCenter() );
chkShowCameraRotationCenter->setChecked( mMap->showCameraRotationCenter() );
chkShowLightSourceOrigins->setChecked( mMap->showLightSourceOrigins() );
mFpsCounterCheckBox->setChecked( mMap->isFpsCounterEnabled() );
@ -336,6 +337,7 @@ void Qgs3DMapConfigWidget::apply()
mMap->setShowTerrainTilesInfo( chkShowTileInfo->isChecked() );
mMap->setShowTerrainBoundingBoxes( chkShowBoundingBoxes->isChecked() );
mMap->setShowCameraViewCenter( chkShowCameraViewCenter->isChecked() );
mMap->setShowCameraRotationCenter( chkShowCameraRotationCenter->isChecked() );
mMap->setShowLightSourceOrigins( chkShowLightSourceOrigins->isChecked() );
mMap->setIsFpsCounterEnabled( mFpsCounterCheckBox->isChecked() );
mMap->setTerrainShadingEnabled( groupTerrainShading->isChecked() );

View File

@ -247,3 +247,23 @@ void Qgs3DNavigationWidget::updateFromCamera()
mCameraInfoItemModel->setData( mCameraInfoItemModel->index( 6, 1 ), QStringLiteral( "%1" ).arg( mParent3DMapCanvas->cameraController()->lookingAtPoint().y() ) );
mCameraInfoItemModel->setData( mCameraInfoItemModel->index( 7, 1 ), QStringLiteral( "%1" ).arg( mParent3DMapCanvas->cameraController()->lookingAtPoint().z() ) );
}
void Qgs3DNavigationWidget::resizeEvent( QResizeEvent *ev )
{
QWidget::resizeEvent( ev );
QSize size = ev->size();
emit sizeChanged( size );
}
void Qgs3DNavigationWidget::hideEvent( QHideEvent *ev )
{
QWidget::hideEvent( ev );
emit sizeChanged( QSize( 0, 0 ) );
}
void Qgs3DNavigationWidget::showEvent( QShowEvent *ev )
{
QWidget::showEvent( ev );
emit sizeChanged( size() );
}

View File

@ -41,6 +41,14 @@ class Qgs3DNavigationWidget : public QWidget
*/
void updateFromCamera();
signals:
void sizeChanged( const QSize &newSize );
protected:
void resizeEvent( QResizeEvent *event ) override;
void hideEvent( QHideEvent *event ) override;
void showEvent( QShowEvent *event ) override;
private:
Qgs3DMapCanvas *mParent3DMapCanvas = nullptr;
QToolButton *mZoomInButton = nullptr;

View File

@ -161,6 +161,13 @@ class CORE_EXPORT QgsVector3D
return str;
}
/**
* Converts the current object to QVector3D
* \warning the conversion may decrease the accuracy (double to float values conversion)
* \since QGIS 3.24
*/
QVector3D toVector3D() const { return QVector3D( mX, mY, mZ ); }
#ifdef SIP_RUN
SIP_PYOBJECT __repr__();
% MethodCode

View File

@ -179,7 +179,7 @@
<item>
<widget class="QStackedWidget" name="m3DOptionsStackedWidget">
<property name="currentIndex">
<number>0</number>
<number>4</number>
</property>
<widget class="QWidget" name="mPageTerrain">
<layout class="QVBoxLayout" name="verticalLayout_61">
@ -730,8 +730,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>305</width>
<height>661</height>
<width>695</width>
<height>690</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayoutAdvanced">
@ -868,6 +868,27 @@
</property>
</widget>
</item>
<item row="10" column="0" colspan="2">
<widget class="QCheckBox" name="chkShowLightSourceOrigins">
<property name="text">
<string>Show light sources</string>
</property>
</widget>
</item>
<item row="7" column="0" colspan="2">
<widget class="QCheckBox" name="chkShowBoundingBoxes">
<property name="text">
<string>Show bounding boxes</string>
</property>
</widget>
</item>
<item row="6" column="0" colspan="2">
<widget class="QCheckBox" name="chkShowTileInfo">
<property name="text">
<string>Show map tile info</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QgsDoubleSpinBox" name="spinGroundError">
<property name="suffix">
@ -887,24 +908,17 @@
</property>
</widget>
</item>
<item row="8" column="0" colspan="2">
<widget class="QCheckBox" name="chkShowCameraViewCenter">
<item row="1" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Show camera's view center</string>
<string>Max. screen error</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_6">
<item row="5" column="0" colspan="2">
<widget class="QCheckBox" name="chkShowLabels">
<property name="text">
<string>Max. ground error</string>
</property>
</widget>
</item>
<item row="7" column="0" colspan="2">
<widget class="QCheckBox" name="chkShowBoundingBoxes">
<property name="text">
<string>Show bounding boxes</string>
<string>Show labels</string>
</property>
</widget>
</item>
@ -918,31 +932,10 @@
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="labelZoomLevels">
<item row="8" column="0" colspan="2">
<widget class="QCheckBox" name="chkShowCameraViewCenter">
<property name="text">
<string>0</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Max. screen error</string>
</property>
</widget>
</item>
<item row="6" column="0" colspan="2">
<widget class="QCheckBox" name="chkShowTileInfo">
<property name="text">
<string>Show map tile info</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<widget class="QCheckBox" name="chkShowLabels">
<property name="text">
<string>Show labels</string>
<string>Show camera's view center</string>
</property>
</widget>
</item>
@ -953,10 +946,10 @@
</property>
</widget>
</item>
<item row="9" column="0" colspan="2">
<widget class="QCheckBox" name="chkShowLightSourceOrigins">
<item row="11" column="0" colspan="2">
<widget class="QCheckBox" name="mFpsCounterCheckBox">
<property name="text">
<string>Show light sources</string>
<string>Show frames per second (FPS)</string>
</property>
</widget>
</item>
@ -967,10 +960,24 @@
</property>
</widget>
</item>
<item row="10" column="0" colspan="2">
<widget class="QCheckBox" name="mFpsCounterCheckBox">
<item row="2" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Show frames per second (FPS)</string>
<string>Max. ground error</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="labelZoomLevels">
<property name="text">
<string>0</string>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QCheckBox" name="chkShowCameraRotationCenter">
<property name="text">
<string>Show camera's rotation center</string>
</property>
</widget>
</item>