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