Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Enable rendering virtual point clouds in 3d views
  • Loading branch information
uclaros authored and wonder-sk committed May 11, 2023
1 parent 761151e commit 452b320
Show file tree
Hide file tree
Showing 17 changed files with 483 additions and 96 deletions.
2 changes: 2 additions & 0 deletions src/3d/CMakeLists.txt
Expand Up @@ -45,6 +45,7 @@ set(QGIS_3D_SRCS

qgspointcloudlayer3drenderer.cpp
qgspointcloudlayerchunkloader_p.cpp
qgsvirtualpointcloudentity_p.cpp

chunks/qgschunkboundsentity_p.cpp
chunks/qgschunkedentity_p.cpp
Expand Down Expand Up @@ -174,6 +175,7 @@ set(QGIS_3D_PRIVATE_HDRS
qgsrulebasedchunkloader_p.h
qgsvectorlayerchunkloader_p.h
qgspointcloudlayerchunkloader_p.h
qgsvirtualpointcloudentity_p.h
chunks/qgschunkboundsentity_p.h
chunks/qgschunkedentity_p.h
chunks/qgschunklist_p.h
Expand Down
27 changes: 1 addition & 26 deletions src/3d/chunks/qgschunkedentity_p.cpp
Expand Up @@ -38,31 +38,6 @@ typedef Qt3DCore::QBuffer Qt3DQBuffer;

///@cond PRIVATE

static float screenSpaceError( float epsilon, float distance, float screenSize, float fov )
{
/* This routine approximately calculates how an error (epsilon) of an object in world coordinates
* at given distance (between camera and the object) will look like in screen coordinates.
*
* the math below simply uses triangle similarity:
*
* epsilon phi
* ----------------------------- = ----------------
* [ frustum width at distance ] [ screen width ]
*
* Then we solve for phi, substituting [frustum width at distance] = 2 * distance * tan(fov / 2)
*
* ________xxx__ xxx = real world error (epsilon)
* \ | / x = screen space error (phi)
* \ | /
* \___|_x_/ near plane (screen space)
* \ | /
* \ | /
* \|/ angle = field of view
* camera
*/
float phi = epsilon * screenSize / ( 2 * distance * tan( fov * M_PI / ( 2 * 180 ) ) );
return phi;
}

static float screenSpaceError( QgsChunkNode *node, const QgsChunkedEntity::SceneState &state )
{
Expand All @@ -73,7 +48,7 @@ static float screenSpaceError( QgsChunkNode *node, const QgsChunkedEntity::Scene

// TODO: what to do when distance == 0 ?

float sse = screenSpaceError( node->error(), dist, state.screenSizePx, state.cameraFov );
float sse = Qgs3DUtils::screenSpaceError( node->error(), dist, state.screenSizePx, state.cameraFov );
return sse;
}

Expand Down
83 changes: 77 additions & 6 deletions src/3d/qgs3dmapscene.cpp
Expand Up @@ -74,6 +74,8 @@
#include "qgsskyboxentity.h"
#include "qgsskyboxsettings.h"

#include "qgsvirtualpointcloudentity_p.h"
#include "qgsvirtualpointcloudprovider.h"
#include "qgswindow3dengine.h"
#include "qgspointcloudlayer.h"

Expand Down Expand Up @@ -205,7 +207,7 @@ void Qgs3DMapScene::viewZoomFull()
const QgsRectangle extent = sceneExtent();
const double side = std::max( extent.width(), extent.height() );
double d = side / 2 / std::tan( cameraController()->camera()->fieldOfView() / 2 * M_PI / 180 );
d += yRange.upper();
d += yRange.isInfinite() ? 0. : yRange.upper();
mCameraController->resetView( static_cast< float >( d ) );
return;
}
Expand Down Expand Up @@ -361,6 +363,34 @@ void addQLayerComponentsToHierarchy( Qt3DCore::QEntity *entity, const QVector<Qt
void Qgs3DMapScene::updateScene()
{
QgsEventTracing::addEvent( QgsEventTracing::Instant, QStringLiteral( "3D" ), QStringLiteral( "Update Scene" ) );

const QSize size = mEngine->size();
const int screenSize = std::max( size.width(), size.height() );
const float fov = mCameraController->camera()->fieldOfView();
const QVector3D &cameraPosition = mCameraController->camera()->position();
for ( QgsVirtualPointCloudEntity *entity : std::as_const( mVirtualPointCloudEntities ) )
{
QgsVirtualPointCloudProvider *provider = entity->provider();

const QList<QgsPointCloudSubIndex *> subIndexes = provider->subIndexes();
for ( int i = 0; i < subIndexes.size(); ++i )
{
const QgsAABB &bbox = entity->boundingBox( i );
// 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( cameraPosition );
const float sse = Qgs3DUtils::screenSpaceError( epsilon, distance, screenSize, fov );
constexpr float THRESHOLD = .2;
const bool displayAsBbox = sse < THRESHOLD;
if ( !displayAsBbox && !subIndexes.at( i )->index() )
provider->loadSubIndex( i );

entity->setRenderSubIndexAsBbox( i, displayAsBbox );
}
entity->updateBboxEntity();
}

for ( QgsChunkedEntity *entity : std::as_const( mChunkEntities ) )
{
if ( entity->isEnabled() )
Expand Down Expand Up @@ -713,15 +743,33 @@ void Qgs3DMapScene::addLayerEntity( QgsMapLayer *layer )

if ( QgsChunkedEntity *chunkedNewEntity = qobject_cast<QgsChunkedEntity *>( newEntity ) )
{
mChunkEntities.append( chunkedNewEntity );
needsSceneUpdate = true;
addNewChunkedEntity( chunkedNewEntity );
}

if ( QgsVirtualPointCloudEntity *virtualPointCloudEntity = qobject_cast<QgsVirtualPointCloudEntity *>( newEntity ) )
{
mVirtualPointCloudEntities.append( virtualPointCloudEntity );

connect( chunkedNewEntity, &QgsChunkedEntity::newEntityCreated, this, [this]( Qt3DCore::QEntity * entity )
QgsVirtualPointCloudProvider *provider = qobject_cast<QgsVirtualPointCloudProvider *>( layer->dataProvider() );
virtualPointCloudEntity->createChunkedEntitiesForLoadedSubIndexes();
const QList<QgsChunkedEntity *> chunkedEntities = virtualPointCloudEntity->chunkedEntities();
for ( QgsChunkedEntity *ce : chunkedEntities )
{
finalizeNewEntity( entity );
} );
ce->setParent( virtualPointCloudEntity );
needsSceneUpdate = true;
addNewChunkedEntity( ce );
}

connect( provider, &QgsVirtualPointCloudProvider::subIndexLoaded, virtualPointCloudEntity, &QgsVirtualPointCloudEntity::createChunkedEntityForSubIndex );

connect( chunkedNewEntity, &QgsChunkedEntity::pendingJobsCountChanged, this, &Qgs3DMapScene::totalPendingJobsCountChanged );
connect( virtualPointCloudEntity, &QgsVirtualPointCloudEntity::newEntityCreated, this, [ = ]( QgsChunkedEntity * newChildChunkedEntity )
{
newChildChunkedEntity->setParent( virtualPointCloudEntity );
addNewChunkedEntity( newChildChunkedEntity );
virtualPointCloudEntity->updateBboxEntity();
onCameraChanged();
} );
}
}
}
Expand Down Expand Up @@ -760,6 +808,17 @@ void Qgs3DMapScene::removeLayerEntity( QgsMapLayer *layer )
mChunkEntities.removeOne( chunkedEntity );
}

if ( QgsVirtualPointCloudEntity *vpcNewEntity = qobject_cast<QgsVirtualPointCloudEntity *>( entity ) )
{
const QList<QgsChunkedEntity *> chunkedEntities = vpcNewEntity->chunkedEntities();
for ( QgsChunkedEntity *chunkedEntity : chunkedEntities )
{
mChunkEntities.removeOne( chunkedEntity );
chunkedEntity->deleteLater();
}
mVirtualPointCloudEntities.removeOne( vpcNewEntity );
}

if ( entity )
entity->deleteLater();

Expand Down Expand Up @@ -850,6 +909,18 @@ void Qgs3DMapScene::finalizeNewEntity( Qt3DCore::QEntity *newEntity )
}
}

