Skip to content

Commit

Permalink
Make 2d terrain rendering optional (#43489)
Browse files Browse the repository at this point in the history
  • Loading branch information
NEDJIMAbelgacem committed Jul 14, 2021
1 parent 2c6d6d8 commit 298a451
Show file tree
Hide file tree
Showing 16 changed files with 289 additions and 109 deletions.
18 changes: 18 additions & 0 deletions python/3d/auto_generated/qgs3dmapsettings.sip.in
Expand Up @@ -625,6 +625,24 @@ Sets whether FPS counter label is enabled
.. seealso:: :py:func:`isFpsCounterEnabled`

.. versionadded:: 3.18
%End

bool terrainRenderingEnabled();
%Docstring
Returns whether the 2D terrain surface will be rendered.

.. seealso:: :py:func:`setTerrainRenderingEnabled`

.. versionadded:: 3.22
%End

void setTerrainRenderingEnabled( bool terrainRenderingEnabled );
%Docstring
Sets whether the 2D terrain surface will be rendered in.

.. seealso:: :py:func:`terrainRenderingEnabled`

.. versionadded:: 3.22
%End

signals:
Expand Down
168 changes: 102 additions & 66 deletions src/3d/qgs3dmapscene.cpp
Expand Up @@ -37,6 +37,7 @@
#include <Qt3DRender/QDepthTest>
#include <QSurface>
#include <QUrl>
#include <QtMath>

#include <QOpenGLContext>
#include <QOpenGLFunctions>
Expand Down Expand Up @@ -238,9 +239,11 @@ Qgs3DMapScene::Qgs3DMapScene( const Qgs3DMapSettings &map, QgsAbstract3DEngine *

void Qgs3DMapScene::viewZoomFull()
{
QgsRectangle extent = mMap.terrainGenerator()->extent();
QgsRectangle extent = sceneExtent();
float side = std::max( extent.width(), extent.height() );
mCameraController->resetView( side ); // assuming FOV being 45 degrees
float a = side / 2.0f / std::sin( qDegreesToRadians( cameraController()->camera()->fieldOfView() ) / 2.0f );
// Note: the 1.5 multiplication is to move the view upwards to look better
mCameraController->resetView( 1.5 * std::sqrt( a * a - side * side ) ); // assuming FOV being 45 degrees
}

int Qgs3DMapScene::terrainPendingJobsCount() const
Expand Down Expand Up @@ -404,13 +407,12 @@ static void _updateNearFarPlane( const QList<QgsChunkNode *> &activeNodes, const
QVector4D p( ( ( i >> 0 ) & 1 ) ? bbox.xMin : bbox.xMax,
( ( i >> 1 ) & 1 ) ? bbox.yMin : bbox.yMax,
( ( i >> 2 ) & 1 ) ? bbox.zMin : bbox.zMax, 1 );

QVector4D pc = viewMatrix * p;

float dst = -pc.z(); // in camera coordinates, x grows right, y grows down, z grows to the back
if ( dst < fnear )
fnear = dst;
if ( dst > ffar )
ffar = dst;
fnear = std::min( fnear, dst );
ffar = std::max( ffar, dst );
}
}
}
Expand All @@ -429,55 +431,56 @@ bool Qgs3DMapScene::updateCameraNearFarPlanes()
// 1. precision errors - if the range is too great
// 2. unwanted clipping of scene - if the range is too small

