QGIS API Documentation 3.43.0-Master (0bee5d6404c)
qgsglobechunkedentity.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsglobechunkedentity.cpp
3 --------------------------------------
4 Date : March 2025
5 Copyright : (C) 2025 by Martin Dobias
6 Email : wonder dot sk at gmail dot com
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 <QByteArray>
19#include <QImage>
20
21#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
22#include <Qt3DRender/QAttribute>
23#include <Qt3DRender/QBuffer>
24#include <Qt3DRender/QGeometry>
25
26typedef Qt3DRender::QAttribute Qt3DQAttribute;
27typedef Qt3DRender::QBuffer Qt3DQBuffer;
28typedef Qt3DRender::QGeometry Qt3DQGeometry;
29#else
30#include <Qt3DCore/QAttribute>
31#include <Qt3DCore/QBuffer>
32#include <Qt3DCore/QGeometry>
33
34typedef Qt3DCore::QAttribute Qt3DQAttribute;
35typedef Qt3DCore::QBuffer Qt3DQBuffer;
36typedef Qt3DCore::QGeometry Qt3DQGeometry;
37#endif
38
39#include <Qt3DCore/QEntity>
40#include <Qt3DRender/QGeometryRenderer>
41#include <Qt3DRender/QTexture>
42#include <Qt3DRender/QTextureImage>
43#include <Qt3DExtras/QTextureMaterial>
44
45#include "qgs3dmapsettings.h"
46#include "qgs3dutils.h"
47#include "qgschunkloader.h"
50#include "qgsdistancearea.h"
51#include "qgseventtracing.h"
52#include "qgsgeotransform.h"
56
57
59
60static Qt3DCore::QEntity *makeGlobeMesh( double lonMin, double lonMax, double latMin, double latMax, int lonSliceCount, int latSliceCount, const QgsCoordinateTransform &globeCrsToLatLon, QImage textureQImage, QString textureDebugText )
61{
62 double lonRange = lonMax - lonMin;
63 double latRange = latMax - latMin;
64 double lonStep = lonRange / ( double ) ( lonSliceCount - 1 );
65 double latStep = latRange / ( double ) ( latSliceCount - 1 );
66
67 std::vector<double> x, y, z;
68 int pointCount = latSliceCount * lonSliceCount;
69 x.reserve( pointCount );
70 y.reserve( pointCount );
71 z.reserve( pointCount );
72
73 for ( int latSliceIndex = 0; latSliceIndex < latSliceCount; ++latSliceIndex )
74 {
75 double lat = latSliceIndex * latStep + latMin;
76 for ( int lonSliceIndex = 0; lonSliceIndex < lonSliceCount; ++lonSliceIndex )
77 {
78 double lon = lonSliceIndex * lonStep + lonMin;
79 x.push_back( lon );
80 y.push_back( lat );
81 z.push_back( 0 );
82 }
83 }
84
85 globeCrsToLatLon.transformCoords( pointCount, x.data(), y.data(), z.data(), Qgis::TransformDirection::Reverse );
86
87 // estimate origin of coordinates for this tile, to make the relative coordinates
88 // small to avoid numerical precision issues when rendering
89 // (avoids mesh jumping around when zoomed in very close to it)
90 QgsVector3D meshOriginLatLon( ( lonMin + lonMax ) / 2, ( latMin + latMax ) / 2, 0 );
91 QgsVector3D meshOrigin = globeCrsToLatLon.transform( meshOriginLatLon, Qgis::TransformDirection::Reverse );
92
93 int stride = ( 3 + 2 + 3 ) * sizeof( float );
94
95 QByteArray bufferBytes;
96 bufferBytes.resize( stride * pointCount );
97 float *fptr = ( float * ) bufferBytes.data();
98 for ( int i = 0; i < ( int ) pointCount; ++i )
99 {
100 *fptr++ = static_cast<float>( x[i] - meshOrigin.x() );
101 *fptr++ = static_cast<float>( y[i] - meshOrigin.y() );
102 *fptr++ = static_cast<float>( z[i] - meshOrigin.z() );
103
104 int vi = i / lonSliceCount;
105 int ui = i % lonSliceCount;
106 float v = static_cast<float>( vi ) / static_cast<float>( latSliceCount - 1 );
107 float u = static_cast<float>( ui ) / static_cast<float>( lonSliceCount - 1 );
108 *fptr++ = u;
109 *fptr++ = 1 - v;
110
111 QVector3D n = QVector3D( static_cast<float>( x[i] ), static_cast<float>( y[i] ), static_cast<float>( z[i] ) ).normalized();
112 *fptr++ = n.x();
113 *fptr++ = n.y();
114 *fptr++ = n.z();
115 }
116
117 int faces = ( lonSliceCount - 1 ) * ( latSliceCount - 1 ) * 2;
118 int indices = faces * 3;
119
120 QByteArray indexBytes;
121 indexBytes.resize( indices * static_cast<int>( sizeof( ushort ) ) );
122
123 quint16 *indexPtr = reinterpret_cast<quint16 *>( indexBytes.data() );
124 for ( int latSliceIndex = 0; latSliceIndex < latSliceCount - 1; ++latSliceIndex )
125 {
126 int latSliceStartIndex = latSliceIndex * lonSliceCount;
127 int nextLatSliceStartIndex = lonSliceCount + latSliceStartIndex;
128 for ( int lonSliceIndex = 0; lonSliceIndex < lonSliceCount - 1; ++lonSliceIndex )
129 {
130 indexPtr[0] = latSliceStartIndex + lonSliceIndex;
131 indexPtr[1] = lonSliceIndex + latSliceStartIndex + 1;
132 indexPtr[2] = nextLatSliceStartIndex + lonSliceIndex;
133
134 indexPtr[3] = nextLatSliceStartIndex + lonSliceIndex;
135 indexPtr[4] = lonSliceIndex + latSliceStartIndex + 1;
136 indexPtr[5] = lonSliceIndex + nextLatSliceStartIndex + 1;
137
138 indexPtr += 6;
139 }
140 }
141
142 Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
143
144 Qt3DQBuffer *vertexBuffer = new Qt3DQBuffer( entity );
145 vertexBuffer->setData( bufferBytes );
146
147 Qt3DQBuffer *indexBuffer = new Qt3DQBuffer( entity );
148 indexBuffer->setData( indexBytes );
149
150 Qt3DQAttribute *positionAttribute = new Qt3DQAttribute( entity );
151 positionAttribute->setName( Qt3DQAttribute::defaultPositionAttributeName() );
152 positionAttribute->setVertexBaseType( Qt3DQAttribute::Float );
153 positionAttribute->setVertexSize( 3 );
154 positionAttribute->setAttributeType( Qt3DQAttribute::VertexAttribute );
155 positionAttribute->setBuffer( vertexBuffer );
156 positionAttribute->setByteStride( stride );
157 positionAttribute->setCount( pointCount );
158
159 Qt3DQAttribute *texCoordAttribute = new Qt3DQAttribute( entity );
160 texCoordAttribute->setName( Qt3DQAttribute::defaultTextureCoordinateAttributeName() );
161 texCoordAttribute->setVertexBaseType( Qt3DQAttribute::Float );
162 texCoordAttribute->setVertexSize( 2 );
163 texCoordAttribute->setAttributeType( Qt3DQAttribute::VertexAttribute );
164 texCoordAttribute->setBuffer( vertexBuffer );
165 texCoordAttribute->setByteStride( stride );
166 texCoordAttribute->setByteOffset( 3 * sizeof( float ) );
167 texCoordAttribute->setCount( pointCount );
168
169 Qt3DQAttribute *normalAttribute = new Qt3DQAttribute( entity );
170 normalAttribute->setName( Qt3DQAttribute::defaultNormalAttributeName() );
171 normalAttribute->setVertexBaseType( Qt3DQAttribute::Float );
172 normalAttribute->setVertexSize( 3 );
173 normalAttribute->setAttributeType( Qt3DQAttribute::VertexAttribute );
174 normalAttribute->setBuffer( vertexBuffer );
175 normalAttribute->setByteStride( stride );
176 normalAttribute->setByteOffset( 5 * sizeof( float ) );
177 normalAttribute->setCount( pointCount );
178
179 Qt3DQAttribute *indexAttribute = new Qt3DQAttribute( entity );
180 indexAttribute->setAttributeType( Qt3DQAttribute::IndexAttribute );
181 indexAttribute->setVertexBaseType( Qt3DQAttribute::UnsignedShort );
182 indexAttribute->setBuffer( indexBuffer );
183 indexAttribute->setCount( faces * 3 );
184
185 Qt3DQGeometry *geometry = new Qt3DQGeometry( entity );
186 geometry->addAttribute( positionAttribute );
187 geometry->addAttribute( texCoordAttribute );
188 geometry->addAttribute( normalAttribute );
189 geometry->addAttribute( indexAttribute );
190
191 Qt3DRender::QGeometryRenderer *geomRenderer = new Qt3DRender::QGeometryRenderer( entity );
192 geomRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::Triangles );
193 geomRenderer->setVertexCount( faces * 3 );
194 geomRenderer->setGeometry( geometry );
195
196 QgsTerrainTextureImage *textureImage = new QgsTerrainTextureImage( textureQImage, QgsRectangle( lonMin, latMin, lonMax, latMax ), textureDebugText, entity );
197
198 Qt3DRender::QTexture2D *texture = new Qt3DRender::QTexture2D( entity );
199 texture->addTextureImage( textureImage );
200 texture->setMinificationFilter( Qt3DRender::QTexture2D::Linear );
201 texture->setMagnificationFilter( Qt3DRender::QTexture2D::Linear );
202
203 Qt3DExtras::QTextureMaterial *material = new Qt3DExtras::QTextureMaterial( entity );
204 material->setTexture( texture );
205
206 QgsGeoTransform *geoTransform = new QgsGeoTransform( entity );
207 geoTransform->setGeoTranslation( meshOrigin );
208
209 entity->addComponent( material );
210 entity->addComponent( geomRenderer );
211 entity->addComponent( geoTransform );
212 return entity;
213}
214
215
216static void globeNodeIdToLatLon( QgsChunkNodeId n, double &latMin, double &latMax, double &lonMin, double &lonMax )
217{
218 if ( n == QgsChunkNodeId( 0, 0, 0, 0 ) )
219 {
220 latMin = -90;
221 lonMin = -180;
222 latMax = 90;
223 lonMax = 180;
224 return;
225 }
226
227 double tileSize = 180.0 / std::pow( 2.0, n.d - 1 );
228 lonMin = n.x * tileSize - 180.0;
229 latMin = n.y * tileSize - 90.0;
230 lonMax = lonMin + tileSize;
231 latMax = latMin + tileSize;
232}
233
234
235static QgsBox3D globeNodeIdToBox3D( QgsChunkNodeId n, const QgsCoordinateTransform &globeCrsToLatLon )
236{
237 double latMin, latMax, lonMin, lonMax;
238 globeNodeIdToLatLon( n, latMin, latMax, lonMin, lonMax );
239
240 Q_ASSERT( latMax - latMin <= 90 && lonMax - lonMin <= 90 ); // for larger extents we would need more points than just corners
241
242 QVector<double> x, y, z;
243 int pointCount = 4;
244 x.reserve( pointCount );
245 y.reserve( pointCount );
246 z.reserve( pointCount );
247
248 x.push_back( lonMin );
249 y.push_back( latMin );
250 z.push_back( 0 );
251 x.push_back( lonMin );
252 y.push_back( latMax );
253 z.push_back( 0 );
254 x.push_back( lonMax );
255 y.push_back( latMin );
256 z.push_back( 0 );
257 x.push_back( lonMax );
258 y.push_back( latMax );
259 z.push_back( 0 );
260
261 globeCrsToLatLon.transformCoords( pointCount, x.data(), y.data(), z.data(), Qgis::TransformDirection::Reverse );
262
263 QgsBox3D box( QgsVector3D( x[0], y[0], z[0] ), QgsVector3D( x[1], y[1], z[1] ) );
264 box.combineWith( x[2], y[2], z[2] );
265 box.combineWith( x[3], y[3], z[3] );
266 return box;
267}
268
269
270// ---------------
271
272
273class QgsGlobeChunkLoader : public QgsChunkLoader
274{
275 public:
276 QgsGlobeChunkLoader( QgsChunkNode *node, QgsTerrainTextureGenerator *textureGenerator, const QgsCoordinateTransform &globeCrsToLatLon )
277 : QgsChunkLoader( node )
278 , mTextureGenerator( textureGenerator )
279 , mGlobeCrsToLatLon( globeCrsToLatLon )
280 {
281 connect( mTextureGenerator, &QgsTerrainTextureGenerator::tileReady, this, [=]( int job, const QImage &img ) {
282 if ( job == mJobId )
283 {
284 mTexture = img;
285 emit finished();
286 }
287 } );
288
289 double latMin, latMax, lonMin, lonMax;
290 globeNodeIdToLatLon( node->tileId(), latMin, latMax, lonMin, lonMax );
291 QgsRectangle extent( lonMin, latMin, lonMax, latMax );
292 mJobId = mTextureGenerator->render( extent, node->tileId(), node->tileId().text() );
293 }
294
295 Qt3DCore::QEntity *createEntity( Qt3DCore::QEntity *parent ) override
296 {
297 if ( mNode->tileId() == QgsChunkNodeId( 0, 0, 0, 0 ) )
298 {
299 return new Qt3DCore::QEntity( parent );
300 }
301
302 double latMin, latMax, lonMin, lonMax;
303 globeNodeIdToLatLon( mNode->tileId(), latMin, latMax, lonMin, lonMax );
304
305 // This is quite ad-hoc estimation how many slices we need. It could
306 // be improved by basing the calculation on sagitta
307 int d = mNode->tileId().d;
308 int slices;
309 if ( d <= 4 )
310 slices = 19;
311 else if ( d <= 8 )
312 slices = 9;
313 else if ( d <= 12 )
314 slices = 5;
315 else
316 slices = 2;
317
318 Qt3DCore::QEntity *e = makeGlobeMesh( lonMin, lonMax, latMin, latMax, slices, slices, mGlobeCrsToLatLon, mTexture, mNode->tileId().text() );
319 e->setParent( parent );
320 return e;
321 }
322
323 private:
324 QgsTerrainTextureGenerator *mTextureGenerator;
325 QgsCoordinateTransform mGlobeCrsToLatLon;
326 int mJobId;
327 QImage mTexture;
328};
329
330
331// ---------------
332
333
334class QgsGlobeChunkLoaderFactory : public QgsChunkLoaderFactory
335{
336 public:
337 QgsGlobeChunkLoaderFactory( Qgs3DMapSettings *mapSettings )
338 : mMapSettings( mapSettings )
339 {
340 mTextureGenerator = new QgsTerrainTextureGenerator( *mapSettings );
341
342 // it does not matter what kind of ellipsoid is used, this is for rough estimates
343 mDistanceArea.setEllipsoid( mapSettings->crs().ellipsoidAcronym() );
344
345 mGlobeCrsToLatLon = QgsCoordinateTransform( mapSettings->crs(), mapSettings->crs().toGeographicCrs(), mapSettings->transformContext() );
346
347 mRadiusX = mGlobeCrsToLatLon.transform( QgsVector3D( 0, 0, 0 ), Qgis::TransformDirection::Reverse ).x();
348 mRadiusY = mGlobeCrsToLatLon.transform( QgsVector3D( 90, 0, 0 ), Qgis::TransformDirection::Reverse ).y();
349 mRadiusZ = mGlobeCrsToLatLon.transform( QgsVector3D( 0, 90, 0 ), Qgis::TransformDirection::Reverse ).z();
350 }
351
352 ~QgsGlobeChunkLoaderFactory()
353 {
354 delete mTextureGenerator;
355 }
356
357 QgsChunkLoader *createChunkLoader( QgsChunkNode *node ) const override
358 {
359 return new QgsGlobeChunkLoader( node, mTextureGenerator, mGlobeCrsToLatLon );
360 }
361
362 QgsChunkNode *createRootNode() const override
363 {
364 QgsBox3D rootNodeBox3D( -mRadiusX, -mRadiusY, -mRadiusZ, +mRadiusX, +mRadiusY, +mRadiusZ );
365 // use very high error to force immediate switch to level 1 (two hemispheres)
366 QgsChunkNode *node = new QgsChunkNode( QgsChunkNodeId( 0, 0, 0, 0 ), rootNodeBox3D, 999'999 );
367 return node;
368 }
369
370 QVector<QgsChunkNode *> createChildren( QgsChunkNode *node ) const override
371 {
372 QVector<QgsChunkNode *> children;
373 if ( node->tileId().d == 0 )
374 {
375 double d1 = mDistanceArea.measureLine( QgsPointXY( 0, 0 ), QgsPointXY( 90, 0 ) );
376 double d2 = mDistanceArea.measureLine( QgsPointXY( 0, 0 ), QgsPointXY( 0, 90 ) );
377 float error = static_cast<float>( std::max( d1, d2 ) ) / static_cast<float>( mMapSettings->terrainSettings()->mapTileResolution() );
378
379 QgsBox3D boxWest( -mRadiusX, -mRadiusY, -mRadiusZ, +mRadiusX, 0, +mRadiusZ );
380 QgsBox3D boxEast( -mRadiusX, 0, -mRadiusY, +mRadiusX, +mRadiusY, +mRadiusZ );
381
382 // two children: western and eastern hemisphere
383 QgsChunkNode *west = new QgsChunkNode( QgsChunkNodeId( 1, 0, 0, 0 ), boxWest, error, node );
384 QgsChunkNode *east = new QgsChunkNode( QgsChunkNodeId( 1, 1, 0, 0 ), boxEast, error, node );
385 children << west << east;
386 }
387 else if ( node->error() > mMapSettings->terrainSettings()->maximumGroundError() )
388 {
389 QgsChunkNodeId nid = node->tileId();
390
391 double latMin, latMax, lonMin, lonMax;
392 globeNodeIdToLatLon( nid, latMin, latMax, lonMin, lonMax );
393 QgsChunkNodeId cid1( nid.d + 1, nid.x * 2, nid.y * 2 );
394 QgsChunkNodeId cid2( nid.d + 1, nid.x * 2 + 1, nid.y * 2 );
395 QgsChunkNodeId cid3( nid.d + 1, nid.x * 2, nid.y * 2 + 1 );
396 QgsChunkNodeId cid4( nid.d + 1, nid.x * 2 + 1, nid.y * 2 + 1 );
397
398 double d1 = mDistanceArea.measureLine( QgsPointXY( lonMin, latMin ), QgsPointXY( lonMin + ( lonMax - lonMin ) / 2, latMin ) );
399 double d2 = mDistanceArea.measureLine( QgsPointXY( lonMin, latMin ), QgsPointXY( lonMin, latMin + ( latMax - latMin ) / 2 ) );
400 float error = static_cast<float>( std::max( d1, d2 ) ) / static_cast<float>( mMapSettings->terrainSettings()->mapTileResolution() );
401
402 children << new QgsChunkNode( cid1, globeNodeIdToBox3D( cid1, mGlobeCrsToLatLon ), error, node )
403 << new QgsChunkNode( cid2, globeNodeIdToBox3D( cid2, mGlobeCrsToLatLon ), error, node )
404 << new QgsChunkNode( cid3, globeNodeIdToBox3D( cid3, mGlobeCrsToLatLon ), error, node )
405 << new QgsChunkNode( cid4, globeNodeIdToBox3D( cid4, mGlobeCrsToLatLon ), error, node );
406 }
407
408 return children;
409 }
410
411 private:
412 Qgs3DMapSettings *mMapSettings = nullptr;
413 QgsTerrainTextureGenerator *mTextureGenerator = nullptr; // owned by the factory
414 QgsDistanceArea mDistanceArea;
415 QgsCoordinateTransform mGlobeCrsToLatLon;
416 double mRadiusX, mRadiusY, mRadiusZ;
417};
418
419
420// ---------------
421
422
424class QgsGlobeMapUpdateJob : public QgsChunkQueueJob
425{
426 public:
427 QgsGlobeMapUpdateJob( QgsTerrainTextureGenerator *textureGenerator, QgsChunkNode *node )
428 : QgsChunkQueueJob( node )
429 , mTextureGenerator( textureGenerator )
430 {
431 // extract our terrain texture image from the 3D entity
432 QVector<Qt3DExtras::QTextureMaterial *> materials = node->entity()->componentsOfType<Qt3DExtras::QTextureMaterial>();
433 Q_ASSERT( materials.count() == 1 );
434 QVector<Qt3DRender::QAbstractTextureImage *> texImages = materials[0]->texture()->textureImages();
435 Q_ASSERT( texImages.count() == 1 );
436 QgsTerrainTextureImage *terrainTexImage = qobject_cast<QgsTerrainTextureImage *>( texImages[0] );
437 Q_ASSERT( terrainTexImage );
438
439 connect( textureGenerator, &QgsTerrainTextureGenerator::tileReady, this, [=]( int jobId, const QImage &image ) {
440 if ( mJobId == jobId )
441 {
442 terrainTexImage->setImage( image );
443 mJobId = -1;
444 emit finished();
445 }
446 } );
447 mJobId = textureGenerator->render( terrainTexImage->imageExtent(), node->tileId(), terrainTexImage->imageDebugText() );
448 }
449
450 void cancel() override
451 {
452 if ( mJobId != -1 )
453 mTextureGenerator->cancelJob( mJobId );
454 }
455
456 private:
457 QgsTerrainTextureGenerator *mTextureGenerator = nullptr;
458 int mJobId;
459};
460
461
462// ---------------
463
464
466class QgsGlobeMapUpdateJobFactory : public QgsChunkQueueJobFactory
467{
468 public:
469 explicit QgsGlobeMapUpdateJobFactory( Qgs3DMapSettings *mapSettings )
470 {
471 mTextureGenerator = new QgsTerrainTextureGenerator( *mapSettings );
472 }
473
474 QgsChunkQueueJob *createJob( QgsChunkNode *chunk ) override
475 {
476 return new QgsGlobeMapUpdateJob( mTextureGenerator, chunk );
477 }
478
479 private:
480 QgsTerrainTextureGenerator *mTextureGenerator = nullptr;
481};
482
483
484// ---------------
485
486
487QgsGlobeEntity::QgsGlobeEntity( Qgs3DMapSettings *mapSettings )
488 : QgsChunkedEntity( mapSettings, mapSettings->terrainSettings()->maximumScreenError(), new QgsGlobeChunkLoaderFactory( mapSettings ), true )
489{
490 connect( mapSettings, &Qgs3DMapSettings::showTerrainBoundingBoxesChanged, this, [=] {
491 setShowBoundingBoxes( mapSettings->showTerrainBoundingBoxes() );
492 } );
493 connect( mapSettings, &Qgs3DMapSettings::showTerrainTilesInfoChanged, this, &QgsGlobeEntity::invalidateMapImages );
494 connect( mapSettings, &Qgs3DMapSettings::showLabelsChanged, this, &QgsGlobeEntity::invalidateMapImages );
495 connect( mapSettings, &Qgs3DMapSettings::layersChanged, this, &QgsGlobeEntity::onLayersChanged );
496 connect( mapSettings, &Qgs3DMapSettings::backgroundColorChanged, this, &QgsGlobeEntity::invalidateMapImages );
497 connect( mapSettings, &Qgs3DMapSettings::terrainMapThemeChanged, this, &QgsGlobeEntity::invalidateMapImages );
498
499 connectToLayersRepaintRequest();
500
501 mUpdateJobFactory.reset( new QgsGlobeMapUpdateJobFactory( mapSettings ) );
502}
503
504QgsGlobeEntity::~QgsGlobeEntity()
505{
506 // cancel / wait for jobs
507 cancelActiveJobs();
508}
509
510QVector<QgsRayCastingUtils::RayHit> QgsGlobeEntity::rayIntersection( const QgsRayCastingUtils::Ray3D &ray, const QgsRayCastingUtils::RayCastContext &context ) const
511{
512 Q_UNUSED( context );
513
514 float minDist = -1;
515 QVector3D intersectionPoint;
516 const QList<QgsChunkNode *> active = activeNodes();
517 for ( QgsChunkNode *node : active )
518 {
519 QgsAABB nodeBbox = Qgs3DUtils::mapToWorldExtent( node->box3D(), mMapSettings->origin() );
520
521 if ( node->entity() && ( minDist < 0 || nodeBbox.distanceFromPoint( ray.origin() ) < minDist ) && QgsRayCastingUtils::rayBoxIntersection( ray, nodeBbox ) )
522 {
523 QgsGeoTransform *nodeGeoTransform = node->entity()->findChild<QgsGeoTransform *>();
524 Q_ASSERT( nodeGeoTransform );
525 const QList<Qt3DRender::QGeometryRenderer *> rendLst = node->entity()->findChildren<Qt3DRender::QGeometryRenderer *>();
526 for ( Qt3DRender::QGeometryRenderer *rend : rendLst )
527 {
528 QVector3D nodeIntPoint;
529 int triangleIndex = -1;
530 bool success = QgsRayCastingUtils::rayMeshIntersection( rend, ray, nodeGeoTransform->matrix(), nodeIntPoint, triangleIndex );
531 if ( success )
532 {
533 float dist = ( ray.origin() - nodeIntPoint ).length();
534 if ( minDist < 0 || dist < minDist )
535 {
536 minDist = dist;
537 intersectionPoint = nodeIntPoint;
538 }
539 }
540 }
541 }
542 }
543
544 QVector<QgsRayCastingUtils::RayHit> result;
545 if ( minDist >= 0 )
546 {
547 result.append( QgsRayCastingUtils::RayHit( minDist, intersectionPoint ) );
548 }
549 return result;
550}
551
552
553void QgsGlobeEntity::invalidateMapImages()
554{
555 QgsEventTracing::addEvent( QgsEventTracing::Instant, QStringLiteral( "3D" ), QStringLiteral( "Invalidate textures" ) );
556
557 // handle active nodes
558
559 updateNodes( mActiveNodes, mUpdateJobFactory.get() );
560
561 // handle inactive nodes afterwards
562
563 QList<QgsChunkNode *> inactiveNodes;
564 const QList<QgsChunkNode *> descendants = mRootNode->descendants();
565 for ( QgsChunkNode *node : descendants )
566 {
567 if ( !node->entity() )
568 continue;
569 if ( mActiveNodes.contains( node ) )
570 continue;
571 if ( !node->parent() )
572 continue; // skip root node because it is not proper QEntity with data
573 inactiveNodes << node;
574 }
575
576 updateNodes( inactiveNodes, mUpdateJobFactory.get() );
577
578 setNeedsUpdate( true );
579}
580
581void QgsGlobeEntity::onLayersChanged()
582{
583 connectToLayersRepaintRequest();
584 invalidateMapImages();
585}
586
587void QgsGlobeEntity::connectToLayersRepaintRequest()
588{
589 for ( QgsMapLayer *layer : std::as_const( mLayers ) )
590 {
591 disconnect( layer, &QgsMapLayer::repaintRequested, this, &QgsGlobeEntity::invalidateMapImages );
592 }
593
594 mLayers = mMapSettings->layers();
595
596 for ( QgsMapLayer *layer : std::as_const( mLayers ) )
597 {
598 connect( layer, &QgsMapLayer::repaintRequested, this, &QgsGlobeEntity::invalidateMapImages );
599 }
600}
601
@ Reverse
Reverse/inverse transform (from destination to source)
Definition of the world.
void backgroundColorChanged()
Emitted when the background color has changed.
void showTerrainBoundingBoxesChanged()
Emitted when the flag whether terrain's bounding boxes are shown has changed.
void terrainMapThemeChanged()
Emitted when terrain's map theme has changed.
bool showTerrainBoundingBoxes() const
Returns whether to display bounding boxes of terrain tiles (for debugging)
void showLabelsChanged()
Emitted when the flag whether labels are displayed on terrain tiles has changed.
void layersChanged()
Emitted when the list of map layers for 3d rendering has changed.
void showTerrainTilesInfoChanged()
Emitted when the flag whether terrain's tile info is shown has changed.
QgsCoordinateReferenceSystem crs() const
Returns coordinate reference system used in the 3D scene.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context, which stores various information regarding which datum tran...
static QgsAABB mapToWorldExtent(const QgsRectangle &extent, double zMin, double zMax, const QgsVector3D &mapOrigin)
Converts map extent to axis aligned bounding box in 3D world coordinates.
Axis-aligned bounding box - in world coords.
Definition qgsaabb.h:35
float distanceFromPoint(float x, float y, float z) const
Returns shortest distance from the box to a point.
Definition qgsaabb.cpp:46
A 3-dimensional box composed of x, y, z coordinates.
Definition qgsbox3d.h:43
QString ellipsoidAcronym() const
Returns the ellipsoid acronym for the ellipsoid used by the CRS.
QgsCoordinateReferenceSystem toGeographicCrs() const
Returns the geographic CRS associated with this CRS object.
Handles coordinate transforms between two coordinate systems.
void transformCoords(int numPoint, double *x, double *y, double *z, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Transform an array of coordinates to the destination CRS.
QgsPointXY transform(const QgsPointXY &point, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Transform the point from the source CRS to the destination CRS.
A general purpose distance and area calculator, capable of performing ellipsoid based calculations.
Base class for all map layer types.
Definition qgsmaplayer.h:77
void repaintRequested(bool deferredUpdate=false)
By emitting this signal the layer tells that either appearance or content have been changed and any v...
Represents a 2D point.
Definition qgspointxy.h:60
double x
Definition qgspointxy.h:63
A rectangle specified with double values.
A 3D vector (similar to QVector3D) with the difference that it uses double precision instead of singl...
Definition qgsvector3d.h:30
double y() const
Returns Y coordinate.
Definition qgsvector3d.h:49
double z() const
Returns Z coordinate.
Definition qgsvector3d.h:51
double x() const
Returns X coordinate.
Definition qgsvector3d.h:47
Qt3DCore::QAttribute Qt3DQAttribute
Qt3DCore::QBuffer Qt3DQBuffer
Qt3DCore::QGeometry Qt3DQGeometry
Qt3DCore::QAttribute Qt3DQAttribute
Qt3DCore::QBuffer Qt3DQBuffer
Qt3DCore::QGeometry Qt3DQGeometry
Helper struct to store ray casting parameters.
Helper struct to store ray casting results.