QGIS API Documentation 3.39.0-Master (47f7b3a4989)
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( 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.
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:82
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.