QGIS API Documentation 3.99.0-Master (752b475928d)
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
90QList<QgsChunkedEntity *> QgsVirtualPointCloudEntity::chunkedEntities() const
91{
92 return mChunkedEntitiesMap.values();
93}
94
95QgsVirtualPointCloudProvider *QgsVirtualPointCloudEntity::provider() const
96{
97 return qobject_cast<QgsVirtualPointCloudProvider *>( mLayer->dataProvider() );
98}
99
100void QgsVirtualPointCloudEntity::createChunkedEntityForSubIndex( int i )
101{
102 const QVector<QgsPointCloudSubIndex> subIndexes = provider()->subIndexes();
103 const QgsPointCloudSubIndex &si = subIndexes.at( i );
104
105 // Skip if Index is not yet loaded or is outside the map extents, or it's not valid (e.g. file is missing)
106 if ( !si.index() || mBboxes.at( i ).isEmpty() || !si.index().isValid() )
107 return;
108
109 QgsPointCloudLayerChunkedEntity *newChunkedEntity = new QgsPointCloudLayerChunkedEntity(
110 mapSettings(),
111 mLayer,
112 si.index(),
113 mCoordinateTransform,
114 static_cast<QgsPointCloud3DSymbol *>( mSymbol->clone() ),
115 mMaximumScreenSpaceError,
116 mShowBoundingBoxes,
117 mZValueScale,
118 mZValueOffset,
119 mPointBudget
120 );
121
122 mChunkedEntitiesMap.insert( i, newChunkedEntity );
123 newChunkedEntity->setParent( this );
124 connect( newChunkedEntity, &QgsChunkedEntity::pendingJobsCountChanged, this, &Qgs3DMapSceneEntity::pendingJobsCountChanged );
125 connect( newChunkedEntity, &QgsChunkedEntity::newEntityCreated, this, &Qgs3DMapSceneEntity::newEntityCreated );
126 emit newEntityCreated( newChunkedEntity );
127}
128
129void QgsVirtualPointCloudEntity::handleSceneUpdate( const SceneContext &sceneContext )
130{
131 QgsVector3D cameraPosMapCoords = QgsVector3D( sceneContext.cameraPos ) + mapSettings()->origin();
132 const QVector<QgsPointCloudSubIndex> subIndexes = provider()->subIndexes();
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 constexpr float THRESHOLD = .2;
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 < THRESHOLD;
159 if ( !displayAsBbox && !subIndexes.at( i ).index() )
160 emit subIndexNeedsLoading( i );
161
162 setRenderSubIndexAsBbox( i, displayAsBbox );
163 if ( !displayAsBbox && mChunkedEntitiesMap.contains( i ) )
164 mChunkedEntitiesMap[i]->handleSceneUpdate( sceneContext );
165 }
166 updateBboxEntity();
167
168 const QgsPointCloudLayer3DRenderer *rendererBehavior = dynamic_cast<QgsPointCloudLayer3DRenderer *>( mLayer->renderer3D() );
169 if ( provider()->overview() && rendererBehavior && ( rendererBehavior->zoomOutBehavior() == Qgis::PointCloudZoomOutRenderBehavior::RenderOverview || rendererBehavior->zoomOutBehavior() == Qgis::PointCloudZoomOutRenderBehavior::RenderOverviewAndExtents ) )
170 {
171 mOverviewEntity->handleSceneUpdate( sceneContext );
172 }
173}
174
175QgsRange<float> QgsVirtualPointCloudEntity::getNearFarPlaneRange( const QMatrix4x4 &viewMatrix ) const
176{
177 float fnear = 1e9;
178 float ffar = 0;
179
180 for ( QgsChunkedEntity *entity : mChunkedEntitiesMap )
181 {
182 if ( entity->isEnabled() )
183 {
184 const QgsRange<float> range = entity->getNearFarPlaneRange( viewMatrix );
185 ffar = std::max( range.upper(), ffar );
186 fnear = std::min( range.lower(), fnear );
187 }
188 }
189
190 // if there were no chunked entities available, we will iterate the bboxes as a fallback instead
191 if ( fnear == 1e9 && ffar == 0 )
192 {
193 for ( const QgsBox3D &box : mBboxes )
194 {
195 QgsAABB aabb = QgsAABB::fromBox3D( box, mMapSettings->origin() );
196 float bboxfnear;
197 float bboxffar;
198 Qgs3DUtils::computeBoundingBoxNearFarPlanes( aabb, viewMatrix, bboxfnear, bboxffar );
199 fnear = std::min( fnear, bboxfnear );
200 ffar = std::max( ffar, bboxffar );
201 }
202 }
203
204 return QgsRange<float>( fnear, ffar );
205}
206
207int QgsVirtualPointCloudEntity::pendingJobsCount() const
208{
209 int jobs = 0;
210 for ( QgsChunkedEntity *entity : mChunkedEntitiesMap )
211 {
212 if ( entity->isEnabled() )
213 jobs += entity->pendingJobsCount();
214 }
215 return jobs;
216}
217
218bool QgsVirtualPointCloudEntity::needsUpdate() const
219{
220 for ( QgsChunkedEntity *entity : mChunkedEntitiesMap )
221 {
222 if ( entity->isEnabled() && entity->needsUpdate() )
223 return true;
224 }
225 return false;
226}
227
228void QgsVirtualPointCloudEntity::updateBboxEntity()
229{
230 QList<QgsBox3D> bboxes;
231 // we want to render bounding boxes only when zoomOutBehavior is RenderExtents or RenderOverviewAndExtents
232 const QgsPointCloudLayer3DRenderer *renderer = dynamic_cast<QgsPointCloudLayer3DRenderer *>( mLayer->renderer3D() );
234 {
235 const QVector<QgsPointCloudSubIndex> subIndexes = provider()->subIndexes();
236 for ( int i = 0; i < subIndexes.size(); ++i )
237 {
238 if ( mChunkedEntitiesMap.contains( i ) && mChunkedEntitiesMap[i]->isEnabled() )
239 continue;
240
241 if ( mBboxes.at( i ).isEmpty() )
242 continue;
243
244 bboxes << mBboxes.at( i );
245 }
246 }
247
248 mBboxesEntity->setBoxes( bboxes );
249}
250
251void QgsVirtualPointCloudEntity::setRenderSubIndexAsBbox( int i, bool asBbox )
252{
253 if ( !mChunkedEntitiesMap.contains( i ) )
254 return;
255
256 mChunkedEntitiesMap[i]->setEnabled( !asBbox );
257}
@ RenderOverviewAndExtents
Render point cloud extents over overview point cloud.
Definition qgis.h:6088
@ RenderOverview
Render overview point cloud when zoomed out.
Definition qgis.h:6087
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:42
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:387
double width() const
Returns the width of the box.
Definition qgsbox3d.h:277
double height() const
Returns the height of the box.
Definition qgsbox3d.h:284
bool isEmpty() const
Returns true if the box is empty.
Definition qgsbox3d.cpp:322
Handles coordinate transforms between two coordinate systems.
QgsCoordinateReferenceSystem crs
Definition qgsmaplayer.h:87
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: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.
A 3D vector (similar to QVector3D) with the difference that it uses double precision instead of singl...
Definition qgsvector3d.h:30