QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
qgspointcloudlayerchunkloader_p.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgspointcloudlayerchunkloader_p.cpp
3  --------------------------------------
4  Date : October 2020
5  Copyright : (C) 2020 by Peter Petrik
6  Email : zilolv dot sk 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 
18 #include "qgs3dutils.h"
20 #include "qgschunknode_p.h"
21 #include "qgslogger.h"
22 #include "qgspointcloudlayer.h"
23 #include "qgspointcloudindex.h"
24 #include "qgseventtracing.h"
25 
26 #include "qgspoint3dsymbol.h"
28 
29 #include "qgspointcloud3dsymbol.h"
30 #include "qgsapplication.h"
31 #include "qgs3dsymbolregistry.h"
32 #include "qgspointcloudattribute.h"
33 #include "qgspointcloudrequest.h"
34 #include "qgscolorramptexture.h"
36 
37 #include <QtConcurrent>
38 #include <Qt3DRender/QAttribute>
39 #include <Qt3DRender/QTechnique>
40 #include <Qt3DRender/QShaderProgram>
41 #include <Qt3DRender/QGraphicsApiFilter>
42 #include <QPointSize>
43 
45 
46 
48 
49 QgsPointCloudLayerChunkLoader::QgsPointCloudLayerChunkLoader( const QgsPointCloudLayerChunkLoaderFactory *factory, QgsChunkNode *node, std::unique_ptr< QgsPointCloud3DSymbol > symbol,
50  const QgsCoordinateTransform &coordinateTransform, double zValueScale, double zValueOffset )
51  : QgsChunkLoader( node )
52  , mFactory( factory )
53  , mContext( factory->mMap, coordinateTransform, std::move( symbol ), zValueScale, zValueOffset )
54 {
55 
56  QgsPointCloudIndex *pc = mFactory->mPointCloudIndex;
57  mContext.setAttributes( pc->attributes() );
58 
59  const QgsChunkNodeId nodeId = node->tileId();
60  const IndexedPointCloudNode pcNode( nodeId.d, nodeId.x, nodeId.y, nodeId.z );
61 
62  Q_ASSERT( pc->hasNode( pcNode ) );
63 
64  QgsDebugMsgLevel( QStringLiteral( "loading entity %1" ).arg( node->tileId().text() ), 2 );
65 
66  if ( mContext.symbol()->symbolType() == QLatin1String( "single-color" ) )
67  mHandler.reset( new QgsSingleColorPointCloud3DSymbolHandler() );
68  else if ( mContext.symbol()->symbolType() == QLatin1String( "color-ramp" ) )
69  mHandler.reset( new QgsColorRampPointCloud3DSymbolHandler() );
70  else if ( mContext.symbol()->symbolType() == QLatin1String( "rgb" ) )
71  mHandler.reset( new QgsRGBPointCloud3DSymbolHandler() );
72  else if ( mContext.symbol()->symbolType() == QLatin1String( "classification" ) )
73  {
74  mHandler.reset( new QgsClassificationPointCloud3DSymbolHandler() );
75  const QgsClassificationPointCloud3DSymbol *classificationSymbol = dynamic_cast<const QgsClassificationPointCloud3DSymbol *>( mContext.symbol() );
76  mContext.setFilteredOutCategories( classificationSymbol->getFilteredOutCategories() );
77  }
78 
79  //
80  // this will be run in a background thread
81  //
82  mFutureWatcher = new QFutureWatcher<void>( this );
83  connect( mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsChunkQueueJob::finished );
84 
85  const QFuture<void> future = QtConcurrent::run( [pc, pcNode, this]
86  {
87  const QgsEventTracing::ScopedEvent e( QStringLiteral( "3D" ), QStringLiteral( "PC chunk load" ) );
88 
89  if ( mContext.isCanceled() )
90  {
91  QgsDebugMsgLevel( QStringLiteral( "canceled" ), 2 );
92  return;
93  }
94  mHandler->processNode( pc, pcNode, mContext );
95  } );
96 
97  // emit finished() as soon as the handler is populated with features
98  mFutureWatcher->setFuture( future );
99 }
100 
101 QgsPointCloudLayerChunkLoader::~QgsPointCloudLayerChunkLoader()
102 {
103  if ( mFutureWatcher && !mFutureWatcher->isFinished() )
104  {
105  disconnect( mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsChunkQueueJob::finished );
106  mContext.cancelRendering();
107  mFutureWatcher->waitForFinished();
108  }
109 }
110 
111 void QgsPointCloudLayerChunkLoader::cancel()
112 {
113  mContext.cancelRendering();
114 }
115 
116 Qt3DCore::QEntity *QgsPointCloudLayerChunkLoader::createEntity( Qt3DCore::QEntity *parent )
117 {
118  QgsPointCloudIndex *pc = mFactory->mPointCloudIndex;
119  const QgsChunkNodeId nodeId = mNode->tileId();
120  const IndexedPointCloudNode pcNode( nodeId.d, nodeId.x, nodeId.y, nodeId.z );
121  Q_ASSERT( pc->hasNode( pcNode ) );
122 
123  Qt3DCore::QEntity *entity = new Qt3DCore::QEntity( parent );
124  mHandler->finalize( entity, mContext );
125  return entity;
126 }
127 
129 
130 
131 QgsPointCloudLayerChunkLoaderFactory::QgsPointCloudLayerChunkLoaderFactory( const Qgs3DMapSettings &map, const QgsCoordinateTransform &coordinateTransform, QgsPointCloudIndex *pc, QgsPointCloud3DSymbol *symbol,
132  double zValueScale, double zValueOffset, int pointBudget )
133  : mMap( map )
134  , mCoordinateTransform( coordinateTransform )
135  , mPointCloudIndex( pc )
136  , mZValueScale( zValueScale )
137  , mZValueOffset( zValueOffset )
138  , mPointBudget( pointBudget )
139 {
140  mSymbol.reset( symbol );
141 }
142 
143 QgsChunkLoader *QgsPointCloudLayerChunkLoaderFactory::createChunkLoader( QgsChunkNode *node ) const
144 {
145  const QgsChunkNodeId id = node->tileId();
146 
147  Q_ASSERT( mPointCloudIndex->hasNode( IndexedPointCloudNode( id.d, id.x, id.y, id.z ) ) );
148  QgsPointCloud3DSymbol *symbol = static_cast< QgsPointCloud3DSymbol * >( mSymbol->clone() );
149  return new QgsPointCloudLayerChunkLoader( this, node, std::unique_ptr< QgsPointCloud3DSymbol >( symbol ), mCoordinateTransform, mZValueScale, mZValueOffset );
150 }
151 
152 int QgsPointCloudLayerChunkLoaderFactory::primitivesCount( QgsChunkNode *node ) const
153 {
154  const QgsChunkNodeId id = node->tileId();
155  const IndexedPointCloudNode n( id.d, id.x, id.y, id.z );
156  Q_ASSERT( mPointCloudIndex->hasNode( n ) );
157  return mPointCloudIndex->nodePointCount( n );
158 }
159 
160 QgsAABB nodeBoundsToAABB( QgsPointCloudDataBounds nodeBounds, QgsVector3D offset, QgsVector3D scale, const Qgs3DMapSettings &map, const QgsCoordinateTransform &coordinateTransform, double zValueOffset );
161 
162 QgsChunkNode *QgsPointCloudLayerChunkLoaderFactory::createRootNode() const
163 {
164  const QgsAABB bbox = nodeBoundsToAABB( mPointCloudIndex->nodeBounds( IndexedPointCloudNode( 0, 0, 0, 0 ) ), mPointCloudIndex->offset(), mPointCloudIndex->scale(), mMap, mCoordinateTransform, mZValueOffset );
165  const float error = mPointCloudIndex->nodeError( IndexedPointCloudNode( 0, 0, 0, 0 ) );
166  return new QgsChunkNode( QgsChunkNodeId( 0, 0, 0, 0 ), bbox, error );
167 }
168 
169 QVector<QgsChunkNode *> QgsPointCloudLayerChunkLoaderFactory::createChildren( QgsChunkNode *node ) const
170 {
171  QVector<QgsChunkNode *> children;
172  const QgsChunkNodeId nodeId = node->tileId();
173  const QgsAABB bbox = node->bbox();
174  const float childError = node->error() / 2;
175  float xc = bbox.xCenter(), yc = bbox.yCenter(), zc = bbox.zCenter();
176 
177  for ( int i = 0; i < 8; ++i )
178  {
179  int dx = i & 1, dy = !!( i & 2 ), dz = !!( i & 4 );
180  const QgsChunkNodeId childId( nodeId.d + 1, nodeId.x * 2 + dx, nodeId.y * 2 + dy, nodeId.z * 2 + dz );
181 
182  if ( !mPointCloudIndex->hasNode( IndexedPointCloudNode( childId.d, childId.x, childId.y, childId.z ) ) )
183  continue;
184 
185  // the Y and Z coordinates below are intentionally flipped, because
186  // in chunk node IDs the X,Y axes define horizontal plane,
187  // while in our 3D scene the X,Z axes define the horizontal plane
188  const float chXMin = dx ? xc : bbox.xMin;
189  const float chXMax = dx ? bbox.xMax : xc;
190  // Z axis: values are increasing to the south
191  const float chZMin = !dy ? zc : bbox.zMin;
192  const float chZMax = !dy ? bbox.zMax : zc;
193  const float chYMin = dz ? yc : bbox.yMin;
194  const float chYMax = dz ? bbox.yMax : yc;
195  children << new QgsChunkNode( childId, QgsAABB( chXMin, chYMin, chZMin, chXMax, chYMax, chZMax ), childError, node );
196  }
197  return children;
198 }
199 
201 
202 
203 QgsAABB nodeBoundsToAABB( QgsPointCloudDataBounds nodeBounds, QgsVector3D offset, QgsVector3D scale, const Qgs3DMapSettings &map, const QgsCoordinateTransform &coordinateTransform, double zValueOffset )
204 {
205  QgsVector3D extentMin3D( nodeBounds.xMin() * scale.x() + offset.x(), nodeBounds.yMin() * scale.y() + offset.y(), nodeBounds.zMin() * scale.z() + offset.z() + zValueOffset );
206  QgsVector3D extentMax3D( nodeBounds.xMax() * scale.x() + offset.x(), nodeBounds.yMax() * scale.y() + offset.y(), nodeBounds.zMax() * scale.z() + offset.z() + zValueOffset );
207  QgsCoordinateTransform extentTransform = coordinateTransform;
208  extentTransform.setBallparkTransformsAreAppropriate( true );
209  try
210  {
211  extentMin3D = extentTransform.transform( extentMin3D );
212  extentMax3D = extentTransform.transform( extentMax3D );
213  }
214  catch ( QgsCsException & )
215  {
216  QgsDebugMsg( QStringLiteral( "Error transforming node bounds coordinate" ) );
217  }
218  const QgsVector3D worldExtentMin3D = Qgs3DUtils::mapToWorldCoordinates( extentMin3D, map.origin() );
219  const QgsVector3D worldExtentMax3D = Qgs3DUtils::mapToWorldCoordinates( extentMax3D, map.origin() );
220  QgsAABB rootBbox( worldExtentMin3D.x(), worldExtentMin3D.y(), worldExtentMin3D.z(),
221  worldExtentMax3D.x(), worldExtentMax3D.y(), worldExtentMax3D.z() );
222  return rootBbox;
223 }
224 
225 
226 QgsPointCloudLayerChunkedEntity::QgsPointCloudLayerChunkedEntity( QgsPointCloudIndex *pc, const Qgs3DMapSettings &map, const QgsCoordinateTransform &coordinateTransform, QgsPointCloud3DSymbol *symbol, float maximumScreenSpaceError, bool showBoundingBoxes, double zValueScale, double zValueOffset, int pointBudget )
227  : QgsChunkedEntity( maximumScreenSpaceError,
228  new QgsPointCloudLayerChunkLoaderFactory( map, coordinateTransform, pc, symbol, zValueScale, zValueOffset, pointBudget ), true, pointBudget )
229 {
230  setUsingAdditiveStrategy( true );
231  setShowBoundingBoxes( showBoundingBoxes );
232 }
233 
234 QgsPointCloudLayerChunkedEntity::~QgsPointCloudLayerChunkedEntity()
235 {
236  // cancel / wait for jobs
237  cancelActiveJobs();
238 }
239 
Represents a indexed point cloud node in octree.
QgsVector3D origin() const
Returns coordinates in map CRS at which 3D scene has origin (0,0,0)
static QgsVector3D mapToWorldCoordinates(const QgsVector3D &mapCoords, const QgsVector3D &origin)
Converts map coordinates to 3D world coordinates (applies offset and turns (x,y,z) into (x,...
Definition: qgs3dutils.cpp:434
3
Definition: qgsaabb.h:34
float yMax
Definition: qgsaabb.h:85
float xMax
Definition: qgsaabb.h:84
float xCenter() const
Returns center in X axis.
Definition: qgsaabb.h:50
float xMin
Definition: qgsaabb.h:81
float zMax
Definition: qgsaabb.h:86
float yMin
Definition: qgsaabb.h:82
float yCenter() const
Returns center in Y axis.
Definition: qgsaabb.h:52
float zMin
Definition: qgsaabb.h:83
float zCenter() const
Returns center in Z axis.
Definition: qgsaabb.h:54
QgsPointCloudCategoryList getFilteredOutCategories() const
Gets the list of categories of the classification that should not be rendered.
Class for doing transforms between two map coordinate systems.
void setBallparkTransformsAreAppropriate(bool appropriate)
Sets whether approximate "ballpark" results are appropriate for this coordinate transform.
QgsPointXY transform(const QgsPointXY &point, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const SIP_THROW(QgsCsException)
Transform the point from the source CRS to the destination CRS.
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:66
Represents packaged data bounds.
qint32 xMax() const
Returns x max.
qint32 xMin() const
Returns x min.
qint32 yMax() const
Returns y max.
qint32 zMax() const
Returns z max.
qint32 yMin() const
Returns y min.
qint32 zMin() const
Returns z min.
Represents a indexed point clouds data in octree.
virtual bool hasNode(const IndexedPointCloudNode &n) const
Returns whether the octree contain given node.
void setAttributes(const QgsPointCloudAttributeCollection &attributes)
Sets native attributes of the data.
QgsPointCloudAttributeCollection attributes() const
Returns all attributes that are stored in the file.
double y() const
Returns Y coordinate.
Definition: qgsvector3d.h:51
double z() const
Returns Z coordinate.
Definition: qgsvector3d.h:53
double x() const
Returns X coordinate.
Definition: qgsvector3d.h:49
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38