Qt3DRender::QCamera *camera = cameraController()->camera();
QMatrix4x4 viewMatrix = camera->viewMatrix();
float fnear = 1e9;
float ffar = 0;
QList<QgsChunkNode *> activeNodes;
if ( mTerrain )
{
Qt3DRender::QCamera *camera = cameraController()->camera();
QMatrix4x4 viewMatrix = camera->viewMatrix();
float fnear = 1e9;
float ffar = 0;

QList<QgsChunkNode *> activeNodes = mTerrain->activeNodes();
activeNodes = mTerrain->activeNodes();

// it could be that there are no active nodes - they could be all culled or because root node
// is not yet loaded - we still need at least something to understand bounds of our scene
// so lets use the root node
if ( activeNodes.isEmpty() )
activeNodes << mTerrain->rootNode();
// it could be that there are no active nodes - they could be all culled or because root node
// is not yet loaded - we still need at least something to understand bounds of our scene
// so lets use the root node
if ( mTerrain && activeNodes.isEmpty() )
activeNodes << mTerrain->rootNode();

_updateNearFarPlane( activeNodes, viewMatrix, fnear, ffar );
_updateNearFarPlane( activeNodes, viewMatrix, fnear, ffar );

// Also involve all the other chunked entities to make sure that they will not get
// clipped by the near or far plane
for ( QgsChunkedEntity *e : std::as_const( mChunkEntities ) )
// Also involve all the other chunked entities to make sure that they will not get
// clipped by the near or far plane
for ( QgsChunkedEntity *e : std::as_const( mChunkEntities ) )
{
if ( e != mTerrain )
{
if ( e != mTerrain )
_updateNearFarPlane( e->activeNodes(), viewMatrix, fnear, ffar );
QList<QgsChunkNode *> activeEntityNodes = e->activeNodes();
if ( activeEntityNodes.empty() )
activeEntityNodes << e->rootNode();
_updateNearFarPlane( activeEntityNodes, viewMatrix, fnear, ffar );
}
}

if ( fnear < 1 )
fnear = 1; // does not really make sense to use negative far plane (behind camera)
if ( fnear < 1 )
fnear = 1; // does not really make sense to use negative far plane (behind camera)

if ( fnear == 1e9 && ffar == 0 )
{
// the update didn't work out... this should not happen
// well at least temporarily use some conservative starting values
qWarning() << "oops... this should not happen! couldn't determine near/far plane. defaulting to 1...1e9";
fnear = 1;
ffar = 1e9;
}
if ( fnear == 1e9 && ffar == 0 )
{
// the update didn't work out... this should not happen
// well at least temporarily use some conservative starting values
qWarning() << "oops... this should not happen! couldn't determine near/far plane. defaulting to 1...1e9";
fnear = 1;
ffar = 1e9;
}

// set near/far plane - with some tolerance in front/behind expected near/far planes
float newFar = ffar * 2;
float newNear = fnear / 2;
if ( !qgsFloatNear( newFar, camera->farPlane() ) || !qgsFloatNear( newNear, camera->nearPlane() ) )
{
camera->setFarPlane( newFar );
camera->setNearPlane( newNear );
return true;
}
// set near/far plane - with some tolerance in front/behind expected near/far planes
float newFar = ffar * 2;
float newNear = fnear / 2;
if ( !qgsFloatNear( newFar, camera->farPlane() ) || !qgsFloatNear( newNear, camera->nearPlane() ) )
{
camera->setFarPlane( newFar );
camera->setNearPlane( newNear );
return true;
}
else
qWarning() << "no terrain - not setting near/far plane";

return false;
}
Expand Down Expand Up @@ -527,8 +530,6 @@ void Qgs3DMapScene::createTerrain()

mTerrain->deleteLater();
mTerrain = nullptr;

emit terrainEntityChanged();
}

if ( !mTerrainUpdateScheduled )
Expand All @@ -538,28 +539,38 @@ void Qgs3DMapScene::createTerrain()
mTerrainUpdateScheduled = true;
setSceneState( Updating );
}
else
{
emit terrainEntityChanged();
}
}

void Qgs3DMapScene::createTerrainDeferred()
{
double tile0width = mMap.terrainGenerator()->extent().width();
int maxZoomLevel = Qgs3DUtils::maxZoomLevel( tile0width, mMap.mapTileResolution(), mMap.maxTerrainGroundError() );
QgsAABB rootBbox = mMap.terrainGenerator()->rootChunkBbox( mMap );
float rootError = mMap.terrainGenerator()->rootChunkError( mMap );
mMap.terrainGenerator()->setupQuadtree( rootBbox, rootError, maxZoomLevel );

mTerrain = new QgsTerrainEntity( mMap );
//mTerrain->setEnabled(false);
mTerrain->setParent( this );
if ( mMap.terrainGenerator() )
{
double tile0width = mMap.terrainGenerator()->extent().width();
int maxZoomLevel = Qgs3DUtils::maxZoomLevel( tile0width, mMap.mapTileResolution(), mMap.maxTerrainGroundError() );
QgsAABB rootBbox = mMap.terrainGenerator()->rootChunkBbox( mMap );
float rootError = mMap.terrainGenerator()->rootChunkError( mMap );
mMap.terrainGenerator()->setupQuadtree( rootBbox, rootError, maxZoomLevel );

if ( mMap.showTerrainBoundingBoxes() )
mTerrain->setShowBoundingBoxes( true );
mTerrain = new QgsTerrainEntity( mMap );
mTerrain->setParent( this );
mTerrain->setShowBoundingBoxes( mMap.showTerrainBoundingBoxes() );

mCameraController->setTerrainEntity( mTerrain );
mCameraController->setTerrainEntity( mTerrain );

mChunkEntities << mTerrain;
mChunkEntities << mTerrain;

onCameraChanged(); // force update of the new terrain
connect( mTerrain, &QgsChunkedEntity::pendingJobsCountChanged, this, &Qgs3DMapScene::totalPendingJobsCountChanged );
connect( mTerrain, &QgsTerrainEntity::pendingJobsCountChanged, this, &Qgs3DMapScene::terrainPendingJobsCountChanged );
}
else
{
mTerrain = nullptr;
mCameraController->setTerrainEntity( mTerrain );
}

// make sure that renderers for layers are re-created as well
const QList<QgsMapLayer *> layers = mMap.layers();
Expand All @@ -572,12 +583,9 @@ void Qgs3DMapScene::createTerrainDeferred()
addLayerEntity( layer );
}

mTerrainUpdateScheduled = false;

connect( mTerrain, &QgsChunkedEntity::pendingJobsCountChanged, this, &Qgs3DMapScene::totalPendingJobsCountChanged );
connect( mTerrain, &QgsTerrainEntity::pendingJobsCountChanged, this, &Qgs3DMapScene::terrainPendingJobsCountChanged );

emit terrainEntityChanged();
onCameraChanged(); // force update of the new terrain
mTerrainUpdateScheduled = false;
}

void Qgs3DMapScene::onBackgroundColorChanged()
Expand Down Expand Up @@ -1107,3 +1115,31 @@ QVector<const QgsChunkNode *> Qgs3DMapScene::getLayerActiveChunkNodes( QgsMapLay
}
return chunks;
}

QgsRectangle Qgs3DMapScene::sceneExtent()
{
QgsRectangle extent;
extent.setMinimal();

for ( QgsMapLayer *layer : mLayerEntities.keys() )
{
Qt3DCore::QEntity *layerEntity = mLayerEntities[ layer ];
QgsChunkedEntity *c = qobject_cast<QgsChunkedEntity *>( layerEntity );
if ( !c )
continue;
QgsChunkNode *chunkNode = c->rootNode();
QgsAABB bbox = chunkNode->bbox();
QgsRectangle layerExtent = Qgs3DUtils::worldToLayerExtent( bbox, layer->crs(), mMap.origin(), mMap.crs(), mMap.transformContext() );
extent.combineExtentWith( layerExtent );
}

if ( QgsTerrainGenerator *terrainGenerator = mMap.terrainGenerator() )
{
QgsRectangle terrainExtent = terrainGenerator->extent();
QgsCoordinateTransform terrainToMapTransform( terrainGenerator->crs(), mMap.crs(), QgsProject::instance() );
terrainExtent = terrainToMapTransform.transformBoundingBox( terrainExtent );
extent.combineExtentWith( terrainExtent );
}

return extent;
}
9 changes: 8 additions & 1 deletion src/3d/qgs3dmapscene.h
Expand Up @@ -121,6 +121,12 @@ class _3D_EXPORT Qgs3DMapScene : public Qt3DCore::QEntity
*/
QVector<const QgsChunkNode *> getLayerActiveChunkNodes( QgsMapLayer *layer ) SIP_SKIP;

/**
* Returns the scene extent in the map's CRS
*
* \since QGIS 3.20
*/
QgsRectangle sceneExtent();
signals:
//! Emitted when the current terrain entity is replaced by a new one
void terrainEntityChanged();
Expand Down Expand Up @@ -163,14 +169,15 @@ class _3D_EXPORT Qgs3DMapScene : public Qt3DCore::QEntity
void onDebugDepthMapSettingsChanged();
void onCameraMovementSpeedChanged();

bool updateCameraNearFarPlanes();

private:
void addLayerEntity( QgsMapLayer *layer );
void removeLayerEntity( QgsMapLayer *layer );
void addCameraViewCenterEntity( Qt3DRender::QCamera *camera );
void setSceneState( SceneState state );
void updateSceneState();
void updateScene();
bool updateCameraNearFarPlanes();
void finalizeNewEntity( Qt3DCore::QEntity *newEntity );
int maximumTextureSize() const;

Expand Down
11 changes: 11 additions & 0 deletions src/3d/qgs3dmapsettings.cpp
Expand Up @@ -77,6 +77,7 @@ Qgs3DMapSettings::Qgs3DMapSettings( const Qgs3DMapSettings &other )
, mDebugDepthMapEnabled( other.mDebugDepthMapEnabled )
, mDebugDepthMapCorner( other.mDebugDepthMapCorner )
, mDebugDepthMapSize( other.mDebugDepthMapSize )
, mTerrainRenderingEnabled( other.mTerrainRenderingEnabled )
{
for ( QgsAbstract3DRenderer *renderer : std::as_const( other.mRenderers ) )
{
Expand Down Expand Up @@ -121,6 +122,7 @@ void Qgs3DMapSettings::readXml( const QDomElement &elem, const QgsReadWriteConte
mCrs.readXml( elemCrs );

QDomElement elemTerrain = elem.firstChildElement( QStringLiteral( "terrain" ) );
mTerrainRenderingEnabled = elemTerrain.attribute( QStringLiteral( "terrain-rendering-enabled" ), QStringLiteral( "1" ) ).toInt();
mTerrainVerticalScale = elemTerrain.attribute( QStringLiteral( "exaggeration" ), QStringLiteral( "1" ) ).toFloat();
mMapTileResolution = elemTerrain.attribute( QStringLiteral( "texture-size" ), QStringLiteral( "512" ) ).toInt();
mMaxTerrainScreenError = elemTerrain.attribute( QStringLiteral( "max-terrain-error" ), QStringLiteral( "3" ) ).toFloat();
Expand Down Expand Up @@ -323,6 +325,7 @@ QDomElement Qgs3DMapSettings::writeXml( QDomDocument &doc, const QgsReadWriteCon
elem.appendChild( elemCrs );

QDomElement elemTerrain = doc.createElement( QStringLiteral( "terrain" ) );
elemTerrain.setAttribute( QStringLiteral( "terrain-rendering-enabled" ), mTerrainRenderingEnabled ? 1 : 0 );
elemTerrain.setAttribute( QStringLiteral( "exaggeration" ), QString::number( mTerrainVerticalScale ) );
elemTerrain.setAttribute( QStringLiteral( "texture-size" ), mMapTileResolution );
elemTerrain.setAttribute( QStringLiteral( "max-terrain-error" ), QString::number( mMaxTerrainScreenError ) );
Expand Down Expand Up @@ -830,3 +833,11 @@ void Qgs3DMapSettings::setIsFpsCounterEnabled( bool fpsCounterEnabled )
mIsFpsCounterEnabled = fpsCounterEnabled;
emit fpsCounterEnabledChanged( mIsFpsCounterEnabled );
}

void Qgs3DMapSettings::setTerrainRenderingEnabled( bool terrainRenderingEnabled )
{
if ( terrainRenderingEnabled == mTerrainRenderingEnabled )
return;
mTerrainRenderingEnabled = terrainRenderingEnabled;
emit terrainGeneratorChanged();
}
24 changes: 22 additions & 2 deletions src/3d/qgs3dmapsettings.h
Expand Up @@ -283,7 +283,12 @@ class _3D_EXPORT Qgs3DMapSettings : public QObject, public QgsTemporalRangeObjec
*/
void setTerrainGenerator( QgsTerrainGenerator *gen SIP_TRANSFER ) SIP_SKIP;
//! Returns terrain generator. It takes care of producing terrain tiles from the input data.
QgsTerrainGenerator *terrainGenerator() const SIP_SKIP { return mTerrainGenerator.get(); }
QgsTerrainGenerator *terrainGenerator() const SIP_SKIP
{
if ( mTerrainRenderingEnabled )
return mTerrainGenerator.get();
return nullptr;
}

/**
* Sets whether terrain shading is enabled.
Expand Down Expand Up @@ -571,6 +576,20 @@ class _3D_EXPORT Qgs3DMapSettings : public QObject, public QgsTemporalRangeObjec
*/
void setIsFpsCounterEnabled( bool fpsCounterEnabled );

/**
* Returns whether the 2D terrain surface will be rendered.
* \see setTerrainRenderingEnabled()
* \since QGIS 3.22
*/
bool terrainRenderingEnabled() { return mTerrainRenderingEnabled; }

/**
* Sets whether the 2D terrain surface will be rendered in.
* \see terrainRenderingEnabled()
* \since QGIS 3.22
*/
void setTerrainRenderingEnabled( bool terrainRenderingEnabled );

signals:
//! Emitted when the background color has changed
void backgroundColorChanged();
Expand Down Expand Up @@ -732,7 +751,6 @@ class _3D_EXPORT Qgs3DMapSettings : public QObject, public QgsTemporalRangeObjec

/**
* Emitted when the FPS counter is enabled or disabled
*
* \since QGIS 3.18
*/
void fpsCounterEnabledChanged( bool fpsCounterEnabled );
Expand Down Expand Up @@ -793,6 +811,8 @@ class _3D_EXPORT Qgs3DMapSettings : public QObject, public QgsTemporalRangeObjec
bool mDebugDepthMapEnabled = false;
Qt::Corner mDebugDepthMapCorner = Qt::Corner::TopRightCorner;
double mDebugDepthMapSize = 0.2;

bool mTerrainRenderingEnabled = true;
};


Expand Down
2 changes: 2 additions & 0 deletions src/3d/qgs3dsceneexporter.cpp
Expand Up @@ -248,6 +248,8 @@ void Qgs3DSceneExporter::parseTerrain( QgsTerrainEntity *terrain, const QString
QgsChunkNode *node = terrain->rootNode();

QgsTerrainGenerator *generator = settings.terrainGenerator();
if ( !generator )
return;
QgsTerrainTileEntity *terrainTile = nullptr;
QgsTerrainTextureGenerator *textureGenerator = terrain->textureGenerator();
textureGenerator->waitForFinished();
Expand Down

0 comments on commit 298a451

Please sign in to comment.