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