38#include <Qt3DCore/QAttribute>
39#include <Qt3DCore/QBuffer>
40#include <Qt3DCore/QEntity>
41#include <Qt3DCore/QGeometry>
42#include <Qt3DRender/QGeometryRenderer>
43#include <Qt3DRender/QTexture>
44#include <Qt3DRender/QTextureImage>
46#include "moc_qgsglobechunkedentity.cpp"
48using namespace Qt::StringLiterals;
52static Qt3DCore::QEntity *makeGlobeMesh(
61 QString textureDebugText,
65 double lonRange = lonMax - lonMin;
66 double latRange = latMax - latMin;
67 double lonStep = lonRange / ( double ) ( lonSliceCount - 1 );
68 double latStep = latRange / ( double ) ( latSliceCount - 1 );
70 std::vector<double> x, y, z;
71 int pointCount = latSliceCount * lonSliceCount;
72 x.reserve( pointCount );
73 y.reserve( pointCount );
74 z.reserve( pointCount );
76 for (
int latSliceIndex = 0; latSliceIndex < latSliceCount; ++latSliceIndex )
78 double lat = latSliceIndex * latStep + latMin;
79 for (
int lonSliceIndex = 0; lonSliceIndex < lonSliceCount; ++lonSliceIndex )
81 double lon = lonSliceIndex * lonStep + lonMin;
93 QgsVector3D meshOriginLatLon( ( lonMin + lonMax ) / 2, ( latMin + latMax ) / 2, 0 );
96 int stride = ( 3 + 2 + 3 ) *
sizeof(
float );
98 QByteArray bufferBytes;
99 bufferBytes.resize( stride * pointCount );
100 float *fptr = (
float * ) bufferBytes.data();
101 for (
int i = 0; i < ( int ) pointCount; ++i )
103 *fptr++ =
static_cast<float>( x[i] - meshOrigin.
x() );
104 *fptr++ =
static_cast<float>( y[i] - meshOrigin.
y() );
105 *fptr++ =
static_cast<float>( z[i] - meshOrigin.
z() );
107 int vi = i / lonSliceCount;
108 int ui = i % lonSliceCount;
109 float v =
static_cast<float>( vi ) /
static_cast<float>( latSliceCount - 1 );
110 float u =
static_cast<float>( ui ) /
static_cast<float>( lonSliceCount - 1 );
114 QVector3D n = QVector3D(
static_cast<float>( x[i] ),
static_cast<float>( y[i] ),
static_cast<float>( z[i] ) ).normalized();
120 int faces = ( lonSliceCount - 1 ) * ( latSliceCount - 1 ) * 2;
121 int indices = faces * 3;
123 QByteArray indexBytes;
124 indexBytes.resize( indices *
static_cast<int>(
sizeof( ushort ) ) );
126 quint16 *indexPtr =
reinterpret_cast<quint16 *
>( indexBytes.data() );
127 for (
int latSliceIndex = 0; latSliceIndex < latSliceCount - 1; ++latSliceIndex )
129 int latSliceStartIndex = latSliceIndex * lonSliceCount;
130 int nextLatSliceStartIndex = lonSliceCount + latSliceStartIndex;
131 for (
int lonSliceIndex = 0; lonSliceIndex < lonSliceCount - 1; ++lonSliceIndex )
133 indexPtr[0] = latSliceStartIndex + lonSliceIndex;
134 indexPtr[1] = lonSliceIndex + latSliceStartIndex + 1;
135 indexPtr[2] = nextLatSliceStartIndex + lonSliceIndex;
137 indexPtr[3] = nextLatSliceStartIndex + lonSliceIndex;
138 indexPtr[4] = lonSliceIndex + latSliceStartIndex + 1;
139 indexPtr[5] = lonSliceIndex + nextLatSliceStartIndex + 1;
145 Qt3DCore::QEntity *entity =
new Qt3DCore::QEntity;
147 Qt3DCore::QBuffer *vertexBuffer =
new Qt3DCore::QBuffer( entity );
148 vertexBuffer->setData( bufferBytes );
150 Qt3DCore::QBuffer *indexBuffer =
new Qt3DCore::QBuffer( entity );
151 indexBuffer->setData( indexBytes );
153 Qt3DCore::QAttribute *positionAttribute =
new Qt3DCore::QAttribute( entity );
154 positionAttribute->setName( Qt3DCore::QAttribute::defaultPositionAttributeName() );
155 positionAttribute->setVertexBaseType( Qt3DCore::QAttribute::Float );
156 positionAttribute->setVertexSize( 3 );
157 positionAttribute->setAttributeType( Qt3DCore::QAttribute::VertexAttribute );
158 positionAttribute->setBuffer( vertexBuffer );
159 positionAttribute->setByteStride( stride );
160 positionAttribute->setCount( pointCount );
162 Qt3DCore::QAttribute *texCoordAttribute =
new Qt3DCore::QAttribute( entity );
163 texCoordAttribute->setName( Qt3DCore::QAttribute::defaultTextureCoordinateAttributeName() );
164 texCoordAttribute->setVertexBaseType( Qt3DCore::QAttribute::Float );
165 texCoordAttribute->setVertexSize( 2 );
166 texCoordAttribute->setAttributeType( Qt3DCore::QAttribute::VertexAttribute );
167 texCoordAttribute->setBuffer( vertexBuffer );
168 texCoordAttribute->setByteStride( stride );
169 texCoordAttribute->setByteOffset( 3 *
sizeof(
float ) );
170 texCoordAttribute->setCount( pointCount );
172 Qt3DCore::QAttribute *normalAttribute =
new Qt3DCore::QAttribute( entity );
173 normalAttribute->setName( Qt3DCore::QAttribute::defaultNormalAttributeName() );
174 normalAttribute->setVertexBaseType( Qt3DCore::QAttribute::Float );
175 normalAttribute->setVertexSize( 3 );
176 normalAttribute->setAttributeType( Qt3DCore::QAttribute::VertexAttribute );
177 normalAttribute->setBuffer( vertexBuffer );
178 normalAttribute->setByteStride( stride );
179 normalAttribute->setByteOffset( 5 *
sizeof(
float ) );
180 normalAttribute->setCount( pointCount );
182 Qt3DCore::QAttribute *indexAttribute =
new Qt3DCore::QAttribute( entity );
183 indexAttribute->setAttributeType( Qt3DCore::QAttribute::IndexAttribute );
184 indexAttribute->setVertexBaseType( Qt3DCore::QAttribute::UnsignedShort );
185 indexAttribute->setBuffer( indexBuffer );
186 indexAttribute->setCount( faces * 3 );
188 Qt3DCore::QGeometry *geometry =
new Qt3DCore::QGeometry( entity );
189 geometry->addAttribute( positionAttribute );
190 geometry->addAttribute( texCoordAttribute );
191 geometry->addAttribute( normalAttribute );
192 geometry->addAttribute( indexAttribute );
194 Qt3DRender::QGeometryRenderer *geomRenderer =
new Qt3DRender::QGeometryRenderer( entity );
195 geomRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::Triangles );
196 geomRenderer->setVertexCount( faces * 3 );
197 geomRenderer->setGeometry( geometry );
199 QgsTerrainTextureImage *textureImage =
new QgsTerrainTextureImage( textureQImage,
QgsRectangle( lonMin, latMin, lonMax, latMax ), textureDebugText, entity );
201 Qt3DRender::QTexture2D *texture =
new Qt3DRender::QTexture2D( entity );
203 texture->addTextureImage( textureImage );
204 texture->setFormat( Qt3DRender::QAbstractTexture::SRGB8_Alpha8 );
206 QgsGlobeMaterial *material =
new QgsGlobeMaterial( entity );
207 material->setTexture( texture );
209 QgsGeoTransform *geoTransform =
new QgsGeoTransform( entity );
210 geoTransform->setGeoTranslation( meshOrigin );
212 entity->addComponent( material );
213 entity->addComponent( geomRenderer );
214 entity->addComponent( geoTransform );
219static void globeNodeIdToLatLon( QgsChunkNodeId n,
double &latMin,
double &latMax,
double &lonMin,
double &lonMax )
221 if ( n == QgsChunkNodeId( 0, 0, 0, 0 ) )
230 double tileSize = 180.0 / std::pow( 2.0, n.d - 1 );
231 lonMin = n.x * tileSize - 180.0;
232 latMin = n.y * tileSize - 90.0;
233 lonMax = lonMin + tileSize;
234 latMax = latMin + tileSize;
240 double latMin, latMax, lonMin, lonMax;
241 globeNodeIdToLatLon( n, latMin, latMax, lonMin, lonMax );
243 Q_ASSERT( latMax - latMin <= 90 && lonMax - lonMin <= 90 );
245 QVector<double> x, y, z;
247 x.reserve( pointCount );
248 y.reserve( pointCount );
249 z.reserve( pointCount );
251 x.push_back( lonMin );
252 y.push_back( latMin );
254 x.push_back( lonMin );
255 y.push_back( latMax );
257 x.push_back( lonMax );
258 y.push_back( latMin );
260 x.push_back( lonMax );
261 y.push_back( latMax );
267 box.combineWith( x[2], y[2], z[2] );
268 box.combineWith( x[3], y[3], z[3] );
277 : QgsChunkLoader( node )
278 , mRenderContext( context )
279 , mTextureGenerator( textureGenerator )
280 , mGlobeCrsToLatLon( globeCrsToLatLon )
283void QgsGlobeChunkLoader::start()
285 QgsChunkNode *node = chunk();
287 connect( mTextureGenerator, &QgsTerrainTextureGenerator::tileReady,
this, [
this](
int job,
const QImage &img ) {
295 double latMin, latMax, lonMin, lonMax;
296 globeNodeIdToLatLon( node->tileId(), latMin, latMax, lonMin, lonMax );
298 mJobId = mTextureGenerator->render( extent, node->tileId(), node->tileId().text() );
301Qt3DCore::QEntity *QgsGlobeChunkLoader::createEntity( Qt3DCore::QEntity *parent )
303 if ( mNode->tileId() == QgsChunkNodeId( 0, 0, 0, 0 ) )
305 return new Qt3DCore::QEntity( parent );
308 double latMin, latMax, lonMin, lonMax;
309 globeNodeIdToLatLon( mNode->tileId(), latMin, latMax, lonMin, lonMax );
313 int d = mNode->tileId().d;
326 Qt3DCore::QEntity *e = makeGlobeMesh( lonMin, lonMax, latMin, latMax, slices, slices, mGlobeCrsToLatLon, mTexture, mNode->tileId().text(), materialContext );
327 e->setParent( parent );
335QgsGlobeChunkLoaderFactory::QgsGlobeChunkLoaderFactory(
Qgs3DMapSettings *mapSettings )
336 : mMapSettings( mapSettings )
338 mTextureGenerator =
new QgsTerrainTextureGenerator( *mapSettings );
350QgsGlobeChunkLoaderFactory::~QgsGlobeChunkLoaderFactory()
352 delete mTextureGenerator;
355QgsChunkLoader *QgsGlobeChunkLoaderFactory::createChunkLoader( QgsChunkNode *node )
const
360QgsChunkNode *QgsGlobeChunkLoaderFactory::createRootNode()
const
362 QgsBox3D rootNodeBox3D( -mRadiusX, -mRadiusY, -mRadiusZ, +mRadiusX, +mRadiusY, +mRadiusZ );
364 QgsChunkNode *node =
new QgsChunkNode( QgsChunkNodeId( 0, 0, 0, 0 ), rootNodeBox3D, 999'999 );
368QVector<QgsChunkNode *> QgsGlobeChunkLoaderFactory::createChildren( QgsChunkNode *node )
const
370 QVector<QgsChunkNode *> children;
371 if ( node->tileId().d == 0 )
375 float error =
static_cast<float>( std::max( d1, d2 ) ) /
static_cast<float>( mMapSettings->terrainSettings()->mapTileResolution() );
377 QgsBox3D boxWest( -mRadiusX, -mRadiusY, -mRadiusZ, +mRadiusX, 0, +mRadiusZ );
378 QgsBox3D boxEast( -mRadiusX, 0, -mRadiusY, +mRadiusX, +mRadiusY, +mRadiusZ );
381 QgsChunkNode *west =
new QgsChunkNode( QgsChunkNodeId( 1, 0, 0, 0 ), boxWest, error, node );
382 QgsChunkNode *east =
new QgsChunkNode( QgsChunkNodeId( 1, 1, 0, 0 ), boxEast, error, node );
383 children << west << east;
385 else if ( node->error() > mMapSettings->terrainSettings()->maximumGroundError() )
387 QgsChunkNodeId nid = node->tileId();
389 double latMin, latMax, lonMin, lonMax;
390 globeNodeIdToLatLon( nid, latMin, latMax, lonMin, lonMax );
391 QgsChunkNodeId cid1( nid.d + 1, nid.x * 2, nid.y * 2 );
392 QgsChunkNodeId cid2( nid.d + 1, nid.x * 2 + 1, nid.y * 2 );
393 QgsChunkNodeId cid3( nid.d + 1, nid.x * 2, nid.y * 2 + 1 );
394 QgsChunkNodeId cid4( nid.d + 1, nid.x * 2 + 1, nid.y * 2 + 1 );
396 double d1 = mDistanceArea.measureLine(
QgsPointXY( lonMin, latMin ),
QgsPointXY( lonMin + ( lonMax - lonMin ) / 2, latMin ) );
397 double d2 = mDistanceArea.measureLine(
QgsPointXY( lonMin, latMin ),
QgsPointXY( lonMin, latMin + ( latMax - latMin ) / 2 ) );
398 float error =
static_cast<float>( std::max( d1, d2 ) ) /
static_cast<float>( mMapSettings->terrainSettings()->mapTileResolution() );
401 <<
new QgsChunkNode( cid1, globeNodeIdToBox3D( cid1, mGlobeCrsToLatLon ), error, node )
402 <<
new QgsChunkNode( cid2, globeNodeIdToBox3D( cid2, mGlobeCrsToLatLon ), error, node )
403 <<
new QgsChunkNode( cid3, globeNodeIdToBox3D( cid3, mGlobeCrsToLatLon ), error, node )
404 <<
new QgsChunkNode( cid4, globeNodeIdToBox3D( cid4, mGlobeCrsToLatLon ), error, node );
413QgsGlobeMapUpdateJob::QgsGlobeMapUpdateJob( QgsTerrainTextureGenerator *textureGenerator, QgsChunkNode *node )
414 : QgsChunkQueueJob( node )
415 , mTextureGenerator( textureGenerator )
418void QgsGlobeMapUpdateJob::start()
420 QgsChunkNode *node = chunk();
423 QVector<QgsGlobeMaterial *> materials = node->entity()->componentsOfType<QgsGlobeMaterial>();
424 Q_ASSERT( materials.count() == 1 );
425 QVector<Qt3DRender::QAbstractTextureImage *> texImages = materials[0]->texture()->textureImages();
426 Q_ASSERT( texImages.count() == 1 );
427 QgsTerrainTextureImage *terrainTexImage = qobject_cast<QgsTerrainTextureImage *>( texImages[0] );
428 Q_ASSERT( terrainTexImage );
430 connect( mTextureGenerator, &QgsTerrainTextureGenerator::tileReady,
this, [
this, terrainTexImage](
int jobId,
const QImage &image ) {
431 if ( mJobId == jobId )
433 terrainTexImage->setImage( image );
438 mJobId = mTextureGenerator->render( terrainTexImage->imageExtent(), node->tileId(), terrainTexImage->imageDebugText() );
441void QgsGlobeMapUpdateJob::cancel()
444 mTextureGenerator->cancelJob( mJobId );
452class QgsGlobeMapUpdateJobFactory :
public QgsChunkQueueJobFactory
455 explicit QgsGlobeMapUpdateJobFactory( Qgs3DMapSettings *mapSettings ) { mTextureGenerator =
new QgsTerrainTextureGenerator( *mapSettings ); }
457 QgsChunkQueueJob *createJob( QgsChunkNode *chunk )
override {
return new QgsGlobeMapUpdateJob( mTextureGenerator, chunk ); }
460 QgsTerrainTextureGenerator *mTextureGenerator =
nullptr;
468 : QgsChunkedEntity( mapSettings, mapSettings->terrainSettings()->maximumScreenError(), new QgsGlobeChunkLoaderFactory( mapSettings ), true )
470 mLayerWatcher.reset(
new QgsLayerStyleWatcher( mapSettings ) );
471 connect( mLayerWatcher.get(), &QgsLayerStyleWatcher::styleChanged,
this, &QgsGlobeEntity::invalidateMapImages );
479 mUpdateJobFactory = std::make_unique<QgsGlobeMapUpdateJobFactory>( mapSettings );
482QgsGlobeEntity::~QgsGlobeEntity()
491 QVector3D intersectionPoint;
492 const QList<QgsChunkNode *> active = activeNodes();
493 for ( QgsChunkNode *node : active )
499 QgsGeoTransform *nodeGeoTransform = node->entity()->findChild<QgsGeoTransform *>();
500 Q_ASSERT( nodeGeoTransform );
501 const QList<Qt3DRender::QGeometryRenderer *> rendLst = node->entity()->findChildren<Qt3DRender::QGeometryRenderer *>();
502 for ( Qt3DRender::QGeometryRenderer *rend : rendLst )
504 QVector3D nodeIntPoint;
505 int triangleIndex = -1;
509 float dist = ( ray.
origin() - nodeIntPoint ).length();
510 if ( minDist < 0 || dist < minDist )
513 intersectionPoint = nodeIntPoint;
525 hit.
setMapCoordinates( mMapSettings->worldToMapCoordinates( intersectionPoint ) );
529void QgsGlobeEntity::invalidateMapImages()
531 QgsEventTracing::addEvent( QgsEventTracing::Instant, u
"3D"_s, u
"Invalidate textures"_s );
535 updateNodes( mActiveNodes, mUpdateJobFactory.get() );
539 QList<QgsChunkNode *> inactiveNodes;
540 const QList<QgsChunkNode *> descendants = mRootNode->descendants();
541 for ( QgsChunkNode *node : descendants )
543 if ( !node->entity() )
545 if ( mActiveNodes.contains( node ) )
547 if ( !node->parent() )
549 inactiveNodes << node;
552 updateNodes( inactiveNodes, mUpdateJobFactory.get() );
554 setNeedsUpdate(
true );
@ 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 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...
Rendering context for preparation of 3D entities.
static Qgs3DRenderContext fromMapSettings(const Qgs3DMapSettings *mapSettings)
Creates an initialized Qgs3DRenderContext instance from given Qgs3DMapSettings.
static void setTextureFiltering(Qt3DRender::QAbstractTexture *texture, const QgsMaterialContext &context)
Sets the default filtering options for a texture.
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.
Context settings for a material.
static QgsMaterialContext fromRenderContext(const Qgs3DRenderContext &context)
Constructs a material context from the settings in a 3D render context.
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.