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