QGIS API Documentation 4.3.0-Master (16eddb2670e)
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 "qgsfutureutils.h"
25#include "qgsgeotransform.h"
26#include "qgsgltf3dutils.h"
27#include "qgslogger.h"
29#include "qgsmesh3dentity_p.h"
30#include "qgsmeshlayerutils.h"
32#include "qgsproject.h"
35#include "qgsrectangle.h"
36#include "qgsterrainentity.h"
38#include "qgsthreadingutils.h"
39#include "qgstiledsceneindex.h"
40#include "qgstiledscenelayer.h"
41#include "qgstiledscenetile.h"
42#include "qgstiles.h"
43#include "qgstriangularmesh.h"
44#include "qgsvector3d.h"
45
46#include <QComponent>
47#include <QDiffuseSpecularMaterial>
48#include <QEntity>
49#include <QPhongMaterial>
50#include <QString>
51#include <QTextureMaterial>
52#include <QThreadPool>
53#include <QtConcurrentRun>
54#include <QtGlobal>
55
56#include "moc_qgsquantizedmeshterraingenerator.cpp"
57
58using namespace Qt::StringLiterals;
59
64
66{
67 mTerrain = t;
68 mTileCrsToMapCrs = QgsCoordinateTransform( mMetadata->mCrs, mTerrain->mapSettings()->crs(), mTerrain->mapSettings()->transformContext() );
69}
70
72{
74 if ( mIsValid )
75 clone->setLayer( layer() );
76 else
77 clone->mLayer = mLayer; // Copy just the reference
78 return clone;
79}
80
85
87{
88 mMapExtent = extent;
89}
90
92{
93 return mMetadata->mBoundingVolume.bounds().toRectangle();
94}
95
97{
98 Q_UNUSED( map );
99 return mMetadata->geometricErrorAtZoom( -1 );
100}
101
102void QgsQuantizedMeshTerrainGenerator::rootChunkHeightRange( float &hMin, float &hMax ) const
103{
104 hMin = mMetadata->mBoundingVolume.bounds().zMinimum();
105 hMax = mMetadata->mBoundingVolume.bounds().xMaximum();
106}
107float QgsQuantizedMeshTerrainGenerator::heightAt( double x, double y, const Qgs3DRenderContext &context ) const
108{
109 // We fetch the most detailed tile containing the given point and then interpolate.
110 QgsTileMatrix zoomedMatrix = QgsTileMatrix::fromTileMatrix( mMetadata->mMaxZoom, mMetadata->mTileMatrix );
111 QgsPointXY point = QgsCoordinateTransform( context.crs(), mMetadata->mCrs, context.transformContext() ).transform( QgsPointXY( x, y ) );
112 QPointF tileCoords = zoomedMatrix.mapToTileCoordinates( point );
113 QgsTileXYZ tileXyz( floor( tileCoords.x() ), floor( tileCoords.y() ), mMetadata->mMaxZoom );
114 if ( !mMetadata->containsTile( tileXyz ) )
115 {
116 // This doesn't deal with a possible dataset where the whole extent doesn't
117 // have full coverage at maxZoom, but has coverage at a lower zoom level.
118 QgsDebugError( u"Quantized Mesh layer doesn't contain max-zoom tile for %1, %2"_s.arg( x ).arg( y ) );
119 return 0;
120 }
121 // TODO: Make heightAt asynchronous?
122 QgsTiledSceneIndex index = mIndex; // Copy to get rid of const
123 QgsTiledSceneTile sceneTile = index.getTile( QgsQuantizedMeshIndex::encodeTileId( tileXyz ) );
124 QString uri = sceneTile.resources().value( u"content"_s ).toString();
125 Q_ASSERT( !uri.isEmpty() );
126
127 uri = sceneTile.baseUrl().resolved( uri ).toString();
128 QByteArray content = index.retrieveContent( uri );
129 QgsQuantizedMeshTile qmTile( content );
131 QgsMesh mesh = qmTile.toMesh( zoomedMatrix.tileExtent( tileXyz ) );
132 QgsTriangularMesh triMesh;
133 triMesh.update( &mesh );
134
135 return QgsMeshLayerUtils::interpolateZForPoint( triMesh, point.x(), point.y() );
136}
137
138QFuture<QgsChunkLoaderResult> QgsQuantizedMeshTerrainGenerator::loadChunk( QgsChunkNode *node )
139{
140 long long tileId = QgsQuantizedMeshIndex::encodeTileId( nodeIdToTile( node->tileId() ) );
141
142 // Access terrain only on the original thread.
143 Qgs3DMapSettings *map = mTerrain->mapSettings();
144 double vertScale = map->terrainSettings()->verticalScale();
145 bool shadingEnabled = map->isTerrainShadingEnabled();
146 QgsVector3D chunkOrigin = node->box3D().center();
148
149 auto textureFuture = loadTextureResources( node );
150 auto chunkDataFuture = QtConcurrent::run( &QgsQuantizedMeshTerrainGenerator::loadEntityInWorker, node, renderCtx, mIndex, mTileCrsToMapCrs, vertScale, shadingEnabled, tileId, chunkOrigin );
151
152 return QgsFutureUtils::combine( textureFuture, chunkDataFuture ).then( this, [this]( std::tuple<QgsTerrainGenerator::TerrainTextureResources, QgsTerrainTileEntity *> results ) {
153 QgsTerrainGenerator::TerrainTextureResources textureResources = std::get<0>( results );
154 QgsTerrainTileEntity *entity = std::get<1>( results );
155
156 return QgsChunkLoaderResult { std::bind_front( &QgsQuantizedMeshTerrainGenerator::finishEntity, this, entity, textureResources ) };
157 } );
158}
159
160QgsTerrainTileEntity *QgsQuantizedMeshTerrainGenerator::loadEntityInWorker(
161 QgsChunkNode *node, Qgs3DRenderContext renderCtx, QgsTiledSceneIndex index, QgsCoordinateTransform tileCrsToMapCrs, double vertScale, bool shadingEnabled, long long tileId, QgsVector3D chunkOrigin
162)
163{
164 if ( tileId == QgsQuantizedMeshIndex::ROOT_TILE_ID )
165 {
166 // Nothing to load for imaginary root tile
167 return nullptr;
168 }
169
170 QgsTiledSceneTile tile = index.getTile( tileId );
171
172 QString uri = tile.resources().value( u"content"_s ).toString();
173 Q_ASSERT( !uri.isEmpty() );
174
175 uri = tile.baseUrl().resolved( uri ).toString();
176 QByteArray content = index.retrieveContent( uri );
177
178 QgsGltf3DUtils::EntityTransform entityTransform;
179 entityTransform.tileTransform = ( tile.transform() ? *tile.transform() : QgsMatrix4x4() );
180 entityTransform.chunkOriginTargetCrs = chunkOrigin;
181 entityTransform.ecefToTargetCrs = &tileCrsToMapCrs;
182 entityTransform.gltfUpAxis = static_cast<Qgis::Axis>( tile.metadata().value( u"gltfUpAxis"_s, static_cast<int>( Qgis::Axis::Y ) ).toInt() );
183
184 try
185 {
186 QgsBox3D box3D = node->box3D();
187 QgsQuantizedMeshTile qmTile( content );
188 qmTile.removeDegenerateTriangles();
189
190 // We now know the exact height range of the tile, set it to the node.
191 box3D.setZMinimum( qmTile.mHeader.MinimumHeight * vertScale );
192 box3D.setZMaximum( qmTile.mHeader.MaximumHeight * vertScale );
193 node->setExactBox3D( box3D );
194
195 if ( shadingEnabled && qmTile.mNormalCoords.size() == 0 )
196 {
197 qmTile.generateNormals();
198 }
199
200 tinygltf::Model model = qmTile.toGltf( true, 100, true );
201
202 QStringList errors;
203 Qt3DCore::QEntity *gltfEntity = QgsGltf3DUtils::parsedGltfToEntity( model, entityTransform, uri, renderCtx, &errors );
204 if ( !errors.isEmpty() )
205 {
206 QgsDebugError( "gltf load errors: " + errors.join( '\n' ) );
207 return nullptr;
208 }
209
210 QgsTerrainTileEntity *terrainEntity = new QgsTerrainTileEntity( node->tileId() );
211 // We count on only having one mesh.
212 Q_ASSERT( gltfEntity->children().size() == 1 );
213 gltfEntity->children()[0]->setParent( terrainEntity );
214
215 QgsGeoTransform *transform = new QgsGeoTransform;
216 transform->setGeoTranslation( chunkOrigin );
217 terrainEntity->addComponent( transform );
218
219 terrainEntity->moveToThread( QgsApplication::instance()->thread() );
220 return terrainEntity;
221 }
222 catch ( QgsQuantizedMeshParsingException &ex )
223 {
224 QgsDebugError( u"Failed to parse tile from '%1'"_s.arg( uri ) );
225 return nullptr;
226 }
227}
228
229Qt3DCore::QEntity *QgsQuantizedMeshTerrainGenerator::finishEntity( QgsTerrainTileEntity *entity, QgsTerrainGenerator::TerrainTextureResources textureResources, Qt3DCore::QEntity *parent )
230{
232 if ( entity )
233 {
234 entity->setParent( parent );
235 Qgs3DMapSettings *map = mTerrain->mapSettings();
236 Qgs3DRenderContext renderCtx = Qgs3DRenderContext::fromMapSettings( map );
237
238 Qt3DRender::QTexture2D *texture = applyTexture( entity, QgsMaterialContext::fromRenderContext( renderCtx ), textureResources );
239
240 // Copied from part of QgsTerrainTileLoader::applyMaterial, since we can't use that directly on the GLTF entity.
241 Qt3DRender::QMaterial *material = nullptr;
242 if ( map->isTerrainShadingEnabled() )
243 {
244 const QgsPhongMaterialSettings &shadingMaterial = map->terrainShadingMaterial();
245 Qt3DExtras::QDiffuseSpecularMaterial *diffuseMapMaterial = new Qt3DExtras::QDiffuseSpecularMaterial;
246 diffuseMapMaterial->setDiffuse( QVariant::fromValue( texture ) );
247 diffuseMapMaterial->setAmbient( shadingMaterial.ambient() );
248 diffuseMapMaterial->setSpecular( shadingMaterial.specular() );
249 diffuseMapMaterial->setShininess( shadingMaterial.shininess() );
250 material = diffuseMapMaterial;
251 }
252 else
253 {
254 Qt3DExtras::QTextureMaterial *textureMaterial = new Qt3DExtras::QTextureMaterial;
255 textureMaterial->setTexture( texture );
256 material = textureMaterial;
257 }
258 // Get the child that actually has the mesh and add the texture
259 Qt3DCore::QEntity *gltfEntity = entity->findChild<Qt3DCore::QEntity *>();
260 // Remove default material
261 auto oldMaterial = gltfEntity->componentsOfType<QgsMetalRoughMaterial>();
262 Q_ASSERT( oldMaterial.size() > 0 );
263 gltfEntity->removeComponent( oldMaterial[0] );
264 gltfEntity->addComponent( material );
265 }
266 return entity;
267}
268
270{
271 return new QgsChunkNode(
272 { 0, 0, 0 },
273 mRootBox3D, // Given to us by setupQuadtree()
274 mMetadata->geometricErrorAtZoom( -1 )
275 );
276}
277
278QFuture<QVector<QgsChunkNode *>> QgsQuantizedMeshTerrainGenerator::createChildren( QgsChunkNode *node )
279{
280 QVector<QgsChunkNode *> children;
281
282 for ( auto offset : std::vector<std::pair<int, int>> { { 0, 0 }, { 0, 1 }, { 1, 0 }, { 1, 1 } } )
283 {
284 QgsChunkNodeId childId( node->tileId().d + 1, node->tileId().x * 2 + offset.first, node->tileId().y * 2 + offset.second );
285 QgsTileXYZ tile = nodeIdToTile( childId );
286 if ( !mMetadata->containsTile( tile ) )
287 continue;
288
289 QgsTileMatrix zoomedTileMatrix = QgsTileMatrix::fromTileMatrix( tile.zoomLevel(), mMetadata->mTileMatrix );
290 QgsRectangle extent2d = zoomedTileMatrix.tileExtent( tile );
291 if ( !extent2d.intersects( mMapExtent ) )
292 continue; // Don't render terrain inside layer extent, but outside map extent
293 Q_ASSERT( mTerrain );
294 QgsRectangle mapExtent2d = mTileCrsToMapCrs.transform( extent2d );
295 QgsVector3D corner1( mapExtent2d.xMinimum(), mapExtent2d.yMinimum(), mMetadata->dummyZRange.lower() );
296 QgsVector3D corner2( mapExtent2d.xMaximum(), mapExtent2d.yMaximum(), mMetadata->dummyZRange.upper() );
297 children.push_back( new QgsChunkNode( childId, QgsBox3D( corner1, corner2 ), mMetadata->geometricErrorAtZoom( tile.zoomLevel() ), node ) );
298 }
299
300 return QtFuture::makeReadyValueFuture( children );
301}
302
304{
305 if ( !layer )
306 {
307 mIsValid = false;
308 return false;
309 }
310
311 mLayer = layer;
312 const QgsQuantizedMeshDataProvider *provider = qobject_cast<const QgsQuantizedMeshDataProvider *>( layer->dataProvider() );
313 if ( !provider )
314 {
315 QgsDebugError( "QgsQuantizedMeshTerrainGenerator provided with non-QM layer" );
316 return false;
317 }
318 mMetadata = provider->quantizedMeshMetadata();
319 mIndex = provider->index();
320
321 mTerrainTilingScheme = QgsTilingScheme( mMetadata->mTileMatrix.extent(), mMetadata->mCrs );
322
323 mIsValid = true;
324 return true;
325}
326
331
332QgsTileXYZ QgsQuantizedMeshTerrainGenerator::nodeIdToTile( QgsChunkNodeId nodeId ) const
333{
334 // nodeId zoom=0 is tile zoom=-1 to get unique root tile
335 if ( nodeId.d == 0 )
336 return { 0, 0, -1 };
337 return { nodeId.x, mMetadata->mTileScheme == "tms"_L1 ? ( 1 << ( nodeId.d - 1 ) ) - nodeId.y - 1 : nodeId.y, nodeId.d - 1 };
338}
Axis
Cartesian axes.
Definition qgis.h:2658
@ Y
Y-axis.
Definition qgis.h:2660
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 QFuture< std::tuple< Ts... > > combine(QFuture< Ts >... futures)
Converts multiple futures into a single future that will finish when all its parts have finished.
static QgsMaterialContext fromRenderContext(const Qgs3DRenderContext &context)
Constructs a material context from the settings in a 3D render context.
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
QFuture< QgsChunkLoaderResult > loadChunk(QgsChunkNode *node) override
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).
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.
QFuture< QVector< QgsChunkNode * > > createChildren(QgsChunkNode *node) 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
Type
Enumeration of the available terrain generators.
@ QuantizedMesh
Terrain is built from quantized mesh tiles.
QgsTilingScheme mTerrainTilingScheme
Tiling scheme of the terrain.
QgsTerrainGenerator()
Create new terrain generator.
QgsTerrainEntity * mTerrain
QFuture< TerrainTextureResources > loadTextureResources(QgsChunkNode *node)
Asynchronously loads resources for creating texture.
Qt3DRender::QTexture2D * applyTexture(QgsTerrainTileEntity *entity, const QgsMaterialContext &context, const TerrainTextureResources &resources) const
Creates a new texture that is linked to the entity.
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:96
QPointF mapToTileCoordinates(const QgsPointXY &mapPoint) const
Returns row/column coordinates (floating point number) from the given point in map coordinates.
Definition qgstiles.cpp:136
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:71
#define QGIS_CHECK_MAIN_THREAD_ACCESS
Mesh - vertices, edges and faces.
QgsMesh toMesh(QgsRectangle tileBounds)