QGIS API Documentation 4.1.0-Master (01362494303)
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 texture->setFormat( Qt3DRender::QAbstractTexture::SRGB8_Alpha8 );
196
197 QgsGlobeMaterial *material = new QgsGlobeMaterial( entity );
198 material->setTexture( texture );
199
200 QgsGeoTransform *geoTransform = new QgsGeoTransform( entity );
201 geoTransform->setGeoTranslation( meshOrigin );
202
203 entity->addComponent( material );
204 entity->addComponent( geomRenderer );
205 entity->addComponent( geoTransform );
206 return entity;
207}
208
209
210static void globeNodeIdToLatLon( QgsChunkNodeId n, double &latMin, double &latMax, double &lonMin, double &lonMax )
211{
212 if ( n == QgsChunkNodeId( 0, 0, 0, 0 ) )
213 {
214 latMin = -90;
215 lonMin = -180;
216 latMax = 90;
217 lonMax = 180;
218 return;
219 }
220
221 double tileSize = 180.0 / std::pow( 2.0, n.d - 1 );
222 lonMin = n.x * tileSize - 180.0;
223 latMin = n.y * tileSize - 90.0;
224 lonMax = lonMin + tileSize;
225 latMax = latMin + tileSize;
226}
227
228
229static QgsBox3D globeNodeIdToBox3D( QgsChunkNodeId n, const QgsCoordinateTransform &globeCrsToLatLon )
230{
231 double latMin, latMax, lonMin, lonMax;
232 globeNodeIdToLatLon( n, latMin, latMax, lonMin, lonMax );
233
234 Q_ASSERT( latMax - latMin <= 90 && lonMax - lonMin <= 90 ); // for larger extents we would need more points than just corners
235
236 QVector<double> x, y, z;
237 int pointCount = 4;
238 x.reserve( pointCount );
239 y.reserve( pointCount );
240 z.reserve( pointCount );
241
242 x.push_back( lonMin );
243 y.push_back( latMin );
244 z.push_back( 0 );
245 x.push_back( lonMin );
246 y.push_back( latMax );
247 z.push_back( 0 );
248 x.push_back( lonMax );
249 y.push_back( latMin );
250 z.push_back( 0 );
251 x.push_back( lonMax );
252 y.push_back( latMax );
253 z.push_back( 0 );
254
255 globeCrsToLatLon.transformCoords( pointCount, x.data(), y.data(), z.data(), Qgis::TransformDirection::Reverse );
256
257 QgsBox3D box( QgsVector3D( x[0], y[0], z[0] ), QgsVector3D( x[1], y[1], z[1] ) );
258 box.combineWith( x[2], y[2], z[2] );
259 box.combineWith( x[3], y[3], z[3] );
260 return box;
261}
262
263
264// ---------------
265
266
267QgsGlobeChunkLoader::QgsGlobeChunkLoader( QgsChunkNode *node, QgsTerrainTextureGenerator *textureGenerator, const QgsCoordinateTransform &globeCrsToLatLon )
268 : QgsChunkLoader( node )
269 , mTextureGenerator( textureGenerator )
270 , mGlobeCrsToLatLon( globeCrsToLatLon )
271{}
272
273void QgsGlobeChunkLoader::start()
274{
275 QgsChunkNode *node = chunk();
276
277 connect( mTextureGenerator, &QgsTerrainTextureGenerator::tileReady, this, [this]( int job, const QImage &img ) {
278 if ( job == mJobId )
279 {
280 mTexture = img;
281 emit finished();
282 }
283 } );
284
285 double latMin, latMax, lonMin, lonMax;
286 globeNodeIdToLatLon( node->tileId(), latMin, latMax, lonMin, lonMax );
287 QgsRectangle extent( lonMin, latMin, lonMax, latMax );
288 mJobId = mTextureGenerator->render( extent, node->tileId(), node->tileId().text() );
289}
290
291Qt3DCore::QEntity *QgsGlobeChunkLoader::createEntity( Qt3DCore::QEntity *parent )
292{
293 if ( mNode->tileId() == QgsChunkNodeId( 0, 0, 0, 0 ) )
294 {
295 return new Qt3DCore::QEntity( parent );
296 }
297
298 double latMin, latMax, lonMin, lonMax;
299 globeNodeIdToLatLon( mNode->tileId(), latMin, latMax, lonMin, lonMax );
300
301 // This is quite ad-hoc estimation how many slices we need. It could
302 // be improved by basing the calculation on sagitta
303 int d = mNode->tileId().d;
304 int slices;
305 if ( d <= 4 )
306 slices = 19;
307 else if ( d <= 8 )
308 slices = 9;
309 else if ( d <= 12 )
310 slices = 5;
311 else
312 slices = 2;
313
314 Qt3DCore::QEntity *e = makeGlobeMesh( lonMin, lonMax, latMin, latMax, slices, slices, mGlobeCrsToLatLon, mTexture, mNode->tileId().text() );
315 e->setParent( parent );
316 return e;
317}
318
319
320// ---------------
321
322
323QgsGlobeChunkLoaderFactory::QgsGlobeChunkLoaderFactory( Qgs3DMapSettings *mapSettings )
324 : mMapSettings( mapSettings )
325{
326 mTextureGenerator = new QgsTerrainTextureGenerator( *mapSettings );
327
328 // it does not matter what kind of ellipsoid is used, this is for rough estimates
329 mDistanceArea.setEllipsoid( mapSettings->crs().ellipsoidAcronym() );
330
331 mGlobeCrsToLatLon = QgsCoordinateTransform( mapSettings->crs(), mapSettings->crs().toGeographicCrs(), mapSettings->transformContext() );
332
333 mRadiusX = mGlobeCrsToLatLon.transform( QgsVector3D( 0, 0, 0 ), Qgis::TransformDirection::Reverse ).x();
334 mRadiusY = mGlobeCrsToLatLon.transform( QgsVector3D( 90, 0, 0 ), Qgis::TransformDirection::Reverse ).y();
335 mRadiusZ = mGlobeCrsToLatLon.transform( QgsVector3D( 0, 90, 0 ), Qgis::TransformDirection::Reverse ).z();
336}
337
338QgsGlobeChunkLoaderFactory::~QgsGlobeChunkLoaderFactory()
339{
340 delete mTextureGenerator;
341}
342
343QgsChunkLoader *QgsGlobeChunkLoaderFactory::createChunkLoader( QgsChunkNode *node ) const
344{
345 return new QgsGlobeChunkLoader( node, mTextureGenerator, mGlobeCrsToLatLon );
346}
347
348QgsChunkNode *QgsGlobeChunkLoaderFactory::createRootNode() const
349{
350 QgsBox3D rootNodeBox3D( -mRadiusX, -mRadiusY, -mRadiusZ, +mRadiusX, +mRadiusY, +mRadiusZ );
351 // use very high error to force immediate switch to level 1 (two hemispheres)
352 QgsChunkNode *node = new QgsChunkNode( QgsChunkNodeId( 0, 0, 0, 0 ), rootNodeBox3D, 999'999 );
353 return node;
354}
355
356QVector<QgsChunkNode *> QgsGlobeChunkLoaderFactory::createChildren( QgsChunkNode *node ) const
357{
358 QVector<QgsChunkNode *> children;
359 if ( node->tileId().d == 0 )
360 {
361 double d1 = mDistanceArea.measureLine( QgsPointXY( 0, 0 ), QgsPointXY( 90, 0 ) );
362 double d2 = mDistanceArea.measureLine( QgsPointXY( 0, 0 ), QgsPointXY( 0, 90 ) );
363 float error = static_cast<float>( std::max( d1, d2 ) ) / static_cast<float>( mMapSettings->terrainSettings()->mapTileResolution() );
364
365 QgsBox3D boxWest( -mRadiusX, -mRadiusY, -mRadiusZ, +mRadiusX, 0, +mRadiusZ );
366 QgsBox3D boxEast( -mRadiusX, 0, -mRadiusY, +mRadiusX, +mRadiusY, +mRadiusZ );
367
368 // two children: western and eastern hemisphere
369 QgsChunkNode *west = new QgsChunkNode( QgsChunkNodeId( 1, 0, 0, 0 ), boxWest, error, node );
370 QgsChunkNode *east = new QgsChunkNode( QgsChunkNodeId( 1, 1, 0, 0 ), boxEast, error, node );
371 children << west << east;
372 }
373 else if ( node->error() > mMapSettings->terrainSettings()->maximumGroundError() )
374 {
375 QgsChunkNodeId nid = node->tileId();
376
377 double latMin, latMax, lonMin, lonMax;
378 globeNodeIdToLatLon( nid, latMin, latMax, lonMin, lonMax );
379 QgsChunkNodeId cid1( nid.d + 1, nid.x * 2, nid.y * 2 );
380 QgsChunkNodeId cid2( nid.d + 1, nid.x * 2 + 1, nid.y * 2 );
381 QgsChunkNodeId cid3( nid.d + 1, nid.x * 2, nid.y * 2 + 1 );
382 QgsChunkNodeId cid4( nid.d + 1, nid.x * 2 + 1, nid.y * 2 + 1 );
383
384 double d1 = mDistanceArea.measureLine( QgsPointXY( lonMin, latMin ), QgsPointXY( lonMin + ( lonMax - lonMin ) / 2, latMin ) );
385 double d2 = mDistanceArea.measureLine( QgsPointXY( lonMin, latMin ), QgsPointXY( lonMin, latMin + ( latMax - latMin ) / 2 ) );
386 float error = static_cast<float>( std::max( d1, d2 ) ) / static_cast<float>( mMapSettings->terrainSettings()->mapTileResolution() );
387
388 children
389 << new QgsChunkNode( cid1, globeNodeIdToBox3D( cid1, mGlobeCrsToLatLon ), error, node )
390 << new QgsChunkNode( cid2, globeNodeIdToBox3D( cid2, mGlobeCrsToLatLon ), error, node )
391 << new QgsChunkNode( cid3, globeNodeIdToBox3D( cid3, mGlobeCrsToLatLon ), error, node )
392 << new QgsChunkNode( cid4, globeNodeIdToBox3D( cid4, mGlobeCrsToLatLon ), error, node );
393 }
394
395 return children;
396}
397
398// ---------------
399
400
401QgsGlobeMapUpdateJob::QgsGlobeMapUpdateJob( QgsTerrainTextureGenerator *textureGenerator, QgsChunkNode *node )
402 : QgsChunkQueueJob( node )
403 , mTextureGenerator( textureGenerator )
404{}
405
406void QgsGlobeMapUpdateJob::start()
407{
408 QgsChunkNode *node = chunk();
409
410 // extract our terrain texture image from the 3D entity
411 QVector<QgsGlobeMaterial *> materials = node->entity()->componentsOfType<QgsGlobeMaterial>();
412 Q_ASSERT( materials.count() == 1 );
413 QVector<Qt3DRender::QAbstractTextureImage *> texImages = materials[0]->texture()->textureImages();
414 Q_ASSERT( texImages.count() == 1 );
415 QgsTerrainTextureImage *terrainTexImage = qobject_cast<QgsTerrainTextureImage *>( texImages[0] );
416 Q_ASSERT( terrainTexImage );
417
418 connect( mTextureGenerator, &QgsTerrainTextureGenerator::tileReady, this, [this, terrainTexImage]( int jobId, const QImage &image ) {
419 if ( mJobId == jobId )
420 {
421 terrainTexImage->setImage( image );
422 mJobId = -1;
423 emit finished();
424 }
425 } );
426 mJobId = mTextureGenerator->render( terrainTexImage->imageExtent(), node->tileId(), terrainTexImage->imageDebugText() );
427}
428
429void QgsGlobeMapUpdateJob::cancel()
430{
431 if ( mJobId != -1 )
432 mTextureGenerator->cancelJob( mJobId );
433}
434
435
436// ---------------
437
438
440class QgsGlobeMapUpdateJobFactory : public QgsChunkQueueJobFactory
441{
442 public:
443 explicit QgsGlobeMapUpdateJobFactory( Qgs3DMapSettings *mapSettings ) { mTextureGenerator = new QgsTerrainTextureGenerator( *mapSettings ); }
444
445 QgsChunkQueueJob *createJob( QgsChunkNode *chunk ) override { return new QgsGlobeMapUpdateJob( mTextureGenerator, chunk ); }
446
447 private:
448 QgsTerrainTextureGenerator *mTextureGenerator = nullptr;
449};
450
451
452// ---------------
453
454
455QgsGlobeEntity::QgsGlobeEntity( Qgs3DMapSettings *mapSettings )
456 : QgsChunkedEntity( mapSettings, mapSettings->terrainSettings()->maximumScreenError(), new QgsGlobeChunkLoaderFactory( mapSettings ), true )
457{
458 connect( mapSettings, &Qgs3DMapSettings::showTerrainBoundingBoxesChanged, this, [this, mapSettings] { setShowBoundingBoxes( mapSettings->showTerrainBoundingBoxes() ); } );
459 connect( mapSettings, &Qgs3DMapSettings::showTerrainTilesInfoChanged, this, &QgsGlobeEntity::invalidateMapImages );
460 connect( mapSettings, &Qgs3DMapSettings::showLabelsChanged, this, &QgsGlobeEntity::invalidateMapImages );
461 connect( mapSettings, &Qgs3DMapSettings::layersChanged, this, &QgsGlobeEntity::onLayersChanged );
462 connect( mapSettings, &Qgs3DMapSettings::backgroundColorChanged, this, &QgsGlobeEntity::invalidateMapImages );
463 connect( mapSettings, &Qgs3DMapSettings::terrainMapThemeChanged, this, &QgsGlobeEntity::invalidateMapImages );
464
465 connectToLayersRepaintRequest();
466
467 mUpdateJobFactory = std::make_unique<QgsGlobeMapUpdateJobFactory>( mapSettings );
468}
469
470QgsGlobeEntity::~QgsGlobeEntity()
471{
472 // cancel / wait for jobs
473 cancelActiveJobs();
474}
475
476QList<QgsRayCastHit> QgsGlobeEntity::rayIntersection( const QgsRay3D &ray, const QgsRayCastContext &context ) const
477{
478 float minDist = -1;
479 QVector3D intersectionPoint;
480 const QList<QgsChunkNode *> active = activeNodes();
481 for ( QgsChunkNode *node : active )
482 {
483 const QgsAABB nodeBbox = Qgs3DUtils::mapToWorldExtent( node->box3D(), mMapSettings->origin() );
484
485 if ( node->entity() && ( minDist < 0 || nodeBbox.distanceFromPoint( ray.origin() ) < minDist ) && QgsRayCastingUtils::rayBoxIntersection( ray, nodeBbox ) )
486 {
487 QgsGeoTransform *nodeGeoTransform = node->entity()->findChild<QgsGeoTransform *>();
488 Q_ASSERT( nodeGeoTransform );
489 const QList<Qt3DRender::QGeometryRenderer *> rendLst = node->entity()->findChildren<Qt3DRender::QGeometryRenderer *>();
490 for ( Qt3DRender::QGeometryRenderer *rend : rendLst )
491 {
492 QVector3D nodeIntPoint;
493 int triangleIndex = -1;
494 bool success = QgsRayCastingUtils::rayMeshIntersection( rend, ray, context.maximumDistance(), nodeGeoTransform->matrix(), nodeIntPoint, triangleIndex );
495 if ( success )
496 {
497 float dist = ( ray.origin() - nodeIntPoint ).length();
498 if ( minDist < 0 || dist < minDist )
499 {
500 minDist = dist;
501 intersectionPoint = nodeIntPoint;
502 }
503 }
504 }
505 }
506 }
507
508 if ( minDist < 0 )
509 return {};
510
511 QgsRayCastHit hit;
512 hit.setDistance( minDist );
513 hit.setMapCoordinates( mMapSettings->worldToMapCoordinates( intersectionPoint ) );
514 return { hit };
515}
516
517
518void QgsGlobeEntity::invalidateMapImages()
519{
520 QgsEventTracing::addEvent( QgsEventTracing::Instant, u"3D"_s, u"Invalidate textures"_s );
521
522 // handle active nodes
523
524 updateNodes( mActiveNodes, mUpdateJobFactory.get() );
525
526 // handle inactive nodes afterwards
527
528 QList<QgsChunkNode *> inactiveNodes;
529 const QList<QgsChunkNode *> descendants = mRootNode->descendants();
530 for ( QgsChunkNode *node : descendants )
531 {
532 if ( !node->entity() )
533 continue;
534 if ( mActiveNodes.contains( node ) )
535 continue;
536 if ( !node->parent() )
537 continue; // skip root node because it is not proper QEntity with data
538 inactiveNodes << node;
539 }
540
541 updateNodes( inactiveNodes, mUpdateJobFactory.get() );
542
543 setNeedsUpdate( true );
544}
545
546void QgsGlobeEntity::onLayersChanged()
547{
548 connectToLayersRepaintRequest();
549 invalidateMapImages();
550}
551
552void QgsGlobeEntity::connectToLayersRepaintRequest()
553{
554 for ( QgsMapLayer *layer : std::as_const( mLayers ) )
555 {
556 disconnect( layer, &QgsMapLayer::repaintRequested, this, &QgsGlobeEntity::invalidateMapImages );
557 }
558
559 mLayers = mMapSettings->layers();
560
561 for ( QgsMapLayer *layer : std::as_const( mLayers ) )
562 {
563 connect( layer, &QgsMapLayer::repaintRequested, this, &QgsGlobeEntity::invalidateMapImages );
564 }
565}
566
@ Reverse
Reverse/inverse transform (from destination to source).
Definition qgis.h:2833
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.