void Qgs3DMapScene::addNewChunkedEntity( QgsChunkedEntity *newEntity )
{
mChunkEntities.append( newEntity );

connect( newEntity, &QgsChunkedEntity::newEntityCreated, this, [this]( Qt3DCore::QEntity * entity )
{
finalizeNewEntity( entity );
} );

connect( newEntity, &QgsChunkedEntity::pendingJobsCountChanged, this, &Qgs3DMapScene::totalPendingJobsCountChanged );
}

int Qgs3DMapScene::maximumTextureSize() const
{
QSurface *surface = mEngine->surface();
Expand Down
3 changes: 3 additions & 0 deletions src/3d/qgs3dmapscene.h
Expand Up @@ -57,6 +57,7 @@ class QgsShadowRenderingFrameGraph;
class QgsPostprocessingEntity;
class QgsChunkNode;
class QgsDoubleRange;
class QgsVirtualPointCloudEntity;


/**
Expand Down Expand Up @@ -271,6 +272,7 @@ class _3D_EXPORT Qgs3DMapScene : public QObject
void updateSceneState();
void updateScene();
void finalizeNewEntity( Qt3DCore::QEntity *newEntity );
void addNewChunkedEntity( QgsChunkedEntity *newEntity );
int maximumTextureSize() const;

private:
Expand All @@ -281,6 +283,7 @@ class _3D_EXPORT Qgs3DMapScene : public QObject
QgsCameraController *mCameraController = nullptr;
QgsTerrainEntity *mTerrain = nullptr;
QList<QgsChunkedEntity *> mChunkEntities;
QList<QgsVirtualPointCloudEntity *> mVirtualPointCloudEntities;
//! Entity that shows view center - useful for debugging camera issues
Qt3DCore::QEntity *mEntityCameraViewCenter = nullptr;
//! Keeps track of entities that belong to a particular layer
Expand Down
26 changes: 26 additions & 0 deletions src/3d/qgs3dutils.cpp
Expand Up @@ -817,3 +817,29 @@ QHash<QgsMapLayer *, QVector<QgsRayCastingUtils::RayHit>> Qgs3DUtils::castRay( Q
}
return results;
}

float Qgs3DUtils::screenSpaceError( float epsilon, float distance, float screenSize, float fov )
{
/* This routine approximately calculates how an error (epsilon) of an object in world coordinates
* at given distance (between camera and the object) will look like in screen coordinates.
*
* the math below simply uses triangle similarity:
*
* epsilon phi
* ----------------------------- = ----------------
* [ frustum width at distance ] [ screen width ]
*
* Then we solve for phi, substituting [frustum width at distance] = 2 * distance * tan(fov / 2)
*
* ________xxx__ xxx = real world error (epsilon)
* \ | / x = screen space error (phi)
* \ | /
* \___|_x_/ near plane (screen space)
* \ | /
* \ | /
* \|/ angle = field of view
* camera
*/
float phi = epsilon * screenSize / ( 2 * distance * tan( fov * M_PI / ( 2 * 180 ) ) );
return phi;
}
13 changes: 13 additions & 0 deletions src/3d/qgs3dutils.h
Expand Up @@ -261,6 +261,19 @@ class _3D_EXPORT Qgs3DUtils
* \since QGIS 3.32
*/
static QgsRectangle tryReprojectExtent2D( const QgsRectangle &extent, const QgsCoordinateReferenceSystem &crs1, const QgsCoordinateReferenceSystem &crs2, const QgsCoordinateTransformContext &context );

