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>
45#include "moc_qgsglobechunkedentity.cpp"
47using namespace Qt::StringLiterals;
51static Qt3DCore::QEntity *makeGlobeMesh(
double lonMin,
double lonMax,
double latMin,
double latMax,
int lonSliceCount,
int latSliceCount,
const QgsCoordinateTransform &globeCrsToLatLon, QImage textureQImage, QString textureDebugText )
53 double lonRange = lonMax - lonMin;
54 double latRange = latMax - latMin;
55 double lonStep = lonRange / ( double ) ( lonSliceCount - 1 );
56 double latStep = latRange / ( double ) ( latSliceCount - 1 );
58 std::vector<double> x, y, z;
59 int pointCount = latSliceCount * lonSliceCount;
60 x.reserve( pointCount );
61 y.reserve( pointCount );
62 z.reserve( pointCount );
64 for (
int latSliceIndex = 0; latSliceIndex < latSliceCount; ++latSliceIndex )
66 double lat = latSliceIndex * latStep + latMin;
67 for (
int lonSliceIndex = 0; lonSliceIndex < lonSliceCount; ++lonSliceIndex )
69 double lon = lonSliceIndex * lonStep + lonMin;
81 QgsVector3D meshOriginLatLon( ( lonMin + lonMax ) / 2, ( latMin + latMax ) / 2, 0 );
84 int stride = ( 3 + 2 + 3 ) *
sizeof(
float );
86 QByteArray bufferBytes;
87 bufferBytes.resize( stride * pointCount );
88 float *fptr = (
float * ) bufferBytes.data();
89 for (
int i = 0; i < ( int ) pointCount; ++i )
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() );
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 );
102 QVector3D n = QVector3D(
static_cast<float>( x[i] ),
static_cast<float>( y[i] ),
static_cast<float>( z[i] ) ).normalized();
108 int faces = ( lonSliceCount - 1 ) * ( latSliceCount - 1 ) * 2;
109 int indices = faces * 3;
111 QByteArray indexBytes;
112 indexBytes.resize( indices *
static_cast<int>(
sizeof( ushort ) ) );
114 quint16 *indexPtr =
reinterpret_cast<quint16 *
>( indexBytes.data() );
115 for (
int latSliceIndex = 0; latSliceIndex < latSliceCount - 1; ++latSliceIndex )
117 int latSliceStartIndex = latSliceIndex * lonSliceCount;
118 int nextLatSliceStartIndex = lonSliceCount + latSliceStartIndex;
119 for (
int lonSliceIndex = 0; lonSliceIndex < lonSliceCount - 1; ++lonSliceIndex )
121 indexPtr[0] = latSliceStartIndex + lonSliceIndex;
122 indexPtr[1] = lonSliceIndex + latSliceStartIndex + 1;
123 indexPtr[2] = nextLatSliceStartIndex + lonSliceIndex;
125 indexPtr[3] = nextLatSliceStartIndex + lonSliceIndex;
126 indexPtr[4] = lonSliceIndex + latSliceStartIndex + 1;
127 indexPtr[5] = lonSliceIndex + nextLatSliceStartIndex + 1;
133 Qt3DCore::QEntity *entity =
new Qt3DCore::QEntity;
135 Qt3DCore::QBuffer *vertexBuffer =
new Qt3DCore::QBuffer( entity );
136 vertexBuffer->setData( bufferBytes );
138 Qt3DCore::QBuffer *indexBuffer =
new Qt3DCore::QBuffer( entity );
139 indexBuffer->setData( indexBytes );
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 );
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 );
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 );
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 );
176 Qt3DCore::QGeometry *geometry =
new Qt3DCore::QGeometry( entity );
177 geometry->addAttribute( positionAttribute );
178 geometry->addAttribute( texCoordAttribute );
179 geometry->addAttribute( normalAttribute );
180 geometry->addAttribute( indexAttribute );
182 Qt3DRender::QGeometryRenderer *geomRenderer =
new Qt3DRender::QGeometryRenderer( entity );
183 geomRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::Triangles );
184 geomRenderer->setVertexCount( faces * 3 );
185 geomRenderer->setGeometry( geometry );
187 QgsTerrainTextureImage *textureImage =
new QgsTerrainTextureImage( textureQImage,
QgsRectangle( lonMin, latMin, lonMax, latMax ), textureDebugText, entity );
189 Qt3DRender::QTexture2D *texture =
new Qt3DRender::QTexture2D( entity );
190 texture->addTextureImage( textureImage );
191 texture->setMinificationFilter( Qt3DRender::QTexture2D::Linear );
192 texture->setMagnificationFilter( Qt3DRender::QTexture2D::Linear );
194 QgsGlobeMaterial *material =
new QgsGlobeMaterial( entity );
195 material->setTexture( texture );
197 QgsGeoTransform *geoTransform =
new QgsGeoTransform( entity );
198 geoTransform->setGeoTranslation( meshOrigin );
200 entity->addComponent( material );
201 entity->addComponent( geomRenderer );
202 entity->addComponent( geoTransform );
207static void globeNodeIdToLatLon( QgsChunkNodeId n,
double &latMin,
double &latMax,
double &lonMin,
double &lonMax )
209 if ( n == QgsChunkNodeId( 0, 0, 0, 0 ) )
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;
228 double latMin, latMax, lonMin, lonMax;
229 globeNodeIdToLatLon( n, latMin, latMax, lonMin, lonMax );
231 Q_ASSERT( latMax - latMin <= 90 && lonMax - lonMin <= 90 );
233 QVector<double> x, y, z;
235 x.reserve( pointCount );
236 y.reserve( pointCount );
237 z.reserve( pointCount );
239 x.push_back( lonMin );
240 y.push_back( latMin );
242 x.push_back( lonMin );
243 y.push_back( latMax );
245 x.push_back( lonMax );
246 y.push_back( latMin );
248 x.push_back( lonMax );
249 y.push_back( latMax );
255 box.combineWith( x[2], y[2], z[2] );
256 box.combineWith( x[3], y[3], z[3] );
264QgsGlobeChunkLoader::QgsGlobeChunkLoader( QgsChunkNode *node, QgsTerrainTextureGenerator *textureGenerator,
const QgsCoordinateTransform &globeCrsToLatLon )
265 : QgsChunkLoader( node )
266 , mTextureGenerator( textureGenerator )
267 , mGlobeCrsToLatLon( globeCrsToLatLon )
271void QgsGlobeChunkLoader::start()
273 QgsChunkNode *node = chunk();
275 connect( mTextureGenerator, &QgsTerrainTextureGenerator::tileReady,
this, [
this](
int job,
const QImage &img ) {
283 double latMin, latMax, lonMin, lonMax;
284 globeNodeIdToLatLon( node->tileId(), latMin, latMax, lonMin, lonMax );
286 mJobId = mTextureGenerator->render( extent, node->tileId(), node->tileId().text() );
289Qt3DCore::QEntity *QgsGlobeChunkLoader::createEntity( Qt3DCore::QEntity *parent )
291 if ( mNode->tileId() == QgsChunkNodeId( 0, 0, 0, 0 ) )
293 return new Qt3DCore::QEntity( parent );
296 double latMin, latMax, lonMin, lonMax;
297 globeNodeIdToLatLon( mNode->tileId(), latMin, latMax, lonMin, lonMax );
301 int d = mNode->tileId().d;
312 Qt3DCore::QEntity *e = makeGlobeMesh( lonMin, lonMax, latMin, latMax, slices, slices, mGlobeCrsToLatLon, mTexture, mNode->tileId().text() );
313 e->setParent( parent );
321QgsGlobeChunkLoaderFactory::QgsGlobeChunkLoaderFactory(
Qgs3DMapSettings *mapSettings )
322 : mMapSettings( mapSettings )
324 mTextureGenerator =
new QgsTerrainTextureGenerator( *mapSettings );
336QgsGlobeChunkLoaderFactory::~QgsGlobeChunkLoaderFactory()
338 delete mTextureGenerator;
341QgsChunkLoader *QgsGlobeChunkLoaderFactory::createChunkLoader( QgsChunkNode *node )
const
343 return new QgsGlobeChunkLoader( node, mTextureGenerator, mGlobeCrsToLatLon );
346QgsChunkNode *QgsGlobeChunkLoaderFactory::createRootNode()
const
348 QgsBox3D rootNodeBox3D( -mRadiusX, -mRadiusY, -mRadiusZ, +mRadiusX, +mRadiusY, +mRadiusZ );
350 QgsChunkNode *node =
new QgsChunkNode( QgsChunkNodeId( 0, 0, 0, 0 ), rootNodeBox3D, 999'999 );
354QVector<QgsChunkNode *> QgsGlobeChunkLoaderFactory::createChildren( QgsChunkNode *node )
const
356 QVector<QgsChunkNode *> children;
357 if ( node->tileId().d == 0 )
361 float error =
static_cast<float>( std::max( d1, d2 ) ) /
static_cast<float>( mMapSettings->terrainSettings()->mapTileResolution() );
363 QgsBox3D boxWest( -mRadiusX, -mRadiusY, -mRadiusZ, +mRadiusX, 0, +mRadiusZ );
364 QgsBox3D boxEast( -mRadiusX, 0, -mRadiusY, +mRadiusX, +mRadiusY, +mRadiusZ );
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;
371 else if ( node->error() > mMapSettings->terrainSettings()->maximumGroundError() )
373 QgsChunkNodeId nid = node->tileId();
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 );
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() );
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 );
398QgsGlobeMapUpdateJob::QgsGlobeMapUpdateJob( QgsTerrainTextureGenerator *textureGenerator, QgsChunkNode *node )
399 : QgsChunkQueueJob( node )
400 , mTextureGenerator( textureGenerator )
404void QgsGlobeMapUpdateJob::start()
406 QgsChunkNode *node = chunk();
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 );
416 connect( mTextureGenerator, &QgsTerrainTextureGenerator::tileReady,
this, [
this, terrainTexImage](
int jobId,
const QImage &image ) {
417 if ( mJobId == jobId )
419 terrainTexImage->setImage( image );
424 mJobId = mTextureGenerator->render( terrainTexImage->imageExtent(), node->tileId(), terrainTexImage->imageDebugText() );
427void QgsGlobeMapUpdateJob::cancel()
430 mTextureGenerator->cancelJob( mJobId );
438class QgsGlobeMapUpdateJobFactory :
public QgsChunkQueueJobFactory
441 explicit QgsGlobeMapUpdateJobFactory( Qgs3DMapSettings *mapSettings )
443 mTextureGenerator =
new QgsTerrainTextureGenerator( *mapSettings );
446 QgsChunkQueueJob *createJob( QgsChunkNode *chunk )
override
448 return new QgsGlobeMapUpdateJob( mTextureGenerator, chunk );
452 QgsTerrainTextureGenerator *mTextureGenerator =
nullptr;
460 : QgsChunkedEntity( mapSettings, mapSettings->terrainSettings()->maximumScreenError(), new QgsGlobeChunkLoaderFactory( mapSettings ), true )
471 connectToLayersRepaintRequest();
473 mUpdateJobFactory = std::make_unique<QgsGlobeMapUpdateJobFactory>( mapSettings );
476QgsGlobeEntity::~QgsGlobeEntity()
485 QVector3D intersectionPoint;
486 const QList<QgsChunkNode *> active = activeNodes();
487 for ( QgsChunkNode *node : active )
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 )
498 QVector3D nodeIntPoint;
499 int triangleIndex = -1;
503 float dist = ( ray.
origin() - nodeIntPoint ).length();
504 if ( minDist < 0 || dist < minDist )
507 intersectionPoint = nodeIntPoint;
519 hit.
setMapCoordinates( mMapSettings->worldToMapCoordinates( intersectionPoint ) );
524void QgsGlobeEntity::invalidateMapImages()
526 QgsEventTracing::addEvent( QgsEventTracing::Instant, u
"3D"_s, u
"Invalidate textures"_s );
530 updateNodes( mActiveNodes, mUpdateJobFactory.get() );
534 QList<QgsChunkNode *> inactiveNodes;
535 const QList<QgsChunkNode *> descendants = mRootNode->descendants();
536 for ( QgsChunkNode *node : descendants )
538 if ( !node->entity() )
540 if ( mActiveNodes.contains( node ) )
542 if ( !node->parent() )
544 inactiveNodes << node;
547 updateNodes( inactiveNodes, mUpdateJobFactory.get() );
549 setNeedsUpdate(
true );
552void QgsGlobeEntity::onLayersChanged()
554 connectToLayersRepaintRequest();
555 invalidateMapImages();
558void QgsGlobeEntity::connectToLayersRepaintRequest()
560 for (
QgsMapLayer *layer : std::as_const( mLayers ) )
565 mLayers = mMapSettings->layers();
567 for (
QgsMapLayer *layer : std::as_const( mLayers ) )
@ Reverse
Reverse/inverse transform (from destination to source).
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.
float distanceFromPoint(float x, float y, float z) const
Returns shortest distance from the box to a point.
A 3-dimensional box composed of x, y, z coordinates.
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.
Base class for all map layer types.
void repaintRequested(bool deferredUpdate=false)
By emitting this signal the layer tells that either appearance or content have been changed and any v...
A representation of a ray in 3D.
QVector3D origin() const
Returns the origin of the ray.
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...
double y() const
Returns Y coordinate.
double z() const
Returns Z coordinate.
double x() const
Returns X coordinate.
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.