QGIS API Documentation 3.32.0-Lima (311a8cb8a6)
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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( provider(), &QgsVirtualPointCloudProvider::subIndexLoaded, this, &QgsVirtualPointCloudEntity::createChunkedEntityForSubIndex );
60}
61
62QList<QgsChunkedEntity *> QgsVirtualPointCloudEntity::chunkedEntities() const
63{
64 return mChunkedEntitiesMap.values();
65}
66
67QgsVirtualPointCloudProvider *QgsVirtualPointCloudEntity::provider() const
68{
69 return qobject_cast<QgsVirtualPointCloudProvider *>( mLayer->dataProvider() );
70}
71
72QgsAABB QgsVirtualPointCloudEntity::boundingBox( int i ) const
73{
74 return mBboxes.at( i );
75}
76
77void QgsVirtualPointCloudEntity::createChunkedEntityForSubIndex( int i )
78{
79 const QVector<QgsPointCloudSubIndex> subIndexes = provider()->subIndexes();
80 const QgsPointCloudSubIndex &si = subIndexes.at( i );
81
82 // Skip if Index is not yet loaded or is outside the map extents
83 if ( !si.index() || mBboxes.at( i ).isEmpty() )
84 return;
85
86 QgsPointCloudLayerChunkedEntity *newChunkedEntity = new QgsPointCloudLayerChunkedEntity( si.index(),
87 mMap,
88 mCoordinateTransform,
89 static_cast< QgsPointCloud3DSymbol * >( mSymbol->clone() ),
90 mMaximumScreenSpaceError,
91 mShowBoundingBoxes,
92 mZValueScale,
93 mZValueOffset,
94 mPointBudget );
95
96 mChunkedEntitiesMap.insert( i, newChunkedEntity );
97 newChunkedEntity->setParent( this );
98 connect( newChunkedEntity, &QgsChunkedEntity::pendingJobsCountChanged, this, &Qgs3DMapSceneEntity::pendingJobsCountChanged );
99 emit newEntityCreated( newChunkedEntity );
100}
101
102void QgsVirtualPointCloudEntity::handleSceneUpdate( const SceneState &state )
103{
104 const QVector<QgsPointCloudSubIndex> subIndexes = provider()->subIndexes();
105 for ( int i = 0; i < subIndexes.size(); ++i )
106 {
107 const QgsAABB &bbox = mBboxes.at( i );
108
109 if ( bbox.isEmpty() )
110 continue;
111
112 // magic number 256 is the common span value for a COPC root node
113 constexpr int SPAN = 256;
114 const float epsilon = std::min( bbox.xExtent(), bbox.yExtent() ) / SPAN;
115 const float distance = bbox.distanceFromPoint( state.cameraPos );
116 const float sse = Qgs3DUtils::screenSpaceError( epsilon, distance, state.screenSizePx, state.cameraFov );
117 constexpr float THRESHOLD = .2;
118 const bool displayAsBbox = sse < THRESHOLD;
119 if ( !displayAsBbox && !subIndexes.at( i ).index() )
120 provider()->loadSubIndex( i );
121
122 setRenderSubIndexAsBbox( i, displayAsBbox );
123 if ( !displayAsBbox && mChunkedEntitiesMap.contains( i ) )
124 mChunkedEntitiesMap[i]->handleSceneUpdate( state );
125 }
126 updateBboxEntity();
127}
128
129QgsRange<float> QgsVirtualPointCloudEntity::getNearFarPlaneRange( const QMatrix4x4 &viewMatrix ) const
130{
131 float fnear = 1e9;
132 float ffar = 0;
133
134 for ( QgsChunkedEntity *entity : mChunkedEntitiesMap )
135 {
136 if ( entity->isEnabled() )
137 {
138 const QgsRange<float> range = entity->getNearFarPlaneRange( viewMatrix );
139 ffar = std::max( range.upper(), ffar );
140 fnear = std::min( range.lower(), fnear );
141 }
142 }
143
144 // if there were no chunked entities available, we will iterate the bboxes as a fallback instead
145 if ( fnear == 1e9 && ffar == 0 )
146 {
147 for ( const QgsAABB &bbox : mBboxes )
148 {
149 for ( int i = 0; i < 8; ++i )
150 {
151 const QVector4D p( ( ( i >> 0 ) & 1 ) ? bbox.xMin : bbox.xMax,
152 ( ( i >> 1 ) & 1 ) ? bbox.yMin : bbox.yMax,
153 ( ( i >> 2 ) & 1 ) ? bbox.zMin : bbox.zMax, 1 );
154
155 const QVector4D pc = viewMatrix * p;
156
157 const float dst = -pc.z(); // in camera coordinates, x grows right, y grows down, z grows to the back
158 fnear = std::min( fnear, dst );
159 ffar = std::max( ffar, dst );
160 }
161 }
162 }
163
164 return QgsRange<float>( fnear, ffar );
165}
166
167int QgsVirtualPointCloudEntity::pendingJobsCount() const
168{
169 int jobs = 0;
170 for ( QgsChunkedEntity *entity : mChunkedEntitiesMap )
171 {
172 if ( entity->isEnabled() )
173 jobs += entity->pendingJobsCount();
174 }
175 return jobs;
176}
177
178bool QgsVirtualPointCloudEntity::needsUpdate() const
179{
180 for ( QgsChunkedEntity *entity : mChunkedEntitiesMap )
181 {
182 if ( entity->isEnabled() && entity->needsUpdate() )
183 return true;
184 }
185 return false;
186}
187
188void QgsVirtualPointCloudEntity::updateBboxEntity()
189{
190 QList<QgsAABB> bboxes;
191 const QVector<QgsPointCloudSubIndex> subIndexes = provider()->subIndexes();
192 for ( int i = 0; i < subIndexes.size(); ++i )
193 {
194 if ( mChunkedEntitiesMap.contains( i ) && mChunkedEntitiesMap[i]->isEnabled() )
195 continue;
196
197 if ( mBboxes.at( i ).isEmpty() )
198 continue;
199
200 bboxes << mBboxes.at( i );
201 }
202
203 mBboxesEntity->setBoxes( bboxes );
204}
205
206void QgsVirtualPointCloudEntity::setRenderSubIndexAsBbox( int i, bool asBbox )
207{
208 if ( !mChunkedEntitiesMap.contains( i ) )
209 return;
210
211 mChunkedEntitiesMap[i]->setEnabled( !asBbox );
212}
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:602
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:569
static float screenSpaceError(float epsilon, float distance, float screenSize, float fov)
This routine approximately calculates how an error (epsilon) of an object in world coordinates at giv...
Definition: qgs3dutils.cpp:824
3
Definition: qgsaabb.h:34
float yMax
Definition: qgsaabb.h:88
float yExtent() const
Returns box width in Y axis.
Definition: qgsaabb.h:45
float xMax
Definition: qgsaabb.h:87
float xExtent() const
Returns box width in X axis.
Definition: qgsaabb.h:43
float xMin
Definition: qgsaabb.h:84
float zMax
Definition: qgsaabb.h:89
float yMin
Definition: qgsaabb.h:85
bool isEmpty() const
Returns true if any of xExtent(), yExtent() or zExtent() is zero, false otherwise.
Definition: qgsaabb.h:82
float distanceFromPoint(float x, float y, float z) const
Returns shortest distance from the box to a point.
Definition: qgsaabb.cpp:50
float zMin
Definition: qgsaabb.h:86
Class for doing transforms between two map coordinate systems.
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:79
Represents a map layer supporting display of point clouds.
A template based class for storing ranges (lower to upper values).
Definition: qgsrange.h:47
T lower() const
Returns the lower bound of the range.
Definition: qgsrange.h:66
T upper() const
Returns the upper bound of the range.
Definition: qgsrange.h:73
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:333