38#include <Qt3DCore/QEntity>
39#include <Qt3DRender/QGeometryRenderer>
40#include <QtConcurrentRun>
42#include "moc_qgstiledscenechunkloader_p.cpp"
44using namespace Qt::StringLiterals;
48size_t qHash(
const QgsChunkNodeId &n )
63 return bounds.
width() > 1e5 || bounds.
height() > 1e5 || bounds.
depth() > 1e5;
68QgsTiledSceneChunkLoader::QgsTiledSceneChunkLoader(
72 .mRenderContext = context,
74 .mZValueScale = zValueScale,
75 .mZValueOffset = zValueOffset,
76 .mLayerCrs = layerCrs,
81QFuture<QgsChunkLoaderResult> QgsTiledSceneChunkLoader::loadChunk( QgsChunkNode *node )
83 const QgsChunkNodeId tileId = node->tileId();
84 const QgsVector3D chunkOrigin = node->box3D().center();
86 return QtConcurrent::run( [data = mData, tileId, chunkOrigin, isGlobe]()
mutable -> QgsChunkLoaderResult {
92 if ( !isGlobe && hasLargeBounds( tile, data.mBoundsTransform ) )
93 return QgsChunkLoaderResult::sEmpty;
95 QString uri = tile.
resources().value( u
"content"_s ).toString();
100 return QgsChunkLoaderResult::sEmpty;
103 uri = tile.
baseUrl().resolved( uri ).toString();
104 QByteArray content = data.mIndex.retrieveContent( uri );
105 if ( content.isEmpty() )
109 return QgsChunkLoaderResult::sEmpty;
112 QgsGltf3DUtils::EntityTransform entityTransform;
114 entityTransform.chunkOriginTargetCrs = chunkOrigin;
115 entityTransform.ecefToTargetCrs = &data.mBoundsTransform;
116 entityTransform.zValueScale = data.mZValueScale;
117 entityTransform.zValueOffset = data.mZValueOffset;
120 const QString &format = tile.
metadata().value( u
"contentFormat"_s ).value<QString>();
122 Qt3DCore::QEntity *entity =
nullptr;
123 if ( format ==
"quantizedmesh"_L1 )
128 qmTile.removeDegenerateTriangles();
129 tinygltf::Model model = qmTile.toGltf(
true, 100 );
130 entity = QgsGltf3DUtils::parsedGltfToEntity( model, entityTransform, uri, data.mRenderContext, &errors );
134 errors.append( u
"Failed to parse tile from '%1'"_s.arg( uri ) );
137 else if ( format ==
"cesiumtiles"_L1 )
140 if ( tileContents.isEmpty() )
141 return QgsChunkLoaderResult::sEmpty;
143 QVector<Qt3DCore::QEntity *> childEntities;
147 if ( innerContent.gltf.isEmpty() )
150 QgsGltf3DUtils::EntityTransform innerTransform = entityTransform;
151 innerTransform.tileTransform.translate( innerContent.rtcCenter );
154 tinygltf::Model model;
155 QString gltfErrors, gltfWarnings;
156 if ( !QgsGltfUtils::loadGltfModel( innerContent.gltf, model, &gltfErrors, &gltfWarnings ) )
158 errors.append( u
"GLTF load error: "_s + gltfErrors );
163 const auto instancedPrimitives =
QgsCesiumUtils::resolveInstancing( model, innerContent.instancing, innerTransform.gltfUpAxis, rawTileTransform, innerContent.rtcCenter );
165 if ( instancedPrimitives.isEmpty() )
168 Qt3DCore::QEntity *e = QgsGltf3DUtils::parsedGltfToEntity( model, innerTransform, uri, data.mRenderContext, &errors );
176 childEntities << QgsGltf3DUtils::createInstancedEntities( model, instancedPrimitives, innerTransform, uri, materialContext, &errors );
178 if ( !innerContent.instancing.has_value() )
182 Qt3DCore::QEntity *nonInstancedEntity = QgsGltf3DUtils::parsedGltfToEntity( model, innerTransform, uri, data.mRenderContext, &errors );
183 if ( nonInstancedEntity )
184 childEntities << nonInstancedEntity;
188 if ( childEntities.size() == 1 )
190 entity = childEntities[0];
194 entity =
new Qt3DCore::QEntity;
195 for ( Qt3DCore::QEntity *e : childEntities )
196 e->setParent( entity );
200 else if ( format ==
"draco"_L1 )
202 QgsGltfUtils::I3SNodeContext i3sContext;
203 i3sContext.initFromTile( tile, data.mLayerCrs, data.mBoundsTransform.sourceCrs(), data.mRenderContext.transformContext() );
205 QString dracoLoadError;
206 tinygltf::Model model;
207 if ( !QgsGltfUtils::loadDracoModel( content, i3sContext, model, &dracoLoadError ) )
209 errors.append( dracoLoadError );
210 return QgsChunkLoaderResult::sEmpty;
213 entity = QgsGltf3DUtils::parsedGltfToEntity( model, entityTransform, QString(), data.mRenderContext, &errors );
216 return QgsChunkLoaderResult::sEmpty;
219 if ( !errors.isEmpty() )
226 QgsGeoTransform *transform =
new QgsGeoTransform;
227 transform->setGeoTranslation( chunkOrigin );
228 entity->addComponent( transform );
233 return QgsChunkLoaderResult { [entity]( Qt3DCore::QEntity *parent ) {
236 entity->setParent( parent );
242QgsChunkNode *QgsTiledSceneChunkLoader::nodeForTile(
const QgsTiledSceneTile &t,
const QgsChunkNodeId &nodeId, QgsChunkNode *parent )
const
244 QgsChunkNode *node =
nullptr;
248 QgsVector3D v0( mData.mRenderContext.extent().xMinimum(), mData.mRenderContext.extent().yMinimum(), -100 );
249 QgsVector3D v1( mData.mRenderContext.extent().xMaximum(), mData.mRenderContext.extent().yMaximum(), +100 );
252 node =
new QgsChunkNode( nodeId, box3D, err, parent );
259 node =
new QgsChunkNode( nodeId, box, t.
geometricError(), parent );
267QgsChunkNode *QgsTiledSceneChunkLoader::createRootNode()
const
270 return nodeForTile( t, QgsChunkNodeId( t.
id() ),
nullptr );
274QFuture<QVector<QgsChunkNode *>> QgsTiledSceneChunkLoader::createChildren( QgsChunkNode *node )
276 return prepareChildren( node ).then(
this, [
this, node]() {
277 QVector<QgsChunkNode *> children;
278 const long long indexTileId = node->tileId().uniqueId;
283 const QVector<long long> childIds = mData.mIndex.childTileIds( indexTileId );
284 for (
long long childId : childIds )
286 const QgsChunkNodeId chId( childId );
293 if ( t.
metadata()[
"contentFormat"] ==
"cesiumtiles"_L1 && mData.mRenderContext.crs().type() !=
Qgis::CrsType::Geocentric && hasLargeBounds( t, mData.mBoundsTransform ) )
299 const QgsPointXY c = mData.mRenderContext.extent().center();
302 const double *half = obb.
halfAxes();
306 half[0], half[3], half[6], 0,
307 half[1], half[4], half[7], 0,
308 half[2], half[5], half[8], 0,
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 )
321 QgsChunkNode *nChild = nodeForTile( t, chId, node );
322 children.append( nChild );
328QFuture<void> QgsTiledSceneChunkLoader::fetchHierarchyForNode(
long long nodeId )
330 if (
auto it = mRunningHierarchyFetches.find( nodeId ); it != mRunningHierarchyFetches.end() )
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 ); } );
338QFuture<void> QgsTiledSceneChunkLoader::prepareChildren( QgsChunkNode *node )
340 long long nodeId = node->tileId().uniqueId;
342 return fetchHierarchyForNode( nodeId )
351 const QVector<long long> childIds = mData.mIndex.childTileIds( nodeId );
352 QList<QFuture<void>> fetches;
353 for (
long long childId : childIds )
355 fetches.append( fetchHierarchyForNode( childId ) );
357 return QtFuture::whenAll( fetches.begin(), fetches.end() );
365QgsTiledSceneLayerChunkedEntity::QgsTiledSceneLayerChunkedEntity(
370 double maximumScreenError,
371 bool showBoundingBoxes,
375 : QgsChunkedEntity( map, maximumScreenError, new QgsTiledSceneChunkLoader(
Qgs3DRenderContext::fromMapSettings( map ), index, tileCrs, layerCrs, zValueScale, zValueOffset ), true )
378 setShowBoundingBoxes( showBoundingBoxes );
381QgsTiledSceneLayerChunkedEntity::~QgsTiledSceneLayerChunkedEntity()
387QList<QgsRayCastHit> QgsTiledSceneLayerChunkedEntity::rayIntersection(
const QgsRay3D &ray,
const QgsRayCastContext &context )
const
397 QList<QgsRayCastHit> result;
399 QVector3D intersectionPoint;
400 QgsChunkNode *minNode =
nullptr;
401 int minTriangleIndex = -1;
403 const QList<QgsChunkNode *> active = activeNodes();
404 for ( QgsChunkNode *node : active )
417 const QList<Qt3DRender::QGeometryRenderer *> rendLst = node->entity()->findChildren<Qt3DRender::QGeometryRenderer *>();
418 for ( Qt3DRender::QGeometryRenderer *rend : rendLst )
420 QVector3D nodeIntPoint;
421 int triangleIndex = -1;
422 QgsGeoTransform *nodeGeoTransform = node->entity()->findChild<QgsGeoTransform *>();
423 Q_ASSERT( nodeGeoTransform );
430 float dist = ( ray.
origin() - nodeIntPoint ).length();
431 if ( minDist < 0 || dist < minDist )
435 minTriangleIndex = triangleIndex;
436 intersectionPoint = nodeIntPoint;
448 vm[u
"node_id"_s] = tile.
id();
450 vm[u
"node_content"_s] = tile.
resources().value( u
"content"_s );
451 vm[u
"triangle_index"_s] = minTriangleIndex;
455 hit.
setMapCoordinates( mMapSettings->worldToMapCoordinates( intersectionPoint ) );
457 result.append( hit );
461 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.
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
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.
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
size_t qHash(const QVariant &variant)
Hash for QVariant.
#define QgsDebugMsgLevel(str, level)
#define QgsDebugError(str)
#define QGIS_CHECK_MAIN_THREAD_ACCESS
Encapsulates the contents of a 3D tile.