Skip to content

Commit

Permalink
QgsChunkBoundsEntity: use 3D boxes in map coordinates + QgsGeoTransform
Browse files Browse the repository at this point in the history
Until now, chunk bounds entity used axis-aligned bboxes in world coordinates (floats)
We are switching to bboxes in map coordinates (doubles) and we use
QgsGeoTransform to react correctly when the origin vector changes.
  • Loading branch information
wonder-sk committed Jan 29, 2025
1 parent 380b3c7 commit f18b61a
Show file tree
Hide file tree
Showing 11 changed files with 100 additions and 35 deletions.
14 changes: 13 additions & 1 deletion python/PyQt6/core/auto_generated/geometry/qgsbox3d.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -375,12 +375,24 @@ Expands the bbox so that it covers both the original rectangle and the given poi
Converts the box to a 2D rectangle.
%End

double distanceTo( const QVector3D &point ) const /HoldGIL/;
double distanceTo( const QVector3D &point ) const /Deprecated="Since 3.42. Use distanceTo() with QgsVector3D instead (QVector3D uses floats)."/;
%Docstring
Returns the smallest distance between the box and the point ``point``
(returns 0 if the point is inside the box)

.. versionadded:: 3.18

.. deprecated:: 3.42

Use :py:func:`~QgsBox3D.distanceTo` with :py:class:`QgsVector3D` instead (QVector3D uses floats).
%End

double distanceTo( const QgsVector3D &point ) const /HoldGIL/;
%Docstring
Returns the smallest distance between the box and the point ``point``
(returns 0 if the point is inside the box)

.. versionadded:: 3.42
%End

bool operator==( const QgsBox3D &other ) const /HoldGIL/;
Expand Down
14 changes: 13 additions & 1 deletion python/core/auto_generated/geometry/qgsbox3d.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -375,12 +375,24 @@ Expands the bbox so that it covers both the original rectangle and the given poi
Converts the box to a 2D rectangle.
%End

double distanceTo( const QVector3D &point ) const /HoldGIL/;
double distanceTo( const QVector3D &point ) const /Deprecated="Since 3.42. Use distanceTo() with QgsVector3D instead (QVector3D uses floats)."/;
%Docstring
Returns the smallest distance between the box and the point ``point``
(returns 0 if the point is inside the box)

.. versionadded:: 3.18

.. deprecated:: 3.42

Use :py:func:`~QgsBox3D.distanceTo` with :py:class:`QgsVector3D` instead (QVector3D uses floats).
%End

double distanceTo( const QgsVector3D &point ) const /HoldGIL/;
%Docstring
Returns the smallest distance between the box and the point ``point``
(returns 0 if the point is inside the box)

.. versionadded:: 3.42
%End

bool operator==( const QgsBox3D &other ) const /HoldGIL/;
Expand Down
18 changes: 15 additions & 3 deletions src/3d/chunks/qgschunkboundsentity_p.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,36 @@

#include "qgsaabb.h"
#include "qgs3dwiredmesh_p.h"
#include "qgsbox3d.h"
#include "qgsgeotransform.h"


///@cond PRIVATE

QgsChunkBoundsEntity::QgsChunkBoundsEntity( Qt3DCore::QNode *parent )
QgsChunkBoundsEntity::QgsChunkBoundsEntity( const QgsVector3D &vertexDataOrigin, Qt3DCore::QNode *parent )
: Qt3DCore::QEntity( parent )
, mVertexDataOrigin( vertexDataOrigin )
{
mAabbMesh = new Qgs3DWiredMesh;
addComponent( mAabbMesh );

Qt3DExtras::QPhongMaterial *bboxesMaterial = new Qt3DExtras::QPhongMaterial;
bboxesMaterial->setAmbient( Qt::red );
addComponent( bboxesMaterial );

QgsGeoTransform *transform = new QgsGeoTransform;
transform->setGeoTranslation( mVertexDataOrigin );
addComponent( transform );
}

void QgsChunkBoundsEntity::setBoxes( const QList<QgsAABB> &bboxes )
void QgsChunkBoundsEntity::setBoxes( const QList<QgsBox3D> &bboxes )
{
mAabbMesh->setVertices( bboxes );
QList<QgsAABB> aabbBoxes;
for ( const QgsBox3D &box : bboxes )
{
aabbBoxes << QgsAABB::fromBox3D( box, mVertexDataOrigin );
}
mAabbMesh->setVertices( aabbBoxes );
}

