QGIS API Documentation 4.3.0-Master (52aa6503bed)
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 "qgsfutureutils.h"
24#include "qgsgeotransform.h"
25#include "qgsgltf3dutils.h"
26#include "qgsgltfutils.h"
29#include "qgsray3d.h"
30#include "qgsraycastcontext.h"
31#include "qgsraycastingutils.h"
32#include "qgsthreadingutils.h"
34#include "qgstiledscenetile.h"
35
36#include <QFuture>
37#include <QString>
38#include <Qt3DCore/QEntity>
39#include <Qt3DRender/QGeometryRenderer>
40#include <QtConcurrentRun>
41
42#include "moc_qgstiledscenechunkloader_p.cpp"
43
44using namespace Qt::StringLiterals;
45
47
48size_t qHash( const QgsChunkNodeId &n )
49{
50 return n.uniqueId;
51}
52
53static bool hasLargeBounds( const QgsTiledSceneTile &t, const QgsCoordinateTransform &boundsTransform )
54{
55 if ( t.geometricError() > 1e6 )
56 return true;
57
58 if ( t.boundingVolume().box().isNull() )
59 return true;
60
61 Q_ASSERT( boundsTransform.destinationCrs().mapUnits() == Qgis::DistanceUnit::Meters );
62 QgsBox3D bounds = t.boundingVolume().bounds( boundsTransform );
63 return bounds.width() > 1e5 || bounds.height() > 1e5 || bounds.depth() > 1e5;
64}
65
67
68QgsTiledSceneChunkLoader::QgsTiledSceneChunkLoader(
69 const Qgs3DRenderContext &context, const QgsTiledSceneIndex &index, QgsCoordinateReferenceSystem tileCrs, QgsCoordinateReferenceSystem layerCrs, double zValueScale, double zValueOffset
70)
71 : mData {
72 .mRenderContext = context,
73 .mIndex = index,
74 .mZValueScale = zValueScale,
75 .mZValueOffset = zValueOffset,
76 .mLayerCrs = layerCrs,
77 .mBoundsTransform = QgsCoordinateTransform( tileCrs, context.crs(), context.transformContext() ),
78 }
79{}
80
81QFuture<QgsChunkLoaderResult> QgsTiledSceneChunkLoader::loadChunk( QgsChunkNode *node )
82{
83 const QgsChunkNodeId tileId = node->tileId();
84 const QgsVector3D chunkOrigin = node->box3D().center();
85 const bool isGlobe = mData.mRenderContext.crs().type() == Qgis::CrsType::Geocentric;
86 return QtConcurrent::run( [data = mData, tileId, chunkOrigin, isGlobe]() mutable -> QgsChunkLoaderResult {
87 const QgsTiledSceneTile tile = data.mIndex.getTile( tileId.uniqueId );
88
89 // we do not load tiles that are too big when not in globe scene mode...
90 // the problem is that their 3D bounding boxes with ECEF coordinates are huge
91 // and we are unable to turn them into planar bounding boxes
92 if ( !isGlobe && hasLargeBounds( tile, data.mBoundsTransform ) )
93 return QgsChunkLoaderResult::sEmpty;
94
95 QString uri = tile.resources().value( u"content"_s ).toString();
96 if ( uri.isEmpty() )
97 {
98 // nothing to show for this tile
99 // TODO: can we skip loading it at all?
100 return QgsChunkLoaderResult::sEmpty;
101 }
102
103 uri = tile.baseUrl().resolved( uri ).toString();
104 QByteArray content = data.mIndex.retrieveContent( uri );
105 if ( content.isEmpty() )
106 {
107 // the request probably failed
108 // TODO: how can we report it?
109 return QgsChunkLoaderResult::sEmpty;
110 }
111
112 QgsGltf3DUtils::EntityTransform entityTransform;
113 entityTransform.tileTransform = ( tile.transform() ? *tile.transform() : QgsMatrix4x4() );
114 entityTransform.chunkOriginTargetCrs = chunkOrigin;
115 entityTransform.ecefToTargetCrs = &data.mBoundsTransform;
116 entityTransform.zValueScale = data.mZValueScale;
117 entityTransform.zValueOffset = data.mZValueOffset;
118 entityTransform.gltfUpAxis = static_cast<Qgis::Axis>( tile.metadata().value( u"gltfUpAxis"_s, static_cast<int>( Qgis::Axis::Y ) ).toInt() );
119
120 const QString &format = tile.metadata().value( u"contentFormat"_s ).value<QString>();
121 QStringList errors;
122 Qt3DCore::QEntity *entity = nullptr;
123 if ( format == "quantizedmesh"_L1 )
124 {
125 try
126 {
127 QgsQuantizedMeshTile qmTile( content );
128 qmTile.removeDegenerateTriangles();
129 tinygltf::Model model = qmTile.toGltf( true, 100 );
130 entity = QgsGltf3DUtils::parsedGltfToEntity( model, entityTransform, uri, data.mRenderContext, &errors );
131 }
133 {
134 errors.append( u"Failed to parse tile from '%1'"_s.arg( uri ) );
135 }
136 }
137 else if ( format == "cesiumtiles"_L1 )
138 {
139 const QVector<QgsCesiumUtils::TileContents> tileContents = QgsCesiumUtils::extractTileContent( content, uri );
140 if ( tileContents.isEmpty() )
141 return QgsChunkLoaderResult::sEmpty;
142
143 QVector<Qt3DCore::QEntity *> childEntities;
144
145 for ( const QgsCesiumUtils::TileContents &innerContent : tileContents )
146 {
147 if ( innerContent.gltf.isEmpty() )
148 continue;
149
150 QgsGltf3DUtils::EntityTransform innerTransform = entityTransform;
151 innerTransform.tileTransform.translate( innerContent.rtcCenter );
152
153 // Check for instancing (i3dm or EXT_mesh_gpu_instancing)
154 tinygltf::Model model;
155 QString gltfErrors, gltfWarnings;
156 if ( !QgsGltfUtils::loadGltfModel( innerContent.gltf, model, &gltfErrors, &gltfWarnings ) )
157 {
158 errors.append( u"GLTF load error: "_s + gltfErrors );
159 continue;
160 }
161
162 const QgsMatrix4x4 rawTileTransform = ( tile.transform() ? *tile.transform() : QgsMatrix4x4() );
163 const auto instancedPrimitives = QgsCesiumUtils::resolveInstancing( model, innerContent.instancing, innerTransform.gltfUpAxis, rawTileTransform, innerContent.rtcCenter );
164
165 if ( instancedPrimitives.isEmpty() )
166 {
167 // the common case (b3dm or glTF tile without EXT_mesh_gpu_instancing)
168 Qt3DCore::QEntity *e = QgsGltf3DUtils::parsedGltfToEntity( model, innerTransform, uri, data.mRenderContext, &errors );
169 if ( e )
170 childEntities << e;
171 }
172 else
173 {
174 // the instanced case (i3dm or glTF tile with EXT_mesh_gpu_instancing)
175 QgsMaterialContext materialContext = QgsMaterialContext::fromRenderContext( data.mRenderContext );
176 childEntities << QgsGltf3DUtils::createInstancedEntities( model, instancedPrimitives, innerTransform, uri, materialContext, &errors );
177
178 if ( !innerContent.instancing.has_value() )
179 {
180 // for glTF tiles with EXT_mesh_gpu_instancing, the model can have a mixture of instanced
181 // and non-instanced nodes. Handle the non-instanced nodes here (if any).
182 Qt3DCore::QEntity *nonInstancedEntity = QgsGltf3DUtils::parsedGltfToEntity( model, innerTransform, uri, data.mRenderContext, &errors );
183 if ( nonInstancedEntity )
184 childEntities << nonInstancedEntity;
185 }
186 }
187
188 if ( childEntities.size() == 1 )
189 {
190 entity = childEntities[0];
191 }
192 else
193 {
194 entity = new Qt3DCore::QEntity;
195 for ( Qt3DCore::QEntity *e : childEntities )
196 e->setParent( entity );
197 }
198 }
199 }
200 else if ( format == "draco"_L1 )
201 {
202 QgsGltfUtils::I3SNodeContext i3sContext;
203 i3sContext.initFromTile( tile, data.mLayerCrs, data.mBoundsTransform.sourceCrs(), data.mRenderContext.transformContext() );
204
205 QString dracoLoadError;
206 tinygltf::Model model;
207 if ( !QgsGltfUtils::loadDracoModel( content, i3sContext, model, &dracoLoadError ) )
208 {
209 errors.append( dracoLoadError );
210 return QgsChunkLoaderResult::sEmpty;
211 }
212
213 entity = QgsGltf3DUtils::parsedGltfToEntity( model, entityTransform, QString(), data.mRenderContext, &errors );
214 }
215 else
216 return QgsChunkLoaderResult::sEmpty; // unsupported tile content type
217
218 // TODO: report errors somewhere?
219 if ( !errors.isEmpty() )
220 {
221 QgsDebugError( "gltf load errors: " + errors.join( '\n' ) );
222 }
223
224 if ( entity )
225 {
226 QgsGeoTransform *transform = new QgsGeoTransform;
227 transform->setGeoTranslation( chunkOrigin );
228 entity->addComponent( transform );
229
230 entity->moveToThread( QgsApplication::instance()->thread() );
231 }
232
233 return QgsChunkLoaderResult { [entity]( Qt3DCore::QEntity *parent ) {
235 if ( entity )
236 entity->setParent( parent );
237 return entity;
238 } };
239 } );
240}
241
242QgsChunkNode *QgsTiledSceneChunkLoader::nodeForTile( const QgsTiledSceneTile &t, const QgsChunkNodeId &nodeId, QgsChunkNode *parent ) const
243{
244 QgsChunkNode *node = nullptr;
245 if ( mData.mRenderContext.crs().type() != Qgis::CrsType::Geocentric && hasLargeBounds( t, mData.mBoundsTransform ) )
246 {
247 // use the full extent of the scene
248 QgsVector3D v0( mData.mRenderContext.extent().xMinimum(), mData.mRenderContext.extent().yMinimum(), -100 );
249 QgsVector3D v1( mData.mRenderContext.extent().xMaximum(), mData.mRenderContext.extent().yMaximum(), +100 );
250 QgsBox3D box3D( v0, v1 );
251 float err = std::min( 1e6, t.geometricError() );
252 node = new QgsChunkNode( nodeId, box3D, err, parent );
253 }
254 else
255 {
256 QgsBox3D box = t.boundingVolume().bounds( mData.mBoundsTransform );
257 box.setZMinimum( box.zMinimum() * mData.mZValueScale + mData.mZValueOffset );
258 box.setZMaximum( box.zMaximum() * mData.mZValueScale + mData.mZValueOffset );
259 node = new QgsChunkNode( nodeId, box, t.geometricError(), parent );
260 }
261
262 node->setRefinementProcess( t.refinementProcess() );
263 return node;
264}
265
266
267QgsChunkNode *QgsTiledSceneChunkLoader::createRootNode() const
268{
269 const QgsTiledSceneTile t = mData.mIndex.rootTile();
270 return nodeForTile( t, QgsChunkNodeId( t.id() ), nullptr );
271}
272
273
274QFuture<QVector<QgsChunkNode *>> QgsTiledSceneChunkLoader::createChildren( QgsChunkNode *node )
275{
276 return prepareChildren( node ).then( this, [this, node]() {
277 QVector<QgsChunkNode *> children;
278 const long long indexTileId = node->tileId().uniqueId;
279
280 // Already fetched by prepareChildren().
281 Q_ASSERT( mData.mIndex.childAvailability( indexTileId ) != Qgis::TileChildrenAvailability::NeedFetching );
282
283 const QVector<long long> childIds = mData.mIndex.childTileIds( indexTileId );
284 for ( long long childId : childIds )
285 {
286 const QgsChunkNodeId chId( childId );
287 QgsTiledSceneTile t = mData.mIndex.getTile( childId );
288
289 // first check if this node should be even considered
290 // XXX: This check doesn't work for Quantized Mesh layers and possibly some
291 // Cesium 3D tiles as well. For now this hack is in place to make sure both
292 // work in practice.
293 if ( t.metadata()["contentFormat"] == "cesiumtiles"_L1 && mData.mRenderContext.crs().type() != Qgis::CrsType::Geocentric && hasLargeBounds( t, mData.mBoundsTransform ) )
294 {
295 // if the tile is huge, let's try to see if our scene is actually inside
296 // (if not, let' skip this child altogether!)
297 // TODO: make OBB of our scene in ECEF rather than just using center of the scene?
298 const QgsOrientedBox3D obb = t.boundingVolume().box();
299 const QgsPointXY c = mData.mRenderContext.extent().center();
300 const QgsVector3D cEcef = mData.mBoundsTransform.transform( QgsVector3D( c.x(), c.y(), 0 ), Qgis::TransformDirection::Reverse );
301 const QgsVector3D ecef2 = cEcef - obb.center();
302 const double *half = obb.halfAxes();
303 // this is an approximate check anyway, no need for double precision matrix/vector
304 // clang-format off
305 QMatrix4x4 rot(
306 half[0], half[3], half[6], 0,
307 half[1], half[4], half[7], 0,
308 half[2], half[5], half[8], 0,
309 0, 0, 0, 1
310 );
311 // clang-format on
312 QVector3D aaa = rot.inverted().map( ecef2.toVector3D() );
313 if ( aaa.x() > 1 || aaa.y() > 1 || aaa.z() > 1 || aaa.x() < -1 || aaa.y() < -1 || aaa.z() < -1 )
314 {
315 continue;
316 }
317 }
318
319 Q_ASSERT( mData.mIndex.childAvailability( childId ) != Qgis::TileChildrenAvailability::NeedFetching );
320
321 QgsChunkNode *nChild = nodeForTile( t, chId, node );
322 children.append( nChild );
323 }
324 return children;
325 } );
326}
327
328QFuture<void> QgsTiledSceneChunkLoader::fetchHierarchyForNode( long long nodeId )
329{
330 if ( auto it = mRunningHierarchyFetches.find( nodeId ); it != mRunningHierarchyFetches.end() )
331 return QgsFutureUtils::clone( it.value() );
332
333 QFuture<void> future = QtConcurrent::run( [nodeId, index = mData.mIndex]() mutable { index.fetchHierarchy( nodeId ); } );
334 mRunningHierarchyFetches[nodeId] = future;
335 return future.then( this, [this, nodeId]() { mRunningHierarchyFetches.remove( nodeId ); } );
336}
337
338QFuture<void> QgsTiledSceneChunkLoader::prepareChildren( QgsChunkNode *node )
339{
340 long long nodeId = node->tileId().uniqueId;
341
342 return fetchHierarchyForNode( nodeId )
343 .then(
344 this,
345 [this, nodeId]() {
346 // we need to make sure that if a child tile's content references
347 // another tileset JSON, we fetch its hierarchy before a chunk node is
348 // created for such child tile - otherwise we end up trying to load
349 // tileset JSON file instead of the actual content
350
351 const QVector<long long> childIds = mData.mIndex.childTileIds( nodeId );
352 QList<QFuture<void>> fetches;
353 for ( long long childId : childIds )
354 {
355 fetches.append( fetchHierarchyForNode( childId ) );
356 }
357 return QtFuture::whenAll( fetches.begin(), fetches.end() );
358 }
359 )
360 .unwrap();
361}
362
364
365QgsTiledSceneLayerChunkedEntity::QgsTiledSceneLayerChunkedEntity(
366 Qgs3DMapSettings *map,
367 const QgsTiledSceneIndex &index,
370 double maximumScreenError,
371 bool showBoundingBoxes,
372 double zValueScale,
373 double zValueOffset
374)
375 : QgsChunkedEntity( map, maximumScreenError, new QgsTiledSceneChunkLoader( Qgs3DRenderContext::fromMapSettings( map ), index, tileCrs, layerCrs, zValueScale, zValueOffset ), true )
376 , mIndex( index )
377{
378 setShowBoundingBoxes( showBoundingBoxes );
379}
380
381QgsTiledSceneLayerChunkedEntity::~QgsTiledSceneLayerChunkedEntity()
382{
383 // cancel / wait for jobs
384 cancelActiveJobs();
385}
386
387QList<QgsRayCastHit> QgsTiledSceneLayerChunkedEntity::rayIntersection( const QgsRay3D &ray, const QgsRayCastContext &context ) const
388{
389 Q_UNUSED( context );
390 QgsDebugMsgLevel( u"Ray cast on tiled scene layer"_s, 2 );
391#ifdef QGISDEBUG
392 int nodeUsed = 0;
393 int nodesAll = 0;
394 int hits = 0;
395#endif
396
397 QList<QgsRayCastHit> result;
398 float minDist = -1;
399 QVector3D intersectionPoint;
400 QgsChunkNode *minNode = nullptr;
401 int minTriangleIndex = -1;
402
403 const QList<QgsChunkNode *> active = activeNodes();
404 for ( QgsChunkNode *node : active )
405 {
406#ifdef QGISDEBUG
407 nodesAll++;
408#endif
409
410 QgsAABB nodeBbox = Qgs3DUtils::mapToWorldExtent( node->box3D(), mMapSettings->origin() );
411
412 if ( node->entity() && ( minDist < 0 || nodeBbox.distanceFromPoint( ray.origin() ) < minDist ) && QgsRayCastingUtils::rayBoxIntersection( ray, nodeBbox ) )
413 {
414#ifdef QGISDEBUG
415 nodeUsed++;
416#endif
417 const QList<Qt3DRender::QGeometryRenderer *> rendLst = node->entity()->findChildren<Qt3DRender::QGeometryRenderer *>();
418 for ( Qt3DRender::QGeometryRenderer *rend : rendLst )
419 {
420 QVector3D nodeIntPoint;
421 int triangleIndex = -1;
422 QgsGeoTransform *nodeGeoTransform = node->entity()->findChild<QgsGeoTransform *>();
423 Q_ASSERT( nodeGeoTransform );
424 bool success = QgsRayCastingUtils::rayMeshIntersection( rend, ray, context.maximumDistance(), nodeGeoTransform->matrix(), nodeIntPoint, triangleIndex );
425 if ( success )
426 {
427#ifdef QGISDEBUG
428 hits++;
429#endif
430 float dist = ( ray.origin() - nodeIntPoint ).length();
431 if ( minDist < 0 || dist < minDist )
432 {
433 minDist = dist;
434 minNode = node;
435 minTriangleIndex = triangleIndex;
436 intersectionPoint = nodeIntPoint;
437 }
438 }
439 }
440 }
441 }
442
443 if ( minDist >= 0 )
444 {
445 QVariantMap vm;
446 QgsTiledSceneTile tile = mIndex.getTile( minNode->tileId().uniqueId );
447 // at this point this is mostly for debugging - we may want to change/rename what's returned here
448 vm[u"node_id"_s] = tile.id();
449 vm[u"node_error"_s] = tile.geometricError();
450 vm[u"node_content"_s] = tile.resources().value( u"content"_s );
451 vm[u"triangle_index"_s] = minTriangleIndex;
452
453 QgsRayCastHit hit;
454 hit.setDistance( minDist );
455 hit.setMapCoordinates( mMapSettings->worldToMapCoordinates( intersectionPoint ) );
456 hit.setProperties( vm );
457 result.append( hit );
458 }
459
460#ifdef QGISDEBUG
461 QgsDebugMsgLevel( u"Active Nodes: %1, checked nodes: %2, hits found: %3"_s.arg( nodesAll ).arg( nodeUsed ).arg( hits ), 2 );
462#endif
463 return result;
464}
465
@ Meters
Meters.
Definition qgis.h:5569
@ Geocentric
Geocentric CRS.
Definition qgis.h:2516
@ NeedFetching
Tile has children, but they are not yet available and must be fetched.
Definition qgis.h:6491
Axis
Cartesian axes.
Definition qgis.h:2658
@ Y
Y-axis.
Definition qgis.h:2660
@ Reverse
Reverse/inverse transform (from destination to source).
Definition qgis.h:2884
Definition of the world.
Rendering context for preparation of 3D entities.
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< QgsGltfUtils::InstancedPrimitive > resolveInstancing(const tinygltf::Model &model, const std::optional< TileI3dmData > &tileInstancing, Qgis::Axis gltfUpAxis, const QgsMatrix4x4 &tileTransform, const QgsVector3D &rtcCenter)
Resolves instancing from either i3dm data or EXT_mesh_gpu_instancing.
static QVector< QgsCesiumUtils::TileContents > extractTileContent(const QByteArray &tileContent, const QString &baseUri=QString())
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...
static QFuture< void > clone(QFuture< void > base)
Returns a future that will finish at the same time base will.
Context settings for a material.
static QgsMaterialContext fromRenderContext(const Qgs3DRenderContext &context)
Constructs a material context from the settings in a 3D render context.
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
size_t qHash(const QVariant &variant)
Hash for QVariant.
Definition qgis.cpp:611
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:80
#define QgsDebugError(str)
Definition qgslogger.h:71
#define QGIS_CHECK_MAIN_THREAD_ACCESS
Encapsulates the contents of a 3D tile.