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