diff --git a/python/3d/auto_generated/qgspointcloudlayer3drenderer.sip.in b/python/3d/auto_generated/qgspointcloudlayer3drenderer.sip.in index 333b4926dcad6..c38bad182225b 100644 --- a/python/3d/auto_generated/qgspointcloudlayer3drenderer.sip.in +++ b/python/3d/auto_generated/qgspointcloudlayer3drenderer.sip.in @@ -104,6 +104,20 @@ Sets the maximum number of points to be rendered in the scene virtual bool convertFrom2DRenderer( QgsPointCloudRenderer *renderer ); + void setZoomOutBehavior( const Qgis::PointCloudZoomOutRenderBehavior behavior ); +%Docstring +Sets the renderer behavior when zoomed out + +.. versionadded:: 3.42 +%End + + Qgis::PointCloudZoomOutRenderBehavior zoomOutBehavior() const; +%Docstring +Returns the renderer behavior when zoomed out + +.. versionadded:: 3.42 +%End + private: QgsPointCloudLayer3DRenderer( const QgsPointCloudLayer3DRenderer & ); QgsPointCloudLayer3DRenderer &operator=( const QgsPointCloudLayer3DRenderer & ); diff --git a/python/PyQt6/3d/auto_generated/qgspointcloudlayer3drenderer.sip.in b/python/PyQt6/3d/auto_generated/qgspointcloudlayer3drenderer.sip.in index 333b4926dcad6..c38bad182225b 100644 --- a/python/PyQt6/3d/auto_generated/qgspointcloudlayer3drenderer.sip.in +++ b/python/PyQt6/3d/auto_generated/qgspointcloudlayer3drenderer.sip.in @@ -104,6 +104,20 @@ Sets the maximum number of points to be rendered in the scene virtual bool convertFrom2DRenderer( QgsPointCloudRenderer *renderer ); + void setZoomOutBehavior( const Qgis::PointCloudZoomOutRenderBehavior behavior ); +%Docstring +Sets the renderer behavior when zoomed out + +.. versionadded:: 3.42 +%End + + Qgis::PointCloudZoomOutRenderBehavior zoomOutBehavior() const; +%Docstring +Returns the renderer behavior when zoomed out + +.. versionadded:: 3.42 +%End + private: QgsPointCloudLayer3DRenderer( const QgsPointCloudLayer3DRenderer & ); QgsPointCloudLayer3DRenderer &operator=( const QgsPointCloudLayer3DRenderer & ); diff --git a/src/3d/qgspointcloudlayer3drenderer.cpp b/src/3d/qgspointcloudlayer3drenderer.cpp index c3525d1ff0733..f73117d3063f8 100644 --- a/src/3d/qgspointcloudlayer3drenderer.cpp +++ b/src/3d/qgspointcloudlayer3drenderer.cpp @@ -145,6 +145,7 @@ QgsPointCloudLayer3DRenderer *QgsPointCloudLayer3DRenderer::clone() const } r->setMaximumScreenError( mMaximumScreenError ); r->setShowBoundingBoxes( mShowBoundingBoxes ); + r->setZoomOutBehavior( mZoomOutBehavior ); return r; } @@ -185,6 +186,7 @@ void QgsPointCloudLayer3DRenderer::writeXml( QDomElement &elem, const QgsReadWri elem.setAttribute( QStringLiteral( "max-screen-error" ), maximumScreenError() ); elem.setAttribute( QStringLiteral( "show-bounding-boxes" ), showBoundingBoxes() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ); elem.setAttribute( QStringLiteral( "point-budget" ), mPointBudget ); + elem.setAttribute( QStringLiteral( "zoomOutBehavior" ), qgsEnumValueToKey( mZoomOutBehavior ) ); QDomElement elemSymbol = doc.createElement( QStringLiteral( "symbol" ) ); if ( mSymbol ) @@ -205,6 +207,7 @@ void QgsPointCloudLayer3DRenderer::readXml( const QDomElement &elem, const QgsRe mShowBoundingBoxes = elem.attribute( QStringLiteral( "show-bounding-boxes" ), QStringLiteral( "0" ) ).toInt(); mMaximumScreenError = elem.attribute( QStringLiteral( "max-screen-error" ), QStringLiteral( "3.0" ) ).toDouble(); mPointBudget = elem.attribute( QStringLiteral( "point-budget" ), QStringLiteral( "5000000" ) ).toInt(); + mZoomOutBehavior = qgsEnumKeyToValue( elem.attribute( QStringLiteral( "zoomOutBehavior" ) ), Qgis::PointCloudZoomOutRenderBehavior::RenderExtents ); if ( symbolType == QLatin1String( "single-color" ) ) mSymbol.reset( new QgsSingleColorPointCloud3DSymbol ); diff --git a/src/3d/qgspointcloudlayer3drenderer.h b/src/3d/qgspointcloudlayer3drenderer.h index ec6482e8de0b9..7b37d30667a33 100644 --- a/src/3d/qgspointcloudlayer3drenderer.h +++ b/src/3d/qgspointcloudlayer3drenderer.h @@ -303,12 +303,25 @@ class _3D_EXPORT QgsPointCloudLayer3DRenderer : public QgsAbstractPointCloud3DRe bool convertFrom2DRenderer( QgsPointCloudRenderer *renderer ) override; + /** + * Sets the renderer behavior when zoomed out + * \since QGIS 3.42 + */ + void setZoomOutBehavior( const Qgis::PointCloudZoomOutRenderBehavior behavior ) { mZoomOutBehavior = behavior; } + + /** + * Returns the renderer behavior when zoomed out + * \since QGIS 3.42 + */ + Qgis::PointCloudZoomOutRenderBehavior zoomOutBehavior() const { return mZoomOutBehavior; } + private: QgsMapLayerRef mLayerRef; //!< Layer used to extract mesh data from std::unique_ptr mSymbol; double mMaximumScreenError = 3.0; bool mShowBoundingBoxes = false; int mPointBudget = 5000000; + Qgis::PointCloudZoomOutRenderBehavior mZoomOutBehavior = Qgis::PointCloudZoomOutRenderBehavior::RenderExtents; private: #ifdef SIP_RUN diff --git a/src/3d/qgsvirtualpointcloudentity_p.cpp b/src/3d/qgsvirtualpointcloudentity_p.cpp index 9552cd4dc321f..bfead9b1d15b8 100644 --- a/src/3d/qgsvirtualpointcloudentity_p.cpp +++ b/src/3d/qgsvirtualpointcloudentity_p.cpp @@ -57,6 +57,24 @@ QgsVirtualPointCloudEntity::QgsVirtualPointCloudEntity( createChunkedEntityForSubIndex( i ); } + if ( provider()->overview() ) + { + mOverviewEntity = new QgsPointCloudLayerChunkedEntity( + mapSettings(), + provider()->overview(), + mCoordinateTransform, + dynamic_cast( mSymbol->clone() ), + mMaximumScreenSpaceError, + false, + mZValueScale, + mZValueOffset, + mPointBudget + ); + mOverviewEntity->setParent( this ); + connect( mOverviewEntity, &QgsChunkedEntity::pendingJobsCountChanged, this, &Qgs3DMapSceneEntity::pendingJobsCountChanged ); + emit newEntityCreated( mOverviewEntity ); + } + updateBboxEntity(); connect( this, &QgsVirtualPointCloudEntity::subIndexNeedsLoading, provider(), &QgsVirtualPointCloudProvider::loadSubIndex, Qt::QueuedConnection ); connect( provider(), &QgsVirtualPointCloudProvider::subIndexLoaded, this, &QgsVirtualPointCloudEntity::createChunkedEntityForSubIndex ); @@ -82,8 +100,8 @@ void QgsVirtualPointCloudEntity::createChunkedEntityForSubIndex( int i ) const QVector subIndexes = provider()->subIndexes(); const QgsPointCloudSubIndex &si = subIndexes.at( i ); - // Skip if Index is not yet loaded or is outside the map extents or it's not valid (e.g. file is missing) - if ( !si.index() || mBboxes.at( i ).isEmpty() || !si.index()->isValid() ) + // Skip if Index is not yet loaded or is outside the map extents, or it's not valid (e.g. file is missing) + if ( !si.index() || mBboxes.at( i ).isEmpty() || !si.index().isValid() ) return; QgsPointCloudLayerChunkedEntity *newChunkedEntity = new QgsPointCloudLayerChunkedEntity( @@ -132,6 +150,19 @@ void QgsVirtualPointCloudEntity::handleSceneUpdate( const SceneContext &sceneCon mChunkedEntitiesMap[i]->handleSceneUpdate( sceneContext ); } updateBboxEntity(); + + // reuse the same logic for showing bounding boxes as above + const QgsRectangle mapExtent = Qgs3DUtils::tryReprojectExtent2D( mMapSettings->extent(), mMapSettings->crs(), mLayer->crs(), mMapSettings->transformContext() ); + const QgsAABB overviewBBox = Qgs3DUtils::mapToWorldExtent( provider()->overview().extent().intersect( mapExtent ), provider()->overview().zMin(), provider()->overview().zMax(), mMapSettings->origin() ); + const float epsilon = std::min( overviewBBox.xExtent(), overviewBBox.yExtent() ) / 256; + const float distance = overviewBBox.distanceFromPoint( sceneContext.cameraPos ); + const float sse = Qgs3DUtils::screenSpaceError( epsilon, distance, sceneContext.screenSizePx, sceneContext.cameraFov ); + const bool displayAsBbox = sceneContext.cameraPos.isNull() || sse < .2; + const auto rendererBehavior = dynamic_cast( mLayer->renderer3D() )->zoomOutBehavior(); + if ( !displayAsBbox && ( rendererBehavior == Qgis::PointCloudZoomOutRenderBehavior::RenderOverview || rendererBehavior == Qgis::PointCloudZoomOutRenderBehavior::RenderOverviewAndExtents ) ) + { + mOverviewEntity->handleSceneUpdate( sceneContext ); + } } QgsRange QgsVirtualPointCloudEntity::getNearFarPlaneRange( const QMatrix4x4 &viewMatrix ) const @@ -189,16 +220,20 @@ bool QgsVirtualPointCloudEntity::needsUpdate() const void QgsVirtualPointCloudEntity::updateBboxEntity() { QList bboxes; - const QVector subIndexes = provider()->subIndexes(); - for ( int i = 0; i < subIndexes.size(); ++i ) + // we want to render bounding boxes only when zoomOutBehavior is RenderExtents or RenderOverviewAndExtents + if ( dynamic_cast( mLayer->renderer3D() )->zoomOutBehavior() != Qgis::PointCloudZoomOutRenderBehavior::RenderOverview ) { - if ( mChunkedEntitiesMap.contains( i ) && mChunkedEntitiesMap[i]->isEnabled() ) - continue; + const QVector subIndexes = provider()->subIndexes(); + for ( int i = 0; i < subIndexes.size(); ++i ) + { + if ( mChunkedEntitiesMap.contains( i ) && mChunkedEntitiesMap[i]->isEnabled() ) + continue; - if ( mBboxes.at( i ).isEmpty() ) - continue; + if ( mBboxes.at( i ).isEmpty() ) + continue; - bboxes << mBboxes.at( i ); + bboxes << mBboxes.at( i ); + } } mBboxesEntity->setBoxes( bboxes ); diff --git a/src/3d/qgsvirtualpointcloudentity_p.h b/src/3d/qgsvirtualpointcloudentity_p.h index 6416f8ba2f75e..ed9b08904da7c 100644 --- a/src/3d/qgsvirtualpointcloudentity_p.h +++ b/src/3d/qgsvirtualpointcloudentity_p.h @@ -32,6 +32,7 @@ #include "qgschunkedentity.h" #include "qgs3dmapsceneentity.h" #include "qgs3drendercontext.h" +#include "qgspointcloudlayerchunkloader_p.h" class QgsAABB; class QgsChunkBoundsEntity; @@ -93,6 +94,7 @@ class QgsVirtualPointCloudEntity : public Qgs3DMapSceneEntity QgsPointCloudLayer *mLayer = nullptr; QMap mChunkedEntitiesMap; QgsChunkBoundsEntity *mBboxesEntity = nullptr; + QgsPointCloudLayerChunkedEntity *mOverviewEntity = nullptr; QList mBboxes; QgsCoordinateTransform mCoordinateTransform; std::unique_ptr mSymbol; diff --git a/src/app/3d/qgspointcloud3dsymbolwidget.cpp b/src/app/3d/qgspointcloud3dsymbolwidget.cpp index d401b54311866..bbd34cc9c9b34 100644 --- a/src/app/3d/qgspointcloud3dsymbolwidget.cpp +++ b/src/app/3d/qgspointcloud3dsymbolwidget.cpp @@ -28,6 +28,7 @@ #include "qgspointcloudclassifiedrendererwidget.h" #include "qgspointcloudlayerelevationproperties.h" #include "qgsstackedwidget.h" +#include "qgsvirtualpointcloudprovider.h" QgsPointCloud3DSymbolWidget::QgsPointCloud3DSymbolWidget( QgsPointCloudLayer *layer, QgsPointCloud3DSymbol *symbol, QWidget *parent ) : QWidget( parent ) @@ -140,6 +141,29 @@ QgsPointCloud3DSymbolWidget::QgsPointCloud3DSymbolWidget( QgsPointCloudLayer *la mPointSizeSpinBox->setToolTip( tr( "The size of each point in pixels" ) ); mMaxScreenErrorSpinBox->setToolTip( tr( "The distance in pixels between the points of the smallest chunk to be rendered.\nRaising this value will result in a less detailed scene which can improve performance" ) ); mPointBudgetSpinBox->setToolTip( tr( "The maximum number of points that will be rendered simultaneously.\nRaising this value may allow missing chunks to be rendered while lowering it may improve performance" ) ); + + if ( !mLayer->dataProvider()->subIndexes().isEmpty() ) + { + mZoomOutOptions->addItem( tr( "Show Extents Only" ), QVariant::fromValue( Qgis::PointCloudZoomOutRenderBehavior::RenderExtents ) ); + if ( const QgsVirtualPointCloudProvider *vpcProvider = dynamic_cast( mLayer->dataProvider() ) ) + { + if ( vpcProvider->overview() ) + { + mZoomOutOptions->addItem( tr( "Show Overview Only" ), QVariant::fromValue( Qgis::PointCloudZoomOutRenderBehavior::RenderOverview ) ); + mZoomOutOptions->addItem( tr( "Show Extents Over Overview" ), QVariant::fromValue( Qgis::PointCloudZoomOutRenderBehavior::RenderOverviewAndExtents ) ); + } + } + else + { + mZoomOutOptions->setEnabled( false ); + } + + connect( mZoomOutOptions, qOverload( &QComboBox::currentIndexChanged ), this, &QgsPointCloud3DSymbolWidget::emitChangedSignal ); + } + else + { + mVpcGroupBox->setVisible( false ); + } } void QgsPointCloud3DSymbolWidget::setSymbol( QgsPointCloud3DSymbol *symbol ) @@ -665,6 +689,16 @@ bool QgsPointCloud3DSymbolWidget::showBoundingBoxes() const return mShowBoundingBoxesCheckBox->isChecked(); } +void QgsPointCloud3DSymbolWidget::setZoomOutBehavior( const Qgis::PointCloudZoomOutRenderBehavior zoomOutBehavior ) +{ + whileBlocking( mZoomOutOptions )->setCurrentIndex( mZoomOutOptions->findData( QVariant::fromValue( zoomOutBehavior ) ) ); +} + +Qgis::PointCloudZoomOutRenderBehavior QgsPointCloud3DSymbolWidget::zoomOutBehavior() const +{ + return mZoomOutOptions->currentData().value(); +} + void QgsPointCloud3DSymbolWidget::connectChildPanels( QgsPanelWidget *parent ) { parent->connectChildPanel( mClassifiedRendererWidget ); diff --git a/src/app/3d/qgspointcloud3dsymbolwidget.h b/src/app/3d/qgspointcloud3dsymbolwidget.h index 387ac2f8cd408..2fef1b011db27 100644 --- a/src/app/3d/qgspointcloud3dsymbolwidget.h +++ b/src/app/3d/qgspointcloud3dsymbolwidget.h @@ -45,6 +45,9 @@ class QgsPointCloud3DSymbolWidget : public QWidget, private Ui::QgsPointCloud3DS void setPointBudget( double budget ); double pointBudget() const; + void setZoomOutBehavior( Qgis::PointCloudZoomOutRenderBehavior zoomOutBehavior ); + Qgis::PointCloudZoomOutRenderBehavior zoomOutBehavior() const; + void connectChildPanels( QgsPanelWidget *parent ); private slots: diff --git a/src/app/3d/qgspointcloudlayer3drendererwidget.cpp b/src/app/3d/qgspointcloudlayer3drendererwidget.cpp index 4e1908f61d0c3..0b450a51a1cf4 100644 --- a/src/app/3d/qgspointcloudlayer3drendererwidget.cpp +++ b/src/app/3d/qgspointcloudlayer3drendererwidget.cpp @@ -51,6 +51,7 @@ void QgsPointCloudLayer3DRendererWidget::setRenderer( const QgsPointCloudLayer3D mWidgetPointCloudSymbol->setPointBudget( renderer->pointRenderingBudget() ); mWidgetPointCloudSymbol->setMaximumScreenError( renderer->maximumScreenError() ); mWidgetPointCloudSymbol->setShowBoundingBoxes( renderer->showBoundingBoxes() ); + mWidgetPointCloudSymbol->setZoomOutBehavior( renderer->zoomOutBehavior() ); } } @@ -63,6 +64,7 @@ QgsPointCloudLayer3DRenderer *QgsPointCloudLayer3DRendererWidget::renderer() renderer->setPointRenderingBudget( mWidgetPointCloudSymbol->pointBudget() ); renderer->setMaximumScreenError( mWidgetPointCloudSymbol->maximumScreenError() ); renderer->setShowBoundingBoxes( mWidgetPointCloudSymbol->showBoundingBoxes() ); + renderer->setZoomOutBehavior( mWidgetPointCloudSymbol->zoomOutBehavior() ); return renderer; } diff --git a/src/app/layers/qgsapplayerhandling.cpp b/src/app/layers/qgsapplayerhandling.cpp index 331d977bdcb1b..138e7af5aa84d 100644 --- a/src/app/layers/qgsapplayerhandling.cpp +++ b/src/app/layers/qgsapplayerhandling.cpp @@ -64,6 +64,7 @@ #include "qgsgdalutils.h" #include "qgstiledscenelayer.h" #include "qgsogrproviderutils.h" +#include "qgsvirtualpointcloudprovider.h" #include #include @@ -196,6 +197,11 @@ void QgsAppLayerHandling::postProcessAddedLayer( QgsMapLayer *layer ) { std::unique_ptr renderer3D = std::make_unique(); renderer3D->convertFrom2DRenderer( pcLayer->renderer() ); + // if overview of the virtual point cloud exists set the zoom out behavior to show it + if ( const QgsVirtualPointCloudProvider *vpcProvider = dynamic_cast( pcLayer->dataProvider() ) ) + { + vpcProvider->overview() ? renderer3D->setZoomOutBehavior( Qgis::PointCloudZoomOutRenderBehavior::RenderOverview ) : renderer3D->setZoomOutBehavior( Qgis::PointCloudZoomOutRenderBehavior::RenderExtents ); + } layer->setRenderer3D( renderer3D.release() ); } } diff --git a/src/ui/3d/qgspointcloud3dsymbolwidget.ui b/src/ui/3d/qgspointcloud3dsymbolwidget.ui index a1bcc3dbce7ac..fe8b069dcf70e 100644 --- a/src/ui/3d/qgspointcloud3dsymbolwidget.ui +++ b/src/ui/3d/qgspointcloud3dsymbolwidget.ui @@ -7,7 +7,7 @@ 0 0 581 - 553 + 728 @@ -34,14 +34,8 @@ false - - - 0 - - - 3 - - + + @@ -120,14 +114,7 @@ - - - - Show bounding boxes - - - - + Render as a Surface (Triangulate) @@ -188,13 +175,45 @@ + + + + Show bounding boxes + + + + + + + Virtual Point Cloud Options + + + false + + + false + + + + + + + + + When zoomed out + + + + + + - 0 + 5