34#include <Qt3DRender/QGeometryRenderer>
35#include <QtConcurrentRun>
37#include "moc_qgstiledscenechunkloader_p.cpp"
39using namespace Qt::StringLiterals;
43size_t qHash(
const QgsChunkNodeId &n )
58 return bounds.
width() > 1e5 || bounds.
height() > 1e5 || bounds.
depth() > 1e5;
63QgsTiledSceneChunkLoader::QgsTiledSceneChunkLoader( QgsChunkNode *node,
const QgsTiledSceneIndex &index,
const QgsTiledSceneChunkLoaderFactory &factory,
double zValueScale,
double zValueOffset )
64 : QgsChunkLoader( node )
66 , mZValueScale( zValueScale )
67 , mZValueOffset( zValueOffset )
71void QgsTiledSceneChunkLoader::start()
73 QgsChunkNode *node = chunk();
75 mFutureWatcher =
new QFutureWatcher<void>(
this );
76 connect( mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsChunkQueueJob::finished );
80 const QgsChunkNodeId tileId = node->tileId();
81 const QgsVector3D chunkOrigin = node->box3D().center();
83 const QFuture<void> future = QtConcurrent::run( [
this, tileId, boundsTransform, chunkOrigin, isGlobe] {
89 if ( !isGlobe && hasLargeBounds( tile, boundsTransform ) )
92 QString uri = tile.
resources().value( u
"content"_s ).toString();
100 uri = tile.
baseUrl().resolved( uri ).toString();
101 QByteArray content = mFactory.mIndex.retrieveContent( uri );
102 if ( content.isEmpty() )
109 QgsGltf3DUtils::EntityTransform entityTransform;
111 entityTransform.chunkOriginTargetCrs = chunkOrigin;
112 entityTransform.ecefToTargetCrs = &mFactory.mBoundsTransform;
113 entityTransform.zValueScale = mZValueScale;
114 entityTransform.zValueOffset = mZValueOffset;
117 const QString &format = tile.
metadata().value( u
"contentFormat"_s ).value<QString>();
119 if ( format ==
"quantizedmesh"_L1 )
124 qmTile.removeDegenerateTriangles();
125 tinygltf::Model model = qmTile.toGltf(
true, 100 );
126 mEntity = QgsGltf3DUtils::parsedGltfToEntity( model, entityTransform, uri, &errors );
130 errors.append( u
"Failed to parse tile from '%1'"_s.arg( uri ) );
133 else if ( format ==
"cesiumtiles"_L1 )
136 if ( tileContents.isEmpty() )
139 if ( tileContents.size() == 1 )
141 if ( tileContents[0].gltf.isEmpty() )
143 entityTransform.tileTransform.translate( tileContents[0].rtcCenter );
144 mEntity = QgsGltf3DUtils::gltfToEntity( tileContents[0].gltf, entityTransform, uri, &errors );
148 mEntity =
new Qt3DCore::QEntity;
151 if ( innerContent.gltf.isEmpty() )
154 QgsGltf3DUtils::EntityTransform innerTransform = entityTransform;
155 innerTransform.tileTransform.translate( innerContent.rtcCenter );
156 Qt3DCore::QEntity *childEntity = QgsGltf3DUtils::gltfToEntity( innerContent.gltf, innerTransform, uri, &errors );
158 childEntity->setParent( mEntity );
162 else if ( format ==
"draco"_L1 )
164 QgsGltfUtils::I3SNodeContext i3sContext;
165 i3sContext.initFromTile( tile, mFactory.mLayerCrs, mFactory.mBoundsTransform.sourceCrs(), mFactory.mRenderContext.transformContext() );
167 QString dracoLoadError;
168 tinygltf::Model model;
169 if ( !QgsGltfUtils::loadDracoModel( content, i3sContext, model, &dracoLoadError ) )
171 errors.append( dracoLoadError );
175 mEntity = QgsGltf3DUtils::parsedGltfToEntity( model, entityTransform, QString(), &errors );
181 if ( !errors.isEmpty() )
188 QgsGeoTransform *transform =
new QgsGeoTransform;
189 transform->setGeoTranslation( chunkOrigin );
190 mEntity->addComponent( transform );
197 mFutureWatcher->setFuture( future );
200QgsTiledSceneChunkLoader::~QgsTiledSceneChunkLoader()
202 if ( !mFutureWatcher->isFinished() )
204 disconnect( mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsChunkQueueJob::finished );
205 mFutureWatcher->waitForFinished();
209Qt3DCore::QEntity *QgsTiledSceneChunkLoader::createEntity( Qt3DCore::QEntity *parent )
212 mEntity->setParent( parent );
218QgsTiledSceneChunkLoaderFactory::QgsTiledSceneChunkLoaderFactory(
221 : mRenderContext( context )
223 , mZValueScale( zValueScale )
224 , mZValueOffset( zValueOffset )
225 , mLayerCrs( layerCrs )
230QgsChunkLoader *QgsTiledSceneChunkLoaderFactory::createChunkLoader( QgsChunkNode *node )
const
232 return new QgsTiledSceneChunkLoader( node, mIndex, *
this, mZValueScale, mZValueOffset );
235QgsChunkNode *QgsTiledSceneChunkLoaderFactory::nodeForTile(
const QgsTiledSceneTile &t,
const QgsChunkNodeId &nodeId, QgsChunkNode *parent )
const
237 QgsChunkNode *node =
nullptr;
241 QgsVector3D v0( mRenderContext.extent().xMinimum(), mRenderContext.extent().yMinimum(), -100 );
242 QgsVector3D v1( mRenderContext.extent().xMaximum(), mRenderContext.extent().yMaximum(), +100 );
245 node =
new QgsChunkNode( nodeId, box3D, err, parent );
252 node =
new QgsChunkNode( nodeId, box, t.
geometricError(), parent );
260QgsChunkNode *QgsTiledSceneChunkLoaderFactory::createRootNode()
const
263 return nodeForTile( t, QgsChunkNodeId( t.
id() ),
nullptr );
267QVector<QgsChunkNode *> QgsTiledSceneChunkLoaderFactory::createChildren( QgsChunkNode *node )
const
269 QVector<QgsChunkNode *> children;
270 const long long indexTileId = node->tileId().uniqueId;
275 const QVector<long long> childIds = mIndex.childTileIds( indexTileId );
276 for (
long long childId : childIds )
278 const QgsChunkNodeId chId( childId );
291 const QgsPointXY c = mRenderContext.extent().center();
294 const double *half = obb.
halfAxes();
298 half[0], half[3], half[6], 0,
299 half[1], half[4], half[7], 0,
300 half[2], half[5], half[8], 0,
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 )
314 QgsChunkNode *nChild = nodeForTile( t, chId, node );
315 children.append( nChild );
320bool QgsTiledSceneChunkLoaderFactory::canCreateChildren( QgsChunkNode *node )
322 long long nodeId = node->tileId().uniqueId;
323 if ( mFutureHierarchyFetches.contains( nodeId ) || mPendingHierarchyFetches.contains( nodeId ) )
328 mFutureHierarchyFetches.insert( nodeId );
336 const QVector<long long> childIds = mIndex.childTileIds( nodeId );
337 for (
long long childId : childIds )
339 if ( mFutureHierarchyFetches.contains( childId ) || mPendingHierarchyFetches.contains( childId ) )
344 mFutureHierarchyFetches.insert( childId );
351void QgsTiledSceneChunkLoaderFactory::fetchHierarchyForNode(
long long nodeId, QgsChunkNode *origNode )
353 Q_ASSERT( !mPendingHierarchyFetches.contains( nodeId ) );
354 mFutureHierarchyFetches.remove( nodeId );
355 mPendingHierarchyFetches.insert( nodeId );
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();
363 futureWatcher->setFuture( QtConcurrent::run( [
this, nodeId] { mIndex.fetchHierarchy( nodeId ); } ) );
366void QgsTiledSceneChunkLoaderFactory::prepareChildren( QgsChunkNode *node )
368 long long nodeId = node->tileId().uniqueId;
369 if ( mFutureHierarchyFetches.contains( nodeId ) )
371 fetchHierarchyForNode( nodeId, node );
379 const QVector<long long> childIds = mIndex.childTileIds( nodeId );
380 for (
long long childId : childIds )
382 if ( mFutureHierarchyFetches.contains( childId ) )
384 fetchHierarchyForNode( childId, node );
392QgsTiledSceneLayerChunkedEntity::QgsTiledSceneLayerChunkedEntity(
397 double maximumScreenError,
398 bool showBoundingBoxes,
402 : QgsChunkedEntity( map, maximumScreenError, new QgsTiledSceneChunkLoaderFactory(
Qgs3DRenderContext::fromMapSettings( map ), index, tileCrs, layerCrs, zValueScale, zValueOffset ), true )
405 setShowBoundingBoxes( showBoundingBoxes );
408QgsTiledSceneLayerChunkedEntity::~QgsTiledSceneLayerChunkedEntity()
414int QgsTiledSceneLayerChunkedEntity::pendingJobsCount()
const
416 return QgsChunkedEntity::pendingJobsCount() +
static_cast<QgsTiledSceneChunkLoaderFactory *
>( mChunkLoaderFactory )->mPendingHierarchyFetches.count();
419QList<QgsRayCastHit> QgsTiledSceneLayerChunkedEntity::rayIntersection(
const QgsRay3D &ray,
const QgsRayCastContext &context )
const
429 QList<QgsRayCastHit> result;
431 QVector3D intersectionPoint;
432 QgsChunkNode *minNode =
nullptr;
433 int minTriangleIndex = -1;
435 const QList<QgsChunkNode *> active = activeNodes();
436 for ( QgsChunkNode *node : active )
449 const QList<Qt3DRender::QGeometryRenderer *> rendLst = node->entity()->findChildren<Qt3DRender::QGeometryRenderer *>();
450 for ( Qt3DRender::QGeometryRenderer *rend : rendLst )
452 QVector3D nodeIntPoint;
453 int triangleIndex = -1;
454 QgsGeoTransform *nodeGeoTransform = node->entity()->findChild<QgsGeoTransform *>();
455 Q_ASSERT( nodeGeoTransform );
462 float dist = ( ray.
origin() - nodeIntPoint ).length();
463 if ( minDist < 0 || dist < minDist )
467 minTriangleIndex = triangleIndex;
468 intersectionPoint = nodeIntPoint;
480 vm[u
"node_id"_s] = tile.
id();
482 vm[u
"node_content"_s] = tile.
resources().value( u
"content"_s );
483 vm[u
"triangle_index"_s] = minTriangleIndex;
487 hit.
setMapCoordinates( mMapSettings->worldToMapCoordinates( intersectionPoint ) );
489 result.append( hit );
492 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< QgsCesiumUtils::TileContents > extractTileContent(const QByteArray &tileContent)
Parses tile content and returns a list of TileContents.
Represents a coordinate reference system (CRS).
Qgis::DistanceUnit mapUnits
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.