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