QGIS API Documentation 3.99.0-Master (8e76e220402)
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
18#include "qgs3dmapsettings.h"
20#include "qgsapplication.h"
21#include "qgschunkloader.h"
22#include "qgschunknode.h"
24#include "qgsgeotransform.h"
25#include "qgsgltf3dutils.h"
26#include "qgslogger.h"
27#include "qgsmesh3dentity_p.h"
28#include "qgsmeshlayerutils.h"
30#include "qgsproject.h"
33#include "qgsrectangle.h"
34#include "qgsterrainentity.h"
37#include "qgstiledsceneindex.h"
38#include "qgstiledscenelayer.h"
39#include "qgstiledscenetile.h"
40#include "qgstiles.h"
41#include "qgstriangularmesh.h"
42#include "qgsvector3d.h"
43
44#include <QComponent>
45#include <QDiffuseSpecularMaterial>
46#include <QEntity>
47#include <QPhongMaterial>
48#include <QString>
49#include <QTextureMaterial>
50#include <QtGlobal>
51
52#include "moc_qgsquantizedmeshterraingenerator.cpp"
53
54using namespace Qt::StringLiterals;
55
57
58class QgsQuantizedMeshTerrainChunkLoader : public QgsTerrainTileLoader
59{
60 Q_OBJECT
61 public:
62 QgsQuantizedMeshTerrainChunkLoader(
63 QgsTerrainEntity *terrain, QgsChunkNode *node, long long tileId, QgsTiledSceneIndex index, const QgsCoordinateTransform &tileCrsToMapCrs
64 );
65 void start() override;
66 Qt3DCore::QEntity *createEntity( Qt3DCore::QEntity *parent ) override;
67
68 protected:
69 void onTextureLoaded() override;
70
71 private:
72 QgsTerrainTileEntity *mEntity = nullptr;
73 bool mMeshLoaded = false;
74 bool mTextureLoaded = false;
75 std::mutex mFinishedMutex;
76 long long mTileId;
77 QgsTiledSceneIndex mIndex;
78 QgsCoordinateTransform mTileCrsToMapCrs;
79};
80
81QgsQuantizedMeshTerrainChunkLoader::QgsQuantizedMeshTerrainChunkLoader( QgsTerrainEntity *terrain_, QgsChunkNode *node, long long tileId, QgsTiledSceneIndex index, const QgsCoordinateTransform &tileCrsToMapCrs )
82 : QgsTerrainTileLoader( terrain_, node )
83 , mTileId( tileId )
84 , mIndex( std::move( index ) )
85 , mTileCrsToMapCrs( tileCrsToMapCrs )
86{
87}
88
89void QgsQuantizedMeshTerrainChunkLoader::start()
90{
91 QgsChunkNode *node = chunk();
92
93 loadTexture(); // Start loading texture
94
95 // Access terrain only on the original thread.
96 Qgs3DMapSettings *map = terrain()->mapSettings();
97 double vertScale = map->terrainSettings()->verticalScale();
98 bool shadingEnabled = map->isTerrainShadingEnabled();
99 QgsVector3D chunkOrigin = node->box3D().center();
100
101 QThreadPool::globalInstance()->start( [this, node, vertScale, chunkOrigin, shadingEnabled]() {
102 if ( mTileId == QgsQuantizedMeshIndex::ROOT_TILE_ID )
103 {
104 // Nothing to load for imaginary root tile
105 emit finished();
106 return;
107 }
108
109 QgsTiledSceneTile tile = mIndex.getTile( mTileId );
110
111 QString uri = tile.resources().value( u"content"_s ).toString();
112 Q_ASSERT( !uri.isEmpty() );
113
114 uri = tile.baseUrl().resolved( uri ).toString();
115 QByteArray content = mIndex.retrieveContent( uri );
116
117 QgsGltf3DUtils::EntityTransform entityTransform;
118 entityTransform.tileTransform = ( tile.transform() ? *tile.transform() : QgsMatrix4x4() );
119 entityTransform.chunkOriginTargetCrs = chunkOrigin;
120 entityTransform.ecefToTargetCrs = &mTileCrsToMapCrs;
121 entityTransform.gltfUpAxis = static_cast<Qgis::Axis>( tile.metadata().value( u"gltfUpAxis"_s, static_cast<int>( Qgis::Axis::Y ) ).toInt() );
122
123 try
124 {
125 QgsBox3D box3D = node->box3D();
126 QgsQuantizedMeshTile qmTile( content );
127 qmTile.removeDegenerateTriangles();
128
129 // We now know the exact height range of the tile, set it to the node.
130 box3D.setZMinimum( qmTile.mHeader.MinimumHeight * vertScale );
131 box3D.setZMaximum( qmTile.mHeader.MaximumHeight * vertScale );
132 node->setExactBox3D( box3D );
133
134 if ( shadingEnabled && qmTile.mNormalCoords.size() == 0 )
135 {
136 qmTile.generateNormals();
137 }
138
139 tinygltf::Model model = qmTile.toGltf( true, 100, true );
140
141 QStringList errors;
142 Qt3DCore::QEntity *gltfEntity = QgsGltf3DUtils::parsedGltfToEntity( model, entityTransform, uri, &errors );
143 if ( !errors.isEmpty() )
144 {
145 QgsDebugError( "gltf load errors: " + errors.join( '\n' ) );
146 emit finished();
147 return;
148 }
149
150 QgsTerrainTileEntity *terrainEntity = new QgsTerrainTileEntity( node->tileId() );
151 // We count on only having one mesh.
152 Q_ASSERT( gltfEntity->children().size() == 1 );
153 gltfEntity->children()[0]->setParent( terrainEntity );
154
155 QgsGeoTransform *transform = new QgsGeoTransform;
156 transform->setGeoTranslation( chunkOrigin );
157 terrainEntity->addComponent( transform );
158
159 terrainEntity->moveToThread( QgsApplication::instance()->thread() );
160 mEntity = terrainEntity;
161 }
163 {
164 QgsDebugError( u"Failed to parse tile from '%1'"_s.arg( uri ) );
165 emit finished();
166 return;
167 }
168
169 {
170 std::lock_guard lock( mFinishedMutex );
171 if ( mTextureLoaded )
172 emit finished();
173 mMeshLoaded = true;
174 }
175 } );
176}
177
178Qt3DCore::QEntity *QgsQuantizedMeshTerrainChunkLoader::createEntity( Qt3DCore::QEntity *parent )
179{
180 if ( mEntity )
181 {
182 mEntity->setParent( parent );
183 Qt3DRender::QTexture2D *texture = createTexture( mEntity );
184
185 // Copied from part of QgsTerrainTileLoader::createTextureComponent, since we can't use that directly on the GLTF entity.
186 Qt3DRender::QMaterial *material = nullptr;
187 Qgs3DMapSettings *map = terrain()->mapSettings();
188 if ( map->isTerrainShadingEnabled() )
189 {
190 const QgsPhongMaterialSettings &shadingMaterial = map->terrainShadingMaterial();
191 Qt3DExtras::QDiffuseSpecularMaterial *diffuseMapMaterial = new Qt3DExtras::QDiffuseSpecularMaterial;
192 diffuseMapMaterial->setDiffuse( QVariant::fromValue( texture ) );
193 diffuseMapMaterial->setAmbient( shadingMaterial.ambient() );
194 diffuseMapMaterial->setSpecular( shadingMaterial.specular() );
195 diffuseMapMaterial->setShininess( shadingMaterial.shininess() );
196 material = diffuseMapMaterial;
197 }
198 else
199 {
200 Qt3DExtras::QTextureMaterial *textureMaterial = new Qt3DExtras::QTextureMaterial;
201 textureMaterial->setTexture( texture );
202 material = textureMaterial;
203 }
204 // Get the child that actually has the mesh and add the texture
205 Qt3DCore::QEntity *gltfEntity = mEntity->findChild<Qt3DCore::QEntity *>();
206 // Remove default material
207 auto oldMaterial = gltfEntity->componentsOfType<QgsMetalRoughMaterial>();
208 Q_ASSERT( oldMaterial.size() > 0 );
209 gltfEntity->removeComponent( oldMaterial[0] );
210 gltfEntity->addComponent( material );
211 }
212 return mEntity;
213}
214
215void QgsQuantizedMeshTerrainChunkLoader::onTextureLoaded()
216{
217 std::lock_guard lock( mFinishedMutex );
218 if ( mMeshLoaded )
219 emit finished();
220 mTextureLoaded = true;
221}
222
224
229
231{
232 mTerrain = t;
233 mTileCrsToMapCrs = QgsCoordinateTransform(
234 mMetadata->mCrs,
235 mTerrain->mapSettings()->crs(),
236 mTerrain->mapSettings()->transformContext()
237 );
238}
239
241{
243 if ( mIsValid )
244 clone->setLayer( layer() );
245 else
246 clone->mLayer = mLayer; // Copy just the reference
247 return clone;
248}
249
254
256{
257 mMapExtent = extent;
258}
259
261{
262 return mMetadata->mBoundingVolume.bounds().toRectangle();
263}
264
266{
267 Q_UNUSED( map );
268 return mMetadata->geometricErrorAtZoom( -1 );
269}
270
271void QgsQuantizedMeshTerrainGenerator::rootChunkHeightRange( float &hMin, float &hMax ) const
272{
273 hMin = mMetadata->mBoundingVolume.bounds().zMinimum();
274 hMax = mMetadata->mBoundingVolume.bounds().xMaximum();
275}
276float QgsQuantizedMeshTerrainGenerator::heightAt( double x, double y, const Qgs3DRenderContext &context ) const
277{
278 // We fetch the most detailed tile containing the given point and then interpolate.
279 QgsTileMatrix zoomedMatrix = QgsTileMatrix::fromTileMatrix( mMetadata->mMaxZoom, mMetadata->mTileMatrix );
280 QgsPointXY point = QgsCoordinateTransform( context.crs(), mMetadata->mCrs, context.transformContext() ).transform( QgsPointXY( x, y ) );
281 QPointF tileCoords = zoomedMatrix.mapToTileCoordinates( point );
282 QgsTileXYZ tileXyz( floor( tileCoords.x() ), floor( tileCoords.y() ), mMetadata->mMaxZoom );
283 if ( !mMetadata->containsTile( tileXyz ) )
284 {
285 // This doesn't deal with a possible dataset where the whole extent doesn't
286 // have full coverage at maxZoom, but has coverage at a lower zoom level.
287 QgsDebugError( u"Quantized Mesh layer doesn't contain max-zoom tile for %1, %2"_s.arg( x ).arg( y ) );
288 return 0;
289 }
290 // TODO: Make heightAt asynchronous?
291 QgsTiledSceneIndex index = mIndex; // Copy to get rid of const
292 QgsTiledSceneTile sceneTile = index.getTile( QgsQuantizedMeshIndex::encodeTileId( tileXyz ) );
293 QString uri = sceneTile.resources().value( u"content"_s ).toString();
294 Q_ASSERT( !uri.isEmpty() );
295
296 uri = sceneTile.baseUrl().resolved( uri ).toString();
297 QByteArray content = index.retrieveContent( uri );
298 QgsQuantizedMeshTile qmTile( content );
300 QgsMesh mesh = qmTile.toMesh( zoomedMatrix.tileExtent( tileXyz ) );
301 QgsTriangularMesh triMesh;
302 triMesh.update( &mesh );
303
304 return QgsMeshLayerUtils::interpolateZForPoint( triMesh, point.x(), point.y() );
305}
306
307QgsChunkLoader *QgsQuantizedMeshTerrainGenerator::createChunkLoader( QgsChunkNode *node ) const
308{
309 long long tileId = QgsQuantizedMeshIndex::encodeTileId( nodeIdToTile( node->tileId() ) );
310 return new QgsQuantizedMeshTerrainChunkLoader( mTerrain, node, tileId, mIndex, mTileCrsToMapCrs );
311}
312
314{
315 return new QgsChunkNode(
316 { 0, 0, 0 },
317 mRootBox3D, // Given to us by setupQuadtree()
318 mMetadata->geometricErrorAtZoom( -1 )
319 );
320}
321
322QVector<QgsChunkNode *> QgsQuantizedMeshTerrainGenerator::createChildren( QgsChunkNode *node ) const
323{
324 QVector<QgsChunkNode *> children;
325
326 for ( auto offset : std::vector<std::pair<int, int>> { { 0, 0 }, { 0, 1 }, { 1, 0 }, { 1, 1 } } )
327 {
328 QgsChunkNodeId childId(
329 node->tileId().d + 1,
330 node->tileId().x * 2 + offset.first,
331 node->tileId().y * 2 + offset.second
332 );
333 QgsTileXYZ tile = nodeIdToTile( childId );
334 if ( !mMetadata->containsTile( tile ) )
335 continue;
336
337 QgsTileMatrix zoomedTileMatrix = QgsTileMatrix::fromTileMatrix( tile.zoomLevel(), mMetadata->mTileMatrix );
338 QgsRectangle extent2d = zoomedTileMatrix.tileExtent( tile );
339 if ( !extent2d.intersects( mMapExtent ) )
340 continue; // Don't render terrain inside layer extent, but outside map extent
341 Q_ASSERT( mTerrain );
342 QgsRectangle mapExtent2d = mTileCrsToMapCrs.transform( extent2d );
343 QgsVector3D corner1( mapExtent2d.xMinimum(), mapExtent2d.yMinimum(), mMetadata->dummyZRange.lower() );
344 QgsVector3D corner2( mapExtent2d.xMaximum(), mapExtent2d.yMaximum(), mMetadata->dummyZRange.upper() );
345 children.push_back(
346 new QgsChunkNode(
347 childId,
348 QgsBox3D( corner1, corner2 ),
349 mMetadata->geometricErrorAtZoom( tile.zoomLevel() ),
350 node
351 )
352 );
353 }
354
355 return children;
356}
357
359{
360 if ( !layer )
361 {
362 mIsValid = false;
363 return false;
364 }
365
366 mLayer = layer;
367 const QgsQuantizedMeshDataProvider *provider = qobject_cast<const QgsQuantizedMeshDataProvider *>( layer->dataProvider() );
368 if ( !provider )
369 {
370 QgsDebugError( "QgsQuantizedMeshTerrainGenerator provided with non-QM layer" );
371 return false;
372 }
373 mMetadata = provider->quantizedMeshMetadata();
374 mIndex = provider->index();
375
376 mTerrainTilingScheme = QgsTilingScheme( mMetadata->mTileMatrix.extent(), mMetadata->mCrs );
377
378 mIsValid = true;
379 return true;
380}
381
386
387QgsTileXYZ QgsQuantizedMeshTerrainGenerator::nodeIdToTile( QgsChunkNodeId nodeId ) const
388{
389 // nodeId zoom=0 is tile zoom=-1 to get unique root tile
390 if ( nodeId.d == 0 )
391 return { 0, 0, -1 };
392 return {
393 nodeId.x,
394 mMetadata->mTileScheme == u"tms"_s
395 ? ( 1 << ( nodeId.d - 1 ) ) - nodeId.y - 1
396 : nodeId.y,
397 nodeId.d - 1
398 };
399}
400
401#include "qgsquantizedmeshterraingenerator.moc"
Axis
Cartesian axes.
Definition qgis.h:2509
@ Y
Y-axis.
Definition qgis.h:2511
Definition of the world.
const QgsAbstractTerrainSettings * terrainSettings() const
Returns the terrain settings.
bool isTerrainShadingEnabled() const
Returns whether terrain shading is enabled.
QgsPhongMaterialSettings terrainShadingMaterial() const
Returns terrain shading material.
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...
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:45
void setZMinimum(double z)
Sets the minimum z value.
Definition qgsbox3d.cpp:94
void setZMaximum(double z)
Sets the maximum z value.
Definition qgsbox3d.cpp:99
Handles coordinate transforms between two 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.
Basic shading material used for rendering based on the Phong shading model with three color component...
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.
Represents a 2D point.
Definition qgspointxy.h:62
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
Exception thrown on failure to parse Quantized Mesh tile (malformed data).
bool setLayer(QgsTiledSceneLayer *layer)
Set layer to take tiles from.
void setTerrain(QgsTerrainEntity *t) override
Sets terrain entity for the generator (does not transfer ownership).
QVector< QgsChunkNode * > createChildren(QgsChunkNode *node) const override
QgsTiledSceneLayer * layer() const
Returns the layer we take tiles from.
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.
void rootChunkHeightRange(float &hMin, float &hMax) const override
Returns height range of the root chunk in world coordinates.
QgsTerrainGenerator::Type type() const override
What texture generator implementation is this.
QgsTerrainGenerator * clone() const override
Makes a copy of the current instance.
float rootChunkError(const Qgs3DMapSettings &map) const override
Returns error of the root chunk in world coordinates.
QgsChunkLoader * createChunkLoader(QgsChunkNode *node) const override
float heightAt(double x, double y, const Qgs3DRenderContext &context) const override
Returns height at (x,y) in map's CRS.
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
Base class for generators of terrain.
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:162
QgsRectangle tileExtent(QgsTileXYZ id) const
Returns extent of the given tile in this matrix.
Definition qgstiles.cpp:85
QPointF mapToTileCoordinates(const QgsPointXY &mapPoint) const
Returns row/column coordinates (floating point number) from the given point in map coordinates.
Definition qgstiles.cpp:125
static QgsTileMatrix fromTileMatrix(int zoomLevel, const QgsTileMatrix &tileMatrix)
Returns a tile matrix based on another one.
Definition qgstiles.cpp:65
Stores coordinates of a tile in a tile matrix set.
Definition qgstiles.h:43
int zoomLevel() const
Returns tile's zoom level (Z).
Definition qgstiles.h:56
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.
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.
Encapsulates tiling schemes (just like with WMTS / TMS / XYZ layers).
A triangular/derived 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.
A 3D vector (similar to QVector3D) with the difference that it uses double precision instead of singl...
Definition qgsvector3d.h:33
#define QgsDebugError(str)
Definition qgslogger.h:59
Mesh - vertices, edges and faces.
QgsMesh toMesh(QgsRectangle tileBounds)