QGIS API Documentation 4.1.0-Master (01362494303)
Loading...
Searching...
No Matches
qgstiledscenechunkloader_p.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgstiledscenechunkloader_p.cpp
3 --------------------------------------
4 Date : July 2023
5 Copyright : (C) 2023 by Martin Dobias
6 Email : wonder 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 "qgs3dmapsettings.h"
19#include "qgs3dutils.h"
20#include "qgsapplication.h"
21#include "qgscesiumutils.h"
23#include "qgsgeotransform.h"
24#include "qgsgltf3dutils.h"
25#include "qgsgltfutils.h"
27#include "qgsray3d.h"
28#include "qgsraycastcontext.h"
29#include "qgsraycastingutils.h"
31#include "qgstiledscenetile.h"
32
33#include <QString>
34#include <Qt3DRender/QGeometryRenderer>
35#include <QtConcurrentRun>
36
37#include "moc_qgstiledscenechunkloader_p.cpp"
38
39using namespace Qt::StringLiterals;
40
42
43size_t qHash( const QgsChunkNodeId &n )
44{
45 return n.uniqueId;
46}
47
48static bool hasLargeBounds( const QgsTiledSceneTile &t, const QgsCoordinateTransform &boundsTransform )
49{
50 if ( t.geometricError() > 1e6 )
51 return true;
52
53 if ( t.boundingVolume().box().isNull() )
54 return true;
55
56 Q_ASSERT( boundsTransform.destinationCrs().mapUnits() == Qgis::DistanceUnit::Meters );
57 QgsBox3D bounds = t.boundingVolume().bounds( boundsTransform );
58 return bounds.width() > 1e5 || bounds.height() > 1e5 || bounds.depth() > 1e5;
59}
60
62
63QgsTiledSceneChunkLoader::QgsTiledSceneChunkLoader( QgsChunkNode *node, const QgsTiledSceneIndex &index, const QgsTiledSceneChunkLoaderFactory &factory, double zValueScale, double zValueOffset )
64 : QgsChunkLoader( node )
65 , mFactory( factory )
66 , mZValueScale( zValueScale )
67 , mZValueOffset( zValueOffset )
68 , mIndex( index )
69{}
70
71void QgsTiledSceneChunkLoader::start()
72{
73 QgsChunkNode *node = chunk();
74
75 mFutureWatcher = new QFutureWatcher<void>( this );
76 connect( mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsChunkQueueJob::finished );
77
78 const QgsCoordinateTransform &boundsTransform = mFactory.mBoundsTransform;
79
80 const QgsChunkNodeId tileId = node->tileId();
81 const QgsVector3D chunkOrigin = node->box3D().center();
82 const bool isGlobe = mFactory.mRenderContext.crs().type() == Qgis::CrsType::Geocentric;
83 const QFuture<void> future = QtConcurrent::run( [this, tileId, boundsTransform, chunkOrigin, isGlobe] {
84 const QgsTiledSceneTile tile = mIndex.getTile( tileId.uniqueId );
85
86 // we do not load tiles that are too big when not in globe scene mode...
87 // the problem is that their 3D bounding boxes with ECEF coordinates are huge
88 // and we are unable to turn them into planar bounding boxes
89 if ( !isGlobe && hasLargeBounds( tile, boundsTransform ) )
90 return;
91
92 QString uri = tile.resources().value( u"content"_s ).toString();
93 if ( uri.isEmpty() )
94 {
95 // nothing to show for this tile
96 // TODO: can we skip loading it at all?
97 return;
98 }
99
100 uri = tile.baseUrl().resolved( uri ).toString();
101 QByteArray content = mFactory.mIndex.retrieveContent( uri );
102 if ( content.isEmpty() )
103 {
104 // the request probably failed
105 // TODO: how can we report it?
106 return;
107 }
108
109 QgsGltf3DUtils::EntityTransform entityTransform;
110 entityTransform.tileTransform = ( tile.transform() ? *tile.transform() : QgsMatrix4x4() );
111 entityTransform.chunkOriginTargetCrs = chunkOrigin;
112 entityTransform.ecefToTargetCrs = &mFactory.mBoundsTransform;
113 entityTransform.zValueScale = mZValueScale;
114 entityTransform.zValueOffset = mZValueOffset;
115 entityTransform.gltfUpAxis = static_cast<Qgis::Axis>( tile.metadata().value( u"gltfUpAxis"_s, static_cast<int>( Qgis::Axis::Y ) ).toInt() );
116
117 const QString &format = tile.metadata().value( u"contentFormat"_s ).value<QString>();
118 QStringList errors;
119 if ( format == "quantizedmesh"_L1 )
120 {
121 try
122 {
123 QgsQuantizedMeshTile qmTile( content );
124 qmTile.removeDegenerateTriangles();
125 tinygltf::Model model = qmTile.toGltf( true, 100 );
126 mEntity = QgsGltf3DUtils::parsedGltfToEntity( model, entityTransform, uri, &errors );
127 }
129 {
130 errors.append( u"Failed to parse tile from '%1'"_s.arg( uri ) );
131 }
132 }
133 else if ( format == "cesiumtiles"_L1 )
134 {
135 const QVector<QgsCesiumUtils::TileContents> tileContents = QgsCesiumUtils::extractTileContent( content );
136 if ( tileContents.isEmpty() )
137 return;
138
139 if ( tileContents.size() == 1 )
140 {
141 if ( tileContents[0].gltf.isEmpty() )
142 return;
143 entityTransform.tileTransform.translate( tileContents[0].rtcCenter );
144 mEntity = QgsGltf3DUtils::gltfToEntity( tileContents[0].gltf, entityTransform, uri, &errors );
145 }
146 else
147 {
148 mEntity = new Qt3DCore::QEntity;
149 for ( const QgsCesiumUtils::TileContents &innerContent : tileContents )
150 {
151 if ( innerContent.gltf.isEmpty() )
152 continue;
153
154 QgsGltf3DUtils::EntityTransform innerTransform = entityTransform;
155 innerTransform.tileTransform.translate( innerContent.rtcCenter );
156 Qt3DCore::QEntity *childEntity = QgsGltf3DUtils::gltfToEntity( innerContent.gltf, innerTransform, uri, &errors );
157 if ( childEntity )
158 childEntity->setParent( mEntity );
159 }
160 }
161 }
162 else if ( format == "draco"_L1 )
163 {
164 QgsGltfUtils::I3SNodeContext i3sContext;
165 i3sContext.initFromTile( tile, mFactory.mLayerCrs, mFactory.mBoundsTransform.sourceCrs(), mFactory.mRenderContext.transformContext() );
166
167 QString dracoLoadError;
168 tinygltf::Model model;
169 if ( !QgsGltfUtils::loadDracoModel( content, i3sContext, model, &dracoLoadError ) )
170 {
171 errors.append( dracoLoadError );
172 return;
173 }
174
175 mEntity = QgsGltf3DUtils::parsedGltfToEntity( model, entityTransform, QString(), &errors );
176 }
177 else
178 return; // unsupported tile content type
179
180 // TODO: report errors somewhere?
181 if ( !errors.isEmpty() )
182 {
183 QgsDebugError( "gltf load errors: " + errors.join( '\n' ) );
184 }
185
186 if ( mEntity )
187 {
188 QgsGeoTransform *transform = new QgsGeoTransform;
189 transform->setGeoTranslation( chunkOrigin );
190 mEntity->addComponent( transform );
191
192 mEntity->moveToThread( QgsApplication::instance()->thread() );
193 }
194 } );
195
196 // emit finished() as soon as the handler is populated with features
197 mFutureWatcher->setFuture( future );
198}
199
200QgsTiledSceneChunkLoader::~QgsTiledSceneChunkLoader()
201{
202 if ( !mFutureWatcher->isFinished() )
203 {
204 disconnect( mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsChunkQueueJob::finished );
205 mFutureWatcher->waitForFinished();
206 }
207}
208
209Qt3DCore::QEntity *QgsTiledSceneChunkLoader::createEntity( Qt3DCore::QEntity *parent )
210{
211 if ( mEntity )
212 mEntity->setParent( parent );
213 return mEntity;
214}
215
217
218QgsTiledSceneChunkLoaderFactory::QgsTiledSceneChunkLoaderFactory(
219 const Qgs3DRenderContext &context, const QgsTiledSceneIndex &index, QgsCoordinateReferenceSystem tileCrs, QgsCoordinateReferenceSystem layerCrs, double zValueScale, double zValueOffset
220)
221 : mRenderContext( context )
222 , mIndex( index )
223 , mZValueScale( zValueScale )
224 , mZValueOffset( zValueOffset )
225 , mLayerCrs( layerCrs )
226{
227 mBoundsTransform = QgsCoordinateTransform( tileCrs, context.crs(), context.transformContext() );
228}
229
230QgsChunkLoader *QgsTiledSceneChunkLoaderFactory::createChunkLoader( QgsChunkNode *node ) const
231{
232 return new QgsTiledSceneChunkLoader( node, mIndex, *this, mZValueScale, mZValueOffset );
233}
234
235QgsChunkNode *QgsTiledSceneChunkLoaderFactory::nodeForTile( const QgsTiledSceneTile &t, const QgsChunkNodeId &nodeId, QgsChunkNode *parent ) const
236{
237 QgsChunkNode *node = nullptr;
238 if ( mRenderContext.crs().type() != Qgis::CrsType::Geocentric && hasLargeBounds( t, mBoundsTransform ) )
239 {
240 // use the full extent of the scene
241 QgsVector3D v0( mRenderContext.extent().xMinimum(), mRenderContext.extent().yMinimum(), -100 );
242 QgsVector3D v1( mRenderContext.extent().xMaximum(), mRenderContext.extent().yMaximum(), +100 );
243 QgsBox3D box3D( v0, v1 );
244 float err = std::min( 1e6, t.geometricError() );
245 node = new QgsChunkNode( nodeId, box3D, err, parent );
246 }
247 else
248 {
249 QgsBox3D box = t.boundingVolume().bounds( mBoundsTransform );
250 box.setZMinimum( box.zMinimum() * mZValueScale + mZValueOffset );
251 box.setZMaximum( box.zMaximum() * mZValueScale + mZValueOffset );
252 node = new QgsChunkNode( nodeId, box, t.geometricError(), parent );
253 }
254
255 node->setRefinementProcess( t.refinementProcess() );
256 return node;
257}
258
259
260QgsChunkNode *QgsTiledSceneChunkLoaderFactory::createRootNode() const
261{
262 const QgsTiledSceneTile t = mIndex.rootTile();
263 return nodeForTile( t, QgsChunkNodeId( t.id() ), nullptr );
264}
265
266
267QVector<QgsChunkNode *> QgsTiledSceneChunkLoaderFactory::createChildren( QgsChunkNode *node ) const
268{
269 QVector<QgsChunkNode *> children;
270 const long long indexTileId = node->tileId().uniqueId;
271
272 // fetching of hierarchy is handled by canCreateChildren() + prepareChildren()
273 Q_ASSERT( mIndex.childAvailability( indexTileId ) != Qgis::TileChildrenAvailability::NeedFetching );
274
275 const QVector<long long> childIds = mIndex.childTileIds( indexTileId );
276 for ( long long childId : childIds )
277 {
278 const QgsChunkNodeId chId( childId );
279 QgsTiledSceneTile t = mIndex.getTile( childId );
280
281 // first check if this node should be even considered
282 // XXX: This check doesn't work for Quantized Mesh layers and possibly some
283 // Cesium 3D tiles as well. For now this hack is in place to make sure both
284 // work in practice.
285 if ( t.metadata()["contentFormat"] == "cesiumtiles"_L1 && mRenderContext.crs().type() != Qgis::CrsType::Geocentric && hasLargeBounds( t, mBoundsTransform ) )
286 {
287 // if the tile is huge, let's try to see if our scene is actually inside
288 // (if not, let' skip this child altogether!)
289 // TODO: make OBB of our scene in ECEF rather than just using center of the scene?
290 const QgsOrientedBox3D obb = t.boundingVolume().box();
291 const QgsPointXY c = mRenderContext.extent().center();
292 const QgsVector3D cEcef = mBoundsTransform.transform( QgsVector3D( c.x(), c.y(), 0 ), Qgis::TransformDirection::Reverse );
293 const QgsVector3D ecef2 = cEcef - obb.center();
294 const double *half = obb.halfAxes();
295 // this is an approximate check anyway, no need for double precision matrix/vector
296 // clang-format off
297 QMatrix4x4 rot(
298 half[0], half[3], half[6], 0,
299 half[1], half[4], half[7], 0,
300 half[2], half[5], half[8], 0,
301 0, 0, 0, 1
302 );
303 // clang-format on
304 QVector3D aaa = rot.inverted().map( ecef2.toVector3D() );
305 if ( aaa.x() > 1 || aaa.y() > 1 || aaa.z() > 1 || aaa.x() < -1 || aaa.y() < -1 || aaa.z() < -1 )
306 {
307 continue;
308 }
309 }
310
311 // fetching of hierarchy is handled by canCreateChildren() + prepareChildren()
312 Q_ASSERT( mIndex.childAvailability( childId ) != Qgis::TileChildrenAvailability::NeedFetching );
313
314 QgsChunkNode *nChild = nodeForTile( t, chId, node );
315 children.append( nChild );
316 }
317 return children;
318}
319
320bool QgsTiledSceneChunkLoaderFactory::canCreateChildren( QgsChunkNode *node )
321{
322 long long nodeId = node->tileId().uniqueId;
323 if ( mFutureHierarchyFetches.contains( nodeId ) || mPendingHierarchyFetches.contains( nodeId ) )
324 return false;
325
326 if ( mIndex.childAvailability( nodeId ) == Qgis::TileChildrenAvailability::NeedFetching )
327 {
328 mFutureHierarchyFetches.insert( nodeId );
329 return false;
330 }
331
332 // we need to make sure that if a child tile's content references another tileset JSON,
333 // we fetch its hierarchy before a chunk node is created for such child tile - otherwise we
334 // end up trying to load tileset JSON file instead of the actual content
335
336 const QVector<long long> childIds = mIndex.childTileIds( nodeId );
337 for ( long long childId : childIds )
338 {
339 if ( mFutureHierarchyFetches.contains( childId ) || mPendingHierarchyFetches.contains( childId ) )
340 return false;
341
342 if ( mIndex.childAvailability( childId ) == Qgis::TileChildrenAvailability::NeedFetching )
343 {
344 mFutureHierarchyFetches.insert( childId );
345 return false;
346 }
347 }
348 return true;
349}
350
351void QgsTiledSceneChunkLoaderFactory::fetchHierarchyForNode( long long nodeId, QgsChunkNode *origNode )
352{
353 Q_ASSERT( !mPendingHierarchyFetches.contains( nodeId ) );
354 mFutureHierarchyFetches.remove( nodeId );
355 mPendingHierarchyFetches.insert( nodeId );
356
357 QFutureWatcher<void> *futureWatcher = new QFutureWatcher<void>( this );
358 connect( futureWatcher, &QFutureWatcher<void>::finished, this, [this, origNode, nodeId, futureWatcher] {
359 mPendingHierarchyFetches.remove( nodeId );
360 emit childrenPrepared( origNode );
361 futureWatcher->deleteLater();
362 } );
363 futureWatcher->setFuture( QtConcurrent::run( [this, nodeId] { mIndex.fetchHierarchy( nodeId ); } ) );
364}
365
366void QgsTiledSceneChunkLoaderFactory::prepareChildren( QgsChunkNode *node )
367{
368 long long nodeId = node->tileId().uniqueId;
369 if ( mFutureHierarchyFetches.contains( nodeId ) )
370 {
371 fetchHierarchyForNode( nodeId, node );
372 return;
373 }
374
375 // we need to make sure that if a child tile's content references another tileset JSON,
376 // we fetch its hierarchy before a chunk node is created for such child tile - otherwise we
377 // end up trying to load tileset JSON file instead of the actual content
378
379 const QVector<long long> childIds = mIndex.childTileIds( nodeId );
380 for ( long long childId : childIds )
381 {
382 if ( mFutureHierarchyFetches.contains( childId ) )
383 {
384 fetchHierarchyForNode( childId, node );
385 }
386 }
387}
388
389
391
392QgsTiledSceneLayerChunkedEntity::QgsTiledSceneLayerChunkedEntity(
393 Qgs3DMapSettings *map,
394 const QgsTiledSceneIndex &index,
397 double maximumScreenError,
398 bool showBoundingBoxes,
399 double zValueScale,
400 double zValueOffset
401)
402 : QgsChunkedEntity( map, maximumScreenError, new QgsTiledSceneChunkLoaderFactory( Qgs3DRenderContext::fromMapSettings( map ), index, tileCrs, layerCrs, zValueScale, zValueOffset ), true )
403 , mIndex( index )
404{
405 setShowBoundingBoxes( showBoundingBoxes );
406}
407
408QgsTiledSceneLayerChunkedEntity::~QgsTiledSceneLayerChunkedEntity()
409{
410 // cancel / wait for jobs
411 cancelActiveJobs();
412}
413
414int QgsTiledSceneLayerChunkedEntity::pendingJobsCount() const
415{
416 return QgsChunkedEntity::pendingJobsCount() + static_cast<QgsTiledSceneChunkLoaderFactory *>( mChunkLoaderFactory )->mPendingHierarchyFetches.count();
417}
418
419QList<QgsRayCastHit> QgsTiledSceneLayerChunkedEntity::rayIntersection( const QgsRay3D &ray, const QgsRayCastContext &context ) const
420{
421 Q_UNUSED( context );
422 QgsDebugMsgLevel( u"Ray cast on tiled scene layer"_s, 2 );
423#ifdef QGISDEBUG
424 int nodeUsed = 0;
425 int nodesAll = 0;
426 int hits = 0;
427#endif
428
429 QList<QgsRayCastHit> result;
430 float minDist = -1;
431 QVector3D intersectionPoint;
432 QgsChunkNode *minNode = nullptr;
433 int minTriangleIndex = -1;
434
435 const QList<QgsChunkNode *> active = activeNodes();
436 for ( QgsChunkNode *node : active )
437 {
438#ifdef QGISDEBUG
439 nodesAll++;
440#endif
441
442 QgsAABB nodeBbox = Qgs3DUtils::mapToWorldExtent( node->box3D(), mMapSettings->origin() );
443
444 if ( node->entity() && ( minDist < 0 || nodeBbox.distanceFromPoint( ray.origin() ) < minDist ) && QgsRayCastingUtils::rayBoxIntersection( ray, nodeBbox ) )
445 {
446#ifdef QGISDEBUG
447 nodeUsed++;
448#endif
449 const QList<Qt3DRender::QGeometryRenderer *> rendLst = node->entity()->findChildren<Qt3DRender::QGeometryRenderer *>();
450 for ( Qt3DRender::QGeometryRenderer *rend : rendLst )
451 {
452 QVector3D nodeIntPoint;
453 int triangleIndex = -1;
454 QgsGeoTransform *nodeGeoTransform = node->entity()->findChild<QgsGeoTransform *>();
455 Q_ASSERT( nodeGeoTransform );
456 bool success = QgsRayCastingUtils::rayMeshIntersection( rend, ray, context.maximumDistance(), nodeGeoTransform->matrix(), nodeIntPoint, triangleIndex );
457 if ( success )
458 {
459#ifdef QGISDEBUG
460 hits++;
461#endif
462 float dist = ( ray.origin() - nodeIntPoint ).length();
463 if ( minDist < 0 || dist < minDist )
464 {
465 minDist = dist;
466 minNode = node;
467 minTriangleIndex = triangleIndex;
468 intersectionPoint = nodeIntPoint;
469 }
470 }
471 }
472 }
473 }
474
475 if ( minDist >= 0 )
476 {
477 QVariantMap vm;
478 QgsTiledSceneTile tile = mIndex.getTile( minNode->tileId().uniqueId );
479 // at this point this is mostly for debugging - we may want to change/rename what's returned here
480 vm[u"node_id"_s] = tile.id();
481 vm[u"node_error"_s] = tile.geometricError();
482 vm[u"node_content"_s] = tile.resources().value( u"content"_s );
483 vm[u"triangle_index"_s] = minTriangleIndex;
484
485 QgsRayCastHit hit;
486 hit.setDistance( minDist );
487 hit.setMapCoordinates( mMapSettings->worldToMapCoordinates( intersectionPoint ) );
488 hit.setProperties( vm );
489 result.append( hit );
490 }
491
492 QgsDebugMsgLevel( u"Active Nodes: %1, checked nodes: %2, hits found: %3"_s.arg( nodesAll ).arg( nodeUsed ).arg( hits ), 2 );
493 return result;
494}
495
@ Meters
Meters.
Definition qgis.h:5315
@ Geocentric
Geocentric CRS.
Definition qgis.h:2465
@ NeedFetching
Tile has children, but they are not yet available and must be fetched.
Definition qgis.h:6171
Axis
Cartesian axes.
Definition qgis.h:2607
@ Y
Y-axis.
Definition qgis.h:2609
@ Reverse
Reverse/inverse transform (from destination to source).
Definition qgis.h:2833
Definition of the world.
Rendering context for preparation of 3D entities.
QgsCoordinateReferenceSystem crs() const
Returns the coordinate reference system used in the 3D scene.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context, which stores various information regarding which datum tran...
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.
Axis-aligned bounding box - in world coords.
Definition qgsaabb.h:33
float distanceFromPoint(float x, float y, float z) const
Returns shortest distance from the box to a point.
Definition qgsaabb.cpp:50
static QgsApplication * instance()
Returns the singleton instance of the QgsApplication.
A 3-dimensional box composed of x, y, z coordinates.
Definition qgsbox3d.h:45
void setZMinimum(double z)
Sets the minimum z value.
Definition qgsbox3d.cpp:94
double depth() const
Returns the depth of the box.
Definition qgsbox3d.h:301
void setZMaximum(double z)
Sets the maximum z value.
Definition qgsbox3d.cpp:99
double zMaximum() const
Returns the maximum z value.
Definition qgsbox3d.h:268
double width() const
Returns the width of the box.
Definition qgsbox3d.h:287
double zMinimum() const
Returns the minimum z value.
Definition qgsbox3d.h:261
double height() const
Returns the height of the box.
Definition qgsbox3d.h:294
static QVector< QgsCesiumUtils::TileContents > extractTileContent(const QByteArray &tileContent)
Parses tile content and returns a list of TileContents.
Represents a coordinate reference system (CRS).
Handles coordinate transforms between two coordinate systems.
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination coordinate reference system, which the transform will transform coordinates t...
A simple 4x4 matrix implementation useful for transformation in 3D space.
Represents a oriented (rotated) box in 3 dimensions.
const double * halfAxes() const
Returns the half axes matrix;.
bool isNull() const
Returns true if the box is a null box.
QgsVector3D center() const
Returns the vector to the center of the box.
Represents a 2D point.
Definition qgspointxy.h:62
Exception thrown on failure to parse Quantized Mesh tile (malformed data).
A representation of a ray in 3D.
Definition qgsray3d.h:31
QVector3D origin() const
Returns the origin of the ray.
Definition qgsray3d.h:43
Responsible for defining parameters of the ray casting operations in 3D map canvases.
float maximumDistance() const
The maximum distance from ray origin to look for hits when casting a ray.
Contains details about the ray intersecting entities when ray casting in a 3D map canvas.
void setProperties(const QVariantMap &attributes)
Sets the point cloud point attributes, empty map if hit was not on a point cloud point.
void setMapCoordinates(const QgsVector3D &point)
Sets the hit point position in 3d map coordinates.
void setDistance(double distance)
Sets the hit's distance from the ray's origin.
QgsOrientedBox3D box() const
Returns the volume's oriented box.
QgsBox3D bounds(const QgsCoordinateTransform &transform=QgsCoordinateTransform(), Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Returns the axis aligned bounding box of the volume.
An index for tiled scene data providers.
Represents an individual tile from a tiled scene data source.
Qgis::TileRefinementProcess refinementProcess() const
Returns the tile's refinement process.
QVariantMap resources() const
Returns the resources attached to the tile.
const QgsTiledSceneBoundingVolume & boundingVolume() const
Returns the bounding volume for the tile.
QVariantMap metadata() const
Returns additional metadata attached to the tile.
long long id() const
Returns the tile's unique ID.
const QgsMatrix4x4 * transform() const
Returns the tile's transform.
double geometricError() const
Returns the tile's geometric error, which is the error, in meters, of the tile's simplified represent...
QUrl baseUrl() const
Returns the tile's base URL.
A 3D vector (similar to QVector3D) with the difference that it uses double precision instead of singl...
Definition qgsvector3d.h:33
QVector3D toVector3D() const
Converts the current object to QVector3D.
bool rayBoxIntersection(const QgsRay3D &ray, const QgsAABB &nodeBbox)
Tests whether an axis aligned box is intersected by a ray.
bool rayMeshIntersection(Qt3DRender::QGeometryRenderer *geometryRenderer, const QgsRay3D &r, float maxDist, const QMatrix4x4 &worldTransform, QVector3D &intPt, int &triangleIndex)
Tests whether a triangular mesh is intersected by a ray.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
uint qHash(const QVariant &variant)
Hash for QVariant.
Definition qgis.cpp:611
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63
#define QgsDebugError(str)
Definition qgslogger.h:59
Encapsulates the contents of a 3D tile.