35#include <Qt3DCore/QEntity>
36#include <Qt3DRender/QGeometryRenderer>
37#include <QtConcurrentRun>
39#include "moc_qgstiledscenechunkloader_p.cpp"
41using namespace Qt::StringLiterals;
45size_t qHash(
const QgsChunkNodeId &n )
60 return bounds.
width() > 1e5 || bounds.
height() > 1e5 || bounds.
depth() > 1e5;
65QgsTiledSceneChunkLoader::QgsTiledSceneChunkLoader( QgsChunkNode *node,
const QgsTiledSceneIndex &index,
const QgsTiledSceneChunkLoaderFactory &factory,
double zValueScale,
double zValueOffset )
66 : QgsChunkLoader( node )
68 , mZValueScale( zValueScale )
69 , mZValueOffset( zValueOffset )
73void QgsTiledSceneChunkLoader::start()
75 QgsChunkNode *node = chunk();
77 mFutureWatcher =
new QFutureWatcher<void>(
this );
78 connect( mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsChunkQueueJob::finished );
82 const QgsChunkNodeId tileId = node->tileId();
83 const QgsVector3D chunkOrigin = node->box3D().center();
85 const QFuture<void> future = QtConcurrent::run( [
this, tileId, boundsTransform, chunkOrigin, isGlobe] {
91 if ( !isGlobe && hasLargeBounds( tile, boundsTransform ) )
94 QString uri = tile.
resources().value( u
"content"_s ).toString();
102 uri = tile.
baseUrl().resolved( uri ).toString();
103 QByteArray content = mFactory.mIndex.retrieveContent( uri );
104 if ( content.isEmpty() )
111 QgsGltf3DUtils::EntityTransform entityTransform;
113 entityTransform.chunkOriginTargetCrs = chunkOrigin;
114 entityTransform.ecefToTargetCrs = &mFactory.mBoundsTransform;
115 entityTransform.zValueScale = mZValueScale;
116 entityTransform.zValueOffset = mZValueOffset;
119 const QString &format = tile.
metadata().value( u
"contentFormat"_s ).value<QString>();
121 if ( format ==
"quantizedmesh"_L1 )
126 qmTile.removeDegenerateTriangles();
127 tinygltf::Model model = qmTile.toGltf(
true, 100 );
128 mEntity = QgsGltf3DUtils::parsedGltfToEntity( model, entityTransform, uri, mFactory.mRenderContext, &errors );
132 errors.append( u
"Failed to parse tile from '%1'"_s.arg( uri ) );
135 else if ( format ==
"cesiumtiles"_L1 )
138 if ( tileContents.isEmpty() )
141 QVector<Qt3DCore::QEntity *> childEntities;
145 if ( innerContent.gltf.isEmpty() )
148 QgsGltf3DUtils::EntityTransform innerTransform = entityTransform;
149 innerTransform.tileTransform.translate( innerContent.rtcCenter );
152 tinygltf::Model model;
153 QString gltfErrors, gltfWarnings;
154 if ( !QgsGltfUtils::loadGltfModel( innerContent.gltf, model, &gltfErrors, &gltfWarnings ) )
156 errors.append( u
"GLTF load error: "_s + gltfErrors );
161 const auto instancedPrimitives =
QgsCesiumUtils::resolveInstancing( model, innerContent.instancing, innerTransform.gltfUpAxis, rawTileTransform, innerContent.rtcCenter );
163 if ( instancedPrimitives.isEmpty() )
166 Qt3DCore::QEntity *e = QgsGltf3DUtils::parsedGltfToEntity( model, innerTransform, uri, mFactory.mRenderContext, &errors );
174 childEntities << QgsGltf3DUtils::createInstancedEntities( model, instancedPrimitives, innerTransform, uri, materialContext, &errors );
176 if ( !innerContent.instancing.has_value() )
180 Qt3DCore::QEntity *nonInstancedEntity = QgsGltf3DUtils::parsedGltfToEntity( model, innerTransform, uri, mFactory.mRenderContext, &errors );
181 if ( nonInstancedEntity )
182 childEntities << nonInstancedEntity;
186 if ( childEntities.size() == 1 )
188 mEntity = childEntities[0];
192 mEntity =
new Qt3DCore::QEntity;
193 for ( Qt3DCore::QEntity *e : childEntities )
194 e->setParent( mEntity );
198 else if ( format ==
"draco"_L1 )
200 QgsGltfUtils::I3SNodeContext i3sContext;
201 i3sContext.initFromTile( tile, mFactory.mLayerCrs, mFactory.mBoundsTransform.sourceCrs(), mFactory.mRenderContext.transformContext() );
203 QString dracoLoadError;
204 tinygltf::Model model;
205 if ( !QgsGltfUtils::loadDracoModel( content, i3sContext, model, &dracoLoadError ) )
207 errors.append( dracoLoadError );
211 mEntity = QgsGltf3DUtils::parsedGltfToEntity( model, entityTransform, QString(), mFactory.mRenderContext, &errors );
217 if ( !errors.isEmpty() )
224 QgsGeoTransform *transform =
new QgsGeoTransform;
225 transform->setGeoTranslation( chunkOrigin );
226 mEntity->addComponent( transform );
233 mFutureWatcher->setFuture( future );
236QgsTiledSceneChunkLoader::~QgsTiledSceneChunkLoader()
238 if ( !mFutureWatcher->isFinished() )
240 disconnect( mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsChunkQueueJob::finished );
241 mFutureWatcher->waitForFinished();
245Qt3DCore::QEntity *QgsTiledSceneChunkLoader::createEntity( Qt3DCore::QEntity *parent )
248 mEntity->setParent( parent );
254QgsTiledSceneChunkLoaderFactory::QgsTiledSceneChunkLoaderFactory(
257 : mRenderContext( context )
259 , mZValueScale( zValueScale )
260 , mZValueOffset( zValueOffset )
261 , mLayerCrs( layerCrs )
266QgsChunkLoader *QgsTiledSceneChunkLoaderFactory::createChunkLoader( QgsChunkNode *node )
const
268 return new QgsTiledSceneChunkLoader( node, mIndex, *
this, mZValueScale, mZValueOffset );
271QgsChunkNode *QgsTiledSceneChunkLoaderFactory::nodeForTile(
const QgsTiledSceneTile &t,
const QgsChunkNodeId &nodeId, QgsChunkNode *parent )
const
273 QgsChunkNode *node =
nullptr;
277 QgsVector3D v0( mRenderContext.extent().xMinimum(), mRenderContext.extent().yMinimum(), -100 );
278 QgsVector3D v1( mRenderContext.extent().xMaximum(), mRenderContext.extent().yMaximum(), +100 );
281 node =
new QgsChunkNode( nodeId, box3D, err, parent );
288 node =
new QgsChunkNode( nodeId, box, t.
geometricError(), parent );
296QgsChunkNode *QgsTiledSceneChunkLoaderFactory::createRootNode()
const
299 return nodeForTile( t, QgsChunkNodeId( t.
id() ),
nullptr );
303QVector<QgsChunkNode *> QgsTiledSceneChunkLoaderFactory::createChildren( QgsChunkNode *node )
const
305 QVector<QgsChunkNode *> children;
306 const long long indexTileId = node->tileId().uniqueId;
311 const QVector<long long> childIds = mIndex.childTileIds( indexTileId );
312 for (
long long childId : childIds )
314 const QgsChunkNodeId chId( childId );
327 const QgsPointXY c = mRenderContext.extent().center();
330 const double *half = obb.
halfAxes();
334 half[0], half[3], half[6], 0,
335 half[1], half[4], half[7], 0,
336 half[2], half[5], half[8], 0,
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 )
350 QgsChunkNode *nChild = nodeForTile( t, chId, node );
351 children.append( nChild );
356bool QgsTiledSceneChunkLoaderFactory::canCreateChildren( QgsChunkNode *node )
358 long long nodeId = node->tileId().uniqueId;
359 if ( mFutureHierarchyFetches.contains( nodeId ) || mPendingHierarchyFetches.contains( nodeId ) )
364 mFutureHierarchyFetches.insert( nodeId );
372 const QVector<long long> childIds = mIndex.childTileIds( nodeId );
373 for (
long long childId : childIds )
375 if ( mFutureHierarchyFetches.contains( childId ) || mPendingHierarchyFetches.contains( childId ) )
380 mFutureHierarchyFetches.insert( childId );
387void QgsTiledSceneChunkLoaderFactory::fetchHierarchyForNode(
long long nodeId, QgsChunkNode *origNode )
389 Q_ASSERT( !mPendingHierarchyFetches.contains( nodeId ) );
390 mFutureHierarchyFetches.remove( nodeId );
391 mPendingHierarchyFetches.insert( nodeId );
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();
399 futureWatcher->setFuture( QtConcurrent::run( [
this, nodeId] { mIndex.fetchHierarchy( nodeId ); } ) );
402void QgsTiledSceneChunkLoaderFactory::prepareChildren( QgsChunkNode *node )
404 long long nodeId = node->tileId().uniqueId;
405 if ( mFutureHierarchyFetches.contains( nodeId ) )
407 fetchHierarchyForNode( nodeId, node );
415 const QVector<long long> childIds = mIndex.childTileIds( nodeId );
416 for (
long long childId : childIds )
418 if ( mFutureHierarchyFetches.contains( childId ) )
420 fetchHierarchyForNode( childId, node );
428QgsTiledSceneLayerChunkedEntity::QgsTiledSceneLayerChunkedEntity(
433 double maximumScreenError,
434 bool showBoundingBoxes,
438 : QgsChunkedEntity( map, maximumScreenError, new QgsTiledSceneChunkLoaderFactory(
Qgs3DRenderContext::fromMapSettings( map ), index, tileCrs, layerCrs, zValueScale, zValueOffset ), true )
441 setShowBoundingBoxes( showBoundingBoxes );
444QgsTiledSceneLayerChunkedEntity::~QgsTiledSceneLayerChunkedEntity()
450int QgsTiledSceneLayerChunkedEntity::pendingJobsCount()
const
452 return QgsChunkedEntity::pendingJobsCount() +
static_cast<QgsTiledSceneChunkLoaderFactory *
>( mChunkLoaderFactory )->mPendingHierarchyFetches.count();
455QList<QgsRayCastHit> QgsTiledSceneLayerChunkedEntity::rayIntersection(
const QgsRay3D &ray,
const QgsRayCastContext &context )
const
465 QList<QgsRayCastHit> result;
467 QVector3D intersectionPoint;
468 QgsChunkNode *minNode =
nullptr;
469 int minTriangleIndex = -1;
471 const QList<QgsChunkNode *> active = activeNodes();
472 for ( QgsChunkNode *node : active )
485 const QList<Qt3DRender::QGeometryRenderer *> rendLst = node->entity()->findChildren<Qt3DRender::QGeometryRenderer *>();
486 for ( Qt3DRender::QGeometryRenderer *rend : rendLst )
488 QVector3D nodeIntPoint;
489 int triangleIndex = -1;
490 QgsGeoTransform *nodeGeoTransform = node->entity()->findChild<QgsGeoTransform *>();
491 Q_ASSERT( nodeGeoTransform );
498 float dist = ( ray.
origin() - nodeIntPoint ).length();
499 if ( minDist < 0 || dist < minDist )
503 minTriangleIndex = triangleIndex;
504 intersectionPoint = nodeIntPoint;
516 vm[u
"node_id"_s] = tile.
id();
518 vm[u
"node_content"_s] = tile.
resources().value( u
"content"_s );
519 vm[u
"triangle_index"_s] = minTriangleIndex;
523 hit.
setMapCoordinates( mMapSettings->worldToMapCoordinates( intersectionPoint ) );
525 result.append( hit );
528 QgsDebugMsgLevel( u
"Active Nodes: %1, checked nodes: %2, hits found: %3"_s.arg( nodesAll ).arg( nodeUsed ).arg( hits ), 2 );
@ Geocentric
Geocentric CRS.
@ NeedFetching
Tile has children, but they are not yet available and must be fetched.
@ Reverse
Reverse/inverse transform (from destination to source).
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.
float distanceFromPoint(float x, float y, float z) const
Returns shortest distance from the box to a point.
static QgsApplication * instance()
Returns the singleton instance of the QgsApplication.
A 3-dimensional box composed of x, y, z coordinates.
void setZMinimum(double z)
Sets the minimum z value.
double depth() const
Returns the depth of the box.
void setZMaximum(double z)
Sets the maximum z value.
double zMaximum() const
Returns the maximum z value.
double width() const
Returns the width of the box.
double zMinimum() const
Returns the minimum z value.
double height() const
Returns the height of the box.
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).
Qgis::DistanceUnit mapUnits
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.
Exception thrown on failure to parse Quantized Mesh tile (malformed data).
A representation of a ray in 3D.
QVector3D origin() const
Returns the origin of the ray.
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...
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.
#define QgsDebugMsgLevel(str, level)
#define QgsDebugError(str)
Encapsulates the contents of a 3D tile.