QGIS API Documentation 3.30.0-'s-Hertogenbosch (f186b8efe0)
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 "qgspointcloudindex.h"
23#include "qgseventtracing.h"
24
25
29
30#include <QtConcurrent>
31#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
32#include <Qt3DRender/QAttribute>
33#else
34#include <Qt3DCore/QAttribute>
35#endif
36#include <Qt3DRender/QTechnique>
37#include <Qt3DRender/QShaderProgram>
38#include <Qt3DRender/QGraphicsApiFilter>
39#include <QPointSize>
40
42
43
45
46QgsPointCloudLayerChunkLoader::QgsPointCloudLayerChunkLoader( const QgsPointCloudLayerChunkLoaderFactory *factory, QgsChunkNode *node, std::unique_ptr< QgsPointCloud3DSymbol > symbol,
47 const QgsCoordinateTransform &coordinateTransform, double zValueScale, double zValueOffset )
48 : QgsChunkLoader( node )
49 , mFactory( factory )
50 , mContext( factory->mMap, coordinateTransform, std::move( symbol ), zValueScale, zValueOffset )
51{
52
53 QgsPointCloudIndex *pc = mFactory->mPointCloudIndex;
54 mContext.setAttributes( pc->attributes() );
55
56 const QgsChunkNodeId nodeId = node->tileId();
57 const IndexedPointCloudNode pcNode( nodeId.d, nodeId.x, nodeId.y, nodeId.z );
58
59 Q_ASSERT( pc->hasNode( pcNode ) );
60
61 QgsDebugMsgLevel( QStringLiteral( "loading entity %1" ).arg( node->tileId().text() ), 2 );
62
63 if ( mContext.symbol()->symbolType() == QLatin1String( "single-color" ) )
64 mHandler.reset( new QgsSingleColorPointCloud3DSymbolHandler() );
65 else if ( mContext.symbol()->symbolType() == QLatin1String( "color-ramp" ) )
66 mHandler.reset( new QgsColorRampPointCloud3DSymbolHandler() );
67 else if ( mContext.symbol()->symbolType() == QLatin1String( "rgb" ) )
68 mHandler.reset( new QgsRGBPointCloud3DSymbolHandler() );
69 else if ( mContext.symbol()->symbolType() == QLatin1String( "classification" ) )
70 {
71 mHandler.reset( new QgsClassificationPointCloud3DSymbolHandler() );
72 const QgsClassificationPointCloud3DSymbol *classificationSymbol = dynamic_cast<const QgsClassificationPointCloud3DSymbol *>( mContext.symbol() );
73 mContext.setFilteredOutCategories( classificationSymbol->getFilteredOutCategories() );
74 }
75
76 //
77 // this will be run in a background thread
78 //
79 mFutureWatcher = new QFutureWatcher<void>( this );
80 connect( mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsChunkQueueJob::finished );
81
82 const QgsAABB bbox = node->bbox();
83 const QFuture<void> future = QtConcurrent::run( [pc, pcNode, bbox, this]
84 {
85 const QgsEventTracing::ScopedEvent e( QStringLiteral( "3D" ), QStringLiteral( "PC chunk load" ) );
86
87 if ( mContext.isCanceled() )
88 {
89 QgsDebugMsgLevel( QStringLiteral( "canceled" ), 2 );
90 return;
91 }
92
93 mHandler->processNode( pc, pcNode, mContext );
94 if ( mContext.symbol()->renderAsTriangles() )
95 mHandler->triangulate( pc, pcNode, mContext, bbox );
96 } );
97
98 // emit finished() as soon as the handler is populated with features
99 mFutureWatcher->setFuture( future );
100}
101
102QgsPointCloudLayerChunkLoader::~QgsPointCloudLayerChunkLoader()
103{
104 if ( mFutureWatcher && !mFutureWatcher->isFinished() )
105 {
106 disconnect( mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsChunkQueueJob::finished );
107 mContext.cancelRendering();
108 mFutureWatcher->waitForFinished();
109 }
110}
111
112void QgsPointCloudLayerChunkLoader::cancel()
113{
114 mContext.cancelRendering();
115}
116
117Qt3DCore::QEntity *QgsPointCloudLayerChunkLoader::createEntity( Qt3DCore::QEntity *parent )
118{
119 QgsPointCloudIndex *pc = mFactory->mPointCloudIndex;
120 const QgsChunkNodeId nodeId = mNode->tileId();
121 const IndexedPointCloudNode pcNode( nodeId.d, nodeId.x, nodeId.y, nodeId.z );
122 Q_ASSERT( pc->hasNode( pcNode ) );
123
124 Qt3DCore::QEntity *entity = new Qt3DCore::QEntity( parent );
125 mHandler->finalize( entity, mContext );
126 return entity;
127}
128
130
131
132QgsPointCloudLayerChunkLoaderFactory::QgsPointCloudLayerChunkLoaderFactory( const Qgs3DMapSettings &map, const QgsCoordinateTransform &coordinateTransform, QgsPointCloudIndex *pc, QgsPointCloud3DSymbol *symbol,
133 double zValueScale, double zValueOffset, int pointBudget )
134 : mMap( map )
135 , mCoordinateTransform( coordinateTransform )
136 , mPointCloudIndex( pc )
137 , mZValueScale( zValueScale )
138 , mZValueOffset( zValueOffset )
139 , mPointBudget( pointBudget )
140{
141 mSymbol.reset( symbol );
142
143 try
144 {
145 mExtent = mCoordinateTransform.transformBoundingBox( mMap.extent(), Qgis::TransformDirection::Reverse );
146 }
147 catch ( const QgsCsException & )
148 {
149 // bad luck, can't reproject for some reason
150 QgsDebugMsg( QStringLiteral( "Transformation of extent failed." ) );
151 }
152}
153
154QgsChunkLoader *QgsPointCloudLayerChunkLoaderFactory::createChunkLoader( QgsChunkNode *node ) const
155{
156 const QgsChunkNodeId id = node->tileId();
157
158 Q_ASSERT( mPointCloudIndex->hasNode( IndexedPointCloudNode( id.d, id.x, id.y, id.z ) ) );
159 QgsPointCloud3DSymbol *symbol = static_cast< QgsPointCloud3DSymbol * >( mSymbol->clone() );
160 return new QgsPointCloudLayerChunkLoader( this, node, std::unique_ptr< QgsPointCloud3DSymbol >( symbol ), mCoordinateTransform, mZValueScale, mZValueOffset );
161}
162
163int QgsPointCloudLayerChunkLoaderFactory::primitivesCount( QgsChunkNode *node ) const
164{
165 const QgsChunkNodeId id = node->tileId();
166 const IndexedPointCloudNode n( id.d, id.x, id.y, id.z );
167 Q_ASSERT( mPointCloudIndex->hasNode( n ) );
168 return mPointCloudIndex->nodePointCount( n );
169}
170
171QgsAABB nodeBoundsToAABB( QgsPointCloudDataBounds nodeBounds, QgsVector3D offset, QgsVector3D scale, const Qgs3DMapSettings &map, const QgsCoordinateTransform &coordinateTransform, double zValueOffset );
172
173QgsChunkNode *QgsPointCloudLayerChunkLoaderFactory::createRootNode() const
174{
175 const QgsAABB bbox = nodeBoundsToAABB( mPointCloudIndex->nodeBounds( IndexedPointCloudNode( 0, 0, 0, 0 ) ), mPointCloudIndex->offset(), mPointCloudIndex->scale(), mMap, mCoordinateTransform, mZValueOffset );
176 const float error = mPointCloudIndex->nodeError( IndexedPointCloudNode( 0, 0, 0, 0 ) );
177 return new QgsChunkNode( QgsChunkNodeId( 0, 0, 0, 0 ), bbox, error );
178}
179
180QVector<QgsChunkNode *> QgsPointCloudLayerChunkLoaderFactory::createChildren( QgsChunkNode *node ) const
181{
182 QVector<QgsChunkNode *> children;
183 const QgsChunkNodeId nodeId = node->tileId();
184 const QgsAABB bbox = node->bbox();
185 const float childError = node->error() / 2;
186 float xc = bbox.xCenter(), yc = bbox.yCenter(), zc = bbox.zCenter();
187
188 for ( int i = 0; i < 8; ++i )
189 {
190 int dx = i & 1, dy = !!( i & 2 ), dz = !!( i & 4 );
191 const QgsChunkNodeId childId( nodeId.d + 1, nodeId.x * 2 + dx, nodeId.y * 2 + dy, nodeId.z * 2 + dz );
192
193 if ( !mPointCloudIndex->hasNode( IndexedPointCloudNode( childId.d, childId.x, childId.y, childId.z ) ) )
194 continue;
195 if ( !mExtent.isEmpty() &&
196 !mPointCloudIndex->nodeMapExtent( IndexedPointCloudNode( childId.d, childId.x, childId.y, childId.z ) ).intersects( mExtent ) )
197 continue;
198
199 // the Y and Z coordinates below are intentionally flipped, because
200 // in chunk node IDs the X,Y axes define horizontal plane,
201 // while in our 3D scene the X,Z axes define the horizontal plane
202 const float chXMin = dx ? xc : bbox.xMin;
203 const float chXMax = dx ? bbox.xMax : xc;
204 // Z axis: values are increasing to the south
205 const float chZMin = !dy ? zc : bbox.zMin;
206 const float chZMax = !dy ? bbox.zMax : zc;
207 const float chYMin = dz ? yc : bbox.yMin;
208 const float chYMax = dz ? bbox.yMax : yc;
209 children << new QgsChunkNode( childId, QgsAABB( chXMin, chYMin, chZMin, chXMax, chYMax, chZMax ), childError, node );
210 }
211 return children;
212}
213
215
216
217QgsAABB nodeBoundsToAABB( QgsPointCloudDataBounds nodeBounds, QgsVector3D offset, QgsVector3D scale, const Qgs3DMapSettings &map, const QgsCoordinateTransform &coordinateTransform, double zValueOffset )
218{
219 QgsVector3D extentMin3D( nodeBounds.xMin() * scale.x() + offset.x(), nodeBounds.yMin() * scale.y() + offset.y(), nodeBounds.zMin() * scale.z() + offset.z() + zValueOffset );
220 QgsVector3D extentMax3D( nodeBounds.xMax() * scale.x() + offset.x(), nodeBounds.yMax() * scale.y() + offset.y(), nodeBounds.zMax() * scale.z() + offset.z() + zValueOffset );
221 QgsCoordinateTransform extentTransform = coordinateTransform;
222 extentTransform.setBallparkTransformsAreAppropriate( true );
223 try
224 {
225 extentMin3D = extentTransform.transform( extentMin3D );
226 extentMax3D = extentTransform.transform( extentMax3D );
227 }
228 catch ( QgsCsException & )
229 {
230 QgsDebugMsg( QStringLiteral( "Error transforming node bounds coordinate" ) );
231 }
232 const QgsVector3D worldExtentMin3D = Qgs3DUtils::mapToWorldCoordinates( extentMin3D, map.origin() );
233 const QgsVector3D worldExtentMax3D = Qgs3DUtils::mapToWorldCoordinates( extentMax3D, map.origin() );
234 QgsAABB rootBbox( worldExtentMin3D.x(), worldExtentMin3D.y(), worldExtentMin3D.z(),
235 worldExtentMax3D.x(), worldExtentMax3D.y(), worldExtentMax3D.z() );
236 return rootBbox;
237}
238
239
240QgsPointCloudLayerChunkedEntity::QgsPointCloudLayerChunkedEntity( QgsPointCloudIndex *pc, const Qgs3DMapSettings &map,
241 const QgsCoordinateTransform &coordinateTransform, QgsPointCloud3DSymbol *symbol,
242 float maximumScreenSpaceError, bool showBoundingBoxes,
243 double zValueScale, double zValueOffset,
244 int pointBudget )
245 : QgsChunkedEntity( maximumScreenSpaceError,
246 new QgsPointCloudLayerChunkLoaderFactory( map, coordinateTransform, pc, symbol, zValueScale, zValueOffset, pointBudget ), true, pointBudget )
247{
248 setUsingAdditiveStrategy( !symbol->renderAsTriangles() );
249 setShowBoundingBoxes( showBoundingBoxes );
250}
251
252QgsPointCloudLayerChunkedEntity::~QgsPointCloudLayerChunkedEntity()
253{
254 // cancel / wait for jobs
255 cancelActiveJobs();
256}
257
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:543
3
Definition: qgsaabb.h:34
float yMax
Definition: qgsaabb.h:88
float xMax
Definition: qgsaabb.h:87
float xCenter() const
Returns center in X axis.
Definition: qgsaabb.h:50
float xMin
Definition: qgsaabb.h:84
float zMax
Definition: qgsaabb.h:89
float yMin
Definition: qgsaabb.h:85
float yCenter() const
Returns center in Y axis.
Definition: qgsaabb.h:52
float zMin
Definition: qgsaabb.h:86
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
bool renderAsTriangles() const
Returns whether points are triangulated to render solid surface.
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