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(
52 double lonMin,
double lonMax,
double latMin,
double latMax,
int lonSliceCount,
int latSliceCount,
const QgsCoordinateTransform &globeCrsToLatLon, QImage textureQImage, QString textureDebugText
55 double lonRange = lonMax - lonMin;
56 double latRange = latMax - latMin;
57 double lonStep = lonRange / ( double ) ( lonSliceCount - 1 );
58 double latStep = latRange / ( double ) ( latSliceCount - 1 );
60 std::vector<double> x, y, z;
61 int pointCount = latSliceCount * lonSliceCount;
62 x.reserve( pointCount );
63 y.reserve( pointCount );
64 z.reserve( pointCount );
66 for (
int latSliceIndex = 0; latSliceIndex < latSliceCount; ++latSliceIndex )
68 double lat = latSliceIndex * latStep + latMin;
69 for (
int lonSliceIndex = 0; lonSliceIndex < lonSliceCount; ++lonSliceIndex )
71 double lon = lonSliceIndex * lonStep + lonMin;
83 QgsVector3D meshOriginLatLon( ( lonMin + lonMax ) / 2, ( latMin + latMax ) / 2, 0 );
86 int stride = ( 3 + 2 + 3 ) *
sizeof(
float );
88 QByteArray bufferBytes;
89 bufferBytes.resize( stride * pointCount );
90 float *fptr = (
float * ) bufferBytes.data();
91 for (
int i = 0; i < ( int ) pointCount; ++i )
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() );
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 );
104 QVector3D n = QVector3D(
static_cast<float>( x[i] ),
static_cast<float>( y[i] ),
static_cast<float>( z[i] ) ).normalized();
110 int faces = ( lonSliceCount - 1 ) * ( latSliceCount - 1 ) * 2;
111 int indices = faces * 3;
113 QByteArray indexBytes;
114 indexBytes.resize( indices *
static_cast<int>(
sizeof( ushort ) ) );
116 quint16 *indexPtr =
reinterpret_cast<quint16 *
>( indexBytes.data() );
117 for (
int latSliceIndex = 0; latSliceIndex < latSliceCount - 1; ++latSliceIndex )
119 int latSliceStartIndex = latSliceIndex * lonSliceCount;
120 int nextLatSliceStartIndex = lonSliceCount + latSliceStartIndex;
121 for (
int lonSliceIndex = 0; lonSliceIndex < lonSliceCount - 1; ++lonSliceIndex )
123 indexPtr[0] = latSliceStartIndex + lonSliceIndex;
124 indexPtr[1] = lonSliceIndex + latSliceStartIndex + 1;
125 indexPtr[2] = nextLatSliceStartIndex + lonSliceIndex;
127 indexPtr[3] = nextLatSliceStartIndex + lonSliceIndex;
128 indexPtr[4] = lonSliceIndex + latSliceStartIndex + 1;
129 indexPtr[5] = lonSliceIndex + nextLatSliceStartIndex + 1;
135 Qt3DCore::QEntity *entity =
new Qt3DCore::QEntity;
137 Qt3DCore::QBuffer *vertexBuffer =
new Qt3DCore::QBuffer( entity );
138 vertexBuffer->setData( bufferBytes );
140 Qt3DCore::QBuffer *indexBuffer =
new Qt3DCore::QBuffer( entity );
141 indexBuffer->setData( indexBytes );
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 );
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 );
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 );
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 );
178 Qt3DCore::QGeometry *geometry =
new Qt3DCore::QGeometry( entity );
179 geometry->addAttribute( positionAttribute );
180 geometry->addAttribute( texCoordAttribute );
181 geometry->addAttribute( normalAttribute );
182 geometry->addAttribute( indexAttribute );
184 Qt3DRender::QGeometryRenderer *geomRenderer =
new Qt3DRender::QGeometryRenderer( entity );
185 geomRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::Triangles );
186 geomRenderer->setVertexCount( faces * 3 );
187 geomRenderer->setGeometry( geometry );
189 QgsTerrainTextureImage *textureImage =
new QgsTerrainTextureImage( textureQImage,
QgsRectangle( lonMin, latMin, lonMax, latMax ), textureDebugText, entity );
191 Qt3DRender::QTexture2D *texture =
new Qt3DRender::QTexture2D( entity );
192 texture->addTextureImage( textureImage );
193 texture->setMinificationFilter( Qt3DRender::QTexture2D::Linear );
194 texture->setMagnificationFilter( Qt3DRender::QTexture2D::Linear );
196 QgsGlobeMaterial *material =
new QgsGlobeMaterial( entity );
197 material->setTexture( texture );
199 QgsGeoTransform *geoTransform =
new QgsGeoTransform( entity );
200 geoTransform->setGeoTranslation( meshOrigin );
202 entity->addComponent( material );
203 entity->addComponent( geomRenderer );
204 entity->addComponent( geoTransform );
209static void globeNodeIdToLatLon( QgsChunkNodeId n,
double &latMin,
double &latMax,
double &lonMin,
double &lonMax )
211 if ( n == QgsChunkNodeId( 0, 0, 0, 0 ) )
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;
230 double latMin, latMax, lonMin, lonMax;
231 globeNodeIdToLatLon( n, latMin, latMax, lonMin, lonMax );
233 Q_ASSERT( latMax - latMin <= 90 && lonMax - lonMin <= 90 );
235 QVector<double> x, y, z;
237 x.reserve( pointCount );
238 y.reserve( pointCount );
239 z.reserve( pointCount );
241 x.push_back( lonMin );
242 y.push_back( latMin );
244 x.push_back( lonMin );
245 y.push_back( latMax );
247 x.push_back( lonMax );
248 y.push_back( latMin );
250 x.push_back( lonMax );
251 y.push_back( latMax );
257 box.combineWith( x[2], y[2], z[2] );
258 box.combineWith( x[3], y[3], z[3] );
266QgsGlobeChunkLoader::QgsGlobeChunkLoader( QgsChunkNode *node, QgsTerrainTextureGenerator *textureGenerator,
const QgsCoordinateTransform &globeCrsToLatLon )
267 : QgsChunkLoader( node )
268 , mTextureGenerator( textureGenerator )
269 , mGlobeCrsToLatLon( globeCrsToLatLon )
272void QgsGlobeChunkLoader::start()
274 QgsChunkNode *node = chunk();
276 connect( mTextureGenerator, &QgsTerrainTextureGenerator::tileReady,
this, [
this](
int job,
const QImage &img ) {
284 double latMin, latMax, lonMin, lonMax;
285 globeNodeIdToLatLon( node->tileId(), latMin, latMax, lonMin, lonMax );
287 mJobId = mTextureGenerator->render( extent, node->tileId(), node->tileId().text() );
290Qt3DCore::QEntity *QgsGlobeChunkLoader::createEntity( Qt3DCore::QEntity *parent )
292 if ( mNode->tileId() == QgsChunkNodeId( 0, 0, 0, 0 ) )
294 return new Qt3DCore::QEntity( parent );
297 double latMin, latMax, lonMin, lonMax;
298 globeNodeIdToLatLon( mNode->tileId(), latMin, latMax, lonMin, lonMax );
302 int d = mNode->tileId().d;
313 Qt3DCore::QEntity *e = makeGlobeMesh( lonMin, lonMax, latMin, latMax, slices, slices, mGlobeCrsToLatLon, mTexture, mNode->tileId().text() );
314 e->setParent( parent );
322QgsGlobeChunkLoaderFactory::QgsGlobeChunkLoaderFactory(
Qgs3DMapSettings *mapSettings )
323 : mMapSettings( mapSettings )
325 mTextureGenerator =
new QgsTerrainTextureGenerator( *mapSettings );
337QgsGlobeChunkLoaderFactory::~QgsGlobeChunkLoaderFactory()
339 delete mTextureGenerator;
342QgsChunkLoader *QgsGlobeChunkLoaderFactory::createChunkLoader( QgsChunkNode *node )
const
344 return new QgsGlobeChunkLoader( node, mTextureGenerator, mGlobeCrsToLatLon );
347QgsChunkNode *QgsGlobeChunkLoaderFactory::createRootNode()
const
349 QgsBox3D rootNodeBox3D( -mRadiusX, -mRadiusY, -mRadiusZ, +mRadiusX, +mRadiusY, +mRadiusZ );
351 QgsChunkNode *node =
new QgsChunkNode( QgsChunkNodeId( 0, 0, 0, 0 ), rootNodeBox3D, 999'999 );
355QVector<QgsChunkNode *> QgsGlobeChunkLoaderFactory::createChildren( QgsChunkNode *node )
const
357 QVector<QgsChunkNode *> children;
358 if ( node->tileId().d == 0 )
362 float error =
static_cast<float>( std::max( d1, d2 ) ) /
static_cast<float>( mMapSettings->terrainSettings()->mapTileResolution() );
364 QgsBox3D boxWest( -mRadiusX, -mRadiusY, -mRadiusZ, +mRadiusX, 0, +mRadiusZ );
365 QgsBox3D boxEast( -mRadiusX, 0, -mRadiusY, +mRadiusX, +mRadiusY, +mRadiusZ );
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;
372 else if ( node->error() > mMapSettings->terrainSettings()->maximumGroundError() )
374 QgsChunkNodeId nid = node->tileId();
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 );
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() );
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 );
400QgsGlobeMapUpdateJob::QgsGlobeMapUpdateJob( QgsTerrainTextureGenerator *textureGenerator, QgsChunkNode *node )
401 : QgsChunkQueueJob( node )
402 , mTextureGenerator( textureGenerator )
405void QgsGlobeMapUpdateJob::start()
407 QgsChunkNode *node = chunk();
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 );
417 connect( mTextureGenerator, &QgsTerrainTextureGenerator::tileReady,
this, [
this, terrainTexImage](
int jobId,
const QImage &image ) {
418 if ( mJobId == jobId )
420 terrainTexImage->setImage( image );
425 mJobId = mTextureGenerator->render( terrainTexImage->imageExtent(), node->tileId(), terrainTexImage->imageDebugText() );
428void QgsGlobeMapUpdateJob::cancel()
431 mTextureGenerator->cancelJob( mJobId );
439class QgsGlobeMapUpdateJobFactory :
public QgsChunkQueueJobFactory
442 explicit QgsGlobeMapUpdateJobFactory( Qgs3DMapSettings *mapSettings ) { mTextureGenerator =
new QgsTerrainTextureGenerator( *mapSettings ); }
444 QgsChunkQueueJob *createJob( QgsChunkNode *chunk )
override {
return new QgsGlobeMapUpdateJob( mTextureGenerator, chunk ); }
447 QgsTerrainTextureGenerator *mTextureGenerator =
nullptr;
455 : QgsChunkedEntity( mapSettings, mapSettings->terrainSettings()->maximumScreenError(), new QgsGlobeChunkLoaderFactory( mapSettings ), true )
464 connectToLayersRepaintRequest();
466 mUpdateJobFactory = std::make_unique<QgsGlobeMapUpdateJobFactory>( mapSettings );
469QgsGlobeEntity::~QgsGlobeEntity()
478 QVector3D intersectionPoint;
479 const QList<QgsChunkNode *> active = activeNodes();
480 for ( QgsChunkNode *node : active )
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 )
491 QVector3D nodeIntPoint;
492 int triangleIndex = -1;
496 float dist = ( ray.
origin() - nodeIntPoint ).length();
497 if ( minDist < 0 || dist < minDist )
500 intersectionPoint = nodeIntPoint;
512 hit.
setMapCoordinates( mMapSettings->worldToMapCoordinates( intersectionPoint ) );
517void QgsGlobeEntity::invalidateMapImages()
519 QgsEventTracing::addEvent( QgsEventTracing::Instant, u
"3D"_s, u
"Invalidate textures"_s );
523 updateNodes( mActiveNodes, mUpdateJobFactory.get() );
527 QList<QgsChunkNode *> inactiveNodes;
528 const QList<QgsChunkNode *> descendants = mRootNode->descendants();
529 for ( QgsChunkNode *node : descendants )
531 if ( !node->entity() )
533 if ( mActiveNodes.contains( node ) )
535 if ( !node->parent() )
537 inactiveNodes << node;
540 updateNodes( inactiveNodes, mUpdateJobFactory.get() );
542 setNeedsUpdate(
true );
545void QgsGlobeEntity::onLayersChanged()
547 connectToLayersRepaintRequest();
548 invalidateMapImages();
551void QgsGlobeEntity::connectToLayersRepaintRequest()
553 for (
QgsMapLayer *layer : std::as_const( mLayers ) )
558 mLayers = mMapSettings->layers();
560 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.