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