Skip to content

Commit

Permalink
Add VPC zoom out behavior for 3D rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
Withalion committed Jan 10, 2025
1 parent 0a0fb70 commit 9e2c217
Show file tree
Hide file tree
Showing 11 changed files with 172 additions and 27 deletions.
14 changes: 14 additions & 0 deletions python/3d/auto_generated/qgspointcloudlayer3drenderer.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -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 & );
Expand Down
14 changes: 14 additions & 0 deletions python/PyQt6/3d/auto_generated/qgspointcloudlayer3drenderer.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -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 & );
Expand Down
3 changes: 3 additions & 0 deletions src/3d/qgspointcloudlayer3drenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ QgsPointCloudLayer3DRenderer *QgsPointCloudLayer3DRenderer::clone() const
}
r->setMaximumScreenError( mMaximumScreenError );
r->setShowBoundingBoxes( mShowBoundingBoxes );
r->setZoomOutBehavior( mZoomOutBehavior );
return r;
}

Expand Down Expand Up @@ -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 )
Expand All @@ -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 );
Expand Down
13 changes: 13 additions & 0 deletions src/3d/qgspointcloudlayer3drenderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<QgsPointCloud3DSymbol> mSymbol;
double mMaximumScreenError = 3.0;
bool mShowBoundingBoxes = false;
int mPointBudget = 5000000;
Qgis::PointCloudZoomOutRenderBehavior mZoomOutBehavior = Qgis::PointCloudZoomOutRenderBehavior::RenderExtents;

private:
#ifdef SIP_RUN
Expand Down
53 changes: 44 additions & 9 deletions src/3d/qgsvirtualpointcloudentity_p.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,24 @@ QgsVirtualPointCloudEntity::QgsVirtualPointCloudEntity(
createChunkedEntityForSubIndex( i );
}

if ( provider()->overview() )
{
mOverviewEntity = new QgsPointCloudLayerChunkedEntity(
mapSettings(),
provider()->overview(),
mCoordinateTransform,
dynamic_cast<QgsPointCloud3DSymbol *>( 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 );
Expand All @@ -82,8 +100,8 @@ void QgsVirtualPointCloudEntity::createChunkedEntityForSubIndex( int i )
const QVector<QgsPointCloudSubIndex> 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(
Expand Down Expand Up @@ -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<QgsPointCloudLayer3DRenderer *>( mLayer->renderer3D() )->zoomOutBehavior();
if ( !displayAsBbox && ( rendererBehavior == Qgis::PointCloudZoomOutRenderBehavior::RenderOverview || rendererBehavior == Qgis::PointCloudZoomOutRenderBehavior::RenderOverviewAndExtents ) )
{
mOverviewEntity->handleSceneUpdate( sceneContext );
}
}

QgsRange<float> QgsVirtualPointCloudEntity::getNearFarPlaneRange( const QMatrix4x4 &viewMatrix ) const
Expand Down Expand Up @@ -189,16 +220,20 @@ bool QgsVirtualPointCloudEntity::needsUpdate() const
void QgsVirtualPointCloudEntity::updateBboxEntity()
{
QList<QgsAABB> bboxes;
const QVector<QgsPointCloudSubIndex> 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<QgsPointCloudLayer3DRenderer *>( mLayer->renderer3D() )->zoomOutBehavior() != Qgis::PointCloudZoomOutRenderBehavior::RenderOverview )
{
if ( mChunkedEntitiesMap.contains( i ) && mChunkedEntitiesMap[i]->isEnabled() )
continue;
const QVector<QgsPointCloudSubIndex> 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 );
Expand Down
2 changes: 2 additions & 0 deletions src/3d/qgsvirtualpointcloudentity_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "qgschunkedentity.h"
#include "qgs3dmapsceneentity.h"
#include "qgs3drendercontext.h"
#include "qgspointcloudlayerchunkloader_p.h"

class QgsAABB;
class QgsChunkBoundsEntity;
Expand Down Expand Up @@ -93,6 +94,7 @@ class QgsVirtualPointCloudEntity : public Qgs3DMapSceneEntity
QgsPointCloudLayer *mLayer = nullptr;
QMap<int, QgsChunkedEntity *> mChunkedEntitiesMap;
QgsChunkBoundsEntity *mBboxesEntity = nullptr;
QgsPointCloudLayerChunkedEntity *mOverviewEntity = nullptr;
QList<QgsAABB> mBboxes;
QgsCoordinateTransform mCoordinateTransform;
std::unique_ptr<QgsPointCloud3DSymbol> mSymbol;
Expand Down
34 changes: 34 additions & 0 deletions src/app/3d/qgspointcloud3dsymbolwidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 )
Expand Down Expand Up @@ -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<QgsVirtualPointCloudProvider *>( 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<int>( &QComboBox::currentIndexChanged ), this, &QgsPointCloud3DSymbolWidget::emitChangedSignal );
}
else
{
mVpcGroupBox->setVisible( false );
}
}

