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