/**
* This routine approximately calculates how an error (\a epsilon) of an object in world coordinates
* at given \a distance (between camera and the object) will look like in screen coordinates.
*
* \param epsilon error in world coordinates
* \param distance distance between camera and object
* \param screenSize screen width or height in pixels
* \param fov camera's field of view in degrees
*
* \since QGIS 3.32
*/
static float screenSpaceError( float epsilon, float distance, float screenSize, float fov );
};

#endif // QGS3DUTILS_H
19 changes: 15 additions & 4 deletions src/3d/qgspointcloudlayer3drenderer.cpp
Expand Up @@ -21,6 +21,7 @@

#include "qgspointcloudindex.h"
#include "qgspointcloudlayer.h"
#include "qgsvirtualpointcloudentity_p.h"
#include "qgsxmlutils.h"
#include "qgsapplication.h"
#include "qgs3dsymbolregistry.h"
Expand Down Expand Up @@ -152,16 +153,26 @@ QgsPointCloudLayer3DRenderer *QgsPointCloudLayer3DRenderer::clone() const
Qt3DCore::QEntity *QgsPointCloudLayer3DRenderer::createEntity( const Qgs3DMapSettings &map ) const
{
QgsPointCloudLayer *pcl = layer();
if ( !pcl || !pcl->dataProvider() || !pcl->dataProvider()->index() )
if ( !pcl || !pcl->dataProvider() || ( !pcl->dataProvider()->index() && pcl->dataProvider()->subIndexes().isEmpty() ) )
return nullptr;
if ( !mSymbol )
return nullptr;

const QgsCoordinateTransform coordinateTransform( pcl->crs(), map.crs(), map.transformContext() );

QgsPointCloudLayerChunkedEntity *entity = new QgsPointCloudLayerChunkedEntity( pcl->dataProvider()->index(), map, coordinateTransform, dynamic_cast<QgsPointCloud3DSymbol *>( mSymbol->clone() ), maximumScreenError(), showBoundingBoxes(),
static_cast< const QgsPointCloudLayerElevationProperties * >( pcl->elevationProperties() )->zScale(),
static_cast< const QgsPointCloudLayerElevationProperties * >( pcl->elevationProperties() )->zOffset(), mPointBudget );
Qt3DCore::QEntity *entity;
if ( pcl->dataProvider()->index() )
{
entity = new QgsPointCloudLayerChunkedEntity( pcl->dataProvider()->index(), map, coordinateTransform, dynamic_cast<QgsPointCloud3DSymbol *>( mSymbol->clone() ), maximumScreenError(), showBoundingBoxes(),
static_cast< const QgsPointCloudLayerElevationProperties * >( pcl->elevationProperties() )->zScale(),
static_cast< const QgsPointCloudLayerElevationProperties * >( pcl->elevationProperties() )->zOffset(), mPointBudget );
}
else
{
entity = new QgsVirtualPointCloudEntity( pcl, pcl->dataProvider()->subIndexes(), map, coordinateTransform, dynamic_cast<QgsPointCloud3DSymbol *>( mSymbol->clone() ), maximumScreenError(), showBoundingBoxes(),
static_cast< const QgsPointCloudLayerElevationProperties * >( pcl->elevationProperties() )->zScale(),
static_cast< const QgsPointCloudLayerElevationProperties * >( pcl->elevationProperties() )->zOffset(), mPointBudget );
}
return entity;
}

Expand Down

0 comments on commit 452b320

Please sign in to comment.