QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
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( QgsPointCloudLayer *layer,
26 const Qgs3DMapSettings &map,
27 const QgsCoordinateTransform &coordinateTransform,
29 float maximumScreenSpaceError,
30 bool showBoundingBoxes,
31 double zValueScale,
32 double zValueOffset,
33 int pointBudget )
34 : Qgs3DMapSceneEntity( nullptr )
35 , mLayer( layer )
36 , mMap( map )
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( mMap.extent(), mMap.crs(), layer->crs(), mMap.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(), mMap.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( si.index(),
89 mMap,
90 mCoordinateTransform,
91 static_cast< QgsPointCloud3DSymbol * >( mSymbol->clone() ),
92 mMaximumScreenSpaceError,
93 mShowBoundingBoxes,
94 mZValueScale,
95 mZValueOffset,
96 mPointBudget );
97
98 mChunkedEntitiesMap.insert( i, newChunkedEntity );
99 newChunkedEntity->setParent( this );
100 connect( newChunkedEntity, &QgsChunkedEntity::pendingJobsCountChanged, this, &Qgs3DMapSceneEntity::pendingJobsCountChanged );
101 emit newEntityCreated( newChunkedEntity );
102}
103
104void QgsVirtualPointCloudEntity::handleSceneUpdate( const SceneContext &sceneContext )
105{
106 const QVector<QgsPointCloudSubIndex> subIndexes = provider()->subIndexes();
107 for ( int i = 0; i < subIndexes.size(); ++i )
108 {
109 const QgsAABB &bbox = mBboxes.at( i );
110
111 if ( bbox.isEmpty() )
112 continue;
113
114 // magic number 256 is the common span value for a COPC root node
115 constexpr int SPAN = 256;
116 const float epsilon = std::min( bbox.xExtent(), bbox.yExtent() ) / SPAN;
117 const float distance = bbox.distanceFromPoint( sceneContext.cameraPos );
118 const float sse = Qgs3DUtils::screenSpaceError( epsilon, distance, sceneContext.screenSizePx, sceneContext.cameraFov );
119 constexpr float THRESHOLD = .2;
120
121 // always display as bbox for the initial temporary camera pos (0, 0, 0)
122 // then once the camera changes we display as bbox depending on screen space error
123 const bool displayAsBbox = sceneContext.cameraPos.isNull() || sse < THRESHOLD;
124 if ( !displayAsBbox && !subIndexes.at( i ).index() )
125 emit subIndexNeedsLoading( i );
126
127 setRenderSubIndexAsBbox( i, displayAsBbox );
128 if ( !displayAsBbox && mChunkedEntitiesMap.contains( i ) )
129 mChunkedEntitiesMap[i]->handleSceneUpdate( sceneContext );
130 }
131 updateBboxEntity();
132}
133
134QgsRange<float> QgsVirtualPointCloudEntity::getNearFarPlaneRange( const QMatrix4x4 &viewMatrix ) const
135{
136 float fnear = 1e9;
137 float ffar = 0;
138
139 for ( QgsChunkedEntity *entity : mChunkedEntitiesMap )
140 {
141 if ( entity->isEnabled() )
142 {
143 const QgsRange<float> range = entity->getNearFarPlaneRange( viewMatrix );
144 ffar = std::max( range.upper(), ffar );
145 fnear = std::min( range.lower(), fnear );
146 }
147 }
148
149 // if there were no chunked entities available, we will iterate the bboxes as a fallback instead
150 if ( fnear == 1e9 && ffar == 0 )
151 {
152 for ( const QgsAABB &bbox : mBboxes )
153 {
154 float bboxfnear;
155 float bboxffar;
156 Qgs3DUtils::computeBoundingBoxNearFarPlanes( bbox, viewMatrix, bboxfnear, bboxffar );
157 fnear = std::min( fnear, bboxfnear );
158 ffar = std::max( ffar, bboxffar );
159 }
160 }
161
162 return QgsRange<float>( fnear, ffar );
163}
164
165int QgsVirtualPointCloudEntity::pendingJobsCount() const
166{
167 int jobs = 0;
168 for ( QgsChunkedEntity *entity : mChunkedEntitiesMap )
169 {
170 if ( entity->isEnabled() )
171 jobs += entity->pendingJobsCount();
172 }
173 return jobs;
174}
175
176bool QgsVirtualPointCloudEntity::needsUpdate() const
177{
178 for ( QgsChunkedEntity *entity : mChunkedEntitiesMap )
179 {
180 if ( entity->isEnabled() && entity->needsUpdate() )
181 return true;
182 }
183 return false;
184}
185
186void QgsVirtualPointCloudEntity::updateBboxEntity()
187{
188 QList<QgsAABB> bboxes;
189 const QVector<QgsPointCloudSubIndex> subIndexes = provider()->subIndexes();
190 for ( int i = 0; i < subIndexes.size(); ++i )
191 {
192 if ( mChunkedEntitiesMap.contains( i ) && mChunkedEntitiesMap[i]->isEnabled() )
193 continue;
194
195 if ( mBboxes.at( i ).isEmpty() )
196 continue;
197
198 bboxes << mBboxes.at( i );
199 }
200
201 mBboxesEntity->setBoxes( bboxes );
202}
203
204void QgsVirtualPointCloudEntity::setRenderSubIndexAsBbox( int i, bool asBbox )
205{
206 if ( !mChunkedEntitiesMap.contains( i ) )
207 return;
208
209 mChunkedEntitiesMap[i]->setEnabled( !asBbox );
210}
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.
Definition: qgs3dutils.cpp:627
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...
Definition: qgs3dutils.cpp:849
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.
Definition: qgs3dutils.cpp:594
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 ...
Definition: qgs3dutils.cpp:875
3
Definition: qgsaabb.h:33
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:81
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.
Definition: qgsrectangle.h:42
QgsRectangle intersect(const QgsRectangle &rect) const
Returns the intersection with the given rectangle.
Definition: qgsrectangle.h:355