mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-02 00:04:27 -04:00
Compare commits
10 Commits
b142ebbede
...
1cf7fdf7cb
Author | SHA1 | Date | |
---|---|---|---|
|
1cf7fdf7cb | ||
|
4cab2daf50 | ||
|
33cba935d2 | ||
|
62830e0412 | ||
|
56598c47f4 | ||
|
9ed2c4e71d | ||
|
df70848176 | ||
|
af2127db90 | ||
|
14b74f0eb7 | ||
|
857626fb2e |
@ -41,7 +41,7 @@ def userFolder():
|
||||
|
||||
|
||||
def defaultOutputFolder():
|
||||
folder = os.path.join(userFolder(), "outputs")
|
||||
folder = os.path.join(QDir.homePath(), "processing")
|
||||
if not QDir(folder).exists():
|
||||
QDir().mkpath(folder)
|
||||
|
||||
|
@ -83,6 +83,7 @@ typedef Qt3DCore::QGeometry Qt3DQGeometry;
|
||||
#include "qgs3dutils.h"
|
||||
#include "qgsimagetexture.h"
|
||||
#include "qgstessellatedpolygongeometry.h"
|
||||
#include "qgsgeotransform.h"
|
||||
|
||||
#include <numeric>
|
||||
|
||||
@ -285,15 +286,15 @@ void Qgs3DSceneExporter::parseTerrain( QgsTerrainEntity *terrain, const QString
|
||||
switch ( generator->type() )
|
||||
{
|
||||
case QgsTerrainGenerator::Dem:
|
||||
terrainTile = getDemTerrainEntity( terrain, node );
|
||||
terrainTile = getDemTerrainEntity( terrain, node, settings->origin() );
|
||||
parseDemTile( terrainTile, layerName + QStringLiteral( "_" ) );
|
||||
break;
|
||||
case QgsTerrainGenerator::Flat:
|
||||
terrainTile = getFlatTerrainEntity( terrain, node );
|
||||
terrainTile = getFlatTerrainEntity( terrain, node, settings->origin() );
|
||||
parseFlatTile( terrainTile, layerName + QStringLiteral( "_" ) );
|
||||
break;
|
||||
case QgsTerrainGenerator::Mesh:
|
||||
terrainTile = getMeshTerrainEntity( terrain, node );
|
||||
terrainTile = getMeshTerrainEntity( terrain, node, settings->origin() );
|
||||
parseMeshTile( terrainTile, layerName + QStringLiteral( "_" ) );
|
||||
break;
|
||||
// TODO: implement other terrain types
|
||||
@ -304,7 +305,7 @@ void Qgs3DSceneExporter::parseTerrain( QgsTerrainEntity *terrain, const QString
|
||||
textureGenerator->setTextureSize( oldResolution );
|
||||
}
|
||||
|
||||
QgsTerrainTileEntity *Qgs3DSceneExporter::getFlatTerrainEntity( QgsTerrainEntity *terrain, QgsChunkNode *node )
|
||||
QgsTerrainTileEntity *Qgs3DSceneExporter::getFlatTerrainEntity( QgsTerrainEntity *terrain, QgsChunkNode *node, const QgsVector3D &mapOrigin )
|
||||
{
|
||||
QgsFlatTerrainGenerator *generator = dynamic_cast<QgsFlatTerrainGenerator *>( terrain->mapSettings()->terrainGenerator() );
|
||||
FlatTerrainChunkLoader *flatTerrainLoader = qobject_cast<FlatTerrainChunkLoader *>( generator->createChunkLoader( node ) );
|
||||
@ -313,10 +314,17 @@ QgsTerrainTileEntity *Qgs3DSceneExporter::getFlatTerrainEntity( QgsTerrainEntity
|
||||
// the entity we created will be deallocated once the scene exporter is deallocated
|
||||
Qt3DCore::QEntity *entity = flatTerrainLoader->createEntity( this );
|
||||
QgsTerrainTileEntity *tileEntity = qobject_cast<QgsTerrainTileEntity *>( entity );
|
||||
|
||||
const QList<QgsGeoTransform *> transforms = entity->findChildren<QgsGeoTransform *>();
|
||||
for ( QgsGeoTransform *transform : transforms )
|
||||
{
|
||||
transform->setOrigin( mapOrigin );
|
||||
}
|
||||
|
||||
return tileEntity;
|
||||
}
|
||||
|
||||
QgsTerrainTileEntity *Qgs3DSceneExporter::getDemTerrainEntity( QgsTerrainEntity *terrain, QgsChunkNode *node )
|
||||
QgsTerrainTileEntity *Qgs3DSceneExporter::getDemTerrainEntity( QgsTerrainEntity *terrain, QgsChunkNode *node, const QgsVector3D &mapOrigin )
|
||||
{
|
||||
// Just create a new tile (we don't need to export exact level of details as in the scene)
|
||||
// create the entity synchronously and then it will be deleted once our scene exporter instance is deallocated
|
||||
@ -327,16 +335,30 @@ QgsTerrainTileEntity *Qgs3DSceneExporter::getDemTerrainEntity( QgsTerrainEntity
|
||||
if ( mExportTextures )
|
||||
terrain->textureGenerator()->waitForFinished();
|
||||
QgsTerrainTileEntity *tileEntity = qobject_cast<QgsTerrainTileEntity *>( loader->createEntity( this ) );
|
||||
|
||||
const QList<QgsGeoTransform *> transforms = tileEntity->findChildren<QgsGeoTransform *>();
|
||||
for ( QgsGeoTransform *transform : transforms )
|
||||
{
|
||||
transform->setOrigin( mapOrigin );
|
||||
}
|
||||
|
||||
delete generator;
|
||||
return tileEntity;
|
||||
}
|
||||
|
||||
QgsTerrainTileEntity *Qgs3DSceneExporter::getMeshTerrainEntity( QgsTerrainEntity *terrain, QgsChunkNode *node )
|
||||
QgsTerrainTileEntity *Qgs3DSceneExporter::getMeshTerrainEntity( QgsTerrainEntity *terrain, QgsChunkNode *node, const QgsVector3D &mapOrigin )
|
||||
{
|
||||
QgsMeshTerrainGenerator *generator = dynamic_cast<QgsMeshTerrainGenerator *>( terrain->mapSettings()->terrainGenerator() );
|
||||
QgsMeshTerrainTileLoader *loader = qobject_cast<QgsMeshTerrainTileLoader *>( generator->createChunkLoader( node ) );
|
||||
// TODO: export textures
|
||||
QgsTerrainTileEntity *tileEntity = qobject_cast<QgsTerrainTileEntity *>( loader->createEntity( this ) );
|
||||
|
||||
const QList<QgsGeoTransform *> transforms = tileEntity->findChildren<QgsGeoTransform *>();
|
||||
for ( QgsGeoTransform *transform : transforms )
|
||||
{
|
||||
transform->setOrigin( mapOrigin );
|
||||
}
|
||||
|
||||
return tileEntity;
|
||||
}
|
||||
|
||||
|
@ -37,12 +37,13 @@ class QgsDemTerrainGenerator;
|
||||
class QgsChunkNode;
|
||||
class Qgs3DExportObject;
|
||||
class QgsTerrainTextureGenerator;
|
||||
class QgsVector3D;
|
||||
class QgsVectorLayer;
|
||||
class QgsPolygon3DSymbol;
|
||||
class QgsLine3DSymbol;
|
||||
class QgsPoint3DSymbol;
|
||||
class QgsMeshEntity;
|
||||
class TestQgs3DRendering;
|
||||
class TestQgs3DExporter;
|
||||
|
||||
#define SIP_NO_FILE
|
||||
|
||||
@ -126,11 +127,11 @@ class _3D_EXPORT Qgs3DSceneExporter : public Qt3DCore::QEntity
|
||||
Qgs3DExportObject *processPoints( Qt3DCore::QEntity *entity, const QString &objectNamePrefix );
|
||||
|
||||
//! Returns a tile entity that contains the geometry to be exported and necessary scaling parameters
|
||||
QgsTerrainTileEntity *getFlatTerrainEntity( QgsTerrainEntity *terrain, QgsChunkNode *node );
|
||||
QgsTerrainTileEntity *getFlatTerrainEntity( QgsTerrainEntity *terrain, QgsChunkNode *node, const QgsVector3D &mapOrigin );
|
||||
//! Returns a tile entity that contains the geometry to be exported and necessary scaling parameters
|
||||
QgsTerrainTileEntity *getDemTerrainEntity( QgsTerrainEntity *terrain, QgsChunkNode *node );
|
||||
QgsTerrainTileEntity *getDemTerrainEntity( QgsTerrainEntity *terrain, QgsChunkNode *node, const QgsVector3D &mapOrigin );
|
||||
//! Returns a tile entity that contains the geometry to be exported and necessary scaling parameters
|
||||
QgsTerrainTileEntity *getMeshTerrainEntity( QgsTerrainEntity *terrain, QgsChunkNode *node );
|
||||
QgsTerrainTileEntity *getMeshTerrainEntity( QgsTerrainEntity *terrain, QgsChunkNode *node, const QgsVector3D &mapOrigin );
|
||||
|
||||
//! Constructs a Qgs3DExportObject from the DEM tile entity
|
||||
void parseDemTile( QgsTerrainTileEntity *tileEntity, const QString &layerName );
|
||||
@ -157,7 +158,7 @@ class _3D_EXPORT Qgs3DSceneExporter : public Qt3DCore::QEntity
|
||||
friend QgsPolygon3DSymbol;
|
||||
friend QgsLine3DSymbol;
|
||||
friend QgsPoint3DSymbol;
|
||||
friend TestQgs3DRendering;
|
||||
friend TestQgs3DExporter;
|
||||
};
|
||||
|
||||
#endif // QGS3DSCENEEXPORTER_H
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include "qgsprocessingcontext.h"
|
||||
#include "qgsprocessingalgorithm.h"
|
||||
#include "qgsfieldmappingwidget.h"
|
||||
#include "qgsapplication.h"
|
||||
#include <QMenu>
|
||||
#include <QFileDialog>
|
||||
#include <QInputDialog>
|
||||
@ -175,7 +176,7 @@ QVariant QgsProcessingLayerOutputDestinationWidget::value() const
|
||||
if ( folder == '.' )
|
||||
{
|
||||
// output name does not include a folder - use default
|
||||
QString defaultFolder = settings.value( QStringLiteral( "/Processing/Configuration/OUTPUTS_FOLDER" ) ).toString();
|
||||
QString defaultFolder = settings.value( QStringLiteral( "/Processing/Configuration/OUTPUTS_FOLDER" ), QStringLiteral( "%1/processing" ).arg( QDir::homePath() ) ).toString();
|
||||
key = QDir( defaultFolder ).filePath( key );
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ add_subdirectory(sandbox)
|
||||
|
||||
set(TESTS
|
||||
testqgs3dcameracontroller.cpp
|
||||
testqgs3dexporter.cpp
|
||||
testqgs3dmapscene.cpp
|
||||
testqgs3dmaterial.cpp
|
||||
testqgs3drendering.cpp
|
||||
|
302
tests/src/3d/testqgs3dexporter.cpp
Normal file
302
tests/src/3d/testqgs3dexporter.cpp
Normal file
@ -0,0 +1,302 @@
|
||||
/***************************************************************************
|
||||
testqgs3dexporter.cpp
|
||||
--------------------------------------
|
||||
Date : September 2025
|
||||
Copyright : (C) 2025 by Jean Felder
|
||||
Email : jean dot felder at oslandia dot com
|
||||
***************************************************************************
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include "qgstest.h"
|
||||
|
||||
#include "qgsrasterlayer.h"
|
||||
#include "qgsvectorlayer.h"
|
||||
|
||||
#include "qgs3d.h"
|
||||
#include "qgs3dmapscene.h"
|
||||
#include "qgs3dmapsettings.h"
|
||||
#include "qgs3drendercontext.h"
|
||||
#include "qgs3dsceneexporter.h"
|
||||
#include "qgs3dutils.h"
|
||||
#include "qgsdemterrainsettings.h"
|
||||
#include "qgsflatterraingenerator.h"
|
||||
#include "qgsflatterrainsettings.h"
|
||||
#include "qgsoffscreen3dengine.h"
|
||||
#include "qgspointlightsettings.h"
|
||||
#include "qgspolygon3dsymbol.h"
|
||||
#include "qgsvectorlayer3drenderer.h"
|
||||
|
||||
|
||||
class TestQgs3DExporter : public QgsTest
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
TestQgs3DExporter()
|
||||
: QgsTest( QStringLiteral( "3D Exporter Tests" ), QStringLiteral( "3d" ) )
|
||||
{}
|
||||
|
||||
private slots:
|
||||
void initTestCase(); // will be called before the first testfunction is executed.
|
||||
void cleanupTestCase(); // will be called after the last testfunction was executed.
|
||||
void test3DSceneExporter();
|
||||
void test3DSceneExporterBig();
|
||||
void test3DSceneExporterFlatTerrain();
|
||||
|
||||
private:
|
||||
void do3DSceneExport( const QString &testName, int zoomLevelsCount, int expectedObjectCount, int expectedFeatureCount, int maxFaceCount, Qgs3DMapScene *scene, QgsVectorLayer *layerPoly, QgsOffscreen3DEngine *engine, QgsTerrainEntity *terrainEntity = nullptr );
|
||||
|
||||
QgsVectorLayer *mLayerBuildings = nullptr;
|
||||
};
|
||||
|
||||
// runs before all tests
|
||||
void TestQgs3DExporter::initTestCase()
|
||||
{
|
||||
// init QGIS's paths - true means that all path will be inited from prefix
|
||||
QgsApplication::init();
|
||||
QgsApplication::initQgis();
|
||||
Qgs3D::initialize();
|
||||
|
||||
mLayerBuildings = new QgsVectorLayer( testDataPath( "/3d/buildings.shp" ), "buildings", "ogr" );
|
||||
QVERIFY( mLayerBuildings->isValid() );
|
||||
|
||||
// best to keep buildings without 2D renderer so it is not painted on the terrain
|
||||
// so we do not get some possible artifacts
|
||||
mLayerBuildings->setRenderer( nullptr );
|
||||
|
||||
QgsPhongMaterialSettings materialSettings;
|
||||
materialSettings.setAmbient( Qt::lightGray );
|
||||
QgsPolygon3DSymbol *symbol3d = new QgsPolygon3DSymbol;
|
||||
symbol3d->setMaterialSettings( materialSettings.clone() );
|
||||
symbol3d->setExtrusionHeight( 10.f );
|
||||
symbol3d->setAltitudeClamping( Qgis::AltitudeClamping::Relative );
|
||||
QgsVectorLayer3DRenderer *renderer3d = new QgsVectorLayer3DRenderer( symbol3d );
|
||||
mLayerBuildings->setRenderer3D( renderer3d );
|
||||
}
|
||||
|
||||
//runs after all tests
|
||||
void TestQgs3DExporter::cleanupTestCase()
|
||||
{
|
||||
QgsApplication::exitQgis();
|
||||
}
|
||||
|
||||
void TestQgs3DExporter::do3DSceneExport( const QString &testName, int zoomLevelsCount, int expectedObjectCount, int expectedFeatureCount, int maxFaceCount, Qgs3DMapScene *scene, QgsVectorLayer *layerPoly, QgsOffscreen3DEngine *engine, QgsTerrainEntity *terrainEntity )
|
||||
{
|
||||
// 3d renderer must be replaced to have the tiling updated
|
||||
QgsVectorLayer3DRenderer *renderer3d = dynamic_cast<QgsVectorLayer3DRenderer *>( layerPoly->renderer3D() );
|
||||
QgsVectorLayer3DRenderer *newRenderer3d = new QgsVectorLayer3DRenderer( renderer3d->symbol()->clone() );
|
||||
QgsVectorLayer3DTilingSettings tilingSettings;
|
||||
tilingSettings.setZoomLevelsCount( zoomLevelsCount );
|
||||
tilingSettings.setShowBoundingBoxes( true );
|
||||
newRenderer3d->setTilingSettings( tilingSettings );
|
||||
layerPoly->setRenderer3D( newRenderer3d );
|
||||
|
||||
Qgs3DUtils::captureSceneImage( *engine, scene );
|
||||
|
||||
Qgs3DSceneExporter exporter;
|
||||
exporter.setTerrainResolution( 128 );
|
||||
exporter.setSmoothEdges( false );
|
||||
exporter.setExportNormals( true );
|
||||
exporter.setExportTextures( false );
|
||||
exporter.setTerrainTextureResolution( 512 );
|
||||
exporter.setScale( 1.0 );
|
||||
|
||||
QVERIFY( exporter.parseVectorLayerEntity( scene->layerEntity( layerPoly ), layerPoly ) );
|
||||
if ( terrainEntity )
|
||||
exporter.parseTerrain( terrainEntity, "DEM_Tile" );
|
||||
|
||||
QString objFileName = QString( "%1-%2" ).arg( testName ).arg( zoomLevelsCount );
|
||||
const bool saved = exporter.save( objFileName, QDir::tempPath(), 3 );
|
||||
QVERIFY( saved );
|
||||
|
||||
size_t sum = 0;
|
||||
for ( auto o : qAsConst( exporter.mObjects ) )
|
||||
{
|
||||
if ( !terrainEntity ) // not compatible with terrain entity
|
||||
QVERIFY( o->indexes().size() * 3 <= o->vertexPosition().size() );
|
||||
sum += o->indexes().size();
|
||||
}
|
||||
|
||||
QCOMPARE( sum, maxFaceCount );
|
||||
QCOMPARE( exporter.mExportedFeatureIds.size(), expectedFeatureCount );
|
||||
QCOMPARE( exporter.mObjects.size(), expectedObjectCount );
|
||||
|
||||
QFile file( QString( "%1/%2.obj" ).arg( QDir::tempPath(), objFileName ) );
|
||||
file.open( QIODevice::ReadOnly | QIODevice::Text );
|
||||
QTextStream fileStream( &file );
|
||||
|
||||
// check the generated obj file
|
||||
QGSCOMPARELONGSTR( testName.toStdString().c_str(), QString( "%1.obj" ).arg( objFileName ), fileStream.readAll().toUtf8() );
|
||||
}
|
||||
|
||||
void TestQgs3DExporter::test3DSceneExporter()
|
||||
{
|
||||
QgsVectorLayer *layerPoly = new QgsVectorLayer( testDataPath( "/3d/polygons.gpkg.gz" ), "polygons", "ogr" );
|
||||
QVERIFY( layerPoly->isValid() );
|
||||
|
||||
const QgsRectangle fullExtent = layerPoly->extent();
|
||||
|
||||
QgsPolygon3DSymbol *symbol3d = new QgsPolygon3DSymbol();
|
||||
symbol3d->setExtrusionHeight( 10.f );
|
||||
QgsPhongMaterialSettings materialSettings;
|
||||
materialSettings.setAmbient( Qt::lightGray );
|
||||
symbol3d->setMaterialSettings( materialSettings.clone() );
|
||||
|
||||
QgsVectorLayer3DRenderer *renderer3d = new QgsVectorLayer3DRenderer( symbol3d );
|
||||
layerPoly->setRenderer3D( renderer3d );
|
||||
|
||||
QgsProject project;
|
||||
project.setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 3857 ) );
|
||||
project.addMapLayer( layerPoly );
|
||||
|
||||
Qgs3DMapSettings mapSettings;
|
||||
mapSettings.setCrs( project.crs() );
|
||||
mapSettings.setExtent( fullExtent );
|
||||
mapSettings.setLayers( { layerPoly } );
|
||||
|
||||
mapSettings.setTransformContext( project.transformContext() );
|
||||
mapSettings.setPathResolver( project.pathResolver() );
|
||||
mapSettings.setMapThemeCollection( project.mapThemeCollection() );
|
||||
mapSettings.setOutputDpi( 92 );
|
||||
|
||||
QPoint winSize = QPoint( 640, 480 ); // default window size
|
||||
|
||||
QgsOffscreen3DEngine engine;
|
||||
engine.setSize( QSize( winSize.x(), winSize.y() ) );
|
||||
Qgs3DMapScene *scene = new Qgs3DMapScene( mapSettings, &engine );
|
||||
|
||||
scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 7000, 20.0, -10.0 );
|
||||
engine.setRootEntity( scene );
|
||||
|
||||
const int nbFaces = 165;
|
||||
const int nbFeat = 3;
|
||||
|
||||
// =========== check with 1 big tile ==> 1 exported object
|
||||
do3DSceneExport( "scene_export", 1, 1, nbFeat, nbFaces, scene, layerPoly, &engine );
|
||||
// =========== check with 4 tiles ==> 1 exported objects
|
||||
do3DSceneExport( "scene_export", 2, 1, nbFeat, nbFaces, scene, layerPoly, &engine );
|
||||
// =========== check with 9 tiles ==> 3 exported objects
|
||||
do3DSceneExport( "scene_export", 3, 3, nbFeat, nbFaces, scene, layerPoly, &engine );
|
||||
// =========== check with 16 tiles ==> 3 exported objects
|
||||
do3DSceneExport( "scene_export", 4, 3, nbFeat, nbFaces, scene, layerPoly, &engine );
|
||||
// =========== check with 25 tiles ==> 3 exported objects
|
||||
do3DSceneExport( "scene_export", 5, 3, nbFeat, nbFaces, scene, layerPoly, &engine );
|
||||
|
||||
delete scene;
|
||||
mapSettings.setLayers( {} );
|
||||
}
|
||||
|
||||
void TestQgs3DExporter::test3DSceneExporterBig()
|
||||
{
|
||||
QgsRasterLayer *layerDtm = new QgsRasterLayer( testDataPath( "/3d/dtm.tif" ), "dtm", "gdal" );
|
||||
QVERIFY( layerDtm->isValid() );
|
||||
|
||||
const QgsRectangle fullExtent = layerDtm->extent();
|
||||
|
||||
QgsProject project;
|
||||
project.setCrs( layerDtm->crs() );
|
||||
project.addMapLayer( layerDtm );
|
||||
|
||||
Qgs3DMapSettings mapSettings;
|
||||
mapSettings.setCrs( project.crs() );
|
||||
mapSettings.setExtent( fullExtent );
|
||||
mapSettings.setLayers( { layerDtm, mLayerBuildings } );
|
||||
|
||||
mapSettings.setTransformContext( project.transformContext() );
|
||||
mapSettings.setPathResolver( project.pathResolver() );
|
||||
mapSettings.setMapThemeCollection( project.mapThemeCollection() );
|
||||
|
||||
QgsDemTerrainSettings *demTerrainSettings = new QgsDemTerrainSettings;
|
||||
demTerrainSettings->setLayer( layerDtm );
|
||||
demTerrainSettings->setVerticalScale( 3 );
|
||||
mapSettings.setTerrainSettings( demTerrainSettings );
|
||||
|
||||
QgsPointLightSettings defaultPointLight;
|
||||
defaultPointLight.setPosition( QgsVector3D( 0, 400, 0 ) );
|
||||
defaultPointLight.setConstantAttenuation( 0 );
|
||||
mapSettings.setLightSources( { defaultPointLight.clone() } );
|
||||
mapSettings.setOutputDpi( 92 );
|
||||
|
||||
QPoint winSize = QPoint( 640, 480 ); // default window size
|
||||
|
||||
QgsOffscreen3DEngine engine;
|
||||
engine.setSize( QSize( winSize.x(), winSize.y() ) );
|
||||
Qgs3DMapScene *scene = new Qgs3DMapScene( mapSettings, &engine );
|
||||
engine.setRootEntity( scene );
|
||||
|
||||
scene->cameraController()->setLookingAtPoint( QVector3D( 0, 0, 0 ), 1500, 40.0, -10.0 );
|
||||
|
||||
const int nbFaces = 19869;
|
||||
const int nbFeat = 401;
|
||||
|
||||
// =========== check with 1 big tile ==> 1 exported object
|
||||
do3DSceneExport( "big_scene_export", 1, 1, nbFeat, nbFaces, scene, mLayerBuildings, &engine );
|
||||
// =========== check with 4 tiles ==> 4 exported objects
|
||||
do3DSceneExport( "big_scene_export", 2, 4, nbFeat, nbFaces, scene, mLayerBuildings, &engine );
|
||||
// =========== check with 9 tiles ==> 14 exported objects
|
||||
do3DSceneExport( "big_scene_export", 3, 14, nbFeat, nbFaces, scene, mLayerBuildings, &engine );
|
||||
// =========== check with 16 tiles ==> 32 exported objects
|
||||
do3DSceneExport( "big_scene_export", 4, 32, nbFeat, nbFaces, scene, mLayerBuildings, &engine );
|
||||
// =========== check with 25 tiles ==> 70 exported objects
|
||||
do3DSceneExport( "big_scene_export", 5, 70, nbFeat, nbFaces, scene, mLayerBuildings, &engine );
|
||||
|
||||
delete scene;
|
||||
mapSettings.setLayers( {} );
|
||||
}
|
||||
|
||||
void TestQgs3DExporter::test3DSceneExporterFlatTerrain()
|
||||
{
|
||||
QgsRasterLayer *layerRgb = new QgsRasterLayer( testDataPath( "/3d/rgb.tif" ), "rgb", "gdal" );
|
||||
QVERIFY( layerRgb->isValid() );
|
||||
|
||||
const QgsRectangle fullExtent = layerRgb->extent();
|
||||
|
||||
QgsProject project;
|
||||
project.setCrs( layerRgb->crs() );
|
||||
project.addMapLayer( layerRgb );
|
||||
|
||||
Qgs3DMapSettings mapSettings;
|
||||
mapSettings.setCrs( project.crs() );
|
||||
mapSettings.setExtent( fullExtent );
|
||||
mapSettings.setLayers( { layerRgb, mLayerBuildings } );
|
||||
|
||||
mapSettings.setTransformContext( project.transformContext() );
|
||||
mapSettings.setPathResolver( project.pathResolver() );
|
||||
mapSettings.setMapThemeCollection( project.mapThemeCollection() );
|
||||
|
||||
QgsFlatTerrainSettings *flatTerrainSettings = new QgsFlatTerrainSettings;
|
||||
mapSettings.setTerrainSettings( flatTerrainSettings );
|
||||
|
||||
std::unique_ptr<QgsTerrainGenerator> generator = flatTerrainSettings->createTerrainGenerator( Qgs3DRenderContext::fromMapSettings( &mapSettings ) );
|
||||
QVERIFY( dynamic_cast<QgsFlatTerrainGenerator *>( generator.get() )->isValid() );
|
||||
QCOMPARE( dynamic_cast<QgsFlatTerrainGenerator *>( generator.get() )->crs(), mapSettings.crs() );
|
||||
|
||||
QgsPointLightSettings defaultPointLight;
|
||||
defaultPointLight.setPosition( QgsVector3D( 0, 400, 0 ) );
|
||||
defaultPointLight.setConstantAttenuation( 0 );
|
||||
mapSettings.setLightSources( { defaultPointLight.clone() } );
|
||||
mapSettings.setOutputDpi( 92 );
|
||||
|
||||
QPoint winSize = QPoint( 640, 480 ); // default window size
|
||||
|
||||
QgsOffscreen3DEngine engine;
|
||||
engine.setSize( QSize( winSize.x(), winSize.y() ) );
|
||||
Qgs3DMapScene *scene = new Qgs3DMapScene( mapSettings, &engine );
|
||||
engine.setRootEntity( scene );
|
||||
|
||||
scene->cameraController()->setLookingAtPoint( QVector3D( 0, 0, 0 ), 1500, 40.0, -10.0 );
|
||||
|
||||
do3DSceneExport( "flat_terrain_scene_export", 5, 70, 401, 19875, scene, mLayerBuildings, &engine, scene->terrainEntity() );
|
||||
|
||||
delete scene;
|
||||
mapSettings.setLayers( {} );
|
||||
}
|
||||
|
||||
QGSTEST_MAIN( TestQgs3DExporter )
|
||||
#include "testqgs3dexporter.moc"
|
@ -50,7 +50,6 @@
|
||||
#include "qgsfillsymbol.h"
|
||||
#include "qgsmarkersymbol.h"
|
||||
#include "qgsgoochmaterialsettings.h"
|
||||
#include "qgs3dsceneexporter.h"
|
||||
#include "qgsdirectionallightsettings.h"
|
||||
#include "qgsmetalroughmaterialsettings.h"
|
||||
#include "qgspointlightsettings.h"
|
||||
@ -109,14 +108,10 @@ class TestQgs3DRendering : public QgsTest
|
||||
void testDepthBuffer();
|
||||
void testAmbientOcclusion();
|
||||
void testDebugMap();
|
||||
void test3DSceneExporter();
|
||||
void test3DSceneExporterBig();
|
||||
|
||||
private:
|
||||
QImage convertDepthImageToGrayscaleImage( const QImage &depthImage );
|
||||
|
||||
void do3DSceneExport( const QString &testName, int zoomLevelsCount, int expectedObjectCount, int expectedFeatureCount, int maxFaceCount, Qgs3DMapScene *scene, QgsVectorLayer *layerPoly, QgsOffscreen3DEngine *engine, QgsTerrainEntity *terrainEntity = nullptr );
|
||||
|
||||
std::unique_ptr<QgsProject> mProject;
|
||||
QgsRasterLayer *mLayerDtm = nullptr;
|
||||
QgsRasterLayer *mLayerRgb = nullptr;
|
||||
@ -2392,190 +2387,5 @@ void TestQgs3DRendering::testDebugMap()
|
||||
mapSettings.setLayers( {} );
|
||||
}
|
||||
|
||||
void TestQgs3DRendering::do3DSceneExport( const QString &testName, int zoomLevelsCount, int expectedObjectCount, int expectedFeatureCount, int maxFaceCount, Qgs3DMapScene *scene, QgsVectorLayer *layerPoly, QgsOffscreen3DEngine *engine, QgsTerrainEntity *terrainEntity )
|
||||
{
|
||||
// 3d renderer must be replaced to have the tiling updated
|
||||
QgsVectorLayer3DRenderer *renderer3d = dynamic_cast<QgsVectorLayer3DRenderer *>( layerPoly->renderer3D() );
|
||||
QgsVectorLayer3DRenderer *newRenderer3d = new QgsVectorLayer3DRenderer( renderer3d->symbol()->clone() );
|
||||
QgsVectorLayer3DTilingSettings tilingSettings;
|
||||
tilingSettings.setZoomLevelsCount( zoomLevelsCount );
|
||||
tilingSettings.setShowBoundingBoxes( true );
|
||||
newRenderer3d->setTilingSettings( tilingSettings );
|
||||
layerPoly->setRenderer3D( newRenderer3d );
|
||||
|
||||
Qgs3DUtils::captureSceneImage( *engine, scene );
|
||||
|
||||
Qgs3DSceneExporter exporter;
|
||||
exporter.setTerrainResolution( 128 );
|
||||
exporter.setSmoothEdges( false );
|
||||
exporter.setExportNormals( true );
|
||||
exporter.setExportTextures( false );
|
||||
exporter.setTerrainTextureResolution( 512 );
|
||||
exporter.setScale( 1.0 );
|
||||
|
||||
QVERIFY( exporter.parseVectorLayerEntity( scene->layerEntity( layerPoly ), layerPoly ) );
|
||||
if ( terrainEntity )
|
||||
exporter.parseTerrain( terrainEntity, "DEM_Tile" );
|
||||
|
||||
QString objFileName = QString( "%1-%2" ).arg( testName ).arg( zoomLevelsCount );
|
||||
const bool saved = exporter.save( objFileName, QDir::tempPath(), 3 );
|
||||
QVERIFY( saved );
|
||||
|
||||
int sum = 0;
|
||||
for ( auto o : qAsConst( exporter.mObjects ) )
|
||||
{
|
||||
if ( !terrainEntity ) // not comptabible with terrain entity
|
||||
QVERIFY( o->indexes().size() * 3 <= o->vertexPosition().size() );
|
||||
sum += o->indexes().size();
|
||||
}
|
||||
|
||||
QCOMPARE( sum, maxFaceCount );
|
||||
QCOMPARE( exporter.mExportedFeatureIds.size(), expectedFeatureCount );
|
||||
QCOMPARE( exporter.mObjects.size(), expectedObjectCount );
|
||||
|
||||
QFile file( QString( "%1/%2.obj" ).arg( QDir::tempPath(), objFileName ) );
|
||||
file.open( QIODevice::ReadOnly | QIODevice::Text );
|
||||
QTextStream fileStream( &file );
|
||||
|
||||
if ( !terrainEntity ) // dump with terrain are too big to stay in GIT
|
||||
QGSCOMPARELONGSTR( testName.toStdString().c_str(), QString( "%1.obj" ).arg( objFileName ), fileStream.readAll().toUtf8() );
|
||||
}
|
||||
|
||||
void TestQgs3DRendering::test3DSceneExporter()
|
||||
{
|
||||
// =============================================
|
||||
// =========== creating Qgs3DMapSettings
|
||||
QgsVectorLayer *layerPoly = new QgsVectorLayer( testDataPath( "/3d/polygons.gpkg.gz" ), "polygons", "ogr" );
|
||||
QVERIFY( layerPoly->isValid() );
|
||||
|
||||
const QgsRectangle fullExtent = layerPoly->extent();
|
||||
|
||||
// =========== create polygon 3D renderer
|
||||
QgsPolygon3DSymbol *symbol3d = new QgsPolygon3DSymbol();
|
||||
symbol3d->setExtrusionHeight( 10.f );
|
||||
QgsPhongMaterialSettings materialSettings;
|
||||
materialSettings.setAmbient( Qt::lightGray );
|
||||
symbol3d->setMaterialSettings( materialSettings.clone() );
|
||||
|
||||
QgsVectorLayer3DRenderer *renderer3d = new QgsVectorLayer3DRenderer( symbol3d );
|
||||
layerPoly->setRenderer3D( renderer3d );
|
||||
|
||||
QgsProject project;
|
||||
project.setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 3857 ) );
|
||||
project.addMapLayer( layerPoly );
|
||||
|
||||
// =========== create scene 3D settings
|
||||
Qgs3DMapSettings mapSettings;
|
||||
mapSettings.setCrs( project.crs() );
|
||||
mapSettings.setExtent( fullExtent );
|
||||
mapSettings.setLayers( { layerPoly } );
|
||||
|
||||
mapSettings.setTransformContext( project.transformContext() );
|
||||
mapSettings.setPathResolver( project.pathResolver() );
|
||||
mapSettings.setMapThemeCollection( project.mapThemeCollection() );
|
||||
mapSettings.setOutputDpi( 92 );
|
||||
|
||||
// =========== creating Qgs3DMapScene
|
||||
QPoint winSize = QPoint( 640, 480 ); // default window size
|
||||
|
||||
QgsOffscreen3DEngine engine;
|
||||
engine.setSize( QSize( winSize.x(), winSize.y() ) );
|
||||
Qgs3DMapScene *scene = new Qgs3DMapScene( mapSettings, &engine );
|
||||
|
||||
scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 7000, 20.0, -10.0 );
|
||||
engine.setRootEntity( scene );
|
||||
|
||||
const int nbFaces = 165;
|
||||
const int nbFeat = 3;
|
||||
|
||||
// =========== check with 1 big tile ==> 1 exported object
|
||||
do3DSceneExport( "scene_export", 1, 1, nbFeat, nbFaces, scene, layerPoly, &engine );
|
||||
// =========== check with 4 tiles ==> 1 exported objects
|
||||
do3DSceneExport( "scene_export", 2, 1, nbFeat, nbFaces, scene, layerPoly, &engine );
|
||||
// =========== check with 9 tiles ==> 3 exported objects
|
||||
do3DSceneExport( "scene_export", 3, 3, nbFeat, nbFaces, scene, layerPoly, &engine );
|
||||
// =========== check with 16 tiles ==> 3 exported objects
|
||||
do3DSceneExport( "scene_export", 4, 3, nbFeat, nbFaces, scene, layerPoly, &engine );
|
||||
// =========== check with 25 tiles ==> 3 exported objects
|
||||
do3DSceneExport( "scene_export", 5, 3, nbFeat, nbFaces, scene, layerPoly, &engine );
|
||||
|
||||
delete scene;
|
||||
mapSettings.setLayers( {} );
|
||||
}
|
||||
|
||||
void TestQgs3DRendering::test3DSceneExporterBig()
|
||||
{
|
||||
// In Qt 6, this test does not work on CI
|
||||
// because somewhere after 10K lines, one of the values is -0.304 instead of -0.305
|
||||
#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 )
|
||||
if ( QgsTest::isCIRun() )
|
||||
{
|
||||
QSKIP( "fails on CI" );
|
||||
}
|
||||
#endif
|
||||
|
||||
// =============================================
|
||||
// =========== creating Qgs3DMapSettings
|
||||
QgsRasterLayer *layerDtm = new QgsRasterLayer( testDataPath( "/3d/dtm.tif" ), "dtm", "gdal" );
|
||||
QVERIFY( layerDtm->isValid() );
|
||||
|
||||
const QgsRectangle fullExtent = layerDtm->extent();
|
||||
|
||||
QgsProject project;
|
||||
project.setCrs( layerDtm->crs() );
|
||||
project.addMapLayer( layerDtm );
|
||||
|
||||
Qgs3DMapSettings mapSettings;
|
||||
mapSettings.setCrs( project.crs() );
|
||||
mapSettings.setExtent( fullExtent );
|
||||
mapSettings.setLayers( { layerDtm, mLayerBuildings } );
|
||||
|
||||
mapSettings.setTransformContext( project.transformContext() );
|
||||
mapSettings.setPathResolver( project.pathResolver() );
|
||||
mapSettings.setMapThemeCollection( project.mapThemeCollection() );
|
||||
|
||||
QgsDemTerrainSettings *demTerrainSettings = new QgsDemTerrainSettings;
|
||||
demTerrainSettings->setLayer( layerDtm );
|
||||
demTerrainSettings->setVerticalScale( 3 );
|
||||
mapSettings.setTerrainSettings( demTerrainSettings );
|
||||
|
||||
QgsPointLightSettings defaultPointLight;
|
||||
defaultPointLight.setPosition( QgsVector3D( 0, 400, 0 ) );
|
||||
defaultPointLight.setConstantAttenuation( 0 );
|
||||
mapSettings.setLightSources( { defaultPointLight.clone() } );
|
||||
mapSettings.setOutputDpi( 92 );
|
||||
|
||||
// =========== creating Qgs3DMapScene
|
||||
QPoint winSize = QPoint( 640, 480 ); // default window size
|
||||
|
||||
QgsOffscreen3DEngine engine;
|
||||
engine.setSize( QSize( winSize.x(), winSize.y() ) );
|
||||
Qgs3DMapScene *scene = new Qgs3DMapScene( mapSettings, &engine );
|
||||
engine.setRootEntity( scene );
|
||||
|
||||
// =========== set camera position
|
||||
scene->cameraController()->setLookingAtPoint( QVector3D( 0, 0, 0 ), 1500, 40.0, -10.0 );
|
||||
|
||||
const int nbFaces = 19869;
|
||||
const int nbFeat = 401;
|
||||
|
||||
// =========== check with 1 big tile ==> 1 exported object
|
||||
do3DSceneExport( "big_scene_export", 1, 1, nbFeat, nbFaces, scene, mLayerBuildings, &engine );
|
||||
// =========== check with 4 tiles ==> 4 exported objects
|
||||
do3DSceneExport( "big_scene_export", 2, 4, nbFeat, nbFaces, scene, mLayerBuildings, &engine );
|
||||
// =========== check with 9 tiles ==> 14 exported objects
|
||||
do3DSceneExport( "big_scene_export", 3, 14, nbFeat, nbFaces, scene, mLayerBuildings, &engine );
|
||||
// =========== check with 16 tiles ==> 32 exported objects
|
||||
do3DSceneExport( "big_scene_export", 4, 32, nbFeat, nbFaces, scene, mLayerBuildings, &engine );
|
||||
// =========== check with 25 tiles ==> 70 exported objects
|
||||
do3DSceneExport( "big_scene_export", 5, 70, nbFeat, nbFaces, scene, mLayerBuildings, &engine );
|
||||
|
||||
// =========== check with 25 tiles + terrain ==> 70+1 exported objects
|
||||
do3DSceneExport( "terrain_scene_export", 5, 71, nbFeat, 119715, scene, mLayerBuildings, &engine, scene->terrainEntity() );
|
||||
|
||||
delete scene;
|
||||
mapSettings.setLayers( {} );
|
||||
}
|
||||
|
||||
QGSTEST_MAIN( TestQgs3DRendering )
|
||||
#include "testqgs3drendering.moc"
|
||||
|
46516
tests/testdata/control_files/3d/expected_flat_terrain_scene_export/expected_flat_terrain_scene_export-5.obj
vendored
Normal file
46516
tests/testdata/control_files/3d/expected_flat_terrain_scene_export/expected_flat_terrain_scene_export-5.obj
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user