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