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