Skip to content
18 changes: 18 additions & 0 deletions python/3d/auto_generated/qgspointcloudlayer3drenderer.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,24 @@ Sets the renderer behavior when zoomed out
Returns the renderer behavior when zoomed out

.. versionadded:: 3.42
%End

void setOverviewSwitchingScale( double scale );
%Docstring
Sets the overview switching scale

Used to determine when point clouds switch from rendering bounding boxes
to displaying full point data. The value is compared against screen
space error (SSE) calculated from camera distance and node size.

.. versionadded:: 4.0
%End

double overviewSwitchingScale() const;
%Docstring
Returns the overview switching scale

.. versionadded:: 4.0
%End

private:
Expand Down
18 changes: 18 additions & 0 deletions python/PyQt6/3d/auto_generated/qgspointcloudlayer3drenderer.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,24 @@ Sets the renderer behavior when zoomed out
Returns the renderer behavior when zoomed out

.. versionadded:: 3.42
%End

void setOverviewSwitchingScale( double scale );
%Docstring
Sets the overview switching scale

Used to determine when point clouds switch from rendering bounding boxes
to displaying full point data. The value is compared against screen
space error (SSE) calculated from camera distance and node size.

.. versionadded:: 4.0
%End

double overviewSwitchingScale() const;
%Docstring
Returns the overview switching scale

.. versionadded:: 4.0
%End

private:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,26 @@ Sets the renderer behavior when zoomed out
Returns the renderer behavior when zoomed out

.. versionadded:: 3.42
%End

void setOverviewSwitchingScale( const double value );
%Docstring
Sets the overview switching scale

Point clouds whose extents intersect the map extent are considered
visible. When zoomed out beyond the overview switching scale (render
extent exceeds average point cloud dimensions by the scale factor), and
overview zoom-out behavior is enabled, the overview is rendered instead
of an individual point cloud.

.. versionadded:: 4.0
%End

double overviewSwitchingScale() const;
%Docstring
Returns the overview switching scale

.. versionadded:: 4.0
%End

protected:
Expand Down
20 changes: 20 additions & 0 deletions python/core/auto_generated/pointcloud/qgspointcloudrenderer.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,26 @@ Sets the renderer behavior when zoomed out
Returns the renderer behavior when zoomed out

.. versionadded:: 3.42
%End

void setOverviewSwitchingScale( const double value );
%Docstring
Sets the overview switching scale

Point clouds whose extents intersect the map extent are considered
visible. When zoomed out beyond the overview switching scale (render
extent exceeds average point cloud dimensions by the scale factor), and
overview zoom-out behavior is enabled, the overview is rendered instead
of an individual point cloud.

.. versionadded:: 4.0
%End

double overviewSwitchingScale() const;
%Docstring
Returns the overview switching scale

.. versionadded:: 4.0
%End

protected:
Expand Down
3 changes: 3 additions & 0 deletions src/3d/qgspointcloudlayer3drenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ QgsPointCloudLayer3DRenderer *QgsPointCloudLayer3DRenderer::clone() const
r->setMaximumScreenError( mMaximumScreenError );
r->setShowBoundingBoxes( mShowBoundingBoxes );
r->setZoomOutBehavior( mZoomOutBehavior );
r->setOverviewSwitchingScale( mOverviewSwitchingScale );
return r;
}

