QGIS API Documentation 4.1.0-Master (9af12b5a203)
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"
28#include "qgsmesh3dentity_p.h"
29#include "qgsmeshlayerutils.h"
31#include "qgsproject.h"
34#include "qgsrectangle.h"
35#include "qgsterrainentity.h"
38#include "qgstiledsceneindex.h"
39#include "qgstiledscenelayer.h"
40#include "qgstiledscenetile.h"
41#include "qgstiles.h"
42#include "qgstriangularmesh.h"
43#include "qgsvector3d.h"
44
45#include <QComponent>
46#include <QDiffuseSpecularMaterial>
47#include <QEntity>
48#include <QPhongMaterial>
49#include <QString>
50#include <QTextureMaterial>
51#include <QThreadPool>
52#include <QtGlobal>
53
54#include "moc_qgsquantizedmeshterraingenerator.cpp"
55
56using namespace Qt::StringLiterals;
57
59
60class QgsQuantizedMeshTerrainChunkLoader : public QgsTerrainTileLoader
61{
62 Q_OBJECT
63 public:
64 QgsQuantizedMeshTerrainChunkLoader( QgsTerrainEntity *terrain, QgsChunkNode *node, long long tileId, QgsTiledSceneIndex index, const QgsCoordinateTransform &tileCrsToMapCrs );
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(
82 QgsTerrainEntity *terrain_, QgsChunkNode *node, long long tileId, QgsTiledSceneIndex index, const QgsCoordinateTransform &tileCrsToMapCrs
83)
84 : QgsTerrainTileLoader( terrain_, node )
85 , mTileId( tileId )
86 , mIndex( std::move( index ) )
87 , mTileCrsToMapCrs( tileCrsToMapCrs )
88{}
89
90void QgsQuantizedMeshTerrainChunkLoader::start()
91{
92 QgsChunkNode *node = chunk();
93
94 loadTexture(); // Start loading texture
95
96 // Access terrain only on the original thread.
97 Qgs3DMapSettings *map = terrain()->mapSettings();
98 double vertScale = map->terrainSettings()->verticalScale();
99 bool shadingEnabled = map->isTerrainShadingEnabled();
100 QgsVector3D chunkOrigin = node->box3D().center();
101
103 QThreadPool::globalInstance()->start( [this, node, vertScale, chunkOrigin, shadingEnabled, context]() {
104 if ( mTileId == QgsQuantizedMeshIndex::ROOT_TILE_ID )
105 {
106 // Nothing to load for imaginary root tile
107 emit finished();
108 return;
109 }
110
111 QgsTiledSceneTile tile = mIndex.getTile( mTileId );
112
113 QString uri = tile.resources().value( u"content"_s ).toString();
114 Q_ASSERT( !uri.isEmpty() );
115
116 uri = tile.baseUrl().resolved( uri ).toString();
117 QByteArray content = mIndex.retrieveContent( uri );
118
119 QgsGltf3DUtils::EntityTransform entityTransform;
120 entityTransform.tileTransform = ( tile.transform() ? *tile.transform() : QgsMatrix4x4() );
121 entityTransform.chunkOriginTargetCrs = chunkOrigin;
122 entityTransform.ecefToTargetCrs = &mTileCrsToMapCrs;
123 entityTransform.gltfUpAxis = static_cast<Qgis::Axis>( tile.metadata().value( u"gltfUpAxis"_s, static_cast<int>( Qgis::Axis::Y ) ).toInt() );
124
125 try
126 {
127 QgsBox3D box3D = node->box3D();
128 QgsQuantizedMeshTile qmTile( content );
129 qmTile.removeDegenerateTriangles();
130
131 // We now know the exact height range of the tile, set it to the node.
132 box3D.setZMinimum( qmTile.mHeader.MinimumHeight * vertScale );
133 box3D.setZMaximum( qmTile.mHeader.MaximumHeight * vertScale );
134 node->setExactBox3D( box3D );
135
136 if ( shadingEnabled && qmTile.mNormalCoords.size() == 0 )
137 {
138 qmTile.generateNormals();
139 }
140
141 tinygltf::Model model = qmTile.toGltf( true, 100, true );
142
143 QStringList errors;
144 Qt3DCore::QEntity *gltfEntity = QgsGltf3DUtils::parsedGltfToEntity( model, entityTransform, uri, context, &errors );
145 if ( !errors.isEmpty() )
146 {
147 QgsDebugError( "gltf load errors: " + errors.join( '\n' ) );
148 emit finished();
149 return;
150 }
151
152 QgsTerrainTileEntity *terrainEntity = new QgsTerrainTileEntity( node->tileId() );
153 // We count on only having one mesh.
154 Q_ASSERT( gltfEntity->children().size() == 1 );
155 gltfEntity->children()[0]->setParent( terrainEntity );
156
157 QgsGeoTransform *transform = new QgsGeoTransform;
158 transform->setGeoTranslation( chunkOrigin );
159 terrainEntity->addComponent( transform );
160
161 terrainEntity->moveToThread( QgsApplication::instance()->thread() );
162 mEntity = terrainEntity;
163 }
165 {
166 QgsDebugError( u"Failed to parse tile from '%1'"_s.arg( uri ) );
167 emit finished();
168 return;
169 }
170
171 {
172 std::lock_guard lock( mFinishedMutex );
173 if ( mTextureLoaded )
174 emit finished();
175 mMeshLoaded = true;
176 }
177 } );
178}
179
180Qt3DCore::QEntity *QgsQuantizedMeshTerrainChunkLoader::createEntity( Qt3DCore::QEntity *parent )
181{
182 if ( mEntity )
183 {
184 mEntity->setParent( parent );
185 Qgs3DMapSettings *map = terrain()->mapSettings();
186
188
189 Qt3DRender::QTexture2D *texture = createTexture( mEntity, QgsMaterialContext::fromRenderContext( context ) );
190
191 // Copied from part of QgsTerrainTileLoader::createTextureComponent, since we can't use that directly on the GLTF entity.
192 Qt3DRender::QMaterial *material = nullptr;
193 if ( map->isTerrainShadingEnabled() )
194 {
195 const QgsPhongMaterialSettings &shadingMaterial = map->terrainShadingMaterial();
196 Qt3DExtras::QDiffuseSpecularMaterial *diffuseMapMaterial = new Qt3DExtras::QDiffuseSpecularMaterial;
197 diffuseMapMaterial->setDiffuse( QVariant::fromValue( texture ) );
198 diffuseMapMaterial->setAmbient( shadingMaterial.ambient() );
199 diffuseMapMaterial->setSpecular( shadingMaterial.specular() );
200 diffuseMapMaterial->setShininess( shadingMaterial.shininess() );
201 material = diffuseMapMaterial;
202 }
203 else
204 {
205 Qt3DExtras::QTextureMaterial *textureMaterial = new Qt3DExtras::QTextureMaterial;
206 textureMaterial->setTexture( texture );
207 material = textureMaterial;
208 }
209 // Get the child that actually has the mesh and add the texture
210 Qt3DCore::QEntity *gltfEntity = mEntity->findChild<Qt3DCore::QEntity *>();
211 // Remove default material
212 auto oldMaterial = gltfEntity->componentsOfType<QgsMetalRoughMaterial>();
213 Q_ASSERT( oldMaterial.size() > 0 );
214 gltfEntity->removeComponent( oldMaterial[0] );
215 gltfEntity->addComponent( material );
216 }
217 return mEntity;
218}
219
220void QgsQuantizedMeshTerrainChunkLoader::onTextureLoaded()
221{
222 std::lock_guard lock( mFinishedMutex );
223 if ( mMeshLoaded )
224 emit finished();
225 mTextureLoaded = true;
226}
227
229
234
236{
237 mTerrain = t;
238 mTileCrsToMapCrs = QgsCoordinateTransform( mMetadata->mCrs, mTerrain->mapSettings()->crs(), mTerrain->mapSettings()->transformContext() );
239}
240
242{
244 if ( mIsValid )
245 clone->setLayer( layer() );
246 else
247 clone->mLayer = mLayer; // Copy just the reference
248 return clone;
249}
250
255
257{
258 mMapExtent = extent;
259}
260
262{
263 return mMetadata->mBoundingVolume.bounds().toRectangle();
264}
265
267{
268 Q_UNUSED( map );
269 return mMetadata->geometricErrorAtZoom( -1 );
270}
271
272void QgsQuantizedMeshTerrainGenerator::rootChunkHeightRange( float &hMin, float &hMax ) const
273{
274 hMin = mMetadata->mBoundingVolume.bounds().zMinimum();
275 hMax = mMetadata->mBoundingVolume.bounds().xMaximum();
276}
277float QgsQuantizedMeshTerrainGenerator::heightAt( double x, double y, const Qgs3DRenderContext &context ) const
278{
279 // We fetch the most detailed tile containing the given point and then interpolate.
280 QgsTileMatrix zoomedMatrix = QgsTileMatrix::fromTileMatrix( mMetadata->mMaxZoom, mMetadata->mTileMatrix );
281 QgsPointXY point = QgsCoordinateTransform( context.crs(), mMetadata->mCrs, context.transformContext() ).transform( QgsPointXY( x, y ) );
282 QPointF tileCoords = zoomedMatrix.mapToTileCoordinates( point );
283 QgsTileXYZ tileXyz( floor( tileCoords.x() ), floor( tileCoords.y() ), mMetadata->mMaxZoom );
284 if ( !mMetadata->containsTile( tileXyz ) )
285 {
286 // This doesn't deal with a possible dataset where the whole extent doesn't
287 // have full coverage at maxZoom, but has coverage at a lower zoom level.
288 QgsDebugError( u"Quantized Mesh layer doesn't contain max-zoom tile for %1, %2"_s.arg( x ).arg( y ) );
289 return 0;
290 }
291 // TODO: Make heightAt asynchronous?
292 QgsTiledSceneIndex index = mIndex; // Copy to get rid of const
293 QgsTiledSceneTile sceneTile = index.getTile( QgsQuantizedMeshIndex::encodeTileId( tileXyz ) );
294 QString uri = sceneTile.resources().value( u"content"_s ).toString();
295 Q_ASSERT( !uri.isEmpty() );
296
297 uri = sceneTile.baseUrl().resolved( uri ).toString();
298 QByteArray content = index.retrieveContent( uri );
299 QgsQuantizedMeshTile qmTile( content );
301 QgsMesh mesh = qmTile.toMesh( zoomedMatrix.tileExtent( tileXyz ) );
302 QgsTriangularMesh triMesh;
303 triMesh.update( &mesh );
304
305 return QgsMeshLayerUtils::interpolateZForPoint( triMesh, point.x(), point.y() );
306}
307
308QgsChunkLoader *QgsQuantizedMeshTerrainGenerator::createChunkLoader( QgsChunkNode *node ) const
309{
310 long long tileId = QgsQuantizedMeshIndex::encodeTileId( nodeIdToTile( node->tileId() ) );
311 return new QgsQuantizedMeshTerrainChunkLoader( mTerrain, node, tileId, mIndex, mTileCrsToMapCrs );
312}
313
315{
316 return new QgsChunkNode(
317 { 0, 0, 0 },
318 mRootBox3D, // Given to us by setupQuadtree()
319 mMetadata->geometricErrorAtZoom( -1 )
320 );
321}
322
323QVector<QgsChunkNode *> QgsQuantizedMeshTerrainGenerator::createChildren( QgsChunkNode *node ) const
324{
325 QVector<QgsChunkNode *> children;
326
327 for ( auto offset : std::vector<std::pair<int, int>> { { 0, 0 }, { 0, 1 }, { 1, 0 }, { 1, 1 } } )
328 {
329 QgsChunkNodeId childId( node->tileId().d + 1, node->tileId().x * 2 + offset.first, node->tileId().y * 2 + offset.second );
330 QgsTileXYZ tile = nodeIdToTile( childId );
331 if ( !mMetadata->containsTile( tile ) )
332 continue;
333
334 QgsTileMatrix zoomedTileMatrix = QgsTileMatrix::fromTileMatrix( tile.zoomLevel(), mMetadata->mTileMatrix );
335 QgsRectangle extent2d = zoomedTileMatrix.tileExtent( tile );
336 if ( !extent2d.intersects( mMapExtent ) )
337 continue; // Don't render terrain inside layer extent, but outside map extent
338 Q_ASSERT( mTerrain );
339 QgsRectangle mapExtent2d = mTileCrsToMapCrs.transform( extent2d );
340 QgsVector3D corner1( mapExtent2d.xMinimum(), mapExtent2d.yMinimum(), mMetadata->dummyZRange.lower() );
341 QgsVector3D corner2( mapExtent2d.xMaximum(), mapExtent2d.yMaximum(), mMetadata->dummyZRange.upper() );
342 children.push_back( new QgsChunkNode( childId, QgsBox3D( corner1, corner2 ), mMetadata->geometricErrorAtZoom( tile.zoomLevel() ), node ) );
343 }
344
345 return children;
346}
347
349{
350 if ( !layer )
351 {
352 mIsValid = false;
353 return false;
354 }
355
356 mLayer = 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
376
377QgsTileXYZ QgsQuantizedMeshTerrainGenerator::nodeIdToTile( QgsChunkNodeId nodeId ) const
378{
379 // nodeId zoom=0 is tile zoom=-1 to get unique root tile
380 if ( nodeId.d == 0 )
381 return { 0, 0, -1 };
382 return { nodeId.x, mMetadata->mTileScheme == "tms"_L1 ? ( 1 << ( nodeId.d - 1 ) ) - nodeId.y - 1 : nodeId.y, nodeId.d - 1 };
383}
384
385#include "qgsquantizedmeshterraingenerator.moc"
Axis
Cartesian axes.
Definition qgis.h:2607
@ Y
Y-axis.
Definition qgis.h:2609
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.
static Qgs3DRenderContext fromMapSettings(const Qgs3DMapSettings *mapSettings)
Creates an initialized Qgs3DRenderContext instance from given Qgs3DMapSettings.
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.
static QgsMaterialContext fromRenderContext(const Qgs3DRenderContext &context)
Constructs a material context from the settings in a 3D render context.
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:171
QgsRectangle tileExtent(QgsTileXYZ id) const
Returns extent of the given tile in this matrix.
Definition qgstiles.cpp:84
QPointF mapToTileCoordinates(const QgsPointXY &mapPoint) const
Returns row/column coordinates (floating point number) from the given point in map coordinates.
Definition qgstiles.cpp:124
static QgsTileMatrix fromTileMatrix(int zoomLevel, const QgsTileMatrix &tileMatrix)
Returns a tile matrix based on another one.
Definition qgstiles.cpp:64
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:57
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)