QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
Loading...
Searching...
No Matches
qgsvirtualpointcloudentity_p.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsvirtualpointcloudentity_p.cpp
3 --------------------------------------
4 Date : April 2023
5 Copyright : (C) 2023 by Stefanos Natsis
6 Email : uclaros at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
17
18#include "qgs3dutils.h"
20#include "qgspointcloudlayer.h"
22#include "qgsvirtualpointcloudprovider.h"
23
24#include "moc_qgsvirtualpointcloudentity_p.cpp"
25
27
28
29QgsVirtualPointCloudEntity::QgsVirtualPointCloudEntity(
31 QgsPointCloudLayer *layer,
32 const QgsCoordinateTransform &coordinateTransform,
34 float maximumScreenSpaceError,
35 bool showBoundingBoxes,
36 double zValueScale,
37 double zValueOffset,
38 int pointBudget
39)
40 : Qgs3DMapSceneEntity( map, nullptr )
41 , mLayer( layer )
42 , mCoordinateTransform( coordinateTransform )
43 , mZValueScale( zValueScale )
44 , mZValueOffset( zValueOffset )
45 , mPointBudget( pointBudget )
46 , mMaximumScreenSpaceError( maximumScreenSpaceError )
47 , mShowBoundingBoxes( showBoundingBoxes )
48{
49 mSymbol.reset( symbol );
50 const QgsRectangle mapExtent = Qgs3DUtils::tryReprojectExtent2D( map->extent(), map->crs(), layer->crs(), map->transformContext() );
51 const QVector<QgsPointCloudSubIndex> subIndexes = provider()->subIndexes();
52 for ( int i = 0; i < subIndexes.size(); ++i )
53 {
54 const QgsPointCloudSubIndex &si = subIndexes.at( i );
55 const QgsRectangle intersection = si.extent().intersect( mapExtent );
56
57 mBboxes << QgsBox3D( intersection, si.zRange().lower(), si.zRange().upper() );
58
59 createChunkedEntityForSubIndex( i );
60 }
61
62 if ( provider()->overview() )
63 {
64 // use -2 as a special identifier for overview files in chunked entity
65 mOverviewEntity
66 = new QgsPointCloudLayerChunkedEntity( mapSettings(), mLayer, -2, mCoordinateTransform, dynamic_cast<QgsPointCloud3DSymbol *>( mSymbol->clone() ), mMaximumScreenSpaceError, false, mZValueScale, mZValueOffset, mPointBudget );
67 mOverviewEntity->setParent( this );
68 connect( mOverviewEntity, &QgsChunkedEntity::pendingJobsCountChanged, this, &Qgs3DMapSceneEntity::pendingJobsCountChanged );
69 connect( mOverviewEntity, &QgsChunkedEntity::newEntityCreated, this, &Qgs3DMapSceneEntity::newEntityCreated );
70 emit newEntityCreated( mOverviewEntity );
71 }
72
73 // this is a rather arbitrary point, it could be somewhere else, ideally near the actual data
74 QgsVector3D boundsEntityOrigin( mapExtent.center().x(), mapExtent.center().y(), 0 );
75
76 mBboxesEntity = new QgsChunkBoundsEntity( boundsEntityOrigin, this );
77 updateBboxEntity();
78 connect( this, &QgsVirtualPointCloudEntity::subIndexNeedsLoading, provider(), &QgsVirtualPointCloudProvider::loadSubIndex, Qt::QueuedConnection );
79 connect( provider(), &QgsVirtualPointCloudProvider::subIndexLoaded, this, &QgsVirtualPointCloudEntity::createChunkedEntityForSubIndex );
80}
81
82QgsVirtualPointCloudEntity::~QgsVirtualPointCloudEntity()
83{
84 qDeleteAll( mChunkedEntitiesMap );
85 mChunkedEntitiesMap.clear();
86
87 delete mOverviewEntity;
88 mOverviewEntity = nullptr;
89}
90
91QList<QgsChunkedEntity *> QgsVirtualPointCloudEntity::chunkedEntities() const
92{
93 return mChunkedEntitiesMap.values();
94}
95
96QgsVirtualPointCloudProvider *QgsVirtualPointCloudEntity::provider() const
97{
98 return qobject_cast<QgsVirtualPointCloudProvider *>( mLayer->dataProvider() );
99}
100
101void QgsVirtualPointCloudEntity::createChunkedEntityForSubIndex( int i )
102{
103 const QVector<QgsPointCloudSubIndex> subIndexes = provider()->subIndexes();
104 const QgsPointCloudSubIndex &si = subIndexes.at( i );
105
106 // Skip if Index is not yet loaded or is outside the map extents, or it's not valid (e.g. file is missing)
107 if ( !si.index() || mBboxes.at( i ).isEmpty() || !si.index().isValid() )
108 return;
109
110 QgsPointCloudLayerChunkedEntity *newChunkedEntity
111 = new QgsPointCloudLayerChunkedEntity( mapSettings(), mLayer, i, mCoordinateTransform, static_cast<QgsPointCloud3DSymbol *>( mSymbol->clone() ), mMaximumScreenSpaceError, mShowBoundingBoxes, mZValueScale, mZValueOffset, mPointBudget );
112
113 mChunkedEntitiesMap.insert( i, newChunkedEntity );
114 newChunkedEntity->setParent( this );
115 connect( newChunkedEntity, &QgsChunkedEntity::pendingJobsCountChanged, this, &Qgs3DMapSceneEntity::pendingJobsCountChanged );
116 connect( newChunkedEntity, &QgsChunkedEntity::newEntityCreated, this, &Qgs3DMapSceneEntity::newEntityCreated );
117 emit newEntityCreated( newChunkedEntity );
118}
119
120void QgsVirtualPointCloudEntity::handleSceneUpdate( const SceneContext &sceneContext )
121{
122 QgsVector3D cameraPosMapCoords = QgsVector3D( sceneContext.cameraPos ) + mapSettings()->origin();
123 const QVector<QgsPointCloudSubIndex> subIndexes = provider()->subIndexes();
124
125 const QgsPointCloudLayer3DRenderer *rendererBehavior = dynamic_cast<QgsPointCloudLayer3DRenderer *>( mLayer->renderer3D() );
126 const double overviewSwitchingScale = rendererBehavior ? rendererBehavior->overviewSwitchingScale() : 1;
127 qsizetype subIndexesRendered = 0;
128
129 for ( int i = 0; i < subIndexes.size(); ++i )
130 {
131 // If the chunked entity needs an update, do it even if it's occluded,
132 // since otherwise we'd return needsUpdate() == true until it comes into
133 // view again.
134 bool needsUpdate = mChunkedEntitiesMap.contains( i ) && mChunkedEntitiesMap[i]->needsUpdate();
135
136 const QgsBox3D &box3D = mBboxes.at( i );
137
138 if ( !needsUpdate && box3D.isEmpty() )
139 continue;
140
141 QgsAABB aabb = QgsAABB::fromBox3D( box3D, mMapSettings->origin() );
142 if ( !needsUpdate && Qgs3DUtils::isCullable( aabb, sceneContext.viewProjectionMatrix ) )
143 continue;
144
145 // magic number 256 is the common span value for a COPC root node
146 constexpr int SPAN = 256;
147 const float epsilon = static_cast<float>( std::min( box3D.width(), box3D.height() ) ) / SPAN;
148 const float distance = static_cast<float>( box3D.distanceTo( cameraPosMapCoords ) );
149 const float sse = Qgs3DUtils::screenSpaceError( epsilon, distance, sceneContext.screenSizePx, sceneContext.cameraFov );
150 const double THRESHOLD = 0.2 / overviewSwitchingScale;
151
152 // always display as bbox for the initial temporary camera pos (0, 0, 0)
153 // then once the camera changes we display as bbox depending on screen space error
154 const bool displayAsBbox = sceneContext.cameraPos.isNull() || sse < static_cast<float>( THRESHOLD );
155 if ( !displayAsBbox )
156 {
157 subIndexesRendered += 1;
158 if ( !subIndexes.at( i ).index() )
159 emit subIndexNeedsLoading( i );
160 }
161 setRenderSubIndexAsBbox( i, displayAsBbox );
162 if ( !displayAsBbox && mChunkedEntitiesMap.contains( i ) )
163 mChunkedEntitiesMap[i]->handleSceneUpdate( sceneContext );
164 }
165 updateBboxEntity();
166
167 if ( provider()->overview()
168 && rendererBehavior
170 {
171 // no need to render the overview if all sub indexes are shown
172 if ( !mChunkedEntitiesMap.isEmpty() && subIndexesRendered == mChunkedEntitiesMap.size() )
173 mOverviewEntity->setEnabled( false );
174 else
175 mOverviewEntity->setEnabled( true );
176 mOverviewEntity->handleSceneUpdate( sceneContext );
177 }
178}
179
180QgsRange<float> QgsVirtualPointCloudEntity::getNearFarPlaneRange( const QMatrix4x4 &viewMatrix ) const
181{
182 float fnear = 1e9;
183 float ffar = 0;
184
185 for ( QgsChunkedEntity *entity : mChunkedEntitiesMap )
186 {
187 if ( entity->isEnabled() )
188 {
189 const QgsRange<float> range = entity->getNearFarPlaneRange( viewMatrix );
190 ffar = std::max( range.upper(), ffar );
191 fnear = std::min( range.lower(), fnear );
192 }
193 }
194
195 // if there were no chunked entities available, we will iterate the bboxes as a fallback instead
196 if ( fnear == 1e9 && ffar == 0 )
197 {
198 for ( const QgsBox3D &box : mBboxes )
199 {
200 QgsAABB aabb = QgsAABB::fromBox3D( box, mMapSettings->origin() );
201 float bboxfnear;
202 float bboxffar;
203 Qgs3DUtils::computeBoundingBoxNearFarPlanes( aabb, viewMatrix, bboxfnear, bboxffar );
204 fnear = std::min( fnear, bboxfnear );
205 ffar = std::max( ffar, bboxffar );
206 }
207 }
208
209 return QgsRange<float>( fnear, ffar );
210}
211
212int QgsVirtualPointCloudEntity::pendingJobsCount() const
213{
214 int jobs = 0;
215 for ( QgsChunkedEntity *entity : mChunkedEntitiesMap )
216 {
217 if ( entity->isEnabled() )
218 jobs += entity->pendingJobsCount();
219 }
220 return jobs;
221}
222
223bool QgsVirtualPointCloudEntity::needsUpdate() const
224{
225 for ( QgsChunkedEntity *entity : mChunkedEntitiesMap )
226 {
227 if ( entity->isEnabled() && entity->needsUpdate() )
228 return true;
229 }
230 return false;
231}
232
233void QgsVirtualPointCloudEntity::updateBboxEntity()
234{
235 QList<QgsBox3D> bboxes;
236 // we want to render bounding boxes only when zoomOutBehavior is RenderExtents or RenderOverviewAndExtents
237 const QgsPointCloudLayer3DRenderer *renderer = dynamic_cast<QgsPointCloudLayer3DRenderer *>( mLayer->renderer3D() );
239 {
240 const QVector<QgsPointCloudSubIndex> subIndexes = provider()->subIndexes();
241 for ( int i = 0; i < subIndexes.size(); ++i )
242 {
243 if ( mChunkedEntitiesMap.contains( i ) && mChunkedEntitiesMap[i]->isEnabled() )
244 continue;
245
246 if ( mBboxes.at( i ).isEmpty() )
247 continue;
248
249 bboxes << mBboxes.at( i );
250 }
251 }
252
253 mBboxesEntity->setBoxes( bboxes );
254}
255
256void QgsVirtualPointCloudEntity::setRenderSubIndexAsBbox( int i, bool asBbox )
257{
258 if ( !mChunkedEntitiesMap.contains( i ) )
259 return;
260
261 mChunkedEntitiesMap[i]->setEnabled( !asBbox );
262}
@ RenderOverviewAndExtents
Render point cloud extents over overview point cloud.
Definition qgis.h:6446
@ RenderOverview
Render overview point cloud when zoomed out.
Definition qgis.h:6445
Definition of the world.
QgsRectangle extent() const
Returns the 3D scene's 2D extent in the 3D scene's CRS.
QgsCoordinateReferenceSystem crs() const
Returns coordinate reference system used in the 3D scene.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context, which stores various information regarding which datum tran...
static bool isCullable(const QgsAABB &bbox, const QMatrix4x4 &viewProjectionMatrix)
Returns true if bbox is completely outside the current viewing volume.
static float screenSpaceError(float epsilon, float distance, int screenSize, float fov)
This routine approximately calculates how an error (epsilon) of an object in world coordinates at giv...
static QgsRectangle tryReprojectExtent2D(const QgsRectangle &extent, const QgsCoordinateReferenceSystem &crs1, const QgsCoordinateReferenceSystem &crs2, const QgsCoordinateTransformContext &context)
Reprojects extent from crs1 to crs2 coordinate reference system with context context.
static void computeBoundingBoxNearFarPlanes(const QgsAABB &bbox, const QMatrix4x4 &viewMatrix, float &fnear, float &ffar)
This routine computes nearPlane farPlane from the closest and farthest corners point of bounding box ...
Axis-aligned bounding box - in world coords.
Definition qgsaabb.h:33
static QgsAABB fromBox3D(const QgsBox3D &box3D, const QgsVector3D &origin)
Constructs bounding box from QgsBox3D by subtracting origin 3D vector.
Definition qgsaabb.h:46
A 3-dimensional box composed of x, y, z coordinates.
Definition qgsbox3d.h:45
Q_DECL_DEPRECATED double distanceTo(const QVector3D &point) const
Returns the smallest distance between the box and the point point (returns 0 if the point is inside t...
Definition qgsbox3d.h:397
double width() const
Returns the width of the box.
Definition qgsbox3d.h:287
double height() const
Returns the height of the box.
Definition qgsbox3d.h:294
bool isEmpty() const
Returns true if the box is empty.
Definition qgsbox3d.cpp:321
Handles coordinate transforms between two coordinate systems.
QgsCoordinateReferenceSystem crs
Definition qgsmaplayer.h:90
3D symbol that draws point cloud geometries as 3D objects.
3D renderer that renders all points from a point cloud layer.
double overviewSwitchingScale() const
Returns the overview switching scale.
Qgis::PointCloudZoomOutRenderBehavior zoomOutBehavior() const
Returns the renderer behavior when zoomed out.
Represents a map layer supporting display of point clouds.
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
A template based class for storing ranges (lower to upper values).
Definition qgsrange.h:48
T lower() const
Returns the lower bound of the range.
Definition qgsrange.h:79
T upper() const
Returns the upper bound of the range.
Definition qgsrange.h:86
A rectangle specified with double values.
QgsPointXY center
QgsRectangle intersect(const QgsRectangle &rect) const
Returns the intersection with the given rectangle.
A 3D vector (similar to QVector3D) with the difference that it uses double precision instead of singl...
Definition qgsvector3d.h:33