Expand Down Expand Up @@ -188,6 +189,7 @@ void QgsPointCloudLayer3DRenderer::writeXml( QDomElement &elem, const QgsReadWri
elem.setAttribute( u"show-bounding-boxes"_s, showBoundingBoxes() ? u"1"_s : u"0"_s );
elem.setAttribute( u"point-budget"_s, mPointBudget );
elem.setAttribute( u"zoom-out-behavior"_s, qgsEnumValueToKey( mZoomOutBehavior ) );
elem.setAttribute( u"overview-switching-scale"_s, mOverviewSwitchingScale );

QDomElement elemSymbol = doc.createElement( u"symbol"_s );
if ( mSymbol )
Expand All @@ -209,6 +211,7 @@ void QgsPointCloudLayer3DRenderer::readXml( const QDomElement &elem, const QgsRe
mMaximumScreenError = elem.attribute( u"max-screen-error"_s, u"3.0"_s ).toDouble();
mPointBudget = elem.attribute( u"point-budget"_s, u"5000000"_s ).toInt();
mZoomOutBehavior = qgsEnumKeyToValue( elem.attribute( u"zoom-out-behavior"_s ), Qgis::PointCloudZoomOutRenderBehavior::RenderExtents );
mOverviewSwitchingScale = elem.attribute( u"overview-switching-scale"_s, u"1.0"_s ).toDouble();

if ( symbolType == "single-color"_L1 )
mSymbol = std::make_unique<QgsSingleColorPointCloud3DSymbol>();
Expand Down
18 changes: 18 additions & 0 deletions src/3d/qgspointcloudlayer3drenderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -315,13 +315,31 @@ class _3D_EXPORT QgsPointCloudLayer3DRenderer : public QgsAbstractPointCloud3DRe
*/
Qgis::PointCloudZoomOutRenderBehavior zoomOutBehavior() const { return mZoomOutBehavior; }

/**
* Sets the overview switching scale
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you expand the two docstrings to explain the effect it has / how it works?

*
* Used to determine when point clouds
* switch from rendering bounding boxes to displaying full point data.
* The value is compared against screen space error (SSE) calculated from
* camera distance and node size.
* \since QGIS 4.0
*/
void setOverviewSwitchingScale( double scale ) { mOverviewSwitchingScale = scale; }

/**
* Returns the overview switching scale
* \since QGIS 4.0
*/
double overviewSwitchingScale() const { return mOverviewSwitchingScale; }

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;
double mOverviewSwitchingScale = 1.0;

private:
#ifdef SIP_RUN
Expand Down
24 changes: 18 additions & 6 deletions src/3d/qgsvirtualpointcloudentity_p.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,11 @@ void QgsVirtualPointCloudEntity::handleSceneUpdate( const SceneContext &sceneCon
{
QgsVector3D cameraPosMapCoords = QgsVector3D( sceneContext.cameraPos ) + mapSettings()->origin();
const QVector<QgsPointCloudSubIndex> subIndexes = provider()->subIndexes();

const QgsPointCloudLayer3DRenderer *rendererBehavior = dynamic_cast<QgsPointCloudLayer3DRenderer *>( mLayer->renderer3D() );
const double overviewSwitchingScale = rendererBehavior ? rendererBehavior->overviewSwitchingScale() : 1;
qsizetype subIndexesRendered = 0;

for ( int i = 0; i < subIndexes.size(); ++i )
{
// If the chunked entity needs an update, do it even if it's occluded,
Expand All @@ -160,23 +165,30 @@ void QgsVirtualPointCloudEntity::handleSceneUpdate( const SceneContext &sceneCon
const float epsilon = static_cast<float>( std::min( box3D.width(), box3D.height() ) ) / SPAN;
const float distance = static_cast<float>( box3D.distanceTo( cameraPosMapCoords ) );
const float sse = Qgs3DUtils::screenSpaceError( epsilon, distance, sceneContext.screenSizePx, sceneContext.cameraFov );
constexpr float THRESHOLD = .2;
const double THRESHOLD = 0.2 / overviewSwitchingScale;

// always display as bbox for the initial temporary camera pos (0, 0, 0)
// then once the camera changes we display as bbox depending on screen space error
const bool displayAsBbox = sceneContext.cameraPos.isNull() || sse < THRESHOLD;
if ( !displayAsBbox && !subIndexes.at( i ).index() )
emit subIndexNeedsLoading( i );

const bool displayAsBbox = sceneContext.cameraPos.isNull() || sse < static_cast<float>( THRESHOLD );
if ( !displayAsBbox )
{
subIndexesRendered += 1;
if ( !subIndexes.at( i ).index() )
emit subIndexNeedsLoading( i );
}
setRenderSubIndexAsBbox( i, displayAsBbox );
if ( !displayAsBbox && mChunkedEntitiesMap.contains( i ) )
mChunkedEntitiesMap[i]->handleSceneUpdate( sceneContext );
}
updateBboxEntity();

const QgsPointCloudLayer3DRenderer *rendererBehavior = dynamic_cast<QgsPointCloudLayer3DRenderer *>( mLayer->renderer3D() );
if ( provider()->overview() && rendererBehavior && ( rendererBehavior->zoomOutBehavior() == Qgis::PointCloudZoomOutRenderBehavior::RenderOverview || rendererBehavior->zoomOutBehavior() == Qgis::PointCloudZoomOutRenderBehavior::RenderOverviewAndExtents ) )
{
// no need to render the overview if all sub indexes are shown
if ( !mChunkedEntitiesMap.isEmpty() && subIndexesRendered == mChunkedEntitiesMap.size() )
mOverviewEntity->setEnabled( false );
else
mOverviewEntity->setEnabled( true );
mOverviewEntity->handleSceneUpdate( sceneContext );
}
}
Expand Down
18 changes: 18 additions & 0 deletions src/app/3d/qgspointcloud3dsymbolwidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -152,14 +152,22 @@ QgsPointCloud3DSymbolWidget::QgsPointCloud3DSymbolWidget( QgsPointCloudLayer *la
{
mZoomOutOptions->addItem( tr( "Show Overview Only" ), QVariant::fromValue( Qgis::PointCloudZoomOutRenderBehavior::RenderOverview ) );
mZoomOutOptions->addItem( tr( "Show Extents Over Overview" ), QVariant::fromValue( Qgis::PointCloudZoomOutRenderBehavior::RenderOverviewAndExtents ) );

for ( auto it = mOverviewSwitchingScaleMap.constBegin(); it != mOverviewSwitchingScaleMap.constEnd(); ++it )
{
mOverviewSwitchingScale->addItem( it.value(), it.key() );
}
setOverviewSwitchingScale( 1.0 );
}
}
else
{
mZoomOutOptions->setEnabled( false );
mOverviewSwitchingScale->setEnabled( false );
}

connect( mZoomOutOptions, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsPointCloud3DSymbolWidget::emitChangedSignal );
connect( mOverviewSwitchingScale, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsPointCloud3DSymbolWidget::emitChangedSignal );
}
else
{
Expand Down Expand Up @@ -700,6 +708,16 @@ Qgis::PointCloudZoomOutRenderBehavior QgsPointCloud3DSymbolWidget::zoomOutBehavi
return mZoomOutOptions->currentData().value<Qgis::PointCloudZoomOutRenderBehavior>();
}

void QgsPointCloud3DSymbolWidget::setOverviewSwitchingScale( double scale )
{
mOverviewSwitchingScale->setCurrentIndex( mOverviewSwitchingScale->findData( scale ) );
}

double QgsPointCloud3DSymbolWidget::overviewSwitchingScale() const
{
return mOverviewSwitchingScaleMap.key( mOverviewSwitchingScale->currentText() );
}

void QgsPointCloud3DSymbolWidget::connectChildPanels( QgsPanelWidget *parent )
{
parent->connectChildPanel( mClassifiedRendererWidget );
Expand Down
11 changes: 11 additions & 0 deletions src/app/3d/qgspointcloud3dsymbolwidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ class QgsPointCloud3DSymbolWidget : public QWidget, private Ui::QgsPointCloud3DS
void setZoomOutBehavior( Qgis::PointCloudZoomOutRenderBehavior zoomOutBehavior );
Qgis::PointCloudZoomOutRenderBehavior zoomOutBehavior() const;

void setOverviewSwitchingScale( double scale );
double overviewSwitchingScale() const;

void connectChildPanels( QgsPanelWidget *parent );

private slots:
Expand Down Expand Up @@ -77,6 +80,14 @@ class QgsPointCloud3DSymbolWidget : public QWidget, private Ui::QgsPointCloud3DS
void setColorRampMinMax( double min, double max );

private:
// for 2D rendering, see values in qgspointcloudrendererpropertieswidget.h
const QMap<double, QString> mOverviewSwitchingScaleMap {
{ 5.0, "Much earlier" },
{ 2.0, "Earlier" },
{ 1.0, "Normal" },
{ 0.5, "Later" }
};

int mBlockChangedSignals = 0;
int mDisableMinMaxWidgetRefresh = 0;
QgsPointCloudClassifiedRendererWidget *mClassifiedRendererWidget = nullptr;
Expand Down
1 change: 1 addition & 0 deletions src/app/3d/qgspointcloudlayer3drendererwidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ QgsPointCloudLayer3DRenderer *QgsPointCloudLayer3DRendererWidget::renderer()
renderer->setMaximumScreenError( mWidgetPointCloudSymbol->maximumScreenError() );
renderer->setShowBoundingBoxes( mWidgetPointCloudSymbol->showBoundingBoxes() );
renderer->setZoomOutBehavior( mWidgetPointCloudSymbol->zoomOutBehavior() );
renderer->setOverviewSwitchingScale( mWidgetPointCloudSymbol->overviewSwitchingScale() );
return renderer;
}

Expand Down
8 changes: 6 additions & 2 deletions src/core/pointcloud/qgspointcloudlayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -987,9 +987,13 @@ void QgsPointCloudLayer::loadIndexesForRenderContext( QgsRenderContext &renderer
if ( subIndex.at( i ).index() )
continue;

const double overviewSwitchingScale = mRenderer ? mRenderer->overviewSwitchingScale() : 1.0;
const double widthThreshold = vpcProvider->averageSubIndexWidth() * overviewSwitchingScale;
const double heightThreshold = vpcProvider->averageSubIndexHeight() * overviewSwitchingScale;

if ( subIndex.at( i ).extent().intersects( renderExtent ) &&
( renderExtent.width() < vpcProvider->averageSubIndexWidth() ||
renderExtent.height() < vpcProvider->averageSubIndexHeight() ) )
( renderExtent.width() < widthThreshold ||
renderExtent.height() < heightThreshold ) )
{
mDataProvider->loadSubIndex( i );
}
Expand Down
5 changes: 3 additions & 2 deletions src/core/pointcloud/qgspointcloudlayerrenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,9 @@ bool QgsPointCloudLayerRenderer::render()
visibleIndexes.append( si );
}
}
const bool zoomedOut = renderExtent.width() > mAverageSubIndexWidth ||
renderExtent.height() > mAverageSubIndexHeight;
const double overviewSwitchingScale = mRenderer->overviewSwitchingScale();
const bool zoomedOut = renderExtent.width() > mAverageSubIndexWidth * overviewSwitchingScale ||
renderExtent.height() > mAverageSubIndexHeight * overviewSwitchingScale;
// if the overview of virtual point cloud exists, and we are zoomed out, we render just overview
if ( mOverviewIndex && mOverviewIndex->isValid() && zoomedOut &&
mRenderer->zoomOutBehavior() == Qgis::PointCloudZoomOutRenderBehavior::RenderOverview )
Expand Down
10 changes: 10 additions & 0 deletions src/core/pointcloud/qgspointcloudrenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,11 @@ void QgsPointCloudRenderer::setMaximumScreenError( double error )
mMaximumScreenError = error;
}

