diff --git a/src/3d/qgs3dmapsettings.cpp b/src/3d/qgs3dmapsettings.cpp index 66e477eb805..1f03cdd66b7 100644 --- a/src/3d/qgs3dmapsettings.cpp +++ b/src/3d/qgs3dmapsettings.cpp @@ -39,6 +39,7 @@ Qgs3DMapSettings::Qgs3DMapSettings( const Qgs3DMapSettings &other ) , mMaxTerrainGroundError( other.mMaxTerrainGroundError ) , mTerrainShadingEnabled( other.mTerrainShadingEnabled ) , mTerrainShadingMaterial( other.mTerrainShadingMaterial ) + , mTerrainMapTheme( other.mTerrainMapTheme ) , mShowTerrainBoundingBoxes( other.mShowTerrainBoundingBoxes ) , mShowTerrainTileInfo( other.mShowTerrainTileInfo ) , mShowCameraViewCenter( other.mShowCameraViewCenter ) @@ -48,6 +49,9 @@ Qgs3DMapSettings::Qgs3DMapSettings( const Qgs3DMapSettings &other ) , mSkyboxEnabled( other.mSkyboxEnabled ) , mSkyboxFileBase( other.mSkyboxFileBase ) , mSkyboxFileExtension( other.mSkyboxFileExtension ) + , mTransformContext( other.mTransformContext ) + , mPathResolver( other.mPathResolver ) + , mMapThemes( other.mMapThemes ) { Q_FOREACH ( QgsAbstract3DRenderer *renderer, other.mRenderers ) { @@ -87,6 +91,7 @@ void Qgs3DMapSettings::readXml( const QDomElement &elem, const QgsReadWriteConte QDomElement elemTerrainShadingMaterial = elemTerrain.firstChildElement( QStringLiteral( "shading-material" ) ); if ( !elemTerrainShadingMaterial.isNull() ) mTerrainShadingMaterial.readXml( elemTerrainShadingMaterial ); + mTerrainMapTheme = elemTerrain.attribute( QStringLiteral( "map-theme" ) ); mShowLabels = elemTerrain.attribute( QStringLiteral( "show-labels" ), QStringLiteral( "0" ) ).toInt(); mPointLights.clear(); @@ -204,6 +209,7 @@ QDomElement Qgs3DMapSettings::writeXml( QDomDocument &doc, const QgsReadWriteCon QDomElement elemTerrainShadingMaterial = doc.createElement( QStringLiteral( "shading-material" ) ); mTerrainShadingMaterial.writeXml( elemTerrainShadingMaterial ); elemTerrain.appendChild( elemTerrainShadingMaterial ); + elemTerrain.setAttribute( QStringLiteral( "map-theme" ), mTerrainMapTheme ); elemTerrain.setAttribute( QStringLiteral( "show-labels" ), mShowLabels ? 1 : 0 ); QDomElement elemPointLights = doc.createElement( QStringLiteral( "point-lights" ) ); @@ -432,6 +438,15 @@ void Qgs3DMapSettings::setTerrainShadingMaterial( const QgsPhongMaterialSettings emit terrainShadingChanged(); } +void Qgs3DMapSettings::setTerrainMapTheme( const QString &theme ) +{ + if ( mTerrainMapTheme == theme ) + return; + + mTerrainMapTheme = theme; + emit terrainMapThemeChanged(); +} + void Qgs3DMapSettings::setRenderers( const QList &renderers ) { mRenderers = renderers; diff --git a/src/3d/qgs3dmapsettings.h b/src/3d/qgs3dmapsettings.h index 27031a89f71..e47499c7e92 100644 --- a/src/3d/qgs3dmapsettings.h +++ b/src/3d/qgs3dmapsettings.h @@ -126,6 +126,21 @@ class _3D_EXPORT Qgs3DMapSettings : public QObject */ void setPathResolver( const QgsPathResolver &resolver ) { mPathResolver = resolver; } + /** + * Returns pointer to the collection of map themes. Normally this would be QgsProject::mapThemeCollection() + * of the currently used project. Without a valid map theme collection object it is not possible + * to resolve map themes from their names. + * \since QGIS 3.6 + */ + QgsMapThemeCollection *mapThemeCollection() const { return mMapThemes; } + + /** + * Sets pointer to the collection of map themes. + * \see mapThemeCollection() + * \since QGIS 3.6 + */ + void setMapThemeCollection( QgsMapThemeCollection *mapThemes ) { mMapThemes = mapThemes; } + //! Sets background color of the 3D map view void setBackgroundColor( const QColor &color ); //! Returns background color of the 3D map view @@ -148,9 +163,16 @@ class _3D_EXPORT Qgs3DMapSettings : public QObject //! Returns vertical scale (exaggeration) of terrain double terrainVerticalScale() const; - //! Sets the list of map layers to be rendered as a texture of the terrain + /** + * Sets the list of map layers to be rendered as a texture of the terrain + * \note If terrain map theme is set, it has a priority over the list of layers specified here. + */ void setLayers( const QList &layers ); - //! Returns the list of map layers to be rendered as a texture of the terrain + + /** + * Returns the list of map layers to be rendered as a texture of the terrain + * \note If terrain map theme is set, it has a priority over the list of layers specified here. + */ QList layers() const; /** @@ -231,6 +253,25 @@ class _3D_EXPORT Qgs3DMapSettings : public QObject */ QgsPhongMaterialSettings terrainShadingMaterial() const { return mTerrainShadingMaterial; } + /** + * Sets name of the map theme. + * \see terrainMapTheme() + * \since QGIS 3.6 + */ + void setTerrainMapTheme( const QString &theme ); + + /** + * Returns name of the map theme (from the active project) that will be used for terrain's texture. + * Empty map theme name means that the map theme is not overridden and the current map theme will be used. + * \note Support for map themes only works if mapThemeCollection() is a valid object (otherwise it is not possible to resolve map themes from names) + * \since QGIS 3.6 + */ + QString terrainMapTheme() const { return mTerrainMapTheme; } + + // + // misc configuration + // + //! Sets list of extra 3D renderers to use in the scene. Takes ownership of the objects. void setRenderers( const QList &renderers SIP_TRANSFER ); //! Returns list of extra 3D renderers @@ -310,6 +351,12 @@ class _3D_EXPORT Qgs3DMapSettings : public QObject * \since QGIS 3.6 */ void terrainShadingChanged(); + + /** + * Emitted when terrain's map theme has changed + * \since QGIS 3.6 + */ + void terrainMapThemeChanged(); //! Emitted when the flag whether terrain's bounding boxes are shown has changed void showTerrainBoundingBoxesChanged(); //! Emitted when the flag whether terrain's tile info is shown has changed @@ -342,6 +389,7 @@ class _3D_EXPORT Qgs3DMapSettings : public QObject float mMaxTerrainGroundError = 1.f; //!< Maximum allowed horizontal map error in map units (determines how many zoom levels will be used) bool mTerrainShadingEnabled = false; //!< Whether terrain should be shaded taking lights into account QgsPhongMaterialSettings mTerrainShadingMaterial; //!< Material to use for the terrain (if shading is enabled). Diffuse color is ignored. + QString mTerrainMapTheme; //!< Name of map theme used for terrain's texture (empty means use the current map theme) bool mShowTerrainBoundingBoxes = false; //!< Whether to show bounding boxes of entities - useful for debugging bool mShowTerrainTileInfo = false; //!< Whether to draw extra information about terrain tiles to the textures - useful for debugging bool mShowCameraViewCenter = false; //!< Whether to show camera view center as a sphere - useful for debugging @@ -355,6 +403,7 @@ class _3D_EXPORT Qgs3DMapSettings : public QObject //! Coordinate transform context QgsCoordinateTransformContext mTransformContext; QgsPathResolver mPathResolver; + QgsMapThemeCollection *mMapThemes = nullptr; //!< Pointer to map themes (e.g. from the current project) to resolve map theme content from the name }; diff --git a/src/3d/qgslayoutitem3dmap.cpp b/src/3d/qgslayoutitem3dmap.cpp index 3301f151652..fdf7e427e4c 100644 --- a/src/3d/qgslayoutitem3dmap.cpp +++ b/src/3d/qgslayoutitem3dmap.cpp @@ -217,7 +217,13 @@ bool QgsLayoutItem3DMap::readPropertiesFromElement( const QDomElement &element, mSettings.reset( new Qgs3DMapSettings ); mSettings->readXml( elemSettings, context ); if ( mLayout->project() ) + { mSettings->resolveReferences( *mLayout->project() ); + + mSettings->setTransformContext( mLayout->project()->transformContext() ); + mSettings->setPathResolver( mLayout->project()->pathResolver() ); + mSettings->setMapThemeCollection( mLayout->project()->mapThemeCollection() ); + } } QDomElement elemCameraPose = element.firstChildElement( QStringLiteral( "camera-pose" ) ); diff --git a/src/3d/terrain/qgsterrainentity_p.cpp b/src/3d/terrain/qgsterrainentity_p.cpp index 75be3e34558..fb0af0e655d 100644 --- a/src/3d/terrain/qgsterrainentity_p.cpp +++ b/src/3d/terrain/qgsterrainentity_p.cpp @@ -69,6 +69,7 @@ QgsTerrainEntity::QgsTerrainEntity( int maxLevel, const Qgs3DMapSettings &map, Q connect( &map, &Qgs3DMapSettings::showLabelsChanged, this, &QgsTerrainEntity::invalidateMapImages ); connect( &map, &Qgs3DMapSettings::layersChanged, this, &QgsTerrainEntity::onLayersChanged ); connect( &map, &Qgs3DMapSettings::backgroundColorChanged, this, &QgsTerrainEntity::invalidateMapImages ); + connect( &map, &Qgs3DMapSettings::terrainMapThemeChanged, this, &QgsTerrainEntity::invalidateMapImages ); connectToLayersRepaintRequest(); diff --git a/src/3d/terrain/qgsterraintexturegenerator_p.cpp b/src/3d/terrain/qgsterraintexturegenerator_p.cpp index a185734e594..89695b0c3ce 100644 --- a/src/3d/terrain/qgsterraintexturegenerator_p.cpp +++ b/src/3d/terrain/qgsterraintexturegenerator_p.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include "qgs3dmapsettings.h" @@ -127,13 +128,26 @@ void QgsTerrainTextureGenerator::onRenderingFinished() QgsMapSettings QgsTerrainTextureGenerator::baseMapSettings() { QgsMapSettings mapSettings; - mapSettings.setLayers( mMap.layers() ); + mapSettings.setOutputSize( QSize( mMap.mapTileResolution(), mMap.mapTileResolution() ) ); mapSettings.setDestinationCrs( mMap.crs() ); mapSettings.setBackgroundColor( mMap.backgroundColor() ); mapSettings.setFlag( QgsMapSettings::DrawLabeling, mMap.showLabels() ); mapSettings.setTransformContext( mMap.transformContext() ); mapSettings.setPathResolver( mMap.pathResolver() ); + + QgsMapThemeCollection *mapThemes = mMap.mapThemeCollection(); + QString mapThemeName = mMap.terrainMapTheme(); + if ( mapThemeName.isEmpty() || !mapThemes || !mapThemes->hasMapTheme( mapThemeName ) ) + { + mapSettings.setLayers( mMap.layers() ); + } + else + { + mapSettings.setLayers( mapThemes->mapThemeVisibleLayers( mapThemeName ) ); + mapSettings.setLayerStyleOverrides( mapThemes->mapThemeStyleOverrides( mapThemeName ) ); + } + return mapSettings; } diff --git a/src/app/3d/qgs3dmapconfigwidget.cpp b/src/app/3d/qgs3dmapconfigwidget.cpp index fcebeb36918..ac7953ee3a3 100644 --- a/src/app/3d/qgs3dmapconfigwidget.cpp +++ b/src/app/3d/qgs3dmapconfigwidget.cpp @@ -21,8 +21,9 @@ #include "qgs3dutils.h" #include "qgsmapcanvas.h" +#include "qgsmapthemecollection.h" #include "qgsrasterlayer.h" -//#include "qgsproject.h" +#include "qgsproject.h" Qgs3DMapConfigWidget::Qgs3DMapConfigWidget( Qgs3DMapSettings *map, QgsMapCanvas *mainCanvas, QWidget *parent ) : QWidget( parent ) @@ -73,6 +74,14 @@ Qgs3DMapConfigWidget::Qgs3DMapConfigWidget( Qgs3DMapSettings *map, QgsMapCanvas widgetTerrainMaterial->setDiffuseVisible( false ); widgetTerrainMaterial->setMaterial( mMap->terrainShadingMaterial() ); + // populate combo box with map themes + const QStringList mapThemeNames = QgsProject::instance()->mapThemeCollection()->mapThemes(); + cboTerrainMapTheme->addItem( QString() ); // empty item for no map theme + for ( QString themeName : mapThemeNames ) + cboTerrainMapTheme->addItem( themeName ); + + cboTerrainMapTheme->setCurrentText( mMap->terrainMapTheme() ); + widgetLights->setPointLights( mMap->pointLights() ); connect( cboTerrainLayer, static_cast( &QgsMapLayerComboBox::currentIndexChanged ), this, &Qgs3DMapConfigWidget::onTerrainLayerChanged ); @@ -144,6 +153,8 @@ void Qgs3DMapConfigWidget::apply() mMap->setTerrainShadingEnabled( groupTerrainShading->isChecked() ); mMap->setTerrainShadingMaterial( widgetTerrainMaterial->material() ); + mMap->setTerrainMapTheme( cboTerrainMapTheme->currentText() ); + mMap->setPointLights( widgetLights->pointLights() ); } diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index a9785aeef28..01fd8f6f6be 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -11141,8 +11141,10 @@ void QgisApp::new3DMapCanvas() map->setSelectionColor( mMapCanvas->selectionColor() ); map->setBackgroundColor( mMapCanvas->canvasColor() ); map->setLayers( mMapCanvas->layers() ); + map->setTransformContext( QgsProject::instance()->transformContext() ); map->setPathResolver( QgsProject::instance()->pathResolver() ); + map->setMapThemeCollection( QgsProject::instance()->mapThemeCollection() ); connect( QgsProject::instance(), &QgsProject::transformContextChanged, map, [map] { map->setTransformContext( QgsProject::instance()->transformContext() ); @@ -13562,6 +13564,14 @@ void QgisApp::readProject( const QDomDocument &doc ) map->readXml( elem3D, readWriteContext ); map->resolveReferences( *QgsProject::instance() ); + map->setTransformContext( QgsProject::instance()->transformContext() ); + map->setPathResolver( QgsProject::instance()->pathResolver() ); + map->setMapThemeCollection( QgsProject::instance()->mapThemeCollection() ); + connect( QgsProject::instance(), &QgsProject::transformContextChanged, map, [map] + { + map->setTransformContext( QgsProject::instance()->transformContext() ); + } ); + // these things are not saved in project map->setSelectionColor( mMapCanvas->selectionColor() ); map->setBackgroundColor( mMapCanvas->canvasColor() ); diff --git a/src/ui/3d/map3dconfigwidget.ui b/src/ui/3d/map3dconfigwidget.ui index f2567f8a1ba..40d2fbff680 100644 --- a/src/ui/3d/map3dconfigwidget.ui +++ b/src/ui/3d/map3dconfigwidget.ui @@ -7,7 +7,7 @@ 0 0 691 - 1135 + 1122 @@ -15,7 +15,7 @@ - + Terrain @@ -84,6 +84,20 @@ + + + + Map theme + + + + + + + false + + + @@ -275,12 +289,15 @@ spinTerrainScale spinTerrainResolution spinTerrainSkirtHeight + cboTerrainMapTheme + groupTerrainShading spinMapResolution spinScreenError spinGroundError chkShowLabels chkShowTileInfo chkShowBoundingBoxes + chkShowCameraViewCenter diff --git a/tests/src/3d/testqgs3drendering.cpp b/tests/src/3d/testqgs3drendering.cpp index 6f2702d0525..f9b96722bae 100644 --- a/tests/src/3d/testqgs3drendering.cpp +++ b/tests/src/3d/testqgs3drendering.cpp @@ -16,8 +16,12 @@ #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 "qgs3dmapscene.h" @@ -43,6 +47,7 @@ class TestQgs3DRendering : public QObject void testFlatTerrain(); void testDemTerrain(); void testExtrudedPolygons(); + void testMapTheme(); private: bool renderCheck( const QString &testName, QImage &image, int mismatchCount = 0 ); @@ -88,6 +93,39 @@ void TestQgs3DRendering::initTestCase() mLayerBuildings->setRenderer3D( renderer3d ); 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 colorRampShader( new QgsColorRampShader( vMin, vMax ) ); + colorRampShader->setColorRampItemList( QList() + << QgsColorRampShader::ColorRampItem( vMin, cMin ) + << QgsColorRampShader::ColorRampItem( vMax, cMax ) ); + std::unique_ptr 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 @@ -204,6 +242,40 @@ void TestQgs3DRendering::testExtrudedPolygons() 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() << 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 ) ); +} + bool TestQgs3DRendering::renderCheck( const QString &testName, QImage &image, int mismatchCount ) { diff --git a/tests/testdata/control_images/3d/expected_terrain_theme/expected_terrain_theme.png b/tests/testdata/control_images/3d/expected_terrain_theme/expected_terrain_theme.png new file mode 100644 index 00000000000..155b1580fd0 Binary files /dev/null and b/tests/testdata/control_images/3d/expected_terrain_theme/expected_terrain_theme.png differ