void QgsPointCloud3DSymbolWidget::setSymbol( QgsPointCloud3DSymbol *symbol )
Expand Down Expand Up @@ -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<Qgis::PointCloudZoomOutRenderBehavior>();
}

void QgsPointCloud3DSymbolWidget::connectChildPanels( QgsPanelWidget *parent )
{
parent->connectChildPanel( mClassifiedRendererWidget );
Expand Down
3 changes: 3 additions & 0 deletions src/app/3d/qgspointcloud3dsymbolwidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions src/app/3d/qgspointcloudlayer3drendererwidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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() );
}
}

Expand All @@ -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;
}

Expand Down
6 changes: 6 additions & 0 deletions src/app/layers/qgsapplayerhandling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
#include "qgsgdalutils.h"
#include "qgstiledscenelayer.h"
#include "qgsogrproviderutils.h"
#include "qgsvirtualpointcloudprovider.h"

#include <QObject>
#include <QMessageBox>
Expand Down Expand Up @@ -196,6 +197,11 @@ void QgsAppLayerHandling::postProcessAddedLayer( QgsMapLayer *layer )
{
std::unique_ptr<QgsPointCloudLayer3DRenderer> renderer3D = std::make_unique<QgsPointCloudLayer3DRenderer>();
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<QgsVirtualPointCloudProvider *>( pcLayer->dataProvider() ) )
{
vpcProvider->overview() ? renderer3D->setZoomOutBehavior( Qgis::PointCloudZoomOutRenderBehavior::RenderOverview ) : renderer3D->setZoomOutBehavior( Qgis::PointCloudZoomOutRenderBehavior::RenderExtents );
}
layer->setRenderer3D( renderer3D.release() );
}
}
Expand Down
55 changes: 37 additions & 18 deletions src/ui/3d/qgspointcloud3dsymbolwidget.ui
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>581</width>
<height>553</height>
<height>728</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
Expand All @@ -34,14 +34,8 @@
<property name="checkable">
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout_5" columnstretch="1,0">
<property name="leftMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<item row="1" column="0">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QGridLayout" name="gridLayout_7">
<item row="0" column="0">
<widget class="QLabel" name="labelPointSize">
Expand Down Expand Up @@ -120,14 +114,7 @@
</item>
</layout>
</item>
<item row="4" column="0" colspan="2">
<widget class="QCheckBox" name="mShowBoundingBoxesCheckBox">
<property name="text">
<string>Show bounding boxes</string>
</property>
</widget>
</item>
<item row="2" column="0">
<item>
<widget class="QgsCollapsibleGroupBox" name="mTriangulateGroupBox">
<property name="title">
<string>Render as a Surface (Triangulate)</string>
Expand Down Expand Up @@ -188,13 +175,45 @@
</layout>
</widget>
</item>
<item>
<widget class="QCheckBox" name="mShowBoundingBoxesCheckBox">
<property name="text">
<string>Show bounding boxes</string>
</property>
</widget>
</item>
<item>
<widget class="QgsCollapsibleGroupBox" name="mVpcGroupBox">
<property name="title">
<string>Virtual Point Cloud Options</string>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="1" colspan="2">
<widget class="QComboBox" name="mZoomOutOptions"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>When zoomed out</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0" colspan="3">
<widget class="QgsStackedWidget" name="mStackedWidget">
<property name="currentIndex">
<number>0</number>
<number>5</number>
</property>
<widget class="QWidget" name="noRendererPage"/>
<widget class="QWidget" name="follow2DRendererPage">
Expand Down

0 comments on commit 9e2c217

Please sign in to comment.