void QgsPointCloudRenderer::setOverviewSwitchingScale( double scale )
{
mOverviewSwitchingScale = scale;
}

Qgis::RenderUnit QgsPointCloudRenderer::maximumScreenErrorUnit() const
{
return mMaximumScreenErrorUnit;
Expand Down Expand Up @@ -221,6 +226,7 @@ void QgsPointCloudRenderer::copyCommonProperties( QgsPointCloudRenderer *destina
destination->setShowLabels( mShowLabels );
destination->setLabelTextFormat( mLabelTextFormat );
destination->setZoomOutBehavior( mZoomOutBehavior );
destination->setOverviewSwitchingScale( mOverviewSwitchingScale );
}

void QgsPointCloudRenderer::restoreCommonProperties( const QDomElement &element, const QgsReadWriteContext &context )
Expand All @@ -246,6 +252,7 @@ void QgsPointCloudRenderer::restoreCommonProperties( const QDomElement &element,
mLabelTextFormat.readXml( element.firstChildElement( u"text-style"_s ), context );
}
mZoomOutBehavior = qgsEnumKeyToValue( element.attribute( u"zoomOutBehavior"_s ), Qgis::PointCloudZoomOutRenderBehavior::RenderExtents );
mOverviewSwitchingScale = element.attribute( u"overviewSwitchingScale"_s, u"1.0"_s ).toDouble();
}

