QGIS API Documentation 3.40.0-Bratislava (b56115d8743)
Loading...
Searching...
No Matches
qgsquantizedmeshterraingenerator.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsterraingenerator.h
3 --------------------------------------
4 Date : August 2024
5 Copyright : (C) 2024 by David Koňařík
6 Email : dvdkon at konarici dot cz
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#include "qgschunkloader.h"
18#include "qgschunknode.h"
20#include "qgslogger.h"
21#include "qgsmesh3dentity_p.h"
22#include "qgsmeshlayerutils.h"
24#include "qgsproject.h"
27#include "qgsrectangle.h"
30#include "qgstiledsceneindex.h"
31#include "qgstiledscenelayer.h"
32#include "qgstiledscenetile.h"
33#include "qgstiles.h"
34#include "qgstriangularmesh.h"
35#include "qgsgltf3dutils.h"
36#include "qgsterrainentity.h"
37#include "qgs3dmapsettings.h"
38#include "qgsvector3d.h"
39#include "qgsapplication.h"
40#include <qcomponent.h>
41#include <qdiffusespecularmaterial.h>
42#include <qentity.h>
43#include <qglobal.h>
44#include <qnamespace.h>
45#include <qphongmaterial.h>
46#include <qtconcurrentrun.h>
47#include <qtexturematerial.h>
48
50
51class QgsQuantizedMeshTerrainChunkLoader : public QgsTerrainTileLoader
52{
53 Q_OBJECT
54 public:
55 QgsQuantizedMeshTerrainChunkLoader(
56 QgsTerrainEntity *terrain, QgsChunkNode *node, long long tileId, QgsTiledSceneIndex index, const QgsCoordinateTransform &tileCrsToMapCrs );
57 virtual Qt3DCore::QEntity *createEntity( Qt3DCore::QEntity *parent ) override;
58
59 protected:
60 virtual void onTextureLoaded() override;
61
62 private:
63 QgsTerrainTileEntity *mEntity = nullptr;
64 bool mMeshLoaded = false;
65 bool mTextureLoaded = false;
66 std::mutex mFinishedMutex;
67};
68
69QgsQuantizedMeshTerrainChunkLoader::QgsQuantizedMeshTerrainChunkLoader( QgsTerrainEntity *terrain_, QgsChunkNode *node, long long tileId, QgsTiledSceneIndex index, const QgsCoordinateTransform &tileCrsToMapCrs )
70 : QgsTerrainTileLoader( terrain_, node )
71{
72 loadTexture(); // Start loading texture
73
74 // Access terrain only on the original thread.
75 Qgs3DMapSettings *map = terrain()->mapSettings();
76 double vertScale = map->terrainVerticalScale();
77 QgsVector3D mapOrigin = map->origin();
78 bool shadingEnabled = map->isTerrainShadingEnabled();
79
80 QThreadPool::globalInstance()->start( [ this, node, tileId, index, tileCrsToMapCrs, vertScale, mapOrigin, shadingEnabled ]()
81 {
82 if ( tileId == QgsQuantizedMeshIndex::ROOT_TILE_ID )
83 {
84 // Nothing to load for imaginary root tile
85 emit finished();
86 return;
87 }
88
89 // We need to copy index, since capture makes it const. It's just a wrapped smart pointer anyway.
90 QgsTiledSceneIndex index2 = index;
91 QgsTiledSceneTile tile = index2.getTile( tileId );
92
93 QString uri = tile.resources().value( QStringLiteral( "content" ) ).toString();
94 Q_ASSERT( !uri.isEmpty() );
95
96 uri = tile.baseUrl().resolved( uri ).toString();
97 QByteArray content = index2.retrieveContent( uri );
98
99 QgsGltf3DUtils::EntityTransform entityTransform;
100 entityTransform.tileTransform = ( tile.transform() ? *tile.transform() : QgsMatrix4x4() );
101 entityTransform.sceneOriginTargetCrs = mapOrigin;
102 entityTransform.ecefToTargetCrs = &tileCrsToMapCrs;
103 entityTransform.gltfUpAxis = static_cast< Qgis::Axis >( tile.metadata().value( QStringLiteral( "gltfUpAxis" ), static_cast< int >( Qgis::Axis::Y ) ).toInt() );
104
105 try
106 {
107 QgsAABB bbox = node->bbox();
108 QgsQuantizedMeshTile qmTile( content );
109 qmTile.removeDegenerateTriangles();
110
111 // We now know the exact height range of the tile, set it to the node.
112 node->setExactBbox(
113 QgsAABB(
114 // Note that in the 3D view, Y is up!
115 bbox.xMin, qmTile.mHeader.MinimumHeight * vertScale, bbox.zMin,
116 bbox.xMax, qmTile.mHeader.MaximumHeight * vertScale, bbox.zMax ) );
117
118 if ( shadingEnabled && qmTile.mNormalCoords.size() == 0 )
119 {
120 qmTile.generateNormals();
121 }
122
123 tinygltf::Model model = qmTile.toGltf( true, 100, true );
124
125 QStringList errors;
126 Qt3DCore::QEntity *gltfEntity = QgsGltf3DUtils::parsedGltfToEntity( model, entityTransform, uri, &errors );
127 if ( !errors.isEmpty() )
128 {
129 QgsDebugError( "gltf load errors: " + errors.join( '\n' ) );
130 emit finished();
131 return;
132 }
133
134 QgsTerrainTileEntity *terrainEntity = new QgsTerrainTileEntity( node->tileId() );
135 // We count on only having one mesh.
136 Q_ASSERT( gltfEntity->children().size() == 1 );
137 gltfEntity->children()[0]->setParent( terrainEntity );
138 terrainEntity->moveToThread( QgsApplication::instance()->thread() );
139 mEntity = terrainEntity;
140 }
142 {
143 QgsDebugError( QStringLiteral( "Failed to parse tile from '%1'" ).arg( uri ) );
144 emit finished();
145 return;
146 }
147
148 {
149 std::lock_guard lock( mFinishedMutex );
150 if ( mTextureLoaded )
151 emit finished();
152 mMeshLoaded = true;
153 }
154 } );
155}
156
157Qt3DCore::QEntity *QgsQuantizedMeshTerrainChunkLoader::createEntity( Qt3DCore::QEntity *parent )
158{
159 if ( mEntity )
160 {
161 mEntity->setParent( parent );
162 Qt3DRender::QTexture2D *texture = createTexture( mEntity );
163
164 // Copied from part of QgsTerrainTileLoader::createTextureComponent, since we can't use that directly on the GLTF entity.
165 Qt3DRender::QMaterial *material = nullptr;
166 Qgs3DMapSettings *map = terrain()->mapSettings();
167 if ( map->isTerrainShadingEnabled() )
168 {
169 const QgsPhongMaterialSettings &shadingMaterial = map->terrainShadingMaterial();
170 Qt3DExtras::QDiffuseSpecularMaterial *diffuseMapMaterial = new Qt3DExtras::QDiffuseSpecularMaterial;
171 diffuseMapMaterial->setDiffuse( QVariant::fromValue( texture ) );
172 diffuseMapMaterial->setAmbient( shadingMaterial.ambient() );
173 diffuseMapMaterial->setSpecular( shadingMaterial.specular() );
174 diffuseMapMaterial->setShininess( shadingMaterial.shininess() );
175 material = diffuseMapMaterial;
176 }
177 else
178 {
179 Qt3DExtras::QTextureMaterial *textureMaterial = new Qt3DExtras::QTextureMaterial;
180 textureMaterial->setTexture( texture );
181 material = textureMaterial;
182 }
183 // Get the child that actually has the mesh and add the texture
184 Qt3DCore::QEntity *gltfEntity = mEntity->findChild<Qt3DCore::QEntity *>();
185 // Remove default material
186 auto oldMaterial = gltfEntity->componentsOfType<QgsMetalRoughMaterial>();
187 Q_ASSERT( oldMaterial.size() > 0 );
188 gltfEntity->removeComponent( oldMaterial[0] );
189 gltfEntity->addComponent( material );
190 }
191 return mEntity;
192}
193
194void QgsQuantizedMeshTerrainChunkLoader::onTextureLoaded()
195{
196 std::lock_guard lock( mFinishedMutex );
197 if ( mMeshLoaded )
198 emit finished();
199 mTextureLoaded = true;
200}
201
203
205{
206 mTerrain = t;
207 mTileCrsToMapCrs =
209 mMetadata->mCrs,
210 mTerrain->mapSettings()->crs(),
211 mTerrain->mapSettings()->transformContext() );
212}
213
215{
217 if ( mIsValid )
218 clone->setLayer( layer() );
219 else
220 clone->mLayerRef = mLayerRef; // Copy just the reference
221 return clone;
222}
223
228
230{
231 mMapExtent = extent;
232}
233
235{
236 return mMetadata->mBoundingVolume.bounds().toRectangle();
237}
238
240{
241 Q_UNUSED( map );
242 return mMetadata->geometricErrorAtZoom( -1 );
243}
244
245void QgsQuantizedMeshTerrainGenerator::rootChunkHeightRange( float &hMin, float &hMax ) const
246{
247 hMin = mMetadata->mBoundingVolume.bounds().zMinimum();
248 hMax = mMetadata->mBoundingVolume.bounds().xMaximum();
249}
250float QgsQuantizedMeshTerrainGenerator::heightAt( double x, double y, const Qgs3DRenderContext &context ) const
251{
252 // We fetch the most detailed tile containing the given point and then interpolate.
253 QgsTileMatrix zoomedMatrix = QgsTileMatrix::fromTileMatrix( mMetadata->mMaxZoom, mMetadata->mTileMatrix );
254 QgsPointXY point = QgsCoordinateTransform( context.crs(), mMetadata->mCrs, context.transformContext() ).transform( QgsPointXY( x, y ) );
255 QPointF tileCoords = zoomedMatrix.mapToTileCoordinates( point );
256 QgsTileXYZ tileXyz( floor( tileCoords.x() ), floor( tileCoords.y() ), mMetadata->mMaxZoom );
257 if ( !mMetadata->containsTile( tileXyz ) )
258 {
259 // This doesn't deal with a possible dataset where the whole extent doesn't
260 // have full coverage at maxZoom, but has coverage at a lower zoom level.
261 QgsDebugError( QStringLiteral( "Quantized Mesh layer doesn't contain max-zoom tile for %1, %2" ).arg( x ).arg( y ) );
262 return 0;
263 }
264 // TODO: Make heightAt asynchronous?
265 QgsTiledSceneIndex index = mIndex; // Copy to get rid of const
266 QgsTiledSceneTile sceneTile = index.getTile( QgsQuantizedMeshIndex::encodeTileId( tileXyz ) );
267 QString uri = sceneTile.resources().value( QStringLiteral( "content" ) ).toString();
268 Q_ASSERT( !uri.isEmpty() );
269
270 uri = sceneTile.baseUrl().resolved( uri ).toString();
271 QByteArray content = index.retrieveContent( uri );
272 QgsQuantizedMeshTile qmTile( content );
274 QgsMesh mesh = qmTile.toMesh( zoomedMatrix.tileExtent( tileXyz ) );
275 QgsTriangularMesh triMesh;
276 triMesh.update( &mesh );
277
278 return QgsMeshLayerUtils::interpolateZForPoint( triMesh, point.x(), point.y() );
279}
280
281void QgsQuantizedMeshTerrainGenerator::writeXml( QDomElement &elem ) const
282{
283 QDomDocument doc = elem.ownerDocument();
284
285 elem.setAttribute( QStringLiteral( "layer" ), mLayerRef.layerId );
286}
287
288void QgsQuantizedMeshTerrainGenerator::readXml( const QDomElement &elem )
289{
290 QgsMapLayerRef layerRef = QgsMapLayerRef( elem.attribute( QStringLiteral( "layer" ) ) );
291 // We can't call setLayer yet, the reference is not resolved
292 mLayerRef = layerRef;
293}
294
296{
297 mLayerRef.resolve( &project );
298 setLayer( layer() );
299}
300
301QgsChunkLoader *QgsQuantizedMeshTerrainGenerator::createChunkLoader( QgsChunkNode *node ) const
302{
303 long long tileId = QgsQuantizedMeshIndex::encodeTileId( nodeIdToTile( node->tileId() ) );
304 return new QgsQuantizedMeshTerrainChunkLoader( mTerrain, node, tileId, mIndex, mTileCrsToMapCrs );
305}
306
308{
309 return new QgsChunkNode(
310 {0, 0, 0},
311 mRootBbox, // Given to us by setupQuadtree()
312 mMetadata->geometricErrorAtZoom( -1 ) );
313}
314
315QVector<QgsChunkNode *> QgsQuantizedMeshTerrainGenerator::createChildren( QgsChunkNode *node ) const
316{
317 QVector<QgsChunkNode *> children;
318
319 for ( auto offset : std::vector<std::pair<int, int>> {{0, 0}, {0, 1}, {1, 0}, {1, 1}} )
320 {
321 QgsChunkNodeId childId(
322 node->tileId().d + 1,
323 node->tileId().x * 2 + offset.first,
324 node->tileId().y * 2 + offset.second
325 );
326 QgsTileXYZ tile = nodeIdToTile( childId );
327 if ( !mMetadata->containsTile( tile ) )
328 continue;
329
330 QgsTileMatrix zoomedTileMatrix = QgsTileMatrix::fromTileMatrix( tile.zoomLevel(), mMetadata->mTileMatrix );
331 QgsRectangle extent2d = zoomedTileMatrix.tileExtent( tile );
332 if ( !extent2d.intersects( mMapExtent ) )
333 continue; // Don't render terrain inside layer extent, but outside map extent
334 Q_ASSERT( mTerrain );
335 QgsRectangle mapExtent2d = mTileCrsToMapCrs.transform( extent2d );
336 QgsVector3D corner1 = mTerrain->mapSettings()->mapToWorldCoordinates(
337 {mapExtent2d.xMinimum(), mapExtent2d.yMinimum(), mMetadata->dummyZRange.lower()} );
338 QgsVector3D corner2 = mTerrain->mapSettings()->mapToWorldCoordinates(
339 {mapExtent2d.xMaximum(), mapExtent2d.yMaximum(), mMetadata->dummyZRange.upper()} );
340 children.push_back(
341 new QgsChunkNode(
342 childId,
343 QgsAABB(
344 corner1.x(), corner1.y(), corner1.z(),
345 corner2.x(), corner2.y(), corner2.z() ),
346 mMetadata->geometricErrorAtZoom( tile.zoomLevel() ),
347 node ) );
348 }
349
350 return children;
351}
352
354{
355 if ( !layer )
356 {
357 mIsValid = false;
358 return false;
359 }
360
361 mLayerRef = layer;
362 const QgsQuantizedMeshDataProvider *provider = qobject_cast<const QgsQuantizedMeshDataProvider *>( layer->dataProvider() );
363 if ( !provider )
364 {
365 QgsDebugError( "QgsQuantizedMeshTerrainGenerator provided with non-QM layer" );
366 return false;
367 }
368 mMetadata = provider->quantizedMeshMetadata();
369 mIndex = provider->index();
370
371 mTerrainTilingScheme = QgsTilingScheme( mMetadata->mTileMatrix.extent(), mMetadata->mCrs );
372
373 mIsValid = true;
374 return true;
375}
376
378{
379 return qobject_cast<QgsTiledSceneLayer *>( mLayerRef.get() );
380}
381
382QgsQuantizedMeshTerrainGenerator::QgsQuantizedMeshTerrainGenerator( QgsMapLayerRef layerRef, const QgsQuantizedMeshMetadata &metadata )
383 : mLayerRef( layerRef )
384 , mMetadata( metadata )
385{
386}
387
388QgsTileXYZ QgsQuantizedMeshTerrainGenerator::nodeIdToTile( QgsChunkNodeId nodeId ) const
389{
390 // nodeId zoom=0 is tile zoom=-1 to get unique root tile
391 if ( nodeId.d == 0 )
392 return { 0, 0, -1 };
393 return
394 {
395 nodeId.x,
396 mMetadata->mTileScheme == QStringLiteral( "tms" )
397 ? ( 1 << ( nodeId.d - 1 ) ) - nodeId.y - 1
398 : nodeId.y,
399 nodeId.d - 1 };
400}
401
402#include "qgsquantizedmeshterraingenerator.moc"
Axis
Cartesian axes.
Definition qgis.h:2283
@ Y
Y-axis.
double terrainVerticalScale() const
Returns vertical scale (exaggeration) of terrain.
bool isTerrainShadingEnabled() const
Returns whether terrain shading is enabled.
QgsPhongMaterialSettings terrainShadingMaterial() const
Returns terrain shading material.
QgsVector3D origin() const
Returns coordinates in map CRS at which 3D scene has origin (0,0,0).
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...
float xMax
Definition qgsaabb.h:89
float xMin
Definition qgsaabb.h:86
float zMax
Definition qgsaabb.h:91
float zMin
Definition qgsaabb.h:88
static QgsApplication * instance()
Returns the singleton instance of the QgsApplication.
Class for doing transforms between two map coordinate systems.
QgsPointXY transform(const QgsPointXY &point, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Transform the point from the source CRS to the destination CRS.
A simple 4x4 matrix implementation useful for transformation in 3D space.
void setDiffuse(const QColor &diffuse)
Sets diffuse color component.
QColor specular() const
Returns specular color component.
QColor ambient() const
Returns ambient color component.
double shininess() const
Returns shininess of the surface.
A class to represent a 2D point.
Definition qgspointxy.h:60
double y
Definition qgspointxy.h:64
double x
Definition qgspointxy.h:63
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition qgsproject.h:107
Exception thrown on failure to parse Quantized Mesh tile (malformed data)
virtual QgsChunkNode * createRootNode() const override
bool setLayer(QgsTiledSceneLayer *layer)
Set layer to take tiles from.
virtual void resolveReferences(const QgsProject &project) override
After read of XML, resolve references to any layers that have been read as layer IDs.
virtual void setTerrain(QgsTerrainEntity *t) override
Sets terrain entity for the generator (does not transfer ownership)
virtual QVector< QgsChunkNode * > createChildren(QgsChunkNode *node) const override
QgsTiledSceneLayer * layer() const
Returns the layer we take tiles from.
virtual QgsRectangle rootChunkExtent() const override
extent of the terrain's root chunk in terrain's CRS
virtual void writeXml(QDomElement &elem) const override
Write terrain generator's configuration to XML.
virtual void rootChunkHeightRange(float &hMin, float &hMax) const override
Returns height range of the root chunk in world coordinates.
virtual QgsTerrainGenerator::Type type() const override
What texture generator implementation is this.
virtual QgsTerrainGenerator * clone() const override
Makes a copy of the current instance.
virtual float rootChunkError(const Qgs3DMapSettings &map) const override
Returns error of the root chunk in world coordinates.
virtual QgsChunkLoader * createChunkLoader(QgsChunkNode *node) const override
virtual void readXml(const QDomElement &elem) override
Read terrain generator's configuration from XML.
virtual float heightAt(double x, double y, const Qgs3DRenderContext &context) const override
Returns height at (x,y) in map's CRS.
virtual void setExtent(const QgsRectangle &extent) override
sets the extent of the terrain in terrain's CRS
A rectangle specified with double values.
double xMinimum() const
Returns the x minimum value (left side of rectangle).
bool intersects(const QgsRectangle &rect) const
Returns true when rectangle intersects with other rectangle.
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
double xMaximum() const
Returns the x maximum value (right side of rectangle).
double yMaximum() const
Returns the y maximum value (top side of rectangle).
Type
Enumeration of the available terrain generators.
@ QuantizedMesh
Terrain is built from quantized mesh tiles.
QgsTilingScheme mTerrainTilingScheme
Tiling scheme of the terrain.
virtual QgsRectangle extent() const
extent of the terrain in terrain's CRS, might be non-square and smaller than rootChunkExtent()
QgsTerrainEntity * mTerrain
Defines a matrix of tiles for a single zoom level: it is defined by its size (width *.
Definition qgstiles.h:136
QgsRectangle tileExtent(QgsTileXYZ id) const
Returns extent of the given tile in this matrix.
Definition qgstiles.cpp:81
QPointF mapToTileCoordinates(const QgsPointXY &mapPoint) const
Returns row/column coordinates (floating point number) from the given point in map coordinates.
Definition qgstiles.cpp:121
static QgsTileMatrix fromTileMatrix(int zoomLevel, const QgsTileMatrix &tileMatrix)
Returns a tile matrix based on another one.
Definition qgstiles.cpp:61
Stores coordinates of a tile in a tile matrix set.
Definition qgstiles.h:40
int zoomLevel() const
Returns tile's zoom level (Z)
Definition qgstiles.h:53
An index for tiled scene data providers.
QByteArray retrieveContent(const QString &uri, QgsFeedback *feedback=nullptr)
Retrieves index content for the specified uri.
QgsTiledSceneTile getTile(long long id)
Returns the tile with matching id, or an invalid tile if the matching tile is not available.
Represents a map layer supporting display of tiled scene objects.
QgsTiledSceneDataProvider * dataProvider() override
Returns the layer's data provider, it may be nullptr.
Represents an individual tile from a tiled scene data source.
QVariantMap resources() const
Returns the resources attached to the tile.
QVariantMap metadata() const
Returns additional metadata attached to the tile.
const QgsMatrix4x4 * transform() const
Returns the tile's transform.
QUrl baseUrl() const
Returns the tile's base URL.
Triangular/Derived Mesh is mesh with vertices in map coordinates.
bool update(QgsMesh *nativeMesh, const QgsCoordinateTransform &transform)
Constructs triangular mesh from layer's native mesh and transform to destination CRS.
Class for storage of 3D vectors similar to QVector3D, with the difference that it uses double precisi...
Definition qgsvector3d.h:31
double y() const
Returns Y coordinate.
Definition qgsvector3d.h:50
double z() const
Returns Z coordinate.
Definition qgsvector3d.h:52
double x() const
Returns X coordinate.
Definition qgsvector3d.h:48
#define QgsDebugError(str)
Definition qgslogger.h:38
_LayerRef< QgsMapLayer > QgsMapLayerRef
Mesh - vertices, edges and faces.
QgsMesh toMesh(QgsRectangle tileBounds)
TYPE * get() const
Returns a pointer to the layer, or nullptr if the reference has not yet been matched to a layer.
TYPE * resolve(const QgsProject *project)
Resolves the map layer by attempting to find a layer with matching ID within a project.
QString layerId
Original layer ID.