QGIS API Documentation 3.99.0-Master (d270888f95f)
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"
21#include "qgsvirtualpointcloudprovider.h"
22
23#include "moc_qgsvirtualpointcloudentity_p.cpp"
24
26
27
28QgsVirtualPointCloudEntity::QgsVirtualPointCloudEntity(
30 QgsPointCloudLayer *layer,
31 const QgsCoordinateTransform &coordinateTransform,
33 float maximumScreenSpaceError,
34 bool showBoundingBoxes,
35 double zValueScale,
36 double zValueOffset,
37 int pointBudget
38)
39 : Qgs3DMapSceneEntity( map, nullptr )
40 , mLayer( layer )
41 , mCoordinateTransform( coordinateTransform )
42 , mZValueScale( zValueScale )
43 , mZValueOffset( zValueOffset )
44 , mPointBudget( pointBudget )
45 , mMaximumScreenSpaceError( maximumScreenSpaceError )
46 , mShowBoundingBoxes( showBoundingBoxes )
47{
48 mSymbol.reset( symbol );
49 const QgsRectangle mapExtent = Qgs3DUtils::tryReprojectExtent2D( map->extent(), map->crs(), layer->crs(), map->transformContext() );
50 const QVector<QgsPointCloudSubIndex> subIndexes = provider()->subIndexes();
51 for ( int i = 0; i < subIndexes.size(); ++i )
52 {
53 const QgsPointCloudSubIndex &si = subIndexes.at( i );
54 const QgsRectangle intersection = si.extent().intersect( mapExtent );
55
56 mBboxes << QgsBox3D( intersection, si.zRange().lower(), si.zRange().upper() );
57
58 createChunkedEntityForSubIndex( i );
59 }
60
61 if ( provider()->overview() )
62 {
63 mOverviewEntity = new QgsPointCloudLayerChunkedEntity(
64 mapSettings(),
65 mLayer,
66 provider()->overview(),
67 mCoordinateTransform,
68 dynamic_cast<QgsPointCloud3DSymbol *>( mSymbol->clone() ),
69 mMaximumScreenSpaceError,
70 false,
71 mZValueScale,
72 mZValueOffset,
73 mPointBudget
74 );
75 mOverviewEntity->setParent( this );
76 connect( mOverviewEntity, &QgsChunkedEntity::pendingJobsCountChanged, this, &Qgs3DMapSceneEntity::pendingJobsCountChanged );
77 connect( mOverviewEntity, &QgsChunkedEntity::newEntityCreated, this, &Qgs3DMapSceneEntity::newEntityCreated );
78 emit newEntityCreated( mOverviewEntity );
79 }
80
81 // this is a rather arbitrary point, it could be somewhere else, ideally near the actual data
82 QgsVector3D boundsEntityOrigin( mapExtent.center().x(), mapExtent.center().y(), 0 );
83
84 mBboxesEntity = new QgsChunkBoundsEntity( boundsEntityOrigin, this );
85 updateBboxEntity();
86 connect( this, &QgsVirtualPointCloudEntity::subIndexNeedsLoading, provider(), &QgsVirtualPointCloudProvider::loadSubIndex, Qt::QueuedConnection );
87 connect( provider(), &QgsVirtualPointCloudProvider::subIndexLoaded, this, &QgsVirtualPointCloudEntity::createChunkedEntityForSubIndex );
88}
89
90QgsVirtualPointCloudEntity::~QgsVirtualPointCloudEntity()
91{
92 qDeleteAll( mChunkedEntitiesMap );
93 mChunkedEntitiesMap.clear();
94
95 delete mOverviewEntity;
96 mOverviewEntity = nullptr;
97}
98
99QList<QgsChunkedEntity *> QgsVirtualPointCloudEntity::chunkedEntities() const
100{
101 return mChunkedEntitiesMap.values();
102}
103
104QgsVirtualPointCloudProvider *QgsVirtualPointCloudEntity::provider() const
105{
106 return qobject_cast<QgsVirtualPointCloudProvider *>( mLayer->dataProvider() );
107}
108
109void QgsVirtualPointCloudEntity::createChunkedEntityForSubIndex( int i )
110{
111 const QVector<QgsPointCloudSubIndex> subIndexes = provider()->subIndexes();
112 const QgsPointCloudSubIndex &si = subIndexes.at( i );
113
114 // Skip if Index is not yet loaded or is outside the map extents, or it's not valid (e.g. file is missing)
115 if ( !si.index() || mBboxes.at( i ).isEmpty() || !si.index().isValid() )
116 return;
117
118 QgsPointCloudLayerChunkedEntity *newChunkedEntity = new QgsPointCloudLayerChunkedEntity(
119 mapSettings(),
120 mLayer,
121 si.index(),
122 mCoordinateTransform,
123 static_cast<QgsPointCloud3DSymbol *>( mSymbol->clone() ),
124 mMaximumScreenSpaceError,
125 mShowBoundingBoxes,
126 mZValueScale,
127 mZValueOffset,
128 mPointBudget
129 );
130
131 mChunkedEntitiesMap.insert( i, newChunkedEntity );
132 newChunkedEntity->setParent( this );
133 connect( newChunkedEntity, &QgsChunkedEntity::pendingJobsCountChanged, this, &Qgs3DMapSceneEntity::pendingJobsCountChanged );
134 connect( newChunkedEntity, &QgsChunkedEntity::newEntityCreated, this, &Qgs3DMapSceneEntity::newEntityCreated );
135 emit newEntityCreated( newChunkedEntity );
136}
137
138void QgsVirtualPointCloudEntity::handleSceneUpdate( const SceneContext &sceneContext )
139{
140 QgsVector3D cameraPosMapCoords = QgsVector3D( sceneContext.cameraPos ) + mapSettings()->origin();
141 const QVector<QgsPointCloudSubIndex> subIndexes = provider()->subIndexes();
142 for ( int i = 0; i < subIndexes.size(); ++i )
143 {
144 // If the chunked entity needs an update, do it even if it's occluded,
145 // since otherwise we'd return needsUpdate() == true until it comes into
146 // view again.
147 bool needsUpdate = mChunkedEntitiesMap.contains( i ) && mChunkedEntitiesMap[i]->needsUpdate();
148
149 const QgsBox3D &box3D = mBboxes.at( i );
150
151 if ( !needsUpdate && box3D.isEmpty() )
152 continue;
153
154 QgsAABB aabb = QgsAABB::fromBox3D( box3D, mMapSettings->origin() );
155 if ( !needsUpdate && Qgs3DUtils::isCullable( aabb, sceneContext.viewProjectionMatrix ) )
156 continue;
157
158 // magic number 256 is the common span value for a COPC root node
159 constexpr int SPAN = 256;
160 const float epsilon = static_cast<float>( std::min( box3D.width(), box3D.height() ) ) / SPAN;
161 const float distance = static_cast<float>( box3D.distanceTo( cameraPosMapCoords ) );
162 const float sse = Qgs3DUtils::screenSpaceError( epsilon, distance, sceneContext.screenSizePx, sceneContext.cameraFov );
163 constexpr float THRESHOLD = .2;
164
165 // always display as bbox for the initial temporary camera pos (0, 0, 0)
166 // then once the camera changes we display as bbox depending on screen space error
167 const bool displayAsBbox = sceneContext.cameraPos.isNull() || sse < THRESHOLD;
168 if ( !displayAsBbox && !subIndexes.at( i ).index() )
169 emit subIndexNeedsLoading( i );
170
171 setRenderSubIndexAsBbox( i, displayAsBbox );
172 if ( !displayAsBbox && mChunkedEntitiesMap.contains( i ) )
173 mChunkedEntitiesMap[i]->handleSceneUpdate( sceneContext );
174 }
175 updateBboxEntity();
176
177 const QgsPointCloudLayer3DRenderer *rendererBehavior = dynamic_cast<QgsPointCloudLayer3DRenderer *>( mLayer->renderer3D() );
178 if ( provider()->overview() && rendererBehavior && ( rendererBehavior->zoomOutBehavior() == Qgis::PointCloudZoomOutRenderBehavior::RenderOverview || rendererBehavior->zoomOutBehavior() == Qgis::PointCloudZoomOutRenderBehavior::RenderOverviewAndExtents ) )
179 {
180 mOverviewEntity->handleSceneUpdate( sceneContext );
181 }
182}
183
184QgsRange<float> QgsVirtualPointCloudEntity::getNearFarPlaneRange( const QMatrix4x4 &viewMatrix ) const
185{
186 float fnear = 1e9;
187 float ffar = 0;
188
189 for ( QgsChunkedEntity *entity : mChunkedEntitiesMap )
190 {
191 if ( entity->isEnabled() )
192 {
193 const QgsRange<float> range = entity->getNearFarPlaneRange( viewMatrix );
194 ffar = std::max( range.upper(), ffar );
195 fnear = std::min( range.lower(), fnear );
196 }
197 }
198
199 // if there were no chunked entities available, we will iterate the bboxes as a fallback instead
200 if ( fnear == 1e9 && ffar == 0 )
201 {
202 for ( const QgsBox3D &box : mBboxes )
203 {
204 QgsAABB aabb = QgsAABB::fromBox3D( box, mMapSettings->origin() );
205 float bboxfnear;
206 float bboxffar;
207 Qgs3DUtils::computeBoundingBoxNearFarPlanes( aabb, viewMatrix, bboxfnear, bboxffar );
208 fnear = std::min( fnear, bboxfnear );
209 ffar = std::max( ffar, bboxffar );
210 }
211 }
212
213 return QgsRange<float>( fnear, ffar );
214}
215
216int QgsVirtualPointCloudEntity::pendingJobsCount() const
217{
218 int jobs = 0;
219 for ( QgsChunkedEntity *entity : mChunkedEntitiesMap )
220 {
221 if ( entity->isEnabled() )
222 jobs += entity->pendingJobsCount();
223 }
224 return jobs;
225}
226
227bool QgsVirtualPointCloudEntity::needsUpdate() const
228{
229 for ( QgsChunkedEntity *entity : mChunkedEntitiesMap )
230 {
231 if ( entity->isEnabled() && entity->needsUpdate() )
232 return true;
233 }
234 return false;
235}
236
237void QgsVirtualPointCloudEntity::updateBboxEntity()
238{
239 QList<QgsBox3D> bboxes;
240 // we want to render bounding boxes only when zoomOutBehavior is RenderExtents or RenderOverviewAndExtents
241 const QgsPointCloudLayer3DRenderer *renderer = dynamic_cast<QgsPointCloudLayer3DRenderer *>( mLayer->renderer3D() );
243 {
244 const QVector<QgsPointCloudSubIndex> subIndexes = provider()->subIndexes();
245 for ( int i = 0; i < subIndexes.size(); ++i )
246 {
247 if ( mChunkedEntitiesMap.contains( i ) && mChunkedEntitiesMap[i]->isEnabled() )
248 continue;
249
250 if ( mBboxes.at( i ).isEmpty() )
251 continue;
252
253 bboxes << mBboxes.at( i );
254 }
255 }
256
257 mBboxesEntity->setBoxes( bboxes );
258}
259
260void QgsVirtualPointCloudEntity::setRenderSubIndexAsBbox( int i, bool asBbox )
261{
262 if ( !mChunkedEntitiesMap.contains( i ) )
263 return;
264
265 mChunkedEntitiesMap[i]->setEnabled( !asBbox );
266}
@ RenderOverviewAndExtents
Render point cloud extents over overview point cloud.
Definition qgis.h:6354
@ RenderOverview
Render overview point cloud when zoomed out.
Definition qgis.h:6353
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:35
static QgsAABB fromBox3D(const QgsBox3D &box3D, const QgsVector3D &origin)
Constructs bounding box from QgsBox3D by subtracting origin 3D vector.
Definition qgsaabb.h:48
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:390
double width() const
Returns the width of the box.
Definition qgsbox3d.h:280
double height() const
Returns the height of the box.
Definition qgsbox3d.h:287
bool isEmpty() const
Returns true if the box is empty.
Definition qgsbox3d.cpp:326
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.
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:49
T lower() const
Returns the lower bound of the range.
Definition qgsrange.h:81
T upper() const
Returns the upper bound of the range.
Definition qgsrange.h:88
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