void QgsPointCloudRenderer::saveCommonProperties( QDomElement &element, const QgsReadWriteContext &context ) const
Expand All @@ -272,7 +279,10 @@ void QgsPointCloudRenderer::saveCommonProperties( QDomElement &element, const Qg
element.appendChild( mLabelTextFormat.writeXml( doc, context ) );
}
if ( mZoomOutBehavior != Qgis::PointCloudZoomOutRenderBehavior::RenderExtents )
{
element.setAttribute( u"zoomOutBehavior"_s, qgsEnumValueToKey( mZoomOutBehavior ) );
element.setAttribute( u"overviewSwitchingScale"_s, qgsDoubleToString( mOverviewSwitchingScale ) );
}
}

Qgis::PointCloudSymbol QgsPointCloudRenderer::pointSymbol() const
Expand Down
18 changes: 18 additions & 0 deletions src/core/pointcloud/qgspointcloudrenderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,23 @@ class CORE_EXPORT QgsPointCloudRenderer
*/
Qgis::PointCloudZoomOutRenderBehavior zoomOutBehavior() const { return mZoomOutBehavior; }

/**
* Sets the overview switching scale
*
* Point clouds whose extents intersect the map extent are considered visible.
* When zoomed out beyond the overview switching scale (render extent exceeds average
* point cloud dimensions by the scale factor), and overview zoom-out behavior is enabled,
* the overview is rendered instead of an individual point cloud.
* \since QGIS 4.0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for these

*/
void setOverviewSwitchingScale( const double value );

/**
* Returns the overview switching scale
* \since QGIS 4.0
*/
double overviewSwitchingScale() const { return mOverviewSwitchingScale; }

protected:

/**
Expand Down Expand Up @@ -855,6 +872,7 @@ class CORE_EXPORT QgsPointCloudRenderer
QgsTextFormat mLabelTextFormat;

Qgis::PointCloudZoomOutRenderBehavior mZoomOutBehavior = Qgis::PointCloudZoomOutRenderBehavior::RenderExtents;
double mOverviewSwitchingScale = 1.0;
};

#endif // QGSPOINTCLOUDRENDERER_H
Loading