mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-03 00:04:47 -04:00
Compare commits
7 Commits
1cf7fdf7cb
...
b142ebbede
Author | SHA1 | Date | |
---|---|---|---|
|
b142ebbede | ||
|
56598c47f4 | ||
|
9ed2c4e71d | ||
|
df70848176 | ||
|
af2127db90 | ||
|
14b74f0eb7 | ||
|
857626fb2e |
@ -83,6 +83,7 @@ typedef Qt3DCore::QGeometry Qt3DQGeometry;
|
|||||||
#include "qgs3dutils.h"
|
#include "qgs3dutils.h"
|
||||||
#include "qgsimagetexture.h"
|
#include "qgsimagetexture.h"
|
||||||
#include "qgstessellatedpolygongeometry.h"
|
#include "qgstessellatedpolygongeometry.h"
|
||||||
|
#include "qgsgeotransform.h"
|
||||||
|
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
|
|
||||||
@ -285,15 +286,15 @@ void Qgs3DSceneExporter::parseTerrain( QgsTerrainEntity *terrain, const QString
|
|||||||
switch ( generator->type() )
|
switch ( generator->type() )
|
||||||
{
|
{
|
||||||
case QgsTerrainGenerator::Dem:
|
case QgsTerrainGenerator::Dem:
|
||||||
terrainTile = getDemTerrainEntity( terrain, node );
|
terrainTile = getDemTerrainEntity( terrain, node, settings->origin() );
|
||||||
parseDemTile( terrainTile, layerName + QStringLiteral( "_" ) );
|
parseDemTile( terrainTile, layerName + QStringLiteral( "_" ) );
|
||||||
break;
|
break;
|
||||||
case QgsTerrainGenerator::Flat:
|
case QgsTerrainGenerator::Flat:
|
||||||
terrainTile = getFlatTerrainEntity( terrain, node );
|
terrainTile = getFlatTerrainEntity( terrain, node, settings->origin() );
|
||||||
parseFlatTile( terrainTile, layerName + QStringLiteral( "_" ) );
|
parseFlatTile( terrainTile, layerName + QStringLiteral( "_" ) );
|
||||||
break;
|
break;
|
||||||
case QgsTerrainGenerator::Mesh:
|
case QgsTerrainGenerator::Mesh:
|
||||||
terrainTile = getMeshTerrainEntity( terrain, node );
|
terrainTile = getMeshTerrainEntity( terrain, node, settings->origin() );
|
||||||
parseMeshTile( terrainTile, layerName + QStringLiteral( "_" ) );
|
parseMeshTile( terrainTile, layerName + QStringLiteral( "_" ) );
|
||||||
break;
|
break;
|
||||||
// TODO: implement other terrain types
|
// TODO: implement other terrain types
|
||||||
@ -304,7 +305,7 @@ void Qgs3DSceneExporter::parseTerrain( QgsTerrainEntity *terrain, const QString
|
|||||||
textureGenerator->setTextureSize( oldResolution );
|
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() );
|
QgsFlatTerrainGenerator *generator = dynamic_cast<QgsFlatTerrainGenerator *>( terrain->mapSettings()->terrainGenerator() );
|
||||||
FlatTerrainChunkLoader *flatTerrainLoader = qobject_cast<FlatTerrainChunkLoader *>( generator->createChunkLoader( node ) );
|
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
|
// the entity we created will be deallocated once the scene exporter is deallocated
|
||||||
Qt3DCore::QEntity *entity = flatTerrainLoader->createEntity( this );
|
Qt3DCore::QEntity *entity = flatTerrainLoader->createEntity( this );
|
||||||
QgsTerrainTileEntity *tileEntity = qobject_cast<QgsTerrainTileEntity *>( entity );
|
QgsTerrainTileEntity *tileEntity = qobject_cast<QgsTerrainTileEntity *>( entity );
|
||||||
|
|
||||||
|
const QList<QgsGeoTransform *> transforms = entity->findChildren<QgsGeoTransform *>();
|
||||||
|
for ( QgsGeoTransform *transform : transforms )
|
||||||
|
{
|
||||||
|
transform->setOrigin( mapOrigin );
|
||||||
|
}
|
||||||
|
|
||||||
return tileEntity;
|
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)
|
// 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
|
// 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 )
|
if ( mExportTextures )
|
||||||
terrain->textureGenerator()->waitForFinished();
|
terrain->textureGenerator()->waitForFinished();
|
||||||
QgsTerrainTileEntity *tileEntity = qobject_cast<QgsTerrainTileEntity *>( loader->createEntity( this ) );
|
QgsTerrainTileEntity *tileEntity = qobject_cast<QgsTerrainTileEntity *>( loader->createEntity( this ) );
|
||||||
|
|
||||||
|
const QList<QgsGeoTransform *> transforms = tileEntity->findChildren<QgsGeoTransform *>();
|
||||||
|
for ( QgsGeoTransform *transform : transforms )
|
||||||
|
{
|
||||||
|
transform->setOrigin( mapOrigin );
|
||||||
|
}
|
||||||
|
|
||||||
delete generator;
|
delete generator;
|
||||||
return tileEntity;
|
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() );
|
QgsMeshTerrainGenerator *generator = dynamic_cast<QgsMeshTerrainGenerator *>( terrain->mapSettings()->terrainGenerator() );
|
||||||
QgsMeshTerrainTileLoader *loader = qobject_cast<QgsMeshTerrainTileLoader *>( generator->createChunkLoader( node ) );
|
QgsMeshTerrainTileLoader *loader = qobject_cast<QgsMeshTerrainTileLoader *>( generator->createChunkLoader( node ) );
|
||||||
// TODO: export textures
|
// TODO: export textures
|
||||||
QgsTerrainTileEntity *tileEntity = qobject_cast<QgsTerrainTileEntity *>( loader->createEntity( this ) );
|
QgsTerrainTileEntity *tileEntity = qobject_cast<QgsTerrainTileEntity *>( loader->createEntity( this ) );
|
||||||
|
|
||||||
|
const QList<QgsGeoTransform *> transforms = tileEntity->findChildren<QgsGeoTransform *>();
|
||||||
|
for ( QgsGeoTransform *transform : transforms )
|
||||||
|
{
|
||||||
|
transform->setOrigin( mapOrigin );
|
||||||
|
}
|
||||||
|
|
||||||
return tileEntity;
|
return tileEntity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,12 +37,13 @@ class QgsDemTerrainGenerator;
|
|||||||
class QgsChunkNode;
|
class QgsChunkNode;
|
||||||
class Qgs3DExportObject;
|
class Qgs3DExportObject;
|
||||||
class QgsTerrainTextureGenerator;
|
class QgsTerrainTextureGenerator;
|
||||||
|
class QgsVector3D;
|
||||||
class QgsVectorLayer;
|
class QgsVectorLayer;
|
||||||
class QgsPolygon3DSymbol;
|
class QgsPolygon3DSymbol;
|
||||||
class QgsLine3DSymbol;
|
class QgsLine3DSymbol;
|
||||||
class QgsPoint3DSymbol;
|
class QgsPoint3DSymbol;
|
||||||
class QgsMeshEntity;
|
class QgsMeshEntity;
|
||||||
class TestQgs3DRendering;
|
class TestQgs3DExporter;
|
||||||
|
|
||||||
#define SIP_NO_FILE
|
#define SIP_NO_FILE
|
||||||
|
|
||||||
@ -126,11 +127,11 @@ class _3D_EXPORT Qgs3DSceneExporter : public Qt3DCore::QEntity
|
|||||||
Qgs3DExportObject *processPoints( Qt3DCore::QEntity *entity, const QString &objectNamePrefix );
|
Qgs3DExportObject *processPoints( Qt3DCore::QEntity *entity, const QString &objectNamePrefix );
|
||||||
|
|
||||||
//! Returns a tile entity that contains the geometry to be exported and necessary scaling parameters
|
//! 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
|
//! 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
|
//! 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
|
//! Constructs a Qgs3DExportObject from the DEM tile entity
|
||||||
void parseDemTile( QgsTerrainTileEntity *tileEntity, const QString &layerName );
|
void parseDemTile( QgsTerrainTileEntity *tileEntity, const QString &layerName );
|
||||||
@ -157,7 +158,7 @@ class _3D_EXPORT Qgs3DSceneExporter : public Qt3DCore::QEntity
|
|||||||
friend QgsPolygon3DSymbol;
|
friend QgsPolygon3DSymbol;
|
||||||
friend QgsLine3DSymbol;
|
friend QgsLine3DSymbol;
|
||||||
friend QgsPoint3DSymbol;
|
friend QgsPoint3DSymbol;
|
||||||
friend TestQgs3DRendering;
|
friend TestQgs3DExporter;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // QGS3DSCENEEXPORTER_H
|
#endif // QGS3DSCENEEXPORTER_H
|
||||||
|
@ -21,6 +21,7 @@ add_subdirectory(sandbox)
|
|||||||
|
|
||||||
set(TESTS
|
set(TESTS
|
||||||
testqgs3dcameracontroller.cpp
|
testqgs3dcameracontroller.cpp
|
||||||
|
testqgs3dexporter.cpp
|
||||||
testqgs3dmapscene.cpp
|
testqgs3dmapscene.cpp
|
||||||
testqgs3dmaterial.cpp
|
testqgs3dmaterial.cpp
|
||||||
testqgs3drendering.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 "qgsfillsymbol.h"
|
||||||
#include "qgsmarkersymbol.h"
|
#include "qgsmarkersymbol.h"
|
||||||
#include "qgsgoochmaterialsettings.h"
|
#include "qgsgoochmaterialsettings.h"
|
||||||
#include "qgs3dsceneexporter.h"
|
|
||||||
#include "qgsdirectionallightsettings.h"
|
#include "qgsdirectionallightsettings.h"
|
||||||
#include "qgsmetalroughmaterialsettings.h"
|
#include "qgsmetalroughmaterialsettings.h"
|
||||||
#include "qgspointlightsettings.h"
|
#include "qgspointlightsettings.h"
|
||||||
@ -109,14 +108,10 @@ class TestQgs3DRendering : public QgsTest
|
|||||||
void testDepthBuffer();
|
void testDepthBuffer();
|
||||||
void testAmbientOcclusion();
|
void testAmbientOcclusion();
|
||||||
void testDebugMap();
|
void testDebugMap();
|
||||||
void test3DSceneExporter();
|
|
||||||
void test3DSceneExporterBig();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QImage convertDepthImageToGrayscaleImage( const QImage &depthImage );
|
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;
|
std::unique_ptr<QgsProject> mProject;
|
||||||
QgsRasterLayer *mLayerDtm = nullptr;
|
QgsRasterLayer *mLayerDtm = nullptr;
|
||||||
QgsRasterLayer *mLayerRgb = nullptr;
|
QgsRasterLayer *mLayerRgb = nullptr;
|
||||||
@ -2392,190 +2387,5 @@ void TestQgs3DRendering::testDebugMap()
|
|||||||
mapSettings.setLayers( {} );
|
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 )
|
QGSTEST_MAIN( TestQgs3DRendering )
|
||||||
#include "testqgs3drendering.moc"
|
#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