QGIS API Documentation 3.41.0-Master (cea29feecf2)
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 mBboxesEntity = new QgsChunkBoundsEntity( this );
48 const QgsRectangle mapExtent = Qgs3DUtils::tryReprojectExtent2D( map->extent(), map->crs(), layer->crs(), map->transformContext() );
49 const QVector<QgsPointCloudSubIndex> subIndexes = provider()->subIndexes();
50 for ( int i = 0; i < subIndexes.size(); ++i )
51 {
52 const QgsPointCloudSubIndex &si = subIndexes.at( i );
53 const QgsRectangle intersection = si.extent().intersect( mapExtent );
54
55 mBboxes << Qgs3DUtils::mapToWorldExtent( intersection, si.zRange().lower(), si.zRange().upper(), map->origin() );
56
57 createChunkedEntityForSubIndex( i );
58 }
59
60 updateBboxEntity();
61 connect( this, &QgsVirtualPointCloudEntity::subIndexNeedsLoading, provider(), &QgsVirtualPointCloudProvider::loadSubIndex, Qt::QueuedConnection );
62 connect( provider(), &QgsVirtualPointCloudProvider::subIndexLoaded, this, &QgsVirtualPointCloudEntity::createChunkedEntityForSubIndex );
63}
64
65QList<QgsChunkedEntity *> QgsVirtualPointCloudEntity::chunkedEntities() const
66{
67 return mChunkedEntitiesMap.values();
68}
69
70QgsVirtualPointCloudProvider *QgsVirtualPointCloudEntity::provider() const
71{
72 return qobject_cast<QgsVirtualPointCloudProvider *>( mLayer->dataProvider() );
73}
74
75QgsAABB QgsVirtualPointCloudEntity::boundingBox( int i ) const
76{
77 return mBboxes.at( i );
78}
79
80void QgsVirtualPointCloudEntity::createChunkedEntityForSubIndex( int i )
81{
82 const QVector<QgsPointCloudSubIndex> subIndexes = provider()->subIndexes();
83 const QgsPointCloudSubIndex &si = subIndexes.at( i );
84
85 // Skip if Index is not yet loaded or is outside the map extents
86 if ( !si.index() || mBboxes.at( i ).isEmpty() )
87 return;
88
89 QgsPointCloudLayerChunkedEntity *newChunkedEntity = new QgsPointCloudLayerChunkedEntity(
90 mapSettings(),
91 si.index(),
92 mCoordinateTransform,
93 static_cast<QgsPointCloud3DSymbol *>( mSymbol->clone() ),
94 mMaximumScreenSpaceError,
95 mShowBoundingBoxes,
96 mZValueScale,
97 mZValueOffset,
98 mPointBudget
99 );
100
101 mChunkedEntitiesMap.insert( i, newChunkedEntity );
102 newChunkedEntity->setParent( this );
103 connect( newChunkedEntity, &QgsChunkedEntity::pendingJobsCountChanged, this, &Qgs3DMapSceneEntity::pendingJobsCountChanged );
104 emit newEntityCreated( newChunkedEntity );
105}
106
107void QgsVirtualPointCloudEntity::handleSceneUpdate( const SceneContext &sceneContext )
108{
109 const QVector<QgsPointCloudSubIndex> subIndexes = provider()->subIndexes();
110 for ( int i = 0; i < subIndexes.size(); ++i )
111 {
112 const QgsAABB &bbox = mBboxes.at( i );
113
114 if ( bbox.isEmpty() )
115 continue;
116
117 // magic number 256 is the common span value for a COPC root node
118 constexpr int SPAN = 256;
119 const float epsilon = std::min( bbox.xExtent(), bbox.yExtent() ) / SPAN;
120 const float distance = bbox.distanceFromPoint( sceneContext.cameraPos );
121 const float sse = Qgs3DUtils::screenSpaceError( epsilon, distance, sceneContext.screenSizePx, sceneContext.cameraFov );
122 constexpr float THRESHOLD = .2;
123
124 // always display as bbox for the initial temporary camera pos (0, 0, 0)
125 // then once the camera changes we display as bbox depending on screen space error
126 const bool displayAsBbox = sceneContext.cameraPos.isNull() || sse < THRESHOLD;
127 if ( !displayAsBbox && !subIndexes.at( i ).index() )
128 emit subIndexNeedsLoading( i );
129
130 setRenderSubIndexAsBbox( i, displayAsBbox );
131 if ( !displayAsBbox && mChunkedEntitiesMap.contains( i ) )
132 mChunkedEntitiesMap[i]->handleSceneUpdate( sceneContext );
133 }
134 updateBboxEntity();
135}
136
137QgsRange<float> QgsVirtualPointCloudEntity::getNearFarPlaneRange( const QMatrix4x4 &viewMatrix ) const
138{
139 float fnear = 1e9;
140 float ffar = 0;
141
142 for ( QgsChunkedEntity *entity : mChunkedEntitiesMap )
143 {
144 if ( entity->isEnabled() )
145 {
146 const QgsRange<float> range = entity->getNearFarPlaneRange( viewMatrix );
147 ffar = std::max( range.upper(), ffar );
148 fnear = std::min( range.lower(), fnear );
149 }
150 }
151
152 // if there were no chunked entities available, we will iterate the bboxes as a fallback instead
153 if ( fnear == 1e9 && ffar == 0 )
154 {
155 for ( const QgsAABB &bbox : mBboxes )
156 {
157 float bboxfnear;
158 float bboxffar;
159 Qgs3DUtils::computeBoundingBoxNearFarPlanes( bbox, viewMatrix, bboxfnear, bboxffar );
160 fnear = std::min( fnear, bboxfnear );
161 ffar = std::max( ffar, bboxffar );
162 }
163 }
164
165 return QgsRange<float>( fnear, ffar );
166}
167
168int QgsVirtualPointCloudEntity::pendingJobsCount() const
169{
170 int jobs = 0;
171 for ( QgsChunkedEntity *entity : mChunkedEntitiesMap )
172 {
173 if ( entity->isEnabled() )
174 jobs += entity->pendingJobsCount();
175 }
176 return jobs;
177}
178
179bool QgsVirtualPointCloudEntity::needsUpdate() const
180{
181 for ( QgsChunkedEntity *entity : mChunkedEntitiesMap )
182 {
183 if ( entity->isEnabled() && entity->needsUpdate() )
184 return true;
185 }
186 return false;
187}
188
189void QgsVirtualPointCloudEntity::updateBboxEntity()
190{
191 QList<QgsAABB> bboxes;
192 const QVector<QgsPointCloudSubIndex> subIndexes = provider()->subIndexes();
193 for ( int i = 0; i < subIndexes.size(); ++i )
194 {
195 if ( mChunkedEntitiesMap.contains( i ) && mChunkedEntitiesMap[i]->isEnabled() )
196 continue;
197
198 if ( mBboxes.at( i ).isEmpty() )
199 continue;
200
201 bboxes << mBboxes.at( i );
202 }
203
204 mBboxesEntity->setBoxes( bboxes );
205}
206
207void QgsVirtualPointCloudEntity::setRenderSubIndexAsBbox( int i, bool asBbox )
208{
209 if ( !mChunkedEntitiesMap.contains( i ) )
210 return;
211
212 mChunkedEntitiesMap[i]->setEnabled( !asBbox );
213}
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:46
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.