mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-11 00:04:27 -04:00
399 lines
15 KiB
C++
399 lines
15 KiB
C++
/***************************************************************************
|
|
testqgs3drendering.cpp
|
|
--------------------------------------
|
|
Date : July 2018
|
|
Copyright : (C) 2018 by Martin Dobias
|
|
Email : wonder dot sk at gmail 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 "qgsrenderchecker.h"
|
|
|
|
#include "qgsmaplayerstylemanager.h"
|
|
#include "qgsmapthemecollection.h"
|
|
#include "qgsproject.h"
|
|
#include "qgsrasterlayer.h"
|
|
#include "qgsrastershader.h"
|
|
#include "qgssinglebandpseudocolorrenderer.h"
|
|
#include "qgsvectorlayer.h"
|
|
#include "qgsmeshlayer.h"
|
|
#include "qgsmeshrenderersettings.h"
|
|
|
|
#include "qgs3dmapscene.h"
|
|
#include "qgs3dmapsettings.h"
|
|
#include "qgs3dutils.h"
|
|
#include "qgscameracontroller.h"
|
|
#include "qgschunknode_p.h"
|
|
#include "qgsdemterraingenerator.h"
|
|
#include "qgsflatterraingenerator.h"
|
|
#include "qgsoffscreen3dengine.h"
|
|
#include "qgspolygon3dsymbol.h"
|
|
#include "qgsrulebased3drenderer.h"
|
|
#include "qgsterrainentity_p.h"
|
|
#include "qgsvectorlayer3drenderer.h"
|
|
#include "qgsmeshlayer3drenderer.h"
|
|
|
|
class TestQgs3DRendering : public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
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 testFlatTerrain();
|
|
void testDemTerrain();
|
|
void testExtrudedPolygons();
|
|
void testMapTheme();
|
|
void testMesh();
|
|
void testRuleBasedRenderer();
|
|
|
|
private:
|
|
bool renderCheck( const QString &testName, QImage &image, int mismatchCount = 0 );
|
|
|
|
QString mReport;
|
|
|
|
std::unique_ptr<QgsProject> mProject;
|
|
QgsRasterLayer *mLayerDtm;
|
|
QgsRasterLayer *mLayerRgb;
|
|
QgsVectorLayer *mLayerBuildings;
|
|
QgsMeshLayer *mLayerMesh;
|
|
};
|
|
|
|
//runs before all tests
|
|
void TestQgs3DRendering::initTestCase()
|
|
{
|
|
// init QGIS's paths - true means that all path will be inited from prefix
|
|
QgsApplication::init();
|
|
QgsApplication::initQgis();
|
|
|
|
mReport = QStringLiteral( "<h1>3D Rendering Tests</h1>\n" );
|
|
|
|
mProject.reset( new QgsProject );
|
|
|
|
QString dataDir( TEST_DATA_DIR );
|
|
mLayerDtm = new QgsRasterLayer( dataDir + "/3d/dtm.tif", "dtm", "gdal" );
|
|
QVERIFY( mLayerDtm->isValid() );
|
|
mProject->addMapLayer( mLayerDtm );
|
|
|
|
mLayerRgb = new QgsRasterLayer( dataDir + "/3d/rgb.tif", "rgb", "gdal" );
|
|
QVERIFY( mLayerRgb->isValid() );
|
|
mProject->addMapLayer( mLayerRgb );
|
|
|
|
mLayerBuildings = new QgsVectorLayer( dataDir + "/3d/buildings.shp", "buildings", "ogr" );
|
|
QVERIFY( mLayerBuildings->isValid() );
|
|
mProject->addMapLayer( mLayerBuildings );
|
|
|
|
// 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 material;
|
|
material.setAmbient( Qt::lightGray );
|
|
QgsPolygon3DSymbol *symbol3d = new QgsPolygon3DSymbol;
|
|
symbol3d->setMaterial( material );
|
|
symbol3d->setExtrusionHeight( 10.f );
|
|
QgsVectorLayer3DRenderer *renderer3d = new QgsVectorLayer3DRenderer( symbol3d );
|
|
mLayerBuildings->setRenderer3D( renderer3d );
|
|
|
|
mLayerMesh = new QgsMeshLayer( dataDir + "/mesh/quad_flower.2dm", "mesh", "mdal" );
|
|
QVERIFY( mLayerMesh->isValid() );
|
|
mLayerMesh->setCrs( mLayerDtm->crs() ); // this testing mesh does not have any CRS defined originally
|
|
// disable rendering of scalar 2d datasets for now
|
|
QgsMeshRendererSettings settings = mLayerMesh->rendererSettings();
|
|
settings.setActiveScalarDataset();
|
|
settings.setActiveVectorDataset();
|
|
mLayerMesh->setRendererSettings( settings );
|
|
mProject->addMapLayer( mLayerMesh );
|
|
|
|
QgsPhongMaterialSettings meshMaterial;
|
|
QgsMesh3DSymbol *symbolMesh3d = new QgsMesh3DSymbol;
|
|
symbolMesh3d->setMaterial( meshMaterial );
|
|
QgsMeshLayer3DRenderer *meshRenderer3d = new QgsMeshLayer3DRenderer( symbolMesh3d );
|
|
mLayerMesh->setRenderer3D( meshRenderer3d );
|
|
|
|
mProject->setCrs( mLayerDtm->crs() );
|
|
|
|
//
|
|
// prepare styles for DTM layer
|
|
//
|
|
|
|
mLayerDtm->styleManager()->addStyleFromLayer( "grayscale" );
|
|
|
|
double vMin = 44, vMax = 198;
|
|
QColor cMin = Qt::red, cMax = Qt::yellow;
|
|
|
|
// change renderer for the new style
|
|
std::unique_ptr<QgsColorRampShader> colorRampShader( new QgsColorRampShader( vMin, vMax ) );
|
|
colorRampShader->setColorRampItemList( QList<QgsColorRampShader::ColorRampItem>()
|
|
<< QgsColorRampShader::ColorRampItem( vMin, cMin )
|
|
<< QgsColorRampShader::ColorRampItem( vMax, cMax ) );
|
|
std::unique_ptr<QgsRasterShader> shader( new QgsRasterShader( vMin, vMax ) );
|
|
shader->setRasterShaderFunction( colorRampShader.release() );
|
|
QgsSingleBandPseudoColorRenderer *r = new QgsSingleBandPseudoColorRenderer( mLayerDtm->renderer()->input(), 1, shader.release() );
|
|
mLayerDtm->setRenderer( r );
|
|
mLayerDtm->styleManager()->addStyleFromLayer( "my_style" );
|
|
|
|
mLayerDtm->styleManager()->setCurrentStyle( "grayscale" );
|
|
|
|
//
|
|
// add map theme
|
|
//
|
|
|
|
QgsMapThemeCollection::MapThemeLayerRecord layerRecord( mLayerDtm );
|
|
layerRecord.usingCurrentStyle = true;
|
|
layerRecord.currentStyle = "my_style";
|
|
QgsMapThemeCollection::MapThemeRecord record;
|
|
record.addLayerRecord( layerRecord );
|
|
mProject->mapThemeCollection()->insert( "theme_dtm", record );
|
|
|
|
}
|
|
|
|
//runs after all tests
|
|
void TestQgs3DRendering::cleanupTestCase()
|
|
{
|
|
mProject.reset();
|
|
|
|
QString myReportFile = QDir::tempPath() + "/qgistest.html";
|
|
QFile myFile( myReportFile );
|
|
if ( myFile.open( QIODevice::WriteOnly | QIODevice::Append ) )
|
|
{
|
|
QTextStream myQTextStream( &myFile );
|
|
myQTextStream << mReport;
|
|
myFile.close();
|
|
}
|
|
|
|
QgsApplication::exitQgis();
|
|
}
|
|
|
|
void TestQgs3DRendering::testFlatTerrain()
|
|
{
|
|
QgsRectangle fullExtent = mLayerDtm->extent();
|
|
|
|
Qgs3DMapSettings *map = new Qgs3DMapSettings;
|
|
map->setCrs( mProject->crs() );
|
|
map->setOrigin( QgsVector3D( fullExtent.center().x(), fullExtent.center().y(), 0 ) );
|
|
map->setLayers( QList<QgsMapLayer *>() << mLayerRgb );
|
|
|
|
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 );
|
|
|
|
// look from the top
|
|
scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2500, 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 );
|
|
QVERIFY( renderCheck( "flat_terrain_1", img, 40 ) );
|
|
|
|
// tilted view (pitch = 60 degrees)
|
|
scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2500, 60, 0 );
|
|
QImage img2 = Qgs3DUtils::captureSceneImage( engine, scene );
|
|
QVERIFY( renderCheck( "flat_terrain_2", img2, 40 ) );
|
|
|
|
// also add horizontal rotation (yaw = 45 degrees)
|
|
scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2500, 60, 45 );
|
|
QImage img3 = Qgs3DUtils::captureSceneImage( engine, scene );
|
|
QVERIFY( renderCheck( "flat_terrain_3", img3, 40 ) );
|
|
}
|
|
|
|
void TestQgs3DRendering::testDemTerrain()
|
|
{
|
|
QgsRectangle fullExtent = mLayerDtm->extent();
|
|
|
|
Qgs3DMapSettings *map = new Qgs3DMapSettings;
|
|
map->setCrs( mProject->crs() );
|
|
map->setOrigin( QgsVector3D( fullExtent.center().x(), fullExtent.center().y(), 0 ) );
|
|
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, 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 img3 = Qgs3DUtils::captureSceneImage( engine, scene );
|
|
|
|
QVERIFY( renderCheck( "dem_terrain_1", img3, 40 ) );
|
|
}
|
|
|
|
void TestQgs3DRendering::testExtrudedPolygons()
|
|
{
|
|
QgsRectangle fullExtent = mLayerDtm->extent();
|
|
|
|
Qgs3DMapSettings *map = new Qgs3DMapSettings;
|
|
map->setCrs( mProject->crs() );
|
|
map->setOrigin( QgsVector3D( fullExtent.center().x(), fullExtent.center().y(), 0 ) );
|
|
map->setLayers( QList<QgsMapLayer *>() << mLayerRgb << mLayerBuildings );
|
|
QgsPointLightSettings defaultLight;
|
|
defaultLight.setPosition( QgsVector3D( 0, 1000, 0 ) );
|
|
map->setPointLights( QList<QgsPointLightSettings>() << defaultLight );
|
|
|
|
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, 250 ), 500, 45, 0 );
|
|
QImage img = Qgs3DUtils::captureSceneImage( engine, scene );
|
|
|
|
QVERIFY( renderCheck( "polygon3d_extrusion", img, 40 ) );
|
|
}
|
|
|
|
void TestQgs3DRendering::testMapTheme()
|
|
{
|
|
QgsRectangle fullExtent = mLayerDtm->extent();
|
|
|
|
Qgs3DMapSettings *map = new Qgs3DMapSettings;
|
|
map->setCrs( mProject->crs() );
|
|
map->setOrigin( QgsVector3D( fullExtent.center().x(), fullExtent.center().y(), 0 ) );
|
|
map->setLayers( QList<QgsMapLayer *>() << mLayerRgb );
|
|
|
|
// set theme - this should override what we set in setLayers()
|
|
map->setMapThemeCollection( mProject->mapThemeCollection() );
|
|
map->setTerrainMapTheme( "theme_dtm" );
|
|
|
|
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 );
|
|
|
|
// look from the top
|
|
scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2500, 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 );
|
|
QVERIFY( renderCheck( "terrain_theme", img, 40 ) );
|
|
}
|
|
|
|
void TestQgs3DRendering::testMesh()
|
|
{
|
|
QgsRectangle fullExtent = mLayerMesh->extent();
|
|
|
|
Qgs3DMapSettings *map = new Qgs3DMapSettings;
|
|
map->setCrs( mProject->crs() );
|
|
map->setOrigin( QgsVector3D( fullExtent.center().x(), fullExtent.center().y(), 0 ) );
|
|
map->setLayers( QList<QgsMapLayer *>() << mLayerMesh );
|
|
QgsPointLightSettings defaultLight;
|
|
defaultLight.setPosition( QgsVector3D( 0, 1000, 0 ) );
|
|
map->setPointLights( QList<QgsPointLightSettings>() << defaultLight );
|
|
|
|
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 );
|
|
QImage img = Qgs3DUtils::captureSceneImage( engine, scene );
|
|
|
|
QVERIFY( renderCheck( "mesh3d", img, 40 ) );
|
|
}
|
|
|
|
void TestQgs3DRendering::testRuleBasedRenderer()
|
|
{
|
|
QgsPhongMaterialSettings material;
|
|
material.setAmbient( Qt::lightGray );
|
|
QgsPolygon3DSymbol *symbol3d = new QgsPolygon3DSymbol;
|
|
symbol3d->setMaterial( material );
|
|
symbol3d->setExtrusionHeight( 10.f );
|
|
|
|
QgsPhongMaterialSettings material2;
|
|
material2.setAmbient( Qt::red );
|
|
QgsPolygon3DSymbol *symbol3d2 = new QgsPolygon3DSymbol;
|
|
symbol3d2->setMaterial( material2 );
|
|
symbol3d2->setExtrusionHeight( 10.f );
|
|
|
|
QgsRuleBased3DRenderer::Rule *root = new QgsRuleBased3DRenderer::Rule( nullptr );
|
|
QgsRuleBased3DRenderer::Rule *rule1 = new QgsRuleBased3DRenderer::Rule( symbol3d, "ogc_fid < 29069", "rule 1" );
|
|
QgsRuleBased3DRenderer::Rule *rule2 = new QgsRuleBased3DRenderer::Rule( symbol3d2, "ogc_fid >= 29069", "rule 2" );
|
|
root->appendChild( rule1 );
|
|
root->appendChild( rule2 );
|
|
QgsRuleBased3DRenderer *renderer3d = new QgsRuleBased3DRenderer( root );
|
|
mLayerBuildings->setRenderer3D( renderer3d );
|
|
|
|
QgsRectangle fullExtent = mLayerDtm->extent();
|
|
|
|
Qgs3DMapSettings *map = new Qgs3DMapSettings;
|
|
map->setCrs( mProject->crs() );
|
|
map->setOrigin( QgsVector3D( fullExtent.center().x(), fullExtent.center().y(), 0 ) );
|
|
map->setLayers( QList<QgsMapLayer *>() << mLayerBuildings );
|
|
|
|
QgsPointLightSettings defaultLight;
|
|
defaultLight.setPosition( QgsVector3D( 0, 1000, 0 ) );
|
|
map->setPointLights( QList<QgsPointLightSettings>() << defaultLight );
|
|
|
|
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, 250 ), 500, 45, 0 );
|
|
QImage img = Qgs3DUtils::captureSceneImage( engine, scene );
|
|
|
|
QVERIFY( renderCheck( "rulebased", img, 40 ) );
|
|
}
|
|
|
|
bool TestQgs3DRendering::renderCheck( const QString &testName, QImage &image, int mismatchCount )
|
|
{
|
|
mReport += "<h2>" + testName + "</h2>\n";
|
|
QString myTmpDir = QDir::tempPath() + '/';
|
|
QString myFileName = myTmpDir + testName + ".png";
|
|
image.save( myFileName, "PNG" );
|
|
QgsRenderChecker myChecker;
|
|
myChecker.setControlPathPrefix( QStringLiteral( "3d" ) );
|
|
myChecker.setControlName( "expected_" + testName );
|
|
myChecker.setRenderedImage( myFileName );
|
|
myChecker.setColorTolerance( 2 ); // color tolerance < 2 was failing polygon3d_extrusion test
|
|
bool myResultFlag = myChecker.compareImages( testName, mismatchCount );
|
|
mReport += myChecker.report();
|
|
return myResultFlag;
|
|
}
|
|
|
|
QGSTEST_MAIN( TestQgs3DRendering )
|
|
#include "testqgs3drendering.moc"
|