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 );
195 texture->setFormat( Qt3DRender::QAbstractTexture::SRGB8_Alpha8 );
197 QgsGlobeMaterial *material =
new QgsGlobeMaterial( entity );
198 material->setTexture( texture );
200 QgsGeoTransform *geoTransform =
new QgsGeoTransform( entity );
201 geoTransform->setGeoTranslation( meshOrigin );
203 entity->addComponent( material );
204 entity->addComponent( geomRenderer );
205 entity->addComponent( geoTransform );
210static void globeNodeIdToLatLon( QgsChunkNodeId n,
double &latMin,
double &latMax,
double &lonMin,
double &lonMax )
212 if ( n == QgsChunkNodeId( 0, 0, 0, 0 ) )
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;
231 double latMin, latMax, lonMin, lonMax;
232 globeNodeIdToLatLon( n, latMin, latMax, lonMin, lonMax );
234 Q_ASSERT( latMax - latMin <= 90 && lonMax - lonMin <= 90 );
236 QVector<double> x, y, z;
238 x.reserve( pointCount );
239 y.reserve( pointCount );
240 z.reserve( pointCount );
242 x.push_back( lonMin );
243 y.push_back( latMin );
245 x.push_back( lonMin );
246 y.push_back( latMax );
248 x.push_back( lonMax );
249 y.push_back( latMin );
251 x.push_back( lonMax );
252 y.push_back( latMax );
258 box.combineWith( x[2], y[2], z[2] );
259 box.combineWith( x[3], y[3], z[3] );
267QgsGlobeChunkLoader::QgsGlobeChunkLoader( QgsChunkNode *node, QgsTerrainTextureGenerator *textureGenerator,
const QgsCoordinateTransform &globeCrsToLatLon )
268 : QgsChunkLoader( node )
269 , mTextureGenerator( textureGenerator )
270 , mGlobeCrsToLatLon( globeCrsToLatLon )
273void QgsGlobeChunkLoader::start()
275 QgsChunkNode *node = chunk();
277 connect( mTextureGenerator, &QgsTerrainTextureGenerator::tileReady,
this, [
this](
int job,
const QImage &img ) {
285 double latMin, latMax, lonMin, lonMax;
286 globeNodeIdToLatLon( node->tileId(), latMin, latMax, lonMin, lonMax );
288 mJobId = mTextureGenerator->render( extent, node->tileId(), node->tileId().text() );
291Qt3DCore::QEntity *QgsGlobeChunkLoader::createEntity( Qt3DCore::QEntity *parent )
293 if ( mNode->tileId() == QgsChunkNodeId( 0, 0, 0, 0 ) )
295 return new Qt3DCore::QEntity( parent );
298 double latMin, latMax, lonMin, lonMax;
299 globeNodeIdToLatLon( mNode->tileId(), latMin, latMax, lonMin, lonMax );
303 int d = mNode->tileId().d;
314 Qt3DCore::QEntity *e = makeGlobeMesh( lonMin, lonMax, latMin, latMax, slices, slices, mGlobeCrsToLatLon, mTexture, mNode->tileId().text() );
315 e->setParent( parent );
323QgsGlobeChunkLoaderFactory::QgsGlobeChunkLoaderFactory(
Qgs3DMapSettings *mapSettings )
324 : mMapSettings( mapSettings )
326 mTextureGenerator =
new QgsTerrainTextureGenerator( *mapSettings );
338QgsGlobeChunkLoaderFactory::~QgsGlobeChunkLoaderFactory()
340 delete mTextureGenerator;
343QgsChunkLoader *QgsGlobeChunkLoaderFactory::createChunkLoader( QgsChunkNode *node )
const
345 return new QgsGlobeChunkLoader( node, mTextureGenerator, mGlobeCrsToLatLon );
348QgsChunkNode *QgsGlobeChunkLoaderFactory::createRootNode()
const
350 QgsBox3D rootNodeBox3D( -mRadiusX, -mRadiusY, -mRadiusZ, +mRadiusX, +mRadiusY, +mRadiusZ );
352 QgsChunkNode *node =
new QgsChunkNode( QgsChunkNodeId( 0, 0, 0, 0 ), rootNodeBox3D, 999'999 );
356QVector<QgsChunkNode *> QgsGlobeChunkLoaderFactory::createChildren( QgsChunkNode *node )
const
358 QVector<QgsChunkNode *> children;
359 if ( node->tileId().d == 0 )
363 float error =
static_cast<float>( std::max( d1, d2 ) ) /
static_cast<float>( mMapSettings->terrainSettings()->mapTileResolution() );
365 QgsBox3D boxWest( -mRadiusX, -mRadiusY, -mRadiusZ, +mRadiusX, 0, +mRadiusZ );
366 QgsBox3D boxEast( -mRadiusX, 0, -mRadiusY, +mRadiusX, +mRadiusY, +mRadiusZ );
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;
373 else if ( node->error() > mMapSettings->terrainSettings()->maximumGroundError() )
375 QgsChunkNodeId nid = node->tileId();
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 );
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() );
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 );
401QgsGlobeMapUpdateJob::QgsGlobeMapUpdateJob( QgsTerrainTextureGenerator *textureGenerator, QgsChunkNode *node )
402 : QgsChunkQueueJob( node )
403 , mTextureGenerator( textureGenerator )
406void QgsGlobeMapUpdateJob::start()
408 QgsChunkNode *node = chunk();
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 );
418 connect( mTextureGenerator, &QgsTerrainTextureGenerator::tileReady,
this, [
this, terrainTexImage](
int jobId,
const QImage &image ) {
419 if ( mJobId == jobId )
421 terrainTexImage->setImage( image );
426 mJobId = mTextureGenerator->render( terrainTexImage->imageExtent(), node->tileId(), terrainTexImage->imageDebugText() );
429void QgsGlobeMapUpdateJob::cancel()
432 mTextureGenerator->cancelJob( mJobId );
440class QgsGlobeMapUpdateJobFactory :
public QgsChunkQueueJobFactory
443 explicit QgsGlobeMapUpdateJobFactory( Qgs3DMapSettings *mapSettings ) { mTextureGenerator =
new QgsTerrainTextureGenerator( *mapSettings ); }
445 QgsChunkQueueJob *createJob( QgsChunkNode *chunk )
override {
return new QgsGlobeMapUpdateJob( mTextureGenerator, chunk ); }
448 QgsTerrainTextureGenerator *mTextureGenerator =
nullptr;
456 : QgsChunkedEntity( mapSettings, mapSettings->terrainSettings()->maximumScreenError(), new QgsGlobeChunkLoaderFactory( mapSettings ), true )
465 connectToLayersRepaintRequest();
467 mUpdateJobFactory = std::make_unique<QgsGlobeMapUpdateJobFactory>( mapSettings );
470QgsGlobeEntity::~QgsGlobeEntity()
479 QVector3D intersectionPoint;
480 const QList<QgsChunkNode *> active = activeNodes();
481 for ( QgsChunkNode *node : active )
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 )
492 QVector3D nodeIntPoint;
493 int triangleIndex = -1;
497 float dist = ( ray.
origin() - nodeIntPoint ).length();
498 if ( minDist < 0 || dist < minDist )
501 intersectionPoint = nodeIntPoint;
513 hit.
setMapCoordinates( mMapSettings->worldToMapCoordinates( intersectionPoint ) );
518void QgsGlobeEntity::invalidateMapImages()
520 QgsEventTracing::addEvent( QgsEventTracing::Instant, u
"3D"_s, u
"Invalidate textures"_s );
524 updateNodes( mActiveNodes, mUpdateJobFactory.get() );
528 QList<QgsChunkNode *> inactiveNodes;
529 const QList<QgsChunkNode *> descendants = mRootNode->descendants();
530 for ( QgsChunkNode *node : descendants )
532 if ( !node->entity() )
534 if ( mActiveNodes.contains( node ) )
536 if ( !node->parent() )
538 inactiveNodes << node;
541 updateNodes( inactiveNodes, mUpdateJobFactory.get() );
543 setNeedsUpdate(
true );
546void QgsGlobeEntity::onLayersChanged()
548 connectToLayersRepaintRequest();
549 invalidateMapImages();
552void QgsGlobeEntity::connectToLayersRepaintRequest()
554 for (
QgsMapLayer *layer : std::as_const( mLayers ) )
559 mLayers = mMapSettings->layers();
561 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.