QGIS API Documentation 3.39.0-Master (d85f3c2a281)
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 "qgsvirtualpointcloudprovider.h"
20#include "qgs3dutils.h"
21
23
24
25QgsVirtualPointCloudEntity::QgsVirtualPointCloudEntity(
27 QgsPointCloudLayer *layer,
28 const QgsCoordinateTransform &coordinateTransform,
30 float maximumScreenSpaceError,
31 bool showBoundingBoxes,
32 double zValueScale,
33 double zValueOffset,
34 int pointBudget )
35 : Qgs3DMapSceneEntity( map, nullptr )
36 , mLayer( layer )
37 , mCoordinateTransform( coordinateTransform )
38 , mZValueScale( zValueScale )
39 , mZValueOffset( zValueOffset )
40 , mPointBudget( pointBudget )
41 , mMaximumScreenSpaceError( maximumScreenSpaceError )
42 , mShowBoundingBoxes( showBoundingBoxes )
43{
44 mSymbol.reset( symbol );
45 mBboxesEntity = new QgsChunkBoundsEntity( this );
46 const QgsRectangle mapExtent = Qgs3DUtils::tryReprojectExtent2D( map->extent(), map->crs(), layer->crs(), map->transformContext() );
47 const QVector<QgsPointCloudSubIndex> subIndexes = provider()->subIndexes();
48 for ( int i = 0; i < subIndexes.size(); ++i )
49 {
50 const QgsPointCloudSubIndex &si = subIndexes.at( i );
51 const QgsRectangle intersection = si.extent().intersect( mapExtent );
52
53 mBboxes << Qgs3DUtils::mapToWorldExtent( intersection, si.zRange().lower(), si.zRange().upper(), map->origin() );
54
55 createChunkedEntityForSubIndex( i );
56 }
57
58 updateBboxEntity();
59 connect( this, &QgsVirtualPointCloudEntity::subIndexNeedsLoading, provider(), &QgsVirtualPointCloudProvider::loadSubIndex, Qt::QueuedConnection );
60 connect( provider(), &QgsVirtualPointCloudProvider::subIndexLoaded, this, &QgsVirtualPointCloudEntity::createChunkedEntityForSubIndex );
61
62}
63
64QList<QgsChunkedEntity *> QgsVirtualPointCloudEntity::chunkedEntities() const
65{
66 return mChunkedEntitiesMap.values();
67}
68
69QgsVirtualPointCloudProvider *QgsVirtualPointCloudEntity::provider() const
70{
71 return qobject_cast<QgsVirtualPointCloudProvider *>( mLayer->dataProvider() );
72}
73
74QgsAABB QgsVirtualPointCloudEntity::boundingBox( int i ) const
75{
76 return mBboxes.at( i );
77}
78
79void QgsVirtualPointCloudEntity::createChunkedEntityForSubIndex( int i )
80{
81 const QVector<QgsPointCloudSubIndex> subIndexes = provider()->subIndexes();
82 const QgsPointCloudSubIndex &si = subIndexes.at( i );
83
84 // Skip if Index is not yet loaded or is outside the map extents
85 if ( !si.index() || mBboxes.at( i ).isEmpty() )
86 return;
87
88 QgsPointCloudLayerChunkedEntity *newChunkedEntity = new QgsPointCloudLayerChunkedEntity(
89 mapSettings(),
90 si.index(),
91 mCoordinateTransform,
92 static_cast< QgsPointCloud3DSymbol * >( mSymbol->clone() ),
93 mMaximumScreenSpaceError,
94 mShowBoundingBoxes,
95 mZValueScale,
96 mZValueOffset,
97 mPointBudget );
98
99 mChunkedEntitiesMap.insert( i, newChunkedEntity );
100 newChunkedEntity->setParent( this );
101 connect( newChunkedEntity, &QgsChunkedEntity::pendingJobsCountChanged, this, &Qgs3DMapSceneEntity::pendingJobsCountChanged );
102 emit newEntityCreated( newChunkedEntity );
103}
104
105void QgsVirtualPointCloudEntity::handleSceneUpdate( const SceneContext &sceneContext )
106{
107 const QVector<QgsPointCloudSubIndex> subIndexes = provider()->subIndexes();
108 for ( int i = 0; i < subIndexes.size(); ++i )
109 {
110 const QgsAABB &bbox = mBboxes.at( i );
111
112 if ( bbox.isEmpty() )
113 continue;
114
115 // magic number 256 is the common span value for a COPC root node
116 constexpr int SPAN = 256;
117 const float epsilon = std::min( bbox.xExtent(), bbox.yExtent() ) / SPAN;
118 const float distance = bbox.distanceFromPoint( sceneContext.cameraPos );
119 const float sse = Qgs3DUtils::screenSpaceError( epsilon, distance, sceneContext.screenSizePx, sceneContext.cameraFov );
120 constexpr float THRESHOLD = .2;
121
122 // always display as bbox for the initial temporary camera pos (0, 0, 0)
123 // then once the camera changes we display as bbox depending on screen space error
124 const bool displayAsBbox = sceneContext.cameraPos.isNull() || sse < THRESHOLD;
125 if ( !displayAsBbox && !subIndexes.at( i ).index() )
126 emit subIndexNeedsLoading( i );
127
128 setRenderSubIndexAsBbox( i, displayAsBbox );
129 if ( !displayAsBbox && mChunkedEntitiesMap.contains( i ) )
130 mChunkedEntitiesMap[i]->handleSceneUpdate( sceneContext );
131 }
132 updateBboxEntity();
133}
134
135QgsRange<float> QgsVirtualPointCloudEntity::getNearFarPlaneRange( const QMatrix4x4 &viewMatrix ) const
136{
137 float fnear = 1e9;
138 float ffar = 0;
139
140 for ( QgsChunkedEntity *entity : mChunkedEntitiesMap )
141 {
142 if ( entity->isEnabled() )
143 {
144 const QgsRange<float> range = entity->getNearFarPlaneRange( viewMatrix );
145 ffar = std::max( range.upper(), ffar );
146 fnear = std::min( range.lower(), fnear );
147 }
148 }
149
150 // if there were no chunked entities available, we will iterate the bboxes as a fallback instead
151 if ( fnear == 1e9 && ffar == 0 )
152 {
153 for ( const QgsAABB &bbox : mBboxes )
154 {
155 float bboxfnear;
156 float bboxffar;
157 Qgs3DUtils::computeBoundingBoxNearFarPlanes( bbox, viewMatrix, bboxfnear, bboxffar );
158 fnear = std::min( fnear, bboxfnear );
159 ffar = std::max( ffar, bboxffar );
160 }
161 }
162
163 return QgsRange<float>( fnear, ffar );
164}
165
166int QgsVirtualPointCloudEntity::pendingJobsCount() const
167{
168 int jobs = 0;
169 for ( QgsChunkedEntity *entity : mChunkedEntitiesMap )
170 {
171 if ( entity->isEnabled() )
172 jobs += entity->pendingJobsCount();
173 }
174 return jobs;
175}
176
177bool QgsVirtualPointCloudEntity::needsUpdate() const
178{
179 for ( QgsChunkedEntity *entity : mChunkedEntitiesMap )
180 {
181 if ( entity->isEnabled() && entity->needsUpdate() )
182 return true;
183 }
184 return false;
185}
186
187void QgsVirtualPointCloudEntity::updateBboxEntity()
188{
189 QList<QgsAABB> bboxes;
190 const QVector<QgsPointCloudSubIndex> subIndexes = provider()->subIndexes();
191 for ( int i = 0; i < subIndexes.size(); ++i )
192 {
193 if ( mChunkedEntitiesMap.contains( i ) && mChunkedEntitiesMap[i]->isEnabled() )
194 continue;
195
196 if ( mBboxes.at( i ).isEmpty() )
197 continue;
198
199 bboxes << mBboxes.at( i );
200 }
201
202 mBboxesEntity->setBoxes( bboxes );
203}
204
205void QgsVirtualPointCloudEntity::setRenderSubIndexAsBbox( int i, bool asBbox )
206{
207 if ( !mChunkedEntitiesMap.contains( i ) )
208 return;
209
210 mChunkedEntitiesMap[i]->setEnabled( !asBbox );
211}
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...
QgsVector3D origin() const
Returns coordinates in map CRS at which 3D scene has origin (0,0,0).
static QgsAABB mapToWorldExtent(const QgsRectangle &extent, double zMin, double zMax, const QgsVector3D &mapOrigin)
Converts map extent to axis aligned bounding box in 3D world coordinates.
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 ...
float yExtent() const
Returns box width in Y axis.
Definition qgsaabb.h:44
float xExtent() const
Returns box width in X axis.
Definition qgsaabb.h:42
bool isEmpty() const
Returns true if xExtent(), yExtent() and zExtent() are all zero, false otherwise.
Definition qgsaabb.h:81
float distanceFromPoint(float x, float y, float z) const
Returns shortest distance from the box to a point.
Definition qgsaabb.cpp:50
Class for doing transforms between two map coordinate systems.
QgsCoordinateReferenceSystem crs
Definition qgsmaplayer.h:83
Represents a map layer supporting display of point clouds.
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.
QgsRectangle intersect(const QgsRectangle &rect) const
Returns the intersection with the given rectangle.