/// @endcond
12 changes: 9 additions & 3 deletions src/3d/chunks/qgschunkboundsentity_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@

#include <Qt3DCore/QEntity>

class QgsAABB;
#include "qgsvector3d.h"

class QgsBox3D;
class Qgs3DWiredMesh;

#define SIP_NO_FILE
Expand All @@ -46,12 +48,16 @@ class QgsChunkBoundsEntity : public Qt3DCore::QEntity

public:
//! Constructs the entity
QgsChunkBoundsEntity( Qt3DCore::QNode *parent = nullptr );
QgsChunkBoundsEntity( const QgsVector3D &vertexDataOrigin, Qt3DCore::QNode *parent = nullptr );

//! Sets a list of bounding boxes to be rendered by the entity
void setBoxes( const QList<QgsAABB> &bboxes );
void setBoxes( const QList<QgsBox3D> &bboxes );

//! Returns origin of vertex data used in this entity
QgsVector3D vertexDataOrigin() const { return mVertexDataOrigin; }

private:
QgsVector3D mVertexDataOrigin;
Qgs3DWiredMesh *mAabbMesh = nullptr;
};

Expand Down
6 changes: 3 additions & 3 deletions src/3d/chunks/qgschunkedentity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -201,9 +201,9 @@ void QgsChunkedEntity::handleSceneUpdate( const SceneContext &sceneContext )

if ( mBboxesEntity )
{
QList<QgsAABB> bboxes;
QList<QgsBox3D> bboxes;
for ( QgsChunkNode *n : std::as_const( mActiveNodes ) )
bboxes << Qgs3DUtils::mapToWorldExtent( n->box3D(), mMapSettings->origin() );
bboxes << n->box3D();
mBboxesEntity->setBoxes( bboxes );
}

Expand Down Expand Up @@ -301,7 +301,7 @@ void QgsChunkedEntity::setShowBoundingBoxes( bool enabled )

if ( enabled )
{
mBboxesEntity = new QgsChunkBoundsEntity( this );
mBboxesEntity = new QgsChunkBoundsEntity( mRootNode->box3D().center(), this );
}
else
{
Expand Down
17 changes: 17 additions & 0 deletions src/3d/qgsaabb.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
#include <QList>
#include <QVector3D>

#include "qgsbox3d.h"

#define SIP_NO_FILE

/**
Expand All @@ -38,6 +40,21 @@ class _3D_EXPORT QgsAABB
//! Constructs bounding box
QgsAABB( float xMin, float yMin, float zMin, float xMax, float yMax, float zMax );

/**
* Constructs bounding box from QgsBox3D by subtracting origin 3D vector.
* Note: this is potentially lossy operation as the coordinates are converted
* from double values to floats!
*/
static QgsAABB fromBox3D( const QgsBox3D &box3D, const QgsVector3D &origin )
{
return QgsAABB( static_cast<float>( box3D.xMinimum() - origin.x() ),
static_cast<float>( box3D.yMinimum() - origin.y() ),
static_cast<float>( box3D.zMinimum() - origin.z() ),
static_cast<float>( box3D.xMaximum() - origin.x() ),
static_cast<float>( box3D.yMaximum() - origin.y() ),
static_cast<float>( box3D.zMaximum() - origin.z() ) );
}

//! Returns box width in X axis
float xExtent() const { return xMax - xMin; }
//! Returns box width in Y axis
Expand Down
28 changes: 14 additions & 14 deletions src/3d/qgsvirtualpointcloudentity_p.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,14 @@ QgsVirtualPointCloudEntity::QgsVirtualPointCloudEntity(
, mShowBoundingBoxes( showBoundingBoxes )
{
mSymbol.reset( symbol );
mBboxesEntity = new QgsChunkBoundsEntity( this );
const QgsRectangle mapExtent = Qgs3DUtils::tryReprojectExtent2D( map->extent(), map->crs(), layer->crs(), map->transformContext() );
const QVector<QgsPointCloudSubIndex> subIndexes = provider()->subIndexes();
for ( int i = 0; i < subIndexes.size(); ++i )
{
const QgsPointCloudSubIndex &si = subIndexes.at( i );
const QgsRectangle intersection = si.extent().intersect( mapExtent );

mBboxes << Qgs3DUtils::mapToWorldExtent( intersection, si.zRange().lower(), si.zRange().upper(), map->origin() );
mBboxes << QgsBox3D( intersection, si.zRange().lower(), si.zRange().upper() );

createChunkedEntityForSubIndex( i );
}
Expand All @@ -76,6 +75,10 @@ QgsVirtualPointCloudEntity::QgsVirtualPointCloudEntity(
emit newEntityCreated( mOverviewEntity );
}

// this is a rather arbitrary point, it could be somewhere else, ideally near the actual data
QgsVector3D boundsEntityOrigin( mapExtent.center().x(), mapExtent.center().y(), 0 );

mBboxesEntity = new QgsChunkBoundsEntity( boundsEntityOrigin, this );
updateBboxEntity();
connect( this, &QgsVirtualPointCloudEntity::subIndexNeedsLoading, provider(), &QgsVirtualPointCloudProvider::loadSubIndex, Qt::QueuedConnection );
connect( provider(), &QgsVirtualPointCloudProvider::subIndexLoaded, this, &QgsVirtualPointCloudEntity::createChunkedEntityForSubIndex );
Expand All @@ -91,11 +94,6 @@ QgsVirtualPointCloudProvider *QgsVirtualPointCloudEntity::provider() const
return qobject_cast<QgsVirtualPointCloudProvider *>( mLayer->dataProvider() );
}

QgsAABB QgsVirtualPointCloudEntity::boundingBox( int i ) const
{
return mBboxes.at( i );
}

void QgsVirtualPointCloudEntity::createChunkedEntityForSubIndex( int i )
{
const QVector<QgsPointCloudSubIndex> subIndexes = provider()->subIndexes();
Expand Down Expand Up @@ -126,18 +124,19 @@ void QgsVirtualPointCloudEntity::createChunkedEntityForSubIndex( int i )

void QgsVirtualPointCloudEntity::handleSceneUpdate( const SceneContext &sceneContext )
{
QgsVector3D cameraPosMapCoords = QgsVector3D( sceneContext.cameraPos ) + mapSettings()->origin();
const QVector<QgsPointCloudSubIndex> subIndexes = provider()->subIndexes();
for ( int i = 0; i < subIndexes.size(); ++i )
{
const QgsAABB &bbox = mBboxes.at( i );
const QgsBox3D &box3D = mBboxes.at( i );

if ( bbox.isEmpty() )
if ( box3D.isEmpty() )
continue;

// magic number 256 is the common span value for a COPC root node
constexpr int SPAN = 256;
const float epsilon = std::min( bbox.xExtent(), bbox.yExtent() ) / SPAN;
const float distance = bbox.distanceFromPoint( sceneContext.cameraPos );
const float epsilon = std::min( box3D.width(), box3D.height() ) / SPAN;
const float distance = box3D.distanceTo( cameraPosMapCoords );
const float sse = Qgs3DUtils::screenSpaceError( epsilon, distance, sceneContext.screenSizePx, sceneContext.cameraFov );
constexpr float THRESHOLD = .2;

Expand Down Expand Up @@ -178,11 +177,12 @@ QgsRange<float> QgsVirtualPointCloudEntity::getNearFarPlaneRange( const QMatrix4
// if there were no chunked entities available, we will iterate the bboxes as a fallback instead
if ( fnear == 1e9 && ffar == 0 )
{
for ( const QgsAABB &bbox : mBboxes )
for ( const QgsBox3D &box : mBboxes )
{
QgsAABB aabb = QgsAABB::fromBox3D( box, mBboxesEntity->vertexDataOrigin() );
float bboxfnear;
float bboxffar;
Qgs3DUtils::computeBoundingBoxNearFarPlanes( bbox, viewMatrix, bboxfnear, bboxffar );
Qgs3DUtils::computeBoundingBoxNearFarPlanes( aabb, viewMatrix, bboxfnear, bboxffar );
fnear = std::min( fnear, bboxfnear );
ffar = std::max( ffar, bboxffar );
}
Expand Down Expand Up @@ -214,7 +214,7 @@ bool QgsVirtualPointCloudEntity::needsUpdate() const

void QgsVirtualPointCloudEntity::updateBboxEntity()
{
QList<QgsAABB> bboxes;
QList<QgsBox3D> bboxes;
// we want to render bounding boxes only when zoomOutBehavior is RenderExtents or RenderOverviewAndExtents
const QgsPointCloudLayer3DRenderer *renderer = dynamic_cast<QgsPointCloudLayer3DRenderer *>( mLayer->renderer3D() );
if ( renderer && renderer->zoomOutBehavior() != Qgis::PointCloudZoomOutRenderBehavior::RenderOverview )
Expand Down
5 changes: 1 addition & 4 deletions src/3d/qgsvirtualpointcloudentity_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,11 @@ class QgsVirtualPointCloudEntity : public Qgs3DMapSceneEntity
//! Returns a pointer to the associated layer's provider
QgsVirtualPointCloudProvider *provider() const;

//! Returns the bounding box for sub index i
QgsAABB boundingBox( int i ) const;

QgsPointCloudLayer *mLayer = nullptr;
QMap<int, QgsChunkedEntity *> mChunkedEntitiesMap;
QgsChunkBoundsEntity *mBboxesEntity = nullptr;
QgsPointCloudLayerChunkedEntity *mOverviewEntity = nullptr;
QList<QgsAABB> mBboxes;
QList<QgsBox3D> mBboxes;
QgsCoordinateTransform mCoordinateTransform;
std::unique_ptr<QgsPointCloud3DSymbol> mSymbol;
double mZValueScale = 1.0;
Expand Down
2 changes: 1 addition & 1 deletion src/core/geometry/qgsbox3d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ void QgsBox3D::combineWith( double x, double y, double z )
}
}

double QgsBox3D::distanceTo( const QVector3D &point ) const
double QgsBox3D::distanceTo( const QgsVector3D &point ) const
{
const double dx = std::max( mBounds2d.xMinimum() - point.x(), std::max( 0., point.x() - mBounds2d.xMaximum() ) );
const double dy = std::max( mBounds2d.yMinimum() - point.y(), std::max( 0., point.y() - mBounds2d.yMaximum() ) );
Expand Down
11 changes: 10 additions & 1 deletion src/core/geometry/qgsbox3d.h
Original file line number Diff line number Diff line change
Expand Up @@ -398,8 +398,17 @@ class CORE_EXPORT QgsBox3D
* (returns 0 if the point is inside the box)
*
* \since QGIS 3.18
* \deprecated QGIS 3.42. Use distanceTo() with QgsVector3D instead (QVector3D uses floats).
*/
double distanceTo( const QVector3D &point ) const SIP_HOLDGIL;
double distanceTo( const QVector3D &point ) const SIP_DEPRECATED { return distanceTo( QgsVector3D( point ) ); }

/**
* Returns the smallest distance between the box and the point \a point
* (returns 0 if the point is inside the box)
*
* \since QGIS 3.42
*/
double distanceTo( const QgsVector3D &point ) const SIP_HOLDGIL;

bool operator==( const QgsBox3D &other ) const SIP_HOLDGIL;

Expand Down
8 changes: 4 additions & 4 deletions tests/src/3d/testqgs3dutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,13 +175,13 @@ void TestQgs3DUtils::testQgsBox3DDistanceTo()
{
{
const QgsBox3D box( -1, -1, -1, 1, 1, 1 );
QCOMPARE( box.distanceTo( QVector3D( 0, 0, 0 ) ), 0.0 );
QCOMPARE( box.distanceTo( QVector3D( 2, 2, 2 ) ), qSqrt( 3.0 ) );
QCOMPARE( box.distanceTo( QgsVector3D( 0, 0, 0 ) ), 0.0 );
QCOMPARE( box.distanceTo( QgsVector3D( 2, 2, 2 ) ), qSqrt( 3.0 ) );
}
{
const QgsBox3D box( 1, 2, 1, 4, 3, 3 );
QCOMPARE( box.distanceTo( QVector3D( 1, 2, 1 ) ), 0.0 );
QCOMPARE( box.distanceTo( QVector3D( 0, 0, 0 ) ), qSqrt( 6.0 ) );
QCOMPARE( box.distanceTo( QgsVector3D( 1, 2, 1 ) ), 0.0 );
QCOMPARE( box.distanceTo( QgsVector3D( 0, 0, 0 ) ), qSqrt( 6.0 ) );
}
}

Expand Down

0 comments on commit f18b61a

Please sign in to comment.