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