Limit 3d scenes' 2d extent

This commit is contained in:
uclaros 2022-12-27 15:46:59 +02:00 committed by Nyall Dawson
parent 694ebda27c
commit f2067aba1e
30 changed files with 419 additions and 92 deletions

View File

@ -49,6 +49,24 @@ Writes configuration to a DOM element, to be used later with :py:func:`~Qgs3DMap
void resolveReferences( const QgsProject &project );
%Docstring
Resolves references to other objects (map layers) after the call to :py:func:`~Qgs3DMapSettings.readXml`
%End
QgsRectangle extent() const;
%Docstring
Returns the 3D scene's 2D extent in project's CRS
.. versionadded:: 3.30
%End
void setExtent( const QgsRectangle &extent );
%Docstring
Sets the 3D scene's 2D ``extent`` in project's CRS, while also setting the scene's origin to the extent's center
This needs to be called during initialization, as terrain will only be generated
within this extent and layer 3D data will only be loaded within this extent too.
.. seealso:: :py:func:`setOrigin`
.. versionadded:: 3.30
%End
void setOrigin( const QgsVector3D &origin );

View File

@ -14,8 +14,9 @@
***************************************************************************/
#include "qgschunkloader_p.h"
#include "qgschunknode_p.h"
#include "qgs3dutils.h"
#include <QVector>
///@cond PRIVATE
@ -60,7 +61,11 @@ QVector<QgsChunkNode *> QgsQuadtreeChunkLoaderFactory::createChildren( QgsChunkN
const float chZMax = dy ? bbox.zMax : zc;
const float chYMin = bbox.yMin;
const float chYMax = bbox.yMax;
children << new QgsChunkNode( childId, QgsAABB( chXMin, chYMin, chZMin, chXMax, chYMax, chZMax ), childError, node );
const QgsAABB childBbox = QgsAABB( chXMin, chYMin, chZMin, chXMax, chYMax, chZMax );
const QgsPointXY center = mExtent.center();
QgsRectangle childRect = Qgs3DUtils::worldToMapExtent( childBbox, QgsVector3D( center.x(), center.y(), 0 ) );
if ( mExtent.intersects( childRect ) )
children << new QgsChunkNode( childId, childBbox, childError, node );
}
return children;
}

View File

@ -28,6 +28,7 @@
//
#include "qgschunkqueuejob_p.h"
#include "qgsrectangle.h"
#define SIP_NO_FILE
@ -109,6 +110,7 @@ class _3D_EXPORT QgsQuadtreeChunkLoaderFactory : public QgsChunkLoaderFactory
float mRootError;
//! maximum allowed depth of quad tree
int mMaxLevel;
QgsRectangle mExtent;
};

View File

@ -94,6 +94,7 @@ Qgs3DMapSettings::Qgs3DMapSettings( const Qgs3DMapSettings &other )
, mRendererUsage( other.mRendererUsage )
, m3dAxisSettings( other.m3dAxisSettings )
, mIsDebugOverlayEnabled( other.mIsDebugOverlayEnabled )
, mExtent( other.mExtent )
{
for ( QgsAbstract3DRenderer *renderer : std::as_const( other.mRenderers ) )
{
@ -128,6 +129,13 @@ void Qgs3DMapSettings::readXml( const QDomElement &elem, const QgsReadWriteConte
elemOrigin.attribute( QStringLiteral( "y" ) ).toDouble(),
elemOrigin.attribute( QStringLiteral( "z" ) ).toDouble() );
QDomElement elemExtent = elem.firstChildElement( QStringLiteral( "extent" ) );
mExtent = QgsRectangle(
elemExtent.attribute( QStringLiteral( "xMin" ) ).toDouble(),
elemExtent.attribute( QStringLiteral( "yMin" ) ).toDouble(),
elemExtent.attribute( QStringLiteral( "xMax" ) ).toDouble(),
elemExtent.attribute( QStringLiteral( "yMax" ) ).toDouble() );
QDomElement elemCamera = elem.firstChildElement( QStringLiteral( "camera" ) );
if ( !elemCamera.isNull() )
{
@ -340,6 +348,13 @@ QDomElement Qgs3DMapSettings::writeXml( QDomDocument &doc, const QgsReadWriteCon
elemOrigin.setAttribute( QStringLiteral( "z" ), QString::number( mOrigin.z() ) );
elem.appendChild( elemOrigin );
QDomElement elemExtent = doc.createElement( QStringLiteral( "extent" ) );
elemExtent.setAttribute( QStringLiteral( "xMin" ), QString::number( mExtent.xMinimum() ) );
elemExtent.setAttribute( QStringLiteral( "yMin" ), QString::number( mExtent.yMinimum() ) );
elemExtent.setAttribute( QStringLiteral( "xMax" ), QString::number( mExtent.xMaximum() ) );
elemExtent.setAttribute( QStringLiteral( "yMax" ), QString::number( mExtent.yMaximum() ) );
elem.appendChild( elemExtent );
QDomElement elemCamera = doc.createElement( QStringLiteral( "camera" ) );
elemCamera.setAttribute( QStringLiteral( "field-of-view" ), mFieldOfView );
elemCamera.setAttribute( QStringLiteral( "projection-type" ), static_cast< int >( mProjectionType ) );
@ -484,6 +499,31 @@ void Qgs3DMapSettings::resolveReferences( const QgsProject &project )
}
}
void Qgs3DMapSettings::setExtent( const QgsRectangle &extent )
{
mExtent = extent;
const QgsPointXY center = mExtent.center();
setOrigin( QgsVector3D( center.x(), center.y(), 0 ) );
if ( mTerrainGenerator )
{
QgsRectangle terrainExtent = mExtent;
if ( mCrs != mTerrainGenerator->crs() )
{
QgsCoordinateTransform ct = QgsCoordinateTransform( mCrs, mTerrainGenerator->crs(), mTransformContext );
ct.setBallparkTransformsAreAppropriate( true );
try
{
terrainExtent = ct.transformBoundingBox( mExtent );
}
catch ( const QgsCsException & )
{
QgsDebugMsg( QStringLiteral( "Transformation of map extent to terrain crs failed." ) );
}
}
mTerrainGenerator->setExtent( terrainExtent );
}
}
QgsVector3D Qgs3DMapSettings::mapToWorldCoordinates( const QgsVector3D &mapCoords ) const
{
return Qgs3DUtils::mapToWorldCoordinates( mapCoords, mOrigin );
@ -581,11 +621,11 @@ QList<QgsMapLayer *> Qgs3DMapSettings::layers() const
void Qgs3DMapSettings::configureTerrainFromProject( QgsProjectElevationProperties *properties, const QgsRectangle &fullExtent )
{
setExtent( fullExtent );
if ( properties->terrainProvider()->type() == QLatin1String( "flat" ) )
{
QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator;
flatTerrain->setCrs( crs() );
flatTerrain->setExtent( fullExtent );
setTerrainGenerator( flatTerrain );
setTerrainElevationOffset( properties->terrainProvider()->offset() );
@ -621,7 +661,6 @@ void Qgs3DMapSettings::configureTerrainFromProject( QgsProjectElevationPropertie
{
QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator;
flatTerrain->setCrs( crs() );
flatTerrain->setExtent( fullExtent );
setTerrainGenerator( flatTerrain );
}
}
@ -684,6 +723,21 @@ void Qgs3DMapSettings::setTerrainGenerator( QgsTerrainGenerator *gen )
disconnect( mTerrainGenerator.get(), &QgsTerrainGenerator::terrainChanged, this, &Qgs3DMapSettings::terrainGeneratorChanged );
}
QgsRectangle terrainExtent = mExtent;
if ( mCrs != gen->crs() )
{
QgsCoordinateTransform ct = QgsCoordinateTransform( mCrs, gen->crs(), mTransformContext );
ct.setBallparkTransformsAreAppropriate( true );
try
{
terrainExtent = ct.transformBoundingBox( mExtent );
}
catch ( const QgsCsException & )
{
QgsDebugMsg( QStringLiteral( "Transformation of map extent to terrain crs failed." ) );
}
}
gen->setExtent( terrainExtent );
mTerrainGenerator.reset( gen );
connect( mTerrainGenerator.get(), &QgsTerrainGenerator::extentChanged, this, &Qgs3DMapSettings::terrainGeneratorChanged );
connect( mTerrainGenerator.get(), &QgsTerrainGenerator::terrainChanged, this, &Qgs3DMapSettings::terrainGeneratorChanged );

View File

@ -74,6 +74,22 @@ class _3D_EXPORT Qgs3DMapSettings : public QObject, public QgsTemporalRangeObjec
//! Resolves references to other objects (map layers) after the call to readXml()
void resolveReferences( const QgsProject &project );
/**
* Returns the 3D scene's 2D extent in project's CRS
* \since QGIS 3.30
*/
QgsRectangle extent() const { return mExtent; }
/**
* Sets the 3D scene's 2D \a extent in project's CRS, while also setting the scene's origin to the extent's center
* This needs to be called during initialization, as terrain will only be generated
* within this extent and layer 3D data will only be loaded within this extent too.
*
* \see setOrigin()
* \since QGIS 3.30
*/
void setExtent( const QgsRectangle &extent );
/**
* Sets coordinates in map CRS at which our 3D world has origin (0,0,0)
*
@ -253,7 +269,7 @@ class _3D_EXPORT Qgs3DMapSettings : public QObject, public QgsTemporalRangeObjec
float terrainElevationOffset() const { return mTerrainElevationOffset; }
/**
* Sets terrain generator.
* Sets terrain generator and sets extent() as the generator's extent.
*
* It takes care of producing terrain tiles from the input data.
* Takes ownership of the generator.
@ -262,6 +278,7 @@ class _3D_EXPORT Qgs3DMapSettings : public QObject, public QgsTemporalRangeObjec
*
* \see terrainGenerator()
* \see setTerrainRenderingEnabled()
* \see setExtent()
*/
void setTerrainGenerator( QgsTerrainGenerator *gen SIP_TRANSFER ) SIP_SKIP;
@ -941,6 +958,8 @@ class _3D_EXPORT Qgs3DMapSettings : public QObject, public QgsTemporalRangeObjec
bool mIsDebugOverlayEnabled = false;
QgsRectangle mExtent; //!< 2d extent used to limit the 3d view
};

View File

@ -35,6 +35,7 @@ QgsPointCloud3DRenderContext::QgsPointCloud3DRenderContext( const Qgs3DMapSettin
, mCoordinateTransform( coordinateTransform )
, mFeedback( new QgsFeedback )
{
updateExtent();
}
void QgsPointCloud3DRenderContext::setAttributes( const QgsPointCloudAttributeCollection &attributes )
@ -63,6 +64,7 @@ QSet<int> QgsPointCloud3DRenderContext::getFilteredOutValues() const
void QgsPointCloud3DRenderContext::setCoordinateTransform( const QgsCoordinateTransform &coordinateTransform )
{
mCoordinateTransform = coordinateTransform;
updateExtent();
}
bool QgsPointCloud3DRenderContext::isCanceled() const
@ -74,6 +76,28 @@ void QgsPointCloud3DRenderContext::cancelRendering() const
{
mFeedback->cancel();
}
void QgsPointCloud3DRenderContext::updateExtent()
{
if ( map().extent().isEmpty() )
{
// an empty extent means no filter, so let's pass it without transformation
mExtent = QgsRectangle();
}
else
{
try
{
mExtent = mCoordinateTransform.transformBoundingBox( map().extent(), Qgis::TransformDirection::Reverse );
}
catch ( const QgsCsException & )
{
// bad luck, can't reproject for some reason. Let's use an empty extent to skip filtering.
QgsDebugMsg( QStringLiteral( "Transformation of extent failed!" ) );
mExtent = QgsRectangle();
}
}
}
// ---------

View File

@ -188,7 +188,16 @@ class _3D_NO_EXPORT QgsPointCloud3DRenderContext : public Qgs3DRenderContext
* Returns the feedback object used to cancel rendering and check if rendering was canceled.
*/
QgsFeedback *feedback() const { return mFeedback.get(); }
/**
* Returns the 3D scene's extent in layer crs.
* \since QGIS 3.30
*/
QgsRectangle extent() const { return mExtent; }
private:
//! Recalculates the 3D scene's extent in layer's crs
void updateExtent();
#ifdef SIP_RUN
QgsPointCloudRenderContext( const QgsPointCloudRenderContext &rh );
#endif
@ -199,6 +208,7 @@ class _3D_NO_EXPORT QgsPointCloud3DRenderContext : public Qgs3DRenderContext
double mZValueFixedOffset = 0;
QgsCoordinateTransform mCoordinateTransform;
std::unique_ptr<QgsFeedback> mFeedback;
QgsRectangle mExtent;
};

View File

@ -139,6 +139,16 @@ QgsPointCloudLayerChunkLoaderFactory::QgsPointCloudLayerChunkLoaderFactory( cons
, mPointBudget( pointBudget )
{
mSymbol.reset( symbol );
try
{
mExtent = mCoordinateTransform.transformBoundingBox( mMap.extent(), Qgis::TransformDirection::Reverse );
}
catch ( const QgsCsException & )
{
// bad luck, can't reproject for some reason
QgsDebugMsg( QStringLiteral( "Transformation of extent failed." ) );
}
}
QgsChunkLoader *QgsPointCloudLayerChunkLoaderFactory::createChunkLoader( QgsChunkNode *node ) const
@ -182,6 +192,9 @@ QVector<QgsChunkNode *> QgsPointCloudLayerChunkLoaderFactory::createChildren( Qg
if ( !mPointCloudIndex->hasNode( IndexedPointCloudNode( childId.d, childId.x, childId.y, childId.z ) ) )
continue;
if ( !mExtent.isEmpty() &&
!mPointCloudIndex->nodeMapExtent( IndexedPointCloudNode( childId.d, childId.x, childId.y, childId.z ) ).intersects( mExtent ) )
continue;
// the Y and Z coordinates below are intentionally flipped, because
// in chunk node IDs the X,Y axes define horizontal plane,

View File

@ -82,6 +82,7 @@ class QgsPointCloudLayerChunkLoaderFactory : public QgsChunkLoaderFactory
double mZValueOffset = 0;
int mPointBudget = 1000000;
bool mTriangulate = false;
QgsRectangle mExtent; //!< This should hold the map's extent in layer's crs
};

View File

@ -166,8 +166,9 @@ QgsRuleBasedChunkLoaderFactory::QgsRuleBasedChunkLoaderFactory( const Qgs3DMapSe
, mRootRule( rootRule->clone() )
, mLeafLevel( leafLevel )
{
const QgsAABB rootBbox = Qgs3DUtils::layerToWorldExtent( vl->extent(), zMin, zMax, vl->crs(), map.origin(), map.crs(), map.transformContext() );
const QgsAABB rootBbox = Qgs3DUtils::mapToWorldExtent( map.extent(), zMin, zMax, map.origin() );
setupQuadtree( rootBbox, -1, leafLevel ); // negative root error means that the node does not contain anything
mExtent = map.extent();
}
QgsRuleBasedChunkLoaderFactory::~QgsRuleBasedChunkLoaderFactory() = default;

View File

@ -20,6 +20,7 @@
QgsTilingScheme::QgsTilingScheme( const QgsRectangle &fullExtent, const QgsCoordinateReferenceSystem &crs )
: mCrs( crs )
, mFullExtent( fullExtent )
{
mMapOrigin = QgsPointXY( fullExtent.xMinimum(), fullExtent.yMinimum() );
mBaseTileSide = std::max( fullExtent.width(), fullExtent.height() );

View File

@ -60,10 +60,19 @@ class _3D_EXPORT QgsTilingScheme
//! Returns CRS of the tiling scheme
QgsCoordinateReferenceSystem crs() const { return mCrs; }
/**
* Returns the full extent used in the constructor, which might not be square.
* Level 0 tile is centered at the full extent and the full extent completely fits into the level 0 tile
* \since QGIS 3.30
*/
QgsRectangle fullExtent() const { return mFullExtent; }
private:
QgsPointXY mMapOrigin; //!< Origin point in map coordinates: (0,0) in the tiling scheme
double mBaseTileSide = 0; //!< Length of tile side at zoom level 0 in map coordinates
QgsCoordinateReferenceSystem mCrs; //!< CRS of the coordinates
QgsRectangle mFullExtent; //!< The fullExtent used in constructor
};

View File

@ -158,7 +158,7 @@ QgsVectorLayerChunkLoaderFactory::QgsVectorLayerChunkLoaderFactory( const Qgs3DM
, mSymbol( symbol->clone() )
, mLeafLevel( leafLevel )
{
QgsAABB rootBbox = Qgs3DUtils::layerToWorldExtent( vl->extent(), zMin, zMax, vl->crs(), map.origin(), map.crs(), map.transformContext() );
QgsAABB rootBbox = Qgs3DUtils::mapToWorldExtent( map.extent(), zMin, zMax, map.origin() );
// add small padding to avoid clipping of point features located at the edge of the bounding box
rootBbox.xMin -= 1.0;
rootBbox.xMax += 1.0;
@ -167,6 +167,7 @@ QgsVectorLayerChunkLoaderFactory::QgsVectorLayerChunkLoaderFactory( const Qgs3DM
rootBbox.zMin -= 1.0;
rootBbox.zMax += 1.0;
setupQuadtree( rootBbox, -1, leafLevel ); // negative root error means that the node does not contain anything
mExtent = map.extent();
}
QgsChunkLoader *QgsVectorLayerChunkLoaderFactory::createChunkLoader( QgsChunkNode *node ) const

View File

@ -515,6 +515,7 @@ void QgsSingleColorPointCloud3DSymbolHandler::processNode( QgsPointCloudIndex *p
QgsPointCloudRequest request;
request.setAttributes( attributes );
request.setFilterRect( context.extent() );
std::unique_ptr<QgsPointCloudBlock> block( pointCloudBlock( pc, n, request, context ) );
if ( !block )
return;
@ -638,6 +639,7 @@ void QgsColorRampPointCloud3DSymbolHandler::processNode( QgsPointCloudIndex *pc,
QgsPointCloudRequest request;
request.setAttributes( attributes );
request.setFilterRect( context.extent() );
std::unique_ptr<QgsPointCloudBlock> block( pointCloudBlock( pc, n, request, context ) );
if ( !block )
return;
@ -743,6 +745,7 @@ void QgsRGBPointCloud3DSymbolHandler::processNode( QgsPointCloudIndex *pc, const
QgsPointCloudRequest request;
request.setAttributes( attributes );
request.setFilterRect( context.extent() );
std::unique_ptr<QgsPointCloudBlock> block( pointCloudBlock( pc, n, request, context ) );
if ( !block )
return;
@ -906,6 +909,7 @@ void QgsClassificationPointCloud3DSymbolHandler::processNode( QgsPointCloudIndex
QgsPointCloudRequest request;
request.setAttributes( attributes );
request.setFilterRect( context.extent() );
std::unique_ptr<QgsPointCloudBlock> block( pointCloudBlock( pc, n, request, context ) );
if ( !block )
return;

View File

@ -51,6 +51,7 @@ QgsTerrainGenerator *QgsDemTerrainGenerator::clone() const
cloned->mLayer = mLayer;
cloned->mResolution = mResolution;
cloned->mSkirtHeight = mSkirtHeight;
cloned->mExtent = mExtent;
cloned->updateGenerator();
return cloned;
}
@ -95,7 +96,8 @@ void QgsDemTerrainGenerator::readXml( const QDomElement &elem )
void QgsDemTerrainGenerator::resolveReferences( const QgsProject &project )
{
mLayer = QgsMapLayerRef( project.mapLayer( mLayer.layerId ) );
updateGenerator();
// now that we have the layer, call setExtent() again so we can keep the intersection of mExtent and layer's extent
setExtent( mExtent );
}
QgsChunkLoader *QgsDemTerrainGenerator::createChunkLoader( QgsChunkNode *node ) const
@ -104,17 +106,42 @@ QgsChunkLoader *QgsDemTerrainGenerator::createChunkLoader( QgsChunkNode *node )
return new QgsDemTerrainTileLoader( mTerrain, node, const_cast<QgsDemTerrainGenerator *>( this ) );
}
void QgsDemTerrainGenerator::setExtent( const QgsRectangle &extent )
{
if ( !mLayer )
{
// Keep the whole extent for now and setExtent() will be called by again by resolveReferences()
mExtent = extent;
return;
}
QgsRectangle layerExtent = mLayer->extent();
if ( mCrs != mLayer->crs() )
{
QgsCoordinateTransform ct( mLayer->crs(), mCrs, mTransformContext );
ct.setBallparkTransformsAreAppropriate( true );
try
{
layerExtent = ct.transformBoundingBox( layerExtent );
}
catch ( const QgsCsException & )
{
QgsDebugMsg( QStringLiteral( "Transformation of layer extent to terrain crs failed." ) );
}
}
// no need to have an mExtent larger than the actual layer's extent
mExtent = extent.intersect( layerExtent );
updateGenerator();
emit extentChanged();
}
void QgsDemTerrainGenerator::updateGenerator()
{
QgsRasterLayer *dem = layer();
if ( dem )
{
QgsRectangle te = dem->extent();
QgsCoordinateTransform terrainToMapTransform( dem->crs(), mCrs, mTransformContext );
terrainToMapTransform.setBallparkTransformsAreAppropriate( true );
te = terrainToMapTransform.transformBoundingBox( te );
mTerrainTilingScheme = QgsTilingScheme( te, mCrs );
mTerrainTilingScheme = QgsTilingScheme( mExtent, mCrs );
delete mHeightMapGenerator;
mHeightMapGenerator = new QgsDemHeightMapGenerator( dem, mTerrainTilingScheme, mResolution, mTransformContext );
mIsValid = true;

View File

@ -71,6 +71,7 @@ class _3D_EXPORT QgsDemTerrainGenerator : public QgsTerrainGenerator
QgsTerrainGenerator *clone() const override SIP_FACTORY;
Type type() const override;
QgsRectangle extent() const override;
void setExtent( const QgsRectangle &extent ) override;
float heightAt( double x, double y, const Qgs3DMapSettings &map ) const override;
void writeXml( QDomElement &elem ) const override;
void readXml( const QDomElement &elem ) override;

View File

@ -168,7 +168,7 @@ QgsDemHeightMapGenerator::~QgsDemHeightMapGenerator()
}
static QByteArray _readDtmData( QgsRasterDataProvider *provider, const QgsRectangle &extent, int res, const QgsCoordinateReferenceSystem &destCrs )
static QByteArray _readDtmData( QgsRasterDataProvider *provider, const QgsRectangle &extent, int res, const QgsCoordinateReferenceSystem &destCrs, const QgsRectangle &clippingExtent )
{
provider->moveToThread( QThread::currentThread() );
@ -190,6 +190,17 @@ static QByteArray _readDtmData( QgsRasterDataProvider *provider, const QgsRectan
if ( block )
{
block->convert( Qgis::DataType::Float32 ); // currently we expect just floats
// set noData outside our clippingExtent
QRect subRect = QgsRasterBlock::subRect( extent, block->width(), block->height(), clippingExtent );
if ( !block->hasNoDataValue() )
{
// QgsRasterBlock::setIsNoDataExcept() misbehaves without a defined no data value
// see https://github.com/qgis/QGIS/issues/51285
block->setNoDataValue( std::numeric_limits<float>::lowest() );
}
block->setIsNoDataExcept( subRect );
data = block->data();
data.detach(); // this should make a deep copy
@ -225,9 +236,9 @@ int QgsDemHeightMapGenerator::render( const QgsChunkNodeId &nodeId )
QgsRectangle extent = mTilingScheme.tileToExtent( nodeId );
float mapUnitsPerPixel = extent.width() / mResolution;
extent.grow( mapUnitsPerPixel / 2 );
// but make sure not to go beyond the full extent (returns invalid values)
QgsRectangle fullExtent = mTilingScheme.tileToExtent( 0, 0, 0 );
extent = extent.intersect( fullExtent );
// but make sure not to go beyond the root tile's full extent (returns invalid values)
QgsRectangle rootTileExtent = mTilingScheme.tileToExtent( 0, 0, 0 );
extent = extent.intersect( rootTileExtent );
JobData jd;
jd.jobId = ++mLastJobId;
@ -241,7 +252,7 @@ int QgsDemHeightMapGenerator::render( const QgsChunkNodeId &nodeId )
if ( mDtm )
{
mClonedProvider->moveToThread( nullptr );
jd.future = QtConcurrent::run( _readDtmData, mClonedProvider, extent, mResolution, mTilingScheme.crs() );
jd.future = QtConcurrent::run( _readDtmData, mClonedProvider, extent, mResolution, mTilingScheme.crs(), mTilingScheme.fullExtent() );
}
else
{

View File

@ -22,6 +22,7 @@
#include "qgschunknode_p.h"
#include "qgsterrainentity_p.h"
#include "qgsterraintileentity_p.h"
#include "qgs3dutils.h"
/// @cond PRIVATE
@ -53,7 +54,6 @@ Qt3DCore::QEntity *FlatTerrainChunkLoader::createEntity( Qt3DCore::QEntity *pare
// create material
const Qgs3DMapSettings &map = terrain()->map3D();
createTextureComponent( entity, map.isTerrainShadingEnabled(), map.terrainShadingMaterial(), !map.layers().empty() );
// create transform
@ -63,11 +63,23 @@ Qt3DCore::QEntity *FlatTerrainChunkLoader::createEntity( Qt3DCore::QEntity *pare
// set up transform according to the extent covered by the quad geometry
const QgsAABB bbox = mNode->bbox();
const double side = bbox.xMax - bbox.xMin;
const double half = side / 2;
transform->setScale( side );
transform->setTranslation( QVector3D( bbox.xMin + half, 0, bbox.zMin + half ) );
const QgsAABB mapFullExtent = Qgs3DUtils::mapToWorldExtent( map.extent(), bbox.yMin, bbox.yMax, map.origin() );
const QgsAABB commonExtent = QgsAABB( std::max( bbox.xMin, mapFullExtent.xMin ),
bbox.yMin,
std::max( bbox.zMin, mapFullExtent.zMin ),
std::min( bbox.xMax, mapFullExtent.xMax ),
bbox.yMax,
std::min( bbox.zMax, mapFullExtent.zMax )
);
const double xSide = commonExtent.xExtent();
const double zSide = commonExtent.zExtent();
transform->setScale3D( QVector3D( xSide, 1, zSide ) );
transform->setTranslation( QVector3D( commonExtent.xMin + xSide / 2, 0, commonExtent.zMin + zSide / 2 ) );
createTextureComponent( entity, map.isTerrainShadingEnabled(), map.terrainShadingMaterial(), !map.layers().empty() );
entity->setParent( parent );
return entity;
@ -109,28 +121,12 @@ void QgsFlatTerrainGenerator::rootChunkHeightRange( float &hMin, float &hMax ) c
void QgsFlatTerrainGenerator::writeXml( QDomElement &elem ) const
{
const QgsRectangle r = mExtent;
QDomElement elemExtent = elem.ownerDocument().createElement( QStringLiteral( "extent" ) );
elemExtent.setAttribute( QStringLiteral( "xmin" ), QString::number( r.xMinimum() ) );
elemExtent.setAttribute( QStringLiteral( "xmax" ), QString::number( r.xMaximum() ) );
elemExtent.setAttribute( QStringLiteral( "ymin" ), QString::number( r.yMinimum() ) );
elemExtent.setAttribute( QStringLiteral( "ymax" ), QString::number( r.yMaximum() ) );
elem.appendChild( elemExtent );
// crs is not read/written - it should be the same as destination crs of the map
Q_UNUSED( elem )
}
void QgsFlatTerrainGenerator::readXml( const QDomElement &elem )
{
const QDomElement elemExtent = elem.firstChildElement( QStringLiteral( "extent" ) );
const double xmin = elemExtent.attribute( QStringLiteral( "xmin" ) ).toDouble();
const double xmax = elemExtent.attribute( QStringLiteral( "xmax" ) ).toDouble();
const double ymin = elemExtent.attribute( QStringLiteral( "ymin" ) ).toDouble();
const double ymax = elemExtent.attribute( QStringLiteral( "ymax" ) ).toDouble();
setExtent( QgsRectangle( xmin, ymin, xmax, ymax ) );
// crs is not read/written - it should be the same as destination crs of the map
Q_UNUSED( elem )
}
void QgsFlatTerrainGenerator::setCrs( const QgsCoordinateReferenceSystem &crs )

View File

@ -20,7 +20,6 @@
#include "qgsterraingenerator.h"
#include "qgsterraintileloader_p.h"
#include "qgsrectangle.h"
#include <Qt3DExtras/QPlaneGeometry>
#define SIP_NO_FILE
@ -76,7 +75,6 @@ class _3D_EXPORT QgsFlatTerrainGenerator : public QgsTerrainGenerator
void updateTilingScheme();
QgsRectangle mExtent;
QgsCoordinateReferenceSystem mCrs;
};

View File

@ -75,7 +75,6 @@ class _3D_EXPORT QgsOnlineTerrainGenerator : public QgsTerrainGenerator
void updateGenerator();
QgsRectangle mExtent;
QgsCoordinateReferenceSystem mCrs;
QgsCoordinateTransformContext mTransformContext;

View File

@ -38,6 +38,22 @@ int QgsTerrainTextureGenerator::render( const QgsRectangle &extent, QgsChunkNode
{
QgsMapSettings mapSettings( baseMapSettings() );
mapSettings.setExtent( extent );
QSize size = QSize( mTextureSize );
if ( mMap.terrainGenerator()->type() == QgsTerrainGenerator::Flat )
{
// The flat terrain generator might have non-square tiles, clipped at the scene's extent.
// We need to produce non-square textures for those cases.
const QgsRectangle clippedExtent = extent.intersect( mMap.extent() );
if ( !qgsDoubleNear( clippedExtent.width(), clippedExtent.height() ) )
{
if ( clippedExtent.height() > clippedExtent.width() )
size.setWidth( std::round( size.width() * clippedExtent.width() / clippedExtent.height() ) );
else if ( clippedExtent.height() < clippedExtent.width() )
size.setHeight( std::round( size.height() * clippedExtent.height() / clippedExtent.width() ) );
}
mapSettings.setExtent( clippedExtent );
}
mapSettings.setOutputSize( size );
QList<QgsMapLayer *> layers = mMap.layers();
QList<QgsMapLayer *> toBeRenderedLayers;

View File

@ -308,6 +308,7 @@ void Qgs3DMapConfigWidget::apply()
{
QgsDemTerrainGenerator *demTerrainGen = new QgsDemTerrainGenerator;
demTerrainGen->setCrs( mMap->crs(), QgsProject::instance()->transformContext() );
demTerrainGen->setExtent( mMap->extent() );
demTerrainGen->setLayer( demLayer );
demTerrainGen->setResolution( spinTerrainResolution->value() );
demTerrainGen->setSkirtHeight( spinTerrainSkirtHeight->value() );

View File

@ -15920,11 +15920,6 @@ void QgisApp::read3DMapViewSettings( Qgs3DMapCanvasWidget *widget, QDomElement &
// these things are not saved in project
map->setSelectionColor( mMapCanvas->selectionColor() );
map->setBackgroundColor( mMapCanvas->canvasColor() );
if ( map->terrainRenderingEnabled() && map->terrainGenerator() && map->terrainGenerator()->type() == QgsTerrainGenerator::Flat )
{
QgsFlatTerrainGenerator *flatTerrainGen = static_cast<QgsFlatTerrainGenerator *>( map->terrainGenerator() );
flatTerrainGen->setExtent( mMapCanvas->projectExtent() );
}
map->setOutputDpi( QGuiApplication::primaryScreen()->logicalDotsPerInch() );
widget->setMapSettings( map );

View File

@ -446,7 +446,7 @@ bool QgsRenderChecker::compareImages( const QString &testName, const QString &re
const QString diffImageFileName = QFileInfo( mDiffImageFile ).fileName();
const QString myImagesString = QString(
"<tr>"
"<td colspan=2>Compare actual and expected result</td>"
"<td colspan=2>Compare expected and actual result</td>"
"<td>Difference (all blue is good, any red is bad)</td>"
"</tr>\n<tr>"
"<td colspan=2 id=\"td-%1-%7\"></td>\n"
@ -500,7 +500,7 @@ bool QgsRenderChecker::compareImages( const QString &testName, const QString &re
const QString diffSizeImagesString = QString(
"<tr>"
"<td colspan=3>Compare actual and expected result</td>"
"<td colspan=3>Compare expected and actual result</td>"
"</tr>\n<tr>"
"<td align=center><img src=\"%1\"></td>\n"
"<td align=center><img width=%3 height=%4 src=\"%2\"></td>\n"

View File

@ -90,6 +90,9 @@ class TestQgs3DRendering : public QgsTest
void testAnimationExport();
void testBillboardRendering();
void testInstancedRendering();
void testFilteredFlatTerrain();
void testFilteredDemTerrain();
void testFilteredExtrudedPolygons();
private:
// color tolerance < 2 was failing polygon3d_extrusion test
@ -220,12 +223,11 @@ void TestQgs3DRendering::testFlatTerrain()
Qgs3DMapSettings *map = new Qgs3DMapSettings;
map->setCrs( mProject->crs() );
map->setOrigin( QgsVector3D( fullExtent.center().x(), fullExtent.center().y(), 0 ) );
map->setExtent( fullExtent );
map->setLayers( QList<QgsMapLayer *>() << mLayerRgb );
QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator;
flatTerrain->setCrs( map->crs() );
flatTerrain->setExtent( fullExtent );
map->setTerrainGenerator( flatTerrain );
QgsOffscreen3DEngine engine;
@ -269,7 +271,7 @@ void TestQgs3DRendering::testDemTerrain()
Qgs3DMapSettings *map = new Qgs3DMapSettings;
map->setCrs( mProject->crs() );
map->setOrigin( QgsVector3D( fullExtent.center().x(), fullExtent.center().y(), 0 ) );
map->setExtent( fullExtent );
map->setLayers( QList<QgsMapLayer *>() << mLayerRgb );
QgsDemTerrainGenerator *demTerrain = new QgsDemTerrainGenerator;
@ -302,7 +304,7 @@ void TestQgs3DRendering::testTerrainShading()
Qgs3DMapSettings *map = new Qgs3DMapSettings;
map->setCrs( mProject->crs() );
map->setOrigin( QgsVector3D( fullExtent.center().x(), fullExtent.center().y(), 0 ) );
map->setExtent( fullExtent );
// no terrain layers set!
QgsDemTerrainGenerator *demTerrain = new QgsDemTerrainGenerator;
@ -350,7 +352,7 @@ void TestQgs3DRendering::testMeshTerrain()
Qgs3DMapSettings *map = new Qgs3DMapSettings;
map->setCrs( mProject->crs() );
map->setOrigin( QgsVector3D( fullExtent.center().x(), fullExtent.center().y(), 0 ) );
map->setExtent( fullExtent );
QgsMeshTerrainGenerator *meshTerrain = new QgsMeshTerrainGenerator;
meshTerrain->setCrs( mProject->crs(), mProject->transformContext() );
@ -385,7 +387,7 @@ void TestQgs3DRendering::testExtrudedPolygons()
Qgs3DMapSettings *map = new Qgs3DMapSettings;
map->setCrs( mProject->crs() );
map->setOrigin( QgsVector3D( fullExtent.center().x(), fullExtent.center().y(), 0 ) );
map->setExtent( fullExtent );
map->setLayers( QList<QgsMapLayer *>() << mLayerBuildings << mLayerRgb );
QgsPointLightSettings defaultLight;
defaultLight.setIntensity( 0.5 );
@ -394,7 +396,6 @@ void TestQgs3DRendering::testExtrudedPolygons()
QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator;
flatTerrain->setCrs( map->crs() );
flatTerrain->setExtent( fullExtent );
map->setTerrainGenerator( flatTerrain );
QgsOffscreen3DEngine engine;
@ -453,7 +454,7 @@ void TestQgs3DRendering::testExtrudedPolygonsDataDefined()
Qgs3DMapSettings *map = new Qgs3DMapSettings;
map->setCrs( mProject->crs() );
map->setOrigin( QgsVector3D( fullExtent.center().x(), fullExtent.center().y(), 0 ) );
map->setExtent( fullExtent );
map->setLayers( QList<QgsMapLayer *>() << mLayerBuildings << mLayerRgb );
QgsPointLightSettings defaultLight;
defaultLight.setIntensity( 0.5 );
@ -462,7 +463,6 @@ void TestQgs3DRendering::testExtrudedPolygonsDataDefined()
QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator;
flatTerrain->setCrs( map->crs() );
flatTerrain->setExtent( fullExtent );
map->setTerrainGenerator( flatTerrain );
QgsOffscreen3DEngine engine;
@ -492,12 +492,11 @@ void TestQgs3DRendering::testExtrudedPolygonsGoochShading()
defaultLight.setIntensity( 0.5 );
defaultLight.setPosition( QgsVector3D( 0, 1000, 0 ) );
map->setLightSources( {defaultLight.clone() } );
map->setOrigin( QgsVector3D( fullExtent.center().x(), fullExtent.center().y(), 0 ) );
map->setExtent( fullExtent );
map->setLayers( QList<QgsMapLayer *>() << mLayerBuildings << mLayerRgb );
QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator;
flatTerrain->setCrs( map->crs() );
flatTerrain->setExtent( fullExtent );
map->setTerrainGenerator( flatTerrain );
QgsOffscreen3DEngine engine;
@ -535,7 +534,7 @@ void TestQgs3DRendering::testPolygonsEdges()
Qgs3DMapSettings *map = new Qgs3DMapSettings;
map->setCrs( mProject->crs() );
map->setOrigin( QgsVector3D( fullExtent.center().x(), fullExtent.center().y(), 0 ) );
map->setExtent( fullExtent );
QgsPhongMaterialSettings materialSettings;
materialSettings.setAmbient( Qt::lightGray );
@ -564,7 +563,6 @@ void TestQgs3DRendering::testPolygonsEdges()
QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator;
flatTerrain->setCrs( map->crs() );
flatTerrain->setExtent( fullExtent );
map->setTerrainGenerator( flatTerrain );
QgsOffscreen3DEngine engine;
@ -610,12 +608,11 @@ void TestQgs3DRendering::testLineRendering()
Qgs3DMapSettings *map = new Qgs3DMapSettings;
map->setCrs( mProject->crs() );
map->setOrigin( QgsVector3D( fullExtent.center().x(), fullExtent.center().y(), 0 ) );
map->setExtent( fullExtent );
map->setLayers( QList<QgsMapLayer *>() << layerLines );
QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator;
flatTerrain->setCrs( map->crs() );
flatTerrain->setExtent( fullExtent );
map->setTerrainGenerator( flatTerrain );
QgsOffscreen3DEngine engine;
@ -674,12 +671,11 @@ void TestQgs3DRendering::testLineRenderingCurved()
Qgs3DMapSettings *map = new Qgs3DMapSettings;
map->setCrs( mProject->crs() );
map->setOrigin( QgsVector3D( fullExtent.center().x(), fullExtent.center().y(), 0 ) );
map->setExtent( fullExtent );
map->setLayers( QList<QgsMapLayer *>() << layerLines );
QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator;
flatTerrain->setCrs( map->crs() );
flatTerrain->setExtent( fullExtent );
map->setTerrainGenerator( flatTerrain );
QgsOffscreen3DEngine engine;
@ -718,7 +714,7 @@ void TestQgs3DRendering::testBufferedLineRendering()
Qgs3DMapSettings *map = new Qgs3DMapSettings;
map->setCrs( mProject->crs() );
map->setOrigin( QgsVector3D( fullExtent.center().x(), fullExtent.center().y(), 0 ) );
map->setExtent( fullExtent );
map->setLayers( QList<QgsMapLayer *>() << layerLines );
QgsPointLightSettings defaultLight;
defaultLight.setIntensity( 0.5 );
@ -727,7 +723,6 @@ void TestQgs3DRendering::testBufferedLineRendering()
QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator;
flatTerrain->setCrs( map->crs() );
flatTerrain->setExtent( fullExtent );
map->setTerrainGenerator( flatTerrain );
QgsOffscreen3DEngine engine;
@ -767,7 +762,7 @@ void TestQgs3DRendering::testBufferedLineRenderingWidth()
Qgs3DMapSettings *map = new Qgs3DMapSettings;
map->setCrs( mProject->crs() );
map->setOrigin( QgsVector3D( fullExtent.center().x(), fullExtent.center().y(), 0 ) );
map->setExtent( fullExtent );
map->setLayers( QList<QgsMapLayer *>() << layerLines );
QgsPointLightSettings defaultLight;
defaultLight.setIntensity( 0.5 );
@ -776,7 +771,6 @@ void TestQgs3DRendering::testBufferedLineRenderingWidth()
QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator;
flatTerrain->setCrs( map->crs() );
flatTerrain->setExtent( fullExtent );
map->setTerrainGenerator( flatTerrain );
QgsOffscreen3DEngine engine;
@ -803,7 +797,7 @@ void TestQgs3DRendering::testMapTheme()
Qgs3DMapSettings *map = new Qgs3DMapSettings;
map->setCrs( mProject->crs() );
map->setOrigin( QgsVector3D( fullExtent.center().x(), fullExtent.center().y(), 0 ) );
map->setExtent( fullExtent );
map->setLayers( QList<QgsMapLayer *>() << mLayerDtm );
// set theme - this should override what we set in setLayers()
@ -812,7 +806,6 @@ void TestQgs3DRendering::testMapTheme()
QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator;
flatTerrain->setCrs( map->crs() );
flatTerrain->setExtent( fullExtent );
map->setTerrainGenerator( flatTerrain );
QgsOffscreen3DEngine engine;
@ -839,7 +832,7 @@ void TestQgs3DRendering::testMesh()
Qgs3DMapSettings *map = new Qgs3DMapSettings;
map->setCrs( mProject->crs() );
map->setOrigin( QgsVector3D( fullExtent.center().x(), fullExtent.center().y(), 0 ) );
map->setExtent( fullExtent );
map->setLayers( QList<QgsMapLayer *>() << mLayerMeshDataset );
QgsPointLightSettings defaultLight;
defaultLight.setIntensity( 0.5 );
@ -848,18 +841,17 @@ void TestQgs3DRendering::testMesh()
QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator;
flatTerrain->setCrs( map->crs() );
flatTerrain->setExtent( fullExtent );
map->setTerrainGenerator( flatTerrain );
QgsOffscreen3DEngine engine;
Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine );
engine.setRootEntity( scene );
scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 3000, 25, 45 );
Qgs3DUtils::captureSceneImage( engine, scene );
// When running the test on Travis, it would initially return empty rendered image.
// Capturing the initial image and throwing it away fixes that. Hopefully we will
// find a better fix in the future.
scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 3000, 25, 45 );
QImage img = Qgs3DUtils::captureSceneImage( engine, scene );
delete scene;
delete map;
@ -882,7 +874,7 @@ void TestQgs3DRendering::testMesh_datasetOnFaces()
Qgs3DMapSettings *map = new Qgs3DMapSettings;
map->setCrs( mProject->crs() );
map->setOrigin( QgsVector3D( fullExtent.center().x(), fullExtent.center().y(), 0 ) );
map->setExtent( fullExtent );
map->setLayers( QList<QgsMapLayer *>() << mLayerMeshDataset );
QgsPointLightSettings defaultLight;
defaultLight.setIntensity( 0.5 );
@ -891,18 +883,17 @@ void TestQgs3DRendering::testMesh_datasetOnFaces()
QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator;
flatTerrain->setCrs( map->crs() );
flatTerrain->setExtent( fullExtent );
map->setTerrainGenerator( flatTerrain );
QgsOffscreen3DEngine engine;
Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine );
engine.setRootEntity( scene );
scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 3000, 25, 45 );
Qgs3DUtils::captureSceneImage( engine, scene );
// When running the test on Travis, it would initially return empty rendered image.
// Capturing the initial image and throwing it away fixes that. Hopefully we will
// find a better fix in the future.
scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 3000, 25, 45 );
QImage img = Qgs3DUtils::captureSceneImage( engine, scene );
delete scene;
delete map;
@ -937,7 +928,7 @@ void TestQgs3DRendering::testMeshSimplified()
Qgs3DMapSettings *map = new Qgs3DMapSettings;
map->setCrs( mProject->crs() );
map->setLayers( QList<QgsMapLayer *>() << mLayerMeshSimplified );
map->setOrigin( QgsVector3D( fullExtent.center().x(), fullExtent.center().y(), 0 ) );
map->setExtent( fullExtent );
QgsMesh3DSymbol *symbolDataset = new QgsMesh3DSymbol;
symbolDataset->setVerticalDatasetGroupIndex( 11 );
@ -1005,7 +996,7 @@ void TestQgs3DRendering::testRuleBasedRenderer()
Qgs3DMapSettings *map = new Qgs3DMapSettings;
map->setCrs( mProject->crs() );
map->setOrigin( QgsVector3D( fullExtent.center().x(), fullExtent.center().y(), 0 ) );
map->setExtent( fullExtent );
map->setLayers( QList<QgsMapLayer *>() << mLayerBuildings );
QgsPointLightSettings defaultLight;
@ -1015,7 +1006,6 @@ void TestQgs3DRendering::testRuleBasedRenderer()
QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator;
flatTerrain->setCrs( map->crs() );
flatTerrain->setExtent( fullExtent );
map->setTerrainGenerator( flatTerrain );
QgsOffscreen3DEngine engine;
@ -1059,11 +1049,10 @@ void TestQgs3DRendering::testAnimationExport()
Qgs3DMapSettings map;
map.setCrs( mProject->crs() );
map.setOrigin( QgsVector3D( fullExtent.center().x(), fullExtent.center().y(), 0 ) );
map.setExtent( fullExtent );
QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator;
flatTerrain->setCrs( map.crs() );
flatTerrain->setExtent( fullExtent );
map.setTerrainGenerator( flatTerrain );
Qgs3DAnimationSettings animSettings;
@ -1129,12 +1118,11 @@ void TestQgs3DRendering::testInstancedRendering()
Qgs3DMapSettings *mapSettings = new Qgs3DMapSettings;
mapSettings->setCrs( mProject->crs() );
mapSettings->setOrigin( QgsVector3D( fullExtent.center().x(), fullExtent.center().y(), 0 ) );
mapSettings->setExtent( fullExtent );
mapSettings->setLayers( QList<QgsMapLayer *>() << layerPointsZ.get() );
QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator;
flatTerrain->setCrs( mapSettings->crs() );
flatTerrain->setExtent( fullExtent );
mapSettings->setTerrainGenerator( flatTerrain );
QgsOffscreen3DEngine engine;
@ -1205,12 +1193,11 @@ void TestQgs3DRendering::testBillboardRendering()
Qgs3DMapSettings *map = new Qgs3DMapSettings;
map->setCrs( mProject->crs() );
map->setOrigin( QgsVector3D( fullExtent.center().x(), fullExtent.center().y(), 0 ) );
map->setExtent( fullExtent );
map->setLayers( QList<QgsMapLayer *>() << layerPointsZ.get() );
QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator;
flatTerrain->setCrs( map->crs() );
flatTerrain->setExtent( fullExtent );
map->setTerrainGenerator( flatTerrain );
QgsOffscreen3DEngine engine;
@ -1259,12 +1246,11 @@ void TestQgs3DRendering::testEpsg4978LineRendering()
Qgs3DMapSettings *map = new Qgs3DMapSettings;
map->setCrs( p.crs() );
map->setOrigin( QgsVector3D( fullExtent.center().x(), fullExtent.center().y(), 0 ) );
map->setExtent( fullExtent );
map->setLayers( QList<QgsMapLayer *>() << layerLines );
QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator;
flatTerrain->setCrs( map->crs() );
flatTerrain->setExtent( fullExtent );
map->setTerrainGenerator( flatTerrain );
QgsOffscreen3DEngine engine;
@ -1293,5 +1279,140 @@ void TestQgs3DRendering::testEpsg4978LineRendering()
QVERIFY( renderCheck( "4978_line_rendering_2", img2, 40, 15 ) );
}
void TestQgs3DRendering::testFilteredFlatTerrain()
{
QgsRectangle fullExtent = mLayerDtm->extent();
// Set the extent to have width > height
fullExtent.setYMaximum( fullExtent.yMaximum() - fullExtent.height() / 3 );
fullExtent.setYMinimum( fullExtent.yMinimum() + fullExtent.height() / 3 );
Qgs3DMapSettings *map = new Qgs3DMapSettings;
map->setCrs( mProject->crs() );
map->setExtent( fullExtent );
map->setLayers( QList<QgsMapLayer *>() << mLayerRgb );
QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator;
flatTerrain->setCrs( map->crs() );
map->setTerrainGenerator( flatTerrain );
QgsOffscreen3DEngine engine;
Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine );
engine.setRootEntity( scene );
// look from the top
scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2000, 0, 0 );
// When running the test on Travis, it would initially return empty rendered image.
// Capturing the initial image and throwing it away fixes that. Hopefully we will
// find a better fix in the future.
Qgs3DUtils::captureSceneImage( engine, scene );
QImage img = Qgs3DUtils::captureSceneImage( engine, scene );
renderCheck( "flat_terrain_filtered_1", img, 40 );
// Now set the extent to have height > width and redo
fullExtent = mLayerDtm->extent();
fullExtent.setXMaximum( fullExtent.xMaximum() - fullExtent.width() / 3 );
fullExtent.setXMinimum( fullExtent.xMinimum() + fullExtent.width() / 3 );
map->setExtent( fullExtent );
QImage img2 = Qgs3DUtils::captureSceneImage( engine, scene );
delete scene;
delete map;
QVERIFY( renderCheck( "flat_terrain_filtered_2", img2, 40 ) );
}
void TestQgs3DRendering::testFilteredDemTerrain()
{
QgsRectangle fullExtent = mLayerDtm->extent();
// Set the extent to have width > height
fullExtent.setYMaximum( fullExtent.yMaximum() - fullExtent.height() / 3 );
fullExtent.setYMinimum( fullExtent.yMinimum() + fullExtent.height() / 3 );
Qgs3DMapSettings *map = new Qgs3DMapSettings;
map->setCrs( mProject->crs() );
map->setExtent( fullExtent );
map->setLayers( QList<QgsMapLayer *>() << mLayerRgb );
QgsDemTerrainGenerator *demTerrain = new QgsDemTerrainGenerator;
demTerrain->setLayer( mLayerDtm );
map->setTerrainGenerator( demTerrain );
map->setTerrainVerticalScale( 3 );
QgsOffscreen3DEngine engine;
Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine );
engine.setRootEntity( scene );
scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2000, 60, 225 );
// When running the test on Travis, it would initially return empty rendered image.
// Capturing the initial image and throwing it away fixes that. Hopefully we will
// find a better fix in the future.
Qgs3DUtils::captureSceneImage( engine, scene );
QImage img1 = Qgs3DUtils::captureSceneImage( engine, scene );
renderCheck( "dem_terrain_filtered_1", img1, 40 );
// Now set the extent to have height > width and redo
fullExtent = mLayerDtm->extent();
fullExtent.setXMaximum( fullExtent.xMaximum() - fullExtent.width() / 3 );
fullExtent.setXMinimum( fullExtent.xMinimum() + fullExtent.width() / 3 );
map->setExtent( fullExtent );
scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2000, 60, 45 );
QImage img2 = Qgs3DUtils::captureSceneImage( engine, scene );
delete scene;
delete map;
QVERIFY( renderCheck( "dem_terrain_filtered_2", img2, 40 ) );
}
void TestQgs3DRendering::testFilteredExtrudedPolygons()
{
const QgsRectangle fullExtent = QgsRectangle( 321720, 129190, 322560, 130060 );
Qgs3DMapSettings *map = new Qgs3DMapSettings;
map->setCrs( mProject->crs() );
map->setExtent( fullExtent );
map->setLayers( QList<QgsMapLayer *>() << mLayerBuildings << mLayerRgb );
QgsPointLightSettings defaultLight;
defaultLight.setIntensity( 0.5 );
defaultLight.setPosition( QgsVector3D( 0, 1000, 0 ) );
map->setLightSources( {defaultLight.clone() } );
QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator;
flatTerrain->setCrs( map->crs() );
map->setTerrainGenerator( flatTerrain );
QgsOffscreen3DEngine engine;
Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine );
engine.setRootEntity( scene );
scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 250 ), 1500, 45, 0 );
QgsPhongMaterialSettings materialSettings;
materialSettings.setAmbient( Qt::lightGray );
QgsPolygon3DSymbol *symbol3d = new QgsPolygon3DSymbol;
symbol3d->setMaterialSettings( materialSettings.clone() );
symbol3d->setExtrusionHeight( 10.f );
QgsVectorLayer3DRenderer *renderer3d = new QgsVectorLayer3DRenderer( symbol3d );
mLayerBuildings->setRenderer3D( renderer3d );
// When running the test on Travis, it would initially return empty rendered image.
// Capturing the initial image and throwing it away fixes that. Hopefully we will
// find a better fix in the future.
Qgs3DUtils::captureSceneImage( engine, scene );
QImage img = Qgs3DUtils::captureSceneImage( engine, scene );
delete scene;
delete map;
QVERIFY( renderCheck( "polygon3d_extrusion_filtered", img, 40 ) );
}
QGSTEST_MAIN( TestQgs3DRendering )
#include "testqgs3drendering.moc"

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB