QGIS API Documentation 3.41.0-Master (45a0abf3bec)
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 "moc_qgsquantizedmeshterraingenerator.cpp"
18#include "qgschunkloader.h"
19#include "qgschunknode.h"
21#include "qgslogger.h"
22#include "qgsmesh3dentity_p.h"
23#include "qgsmeshlayerutils.h"
25#include "qgsproject.h"
28#include "qgsrectangle.h"
31#include "qgstiledsceneindex.h"
32#include "qgstiledscenelayer.h"
33#include "qgstiledscenetile.h"
34#include "qgstiles.h"
35#include "qgstriangularmesh.h"
36#include "qgsgltf3dutils.h"
37#include "qgsterrainentity.h"
38#include "qgs3dmapsettings.h"
39#include "qgsvector3d.h"
40#include "qgsapplication.h"
41#include <qcomponent.h>
42#include <qdiffusespecularmaterial.h>
43#include <qentity.h>
44#include <qglobal.h>
45#include <qnamespace.h>
46#include <qphongmaterial.h>
47#include <qtconcurrentrun.h>
48#include <qtexturematerial.h>
49
51
52class QgsQuantizedMeshTerrainChunkLoader : public QgsTerrainTileLoader
53{
54 Q_OBJECT
55 public:
56 QgsQuantizedMeshTerrainChunkLoader(
57 QgsTerrainEntity *terrain, QgsChunkNode *node, long long tileId, QgsTiledSceneIndex index, const QgsCoordinateTransform &tileCrsToMapCrs );
58 virtual Qt3DCore::QEntity *createEntity( Qt3DCore::QEntity *parent ) override;
59
60 protected:
61 virtual void onTextureLoaded() override;
62
63 private:
64 QgsTerrainTileEntity *mEntity = nullptr;
65 bool mMeshLoaded = false;
66 bool mTextureLoaded = false;
67 std::mutex mFinishedMutex;
68};
69
70QgsQuantizedMeshTerrainChunkLoader::QgsQuantizedMeshTerrainChunkLoader( QgsTerrainEntity *terrain_, QgsChunkNode *node, long long tileId, QgsTiledSceneIndex index, const QgsCoordinateTransform &tileCrsToMapCrs )
71 : QgsTerrainTileLoader( terrain_, node )
72{
73 loadTexture(); // Start loading texture
74
75 // Access terrain only on the original thread.
76 Qgs3DMapSettings *map = terrain()->mapSettings();
77 double vertScale = map->terrainVerticalScale();
78 QgsVector3D mapOrigin = map->origin();
79 bool shadingEnabled = map->isTerrainShadingEnabled();
80
81 QThreadPool::globalInstance()->start( [ this, node, tileId, index, tileCrsToMapCrs, vertScale, mapOrigin, shadingEnabled ]()
82 {
83 if ( tileId == QgsQuantizedMeshIndex::ROOT_TILE_ID )
84 {
85 // Nothing to load for imaginary root tile
86 emit finished();
87 return;
88 }
89
90 // We need to copy index, since capture makes it const. It's just a wrapped smart pointer anyway.
91 QgsTiledSceneIndex index2 = index;
92 QgsTiledSceneTile tile = index2.getTile( tileId );
93
94 QString uri = tile.resources().value( QStringLiteral( "content" ) ).toString();
95 Q_ASSERT( !uri.isEmpty() );
96
97 uri = tile.baseUrl().resolved( uri ).toString();
98 QByteArray content = index2.retrieveContent( uri );
99
100 QgsGltf3DUtils::EntityTransform entityTransform;
101 entityTransform.tileTransform = ( tile.transform() ? *tile.transform() : QgsMatrix4x4() );
102 entityTransform.sceneOriginTargetCrs = mapOrigin;
103 entityTransform.ecefToTargetCrs = &tileCrsToMapCrs;
104 entityTransform.gltfUpAxis = static_cast< Qgis::Axis >( tile.metadata().value( QStringLiteral( "gltfUpAxis" ), static_cast< int >( Qgis::Axis::Y ) ).toInt() );
105
106 try
107 {
108 QgsBox3D box3D = node->box3D();
109 QgsQuantizedMeshTile qmTile( content );
110 qmTile.removeDegenerateTriangles();
111
112 // We now know the exact height range of the tile, set it to the node.
113 box3D.setZMinimum( qmTile.mHeader.MinimumHeight * vertScale );
114 box3D.setZMaximum( qmTile.mHeader.MaximumHeight * vertScale );
115 node->setExactBox3D( box3D );
116
117 if ( shadingEnabled && qmTile.mNormalCoords.size() == 0 )
118 {
119 qmTile.generateNormals();
120 }
121
122 tinygltf::Model model = qmTile.toGltf( true, 100, true );
123
124 QStringList errors;
125 Qt3DCore::QEntity *gltfEntity = QgsGltf3DUtils::parsedGltfToEntity( model, entityTransform, uri, &errors );
126 if ( !errors.isEmpty() )
127 {
128 QgsDebugError( "gltf load errors: " + errors.join( '\n' ) );
129 emit finished();
130 return;
131 }
132
133 QgsTerrainTileEntity *terrainEntity = new QgsTerrainTileEntity( node->tileId() );
134 // We count on only having one mesh.
135 Q_ASSERT( gltfEntity->children().size() == 1 );
136 gltfEntity->children()[0]->setParent( terrainEntity );
137 terrainEntity->moveToThread( QgsApplication::instance()->thread() );
138 mEntity = terrainEntity;
139 }
141 {
142 QgsDebugError( QStringLiteral( "Failed to parse tile from '%1'" ).arg( uri ) );
143 emit finished();
144 return;
145 }
146
147 {
148 std::lock_guard lock( mFinishedMutex );
149 if ( mTextureLoaded )
150 emit finished();
151 mMeshLoaded = true;
152 }
153 } );
154}
155
156Qt3DCore::QEntity *QgsQuantizedMeshTerrainChunkLoader::createEntity( Qt3DCore::QEntity *parent )
157{
158 if ( mEntity )
159 {
160 mEntity->setParent( parent );
161 Qt3DRender::QTexture2D *texture = createTexture( mEntity );
162
163 // Copied from part of QgsTerrainTileLoader::createTextureComponent, since we can't use that directly on the GLTF entity.
164 Qt3DRender::QMaterial *material = nullptr;
165 Qgs3DMapSettings *map = terrain()->mapSettings();
166 if ( map->isTerrainShadingEnabled() )
167 {
168 const QgsPhongMaterialSettings &shadingMaterial = map->terrainShadingMaterial();
169 Qt3DExtras::QDiffuseSpecularMaterial *diffuseMapMaterial = new Qt3DExtras::QDiffuseSpecularMaterial;
170 diffuseMapMaterial->setDiffuse( QVariant::fromValue( texture ) );
171 diffuseMapMaterial->setAmbient( shadingMaterial.ambient() );
172 diffuseMapMaterial->setSpecular( shadingMaterial.specular() );
173 diffuseMapMaterial->setShininess( shadingMaterial.shininess() );
174 material = diffuseMapMaterial;
175 }
176 else
177 {
178 Qt3DExtras::QTextureMaterial *textureMaterial = new Qt3DExtras::QTextureMaterial;
179 textureMaterial->setTexture( texture );
180 material = textureMaterial;
181 }
182 // Get the child that actually has the mesh and add the texture
183 Qt3DCore::QEntity *gltfEntity = mEntity->findChild<Qt3DCore::QEntity *>();
184 // Remove default material
185 auto oldMaterial = gltfEntity->componentsOfType<QgsMetalRoughMaterial>();
186 Q_ASSERT( oldMaterial.size() > 0 );
187 gltfEntity->removeComponent( oldMaterial[0] );
188 gltfEntity->addComponent( material );
189 }
190 return mEntity;
191}
192
193void QgsQuantizedMeshTerrainChunkLoader::onTextureLoaded()
194{
195 std::lock_guard lock( mFinishedMutex );
196 if ( mMeshLoaded )
197 emit finished();
198 mTextureLoaded = true;
199}
200
202
204{
205 mTerrain = t;
206 mTileCrsToMapCrs =
208 mMetadata->mCrs,
209 mTerrain->mapSettings()->crs(),
210 mTerrain->mapSettings()->transformContext() );
211}
212
214{
216 if ( mIsValid )
217 clone->setLayer( layer() );
218 else
219 clone->mLayerRef = mLayerRef; // Copy just the reference
220 return clone;
221}
222
227
229{
230 mMapExtent = extent;
231}
232
234{
235 return mMetadata->mBoundingVolume.bounds().toRectangle();
236}
237
239{
240 Q_UNUSED( map );
241 return mMetadata->geometricErrorAtZoom( -1 );
242}
243
244void QgsQuantizedMeshTerrainGenerator::rootChunkHeightRange( float &hMin, float &hMax ) const
245{
246 hMin = mMetadata->mBoundingVolume.bounds().zMinimum();
247 hMax = mMetadata->mBoundingVolume.bounds().xMaximum();
248}
249float QgsQuantizedMeshTerrainGenerator::heightAt( double x, double y, const Qgs3DRenderContext &context ) const
250{
251 // We fetch the most detailed tile containing the given point and then interpolate.
252 QgsTileMatrix zoomedMatrix = QgsTileMatrix::fromTileMatrix( mMetadata->mMaxZoom, mMetadata->mTileMatrix );
253 QgsPointXY point = QgsCoordinateTransform( context.crs(), mMetadata->mCrs, context.transformContext() ).transform( QgsPointXY( x, y ) );
254 QPointF tileCoords = zoomedMatrix.mapToTileCoordinates( point );
255 QgsTileXYZ tileXyz( floor( tileCoords.x() ), floor( tileCoords.y() ), mMetadata->mMaxZoom );
256 if ( !mMetadata->containsTile( tileXyz ) )
257 {
258 // This doesn't deal with a possible dataset where the whole extent doesn't
259 // have full coverage at maxZoom, but has coverage at a lower zoom level.
260 QgsDebugError( QStringLiteral( "Quantized Mesh layer doesn't contain max-zoom tile for %1, %2" ).arg( x ).arg( y ) );
261 return 0;
262 }
263 // TODO: Make heightAt asynchronous?
264 QgsTiledSceneIndex index = mIndex; // Copy to get rid of const
265 QgsTiledSceneTile sceneTile = index.getTile( QgsQuantizedMeshIndex::encodeTileId( tileXyz ) );
266 QString uri = sceneTile.resources().value( QStringLiteral( "content" ) ).toString();
267 Q_ASSERT( !uri.isEmpty() );
268
269 uri = sceneTile.baseUrl().resolved( uri ).toString();
270 QByteArray content = index.retrieveContent( uri );
271 QgsQuantizedMeshTile qmTile( content );
273 QgsMesh mesh = qmTile.toMesh( zoomedMatrix.tileExtent( tileXyz ) );
274 QgsTriangularMesh triMesh;
275 triMesh.update( &mesh );
276
277 return QgsMeshLayerUtils::interpolateZForPoint( triMesh, point.x(), point.y() );
278}
279
280void QgsQuantizedMeshTerrainGenerator::writeXml( QDomElement &elem ) const
281{
282 QDomDocument doc = elem.ownerDocument();
283
284 elem.setAttribute( QStringLiteral( "layer" ), mLayerRef.layerId );
285}
286
287void QgsQuantizedMeshTerrainGenerator::readXml( const QDomElement &elem )
288{
289 QgsMapLayerRef layerRef = QgsMapLayerRef( elem.attribute( QStringLiteral( "layer" ) ) );
290 // We can't call setLayer yet, the reference is not resolved
291 mLayerRef = layerRef;
292}
293
295{
296 mLayerRef.resolve( &project );
297 setLayer( layer() );
298}
299
300QgsChunkLoader *QgsQuantizedMeshTerrainGenerator::createChunkLoader( QgsChunkNode *node ) const
301{
302 long long tileId = QgsQuantizedMeshIndex::encodeTileId( nodeIdToTile( node->tileId() ) );
303 return new QgsQuantizedMeshTerrainChunkLoader( mTerrain, node, tileId, mIndex, mTileCrsToMapCrs );
304}
305
307{
308 return new QgsChunkNode(
309 {0, 0, 0},
310 mRootBox3D, // Given to us by setupQuadtree()
311 mMetadata->geometricErrorAtZoom( -1 ) );
312}
313
314QVector<QgsChunkNode *> QgsQuantizedMeshTerrainGenerator::createChildren( QgsChunkNode *node ) const
315{
316 QVector<QgsChunkNode *> children;
317
318 for ( auto offset : std::vector<std::pair<int, int>> {{0, 0}, {0, 1}, {1, 0}, {1, 1}} )
319 {
320 QgsChunkNodeId childId(
321 node->tileId().d + 1,
322 node->tileId().x * 2 + offset.first,
323 node->tileId().y * 2 + offset.second
324 );
325 QgsTileXYZ tile = nodeIdToTile( childId );
326 if ( !mMetadata->containsTile( tile ) )
327 continue;
328
329 QgsTileMatrix zoomedTileMatrix = QgsTileMatrix::fromTileMatrix( tile.zoomLevel(), mMetadata->mTileMatrix );
330 QgsRectangle extent2d = zoomedTileMatrix.tileExtent( tile );
331 if ( !extent2d.intersects( mMapExtent ) )
332 continue; // Don't render terrain inside layer extent, but outside map extent
333 Q_ASSERT( mTerrain );
334 QgsRectangle mapExtent2d = mTileCrsToMapCrs.transform( extent2d );
335 QgsVector3D corner1( mapExtent2d.xMinimum(), mapExtent2d.yMinimum(), mMetadata->dummyZRange.lower() );
336 QgsVector3D corner2( mapExtent2d.xMaximum(), mapExtent2d.yMaximum(), mMetadata->dummyZRange.upper() );
337 children.push_back(
338 new QgsChunkNode(
339 childId,
340 QgsBox3D( corner1, corner2 ),
341 mMetadata->geometricErrorAtZoom( tile.zoomLevel() ),
342 node ) );
343 }
344
345 return children;
346}
347
349{
350 if ( !layer )
351 {
352 mIsValid = false;
353 return false;
354 }
355
356 mLayerRef = layer;
357 const QgsQuantizedMeshDataProvider *provider = qobject_cast<const QgsQuantizedMeshDataProvider *>( layer->dataProvider() );
358 if ( !provider )
359 {
360 QgsDebugError( "QgsQuantizedMeshTerrainGenerator provided with non-QM layer" );
361 return false;
362 }
363 mMetadata = provider->quantizedMeshMetadata();
364 mIndex = provider->index();
365
366 mTerrainTilingScheme = QgsTilingScheme( mMetadata->mTileMatrix.extent(), mMetadata->mCrs );
367
368 mIsValid = true;
369 return true;
370}
371
373{
374 return qobject_cast<QgsTiledSceneLayer *>( mLayerRef.get() );
375}
376
377QgsQuantizedMeshTerrainGenerator::QgsQuantizedMeshTerrainGenerator( QgsMapLayerRef layerRef, const QgsQuantizedMeshMetadata &metadata )
378 : mLayerRef( layerRef )
379 , mMetadata( metadata )
380{
381}
382
383QgsTileXYZ QgsQuantizedMeshTerrainGenerator::nodeIdToTile( QgsChunkNodeId nodeId ) const
384{
385 // nodeId zoom=0 is tile zoom=-1 to get unique root tile
386 if ( nodeId.d == 0 )
387 return { 0, 0, -1 };
388 return
389 {
390 nodeId.x,
391 mMetadata->mTileScheme == QStringLiteral( "tms" )
392 ? ( 1 << ( nodeId.d - 1 ) ) - nodeId.y - 1
393 : nodeId.y,
394 nodeId.d - 1 };
395}
396
397#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...
static QgsApplication * instance()
Returns the singleton instance of the QgsApplication.
A 3-dimensional box composed of x, y, z coordinates.
Definition qgsbox3d.h:43
void setZMinimum(double z)
Sets the minimum z value.
Definition qgsbox3d.cpp:88
void setZMaximum(double z)
Sets the maximum z value.
Definition qgsbox3d.cpp:93
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
#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.