Add missing Q_OBJECT macros

This commit is contained in:
Nyall Dawson 2025-08-12 15:10:00 +10:00
parent cf24023fa6
commit a9a77de725
2 changed files with 178 additions and 153 deletions

View File

@ -43,7 +43,6 @@ typedef Qt3DCore::QGeometry Qt3DQGeometry;
#include "qgs3dmapsettings.h"
#include "qgs3dutils.h"
#include "qgschunkloader.h"
#include "qgscoordinatereferencesystem.h"
#include "qgscoordinatetransform.h"
#include "qgsdistancearea.h"
@ -270,193 +269,162 @@ static QgsBox3D globeNodeIdToBox3D( QgsChunkNodeId n, const QgsCoordinateTransfo
// ---------------
class QgsGlobeChunkLoader : public QgsChunkLoader
QgsGlobeChunkLoader::QgsGlobeChunkLoader( QgsChunkNode *node, QgsTerrainTextureGenerator *textureGenerator, const QgsCoordinateTransform &globeCrsToLatLon )
: QgsChunkLoader( node )
, mTextureGenerator( textureGenerator )
, mGlobeCrsToLatLon( globeCrsToLatLon )
{
public:
QgsGlobeChunkLoader( QgsChunkNode *node, QgsTerrainTextureGenerator *textureGenerator, const QgsCoordinateTransform &globeCrsToLatLon )
: QgsChunkLoader( node )
, mTextureGenerator( textureGenerator )
, mGlobeCrsToLatLon( globeCrsToLatLon )
connect( mTextureGenerator, &QgsTerrainTextureGenerator::tileReady, this, [this]( int job, const QImage &img ) {
if ( job == mJobId )
{
connect( mTextureGenerator, &QgsTerrainTextureGenerator::tileReady, this, [this]( int job, const QImage &img ) {
if ( job == mJobId )
{
mTexture = img;
emit finished();
}
} );
double latMin, latMax, lonMin, lonMax;
globeNodeIdToLatLon( node->tileId(), latMin, latMax, lonMin, lonMax );
QgsRectangle extent( lonMin, latMin, lonMax, latMax );
mJobId = mTextureGenerator->render( extent, node->tileId(), node->tileId().text() );
mTexture = img;
emit finished();
}
} );
Qt3DCore::QEntity *createEntity( Qt3DCore::QEntity *parent ) override
{
if ( mNode->tileId() == QgsChunkNodeId( 0, 0, 0, 0 ) )
{
return new Qt3DCore::QEntity( parent );
}
double latMin, latMax, lonMin, lonMax;
globeNodeIdToLatLon( node->tileId(), latMin, latMax, lonMin, lonMax );
QgsRectangle extent( lonMin, latMin, lonMax, latMax );
mJobId = mTextureGenerator->render( extent, node->tileId(), node->tileId().text() );
}
double latMin, latMax, lonMin, lonMax;
globeNodeIdToLatLon( mNode->tileId(), latMin, latMax, lonMin, lonMax );
Qt3DCore::QEntity *QgsGlobeChunkLoader::createEntity( Qt3DCore::QEntity *parent )
{
if ( mNode->tileId() == QgsChunkNodeId( 0, 0, 0, 0 ) )
{
return new Qt3DCore::QEntity( parent );
}
// This is quite ad-hoc estimation how many slices we need. It could
// be improved by basing the calculation on sagitta
int d = mNode->tileId().d;
int slices;
if ( d <= 4 )
slices = 19;
else if ( d <= 8 )
slices = 9;
else if ( d <= 12 )
slices = 5;
else
slices = 2;
double latMin, latMax, lonMin, lonMax;
globeNodeIdToLatLon( mNode->tileId(), latMin, latMax, lonMin, lonMax );
Qt3DCore::QEntity *e = makeGlobeMesh( lonMin, lonMax, latMin, latMax, slices, slices, mGlobeCrsToLatLon, mTexture, mNode->tileId().text() );
e->setParent( parent );
return e;
}
// This is quite ad-hoc estimation how many slices we need. It could
// be improved by basing the calculation on sagitta
int d = mNode->tileId().d;
int slices;
if ( d <= 4 )
slices = 19;
else if ( d <= 8 )
slices = 9;
else if ( d <= 12 )
slices = 5;
else
slices = 2;
private:
QgsTerrainTextureGenerator *mTextureGenerator;
QgsCoordinateTransform mGlobeCrsToLatLon;
int mJobId;
QImage mTexture;
};
Qt3DCore::QEntity *e = makeGlobeMesh( lonMin, lonMax, latMin, latMax, slices, slices, mGlobeCrsToLatLon, mTexture, mNode->tileId().text() );
e->setParent( parent );
return e;
}
// ---------------
class QgsGlobeChunkLoaderFactory : public QgsChunkLoaderFactory
QgsGlobeChunkLoaderFactory::QgsGlobeChunkLoaderFactory( Qgs3DMapSettings *mapSettings )
: mMapSettings( mapSettings )
{
public:
QgsGlobeChunkLoaderFactory( Qgs3DMapSettings *mapSettings )
: mMapSettings( mapSettings )
{
mTextureGenerator = new QgsTerrainTextureGenerator( *mapSettings );
mTextureGenerator = new QgsTerrainTextureGenerator( *mapSettings );
// it does not matter what kind of ellipsoid is used, this is for rough estimates
mDistanceArea.setEllipsoid( mapSettings->crs().ellipsoidAcronym() );
// it does not matter what kind of ellipsoid is used, this is for rough estimates
mDistanceArea.setEllipsoid( mapSettings->crs().ellipsoidAcronym() );
mGlobeCrsToLatLon = QgsCoordinateTransform( mapSettings->crs(), mapSettings->crs().toGeographicCrs(), mapSettings->transformContext() );
mGlobeCrsToLatLon = QgsCoordinateTransform( mapSettings->crs(), mapSettings->crs().toGeographicCrs(), mapSettings->transformContext() );
mRadiusX = mGlobeCrsToLatLon.transform( QgsVector3D( 0, 0, 0 ), Qgis::TransformDirection::Reverse ).x();
mRadiusY = mGlobeCrsToLatLon.transform( QgsVector3D( 90, 0, 0 ), Qgis::TransformDirection::Reverse ).y();
mRadiusZ = mGlobeCrsToLatLon.transform( QgsVector3D( 0, 90, 0 ), Qgis::TransformDirection::Reverse ).z();
}
mRadiusX = mGlobeCrsToLatLon.transform( QgsVector3D( 0, 0, 0 ), Qgis::TransformDirection::Reverse ).x();
mRadiusY = mGlobeCrsToLatLon.transform( QgsVector3D( 90, 0, 0 ), Qgis::TransformDirection::Reverse ).y();
mRadiusZ = mGlobeCrsToLatLon.transform( QgsVector3D( 0, 90, 0 ), Qgis::TransformDirection::Reverse ).z();
}
~QgsGlobeChunkLoaderFactory()
{
delete mTextureGenerator;
}
QgsGlobeChunkLoaderFactory::~QgsGlobeChunkLoaderFactory()
{
delete mTextureGenerator;
}
QgsChunkLoader *createChunkLoader( QgsChunkNode *node ) const override
{
return new QgsGlobeChunkLoader( node, mTextureGenerator, mGlobeCrsToLatLon );
}
QgsChunkLoader *QgsGlobeChunkLoaderFactory::createChunkLoader( QgsChunkNode *node ) const
{
return new QgsGlobeChunkLoader( node, mTextureGenerator, mGlobeCrsToLatLon );
}
QgsChunkNode *createRootNode() const override
{
QgsBox3D rootNodeBox3D( -mRadiusX, -mRadiusY, -mRadiusZ, +mRadiusX, +mRadiusY, +mRadiusZ );
// use very high error to force immediate switch to level 1 (two hemispheres)
QgsChunkNode *node = new QgsChunkNode( QgsChunkNodeId( 0, 0, 0, 0 ), rootNodeBox3D, 999'999 );
return node;
}
QgsChunkNode *QgsGlobeChunkLoaderFactory::createRootNode() const
{
QgsBox3D rootNodeBox3D( -mRadiusX, -mRadiusY, -mRadiusZ, +mRadiusX, +mRadiusY, +mRadiusZ );
// use very high error to force immediate switch to level 1 (two hemispheres)
QgsChunkNode *node = new QgsChunkNode( QgsChunkNodeId( 0, 0, 0, 0 ), rootNodeBox3D, 999'999 );
return node;
}
QVector<QgsChunkNode *> createChildren( QgsChunkNode *node ) const override
{
QVector<QgsChunkNode *> children;
if ( node->tileId().d == 0 )
{
double d1 = mDistanceArea.measureLine( QgsPointXY( 0, 0 ), QgsPointXY( 90, 0 ) );
double d2 = mDistanceArea.measureLine( QgsPointXY( 0, 0 ), QgsPointXY( 0, 90 ) );
float error = static_cast<float>( std::max( d1, d2 ) ) / static_cast<float>( mMapSettings->terrainSettings()->mapTileResolution() );
QVector<QgsChunkNode *> QgsGlobeChunkLoaderFactory::createChildren( QgsChunkNode *node ) const
{
QVector<QgsChunkNode *> children;
if ( node->tileId().d == 0 )
{
double d1 = mDistanceArea.measureLine( QgsPointXY( 0, 0 ), QgsPointXY( 90, 0 ) );
double d2 = mDistanceArea.measureLine( QgsPointXY( 0, 0 ), QgsPointXY( 0, 90 ) );
float error = static_cast<float>( std::max( d1, d2 ) ) / static_cast<float>( mMapSettings->terrainSettings()->mapTileResolution() );
QgsBox3D boxWest( -mRadiusX, -mRadiusY, -mRadiusZ, +mRadiusX, 0, +mRadiusZ );
QgsBox3D boxEast( -mRadiusX, 0, -mRadiusY, +mRadiusX, +mRadiusY, +mRadiusZ );
QgsBox3D boxWest( -mRadiusX, -mRadiusY, -mRadiusZ, +mRadiusX, 0, +mRadiusZ );
QgsBox3D boxEast( -mRadiusX, 0, -mRadiusY, +mRadiusX, +mRadiusY, +mRadiusZ );
// two children: western and eastern hemisphere
QgsChunkNode *west = new QgsChunkNode( QgsChunkNodeId( 1, 0, 0, 0 ), boxWest, error, node );
QgsChunkNode *east = new QgsChunkNode( QgsChunkNodeId( 1, 1, 0, 0 ), boxEast, error, node );
children << west << east;
}
else if ( node->error() > mMapSettings->terrainSettings()->maximumGroundError() )
{
QgsChunkNodeId nid = node->tileId();
// two children: western and eastern hemisphere
QgsChunkNode *west = new QgsChunkNode( QgsChunkNodeId( 1, 0, 0, 0 ), boxWest, error, node );
QgsChunkNode *east = new QgsChunkNode( QgsChunkNodeId( 1, 1, 0, 0 ), boxEast, error, node );
children << west << east;
}
else if ( node->error() > mMapSettings->terrainSettings()->maximumGroundError() )
{
QgsChunkNodeId nid = node->tileId();
double latMin, latMax, lonMin, lonMax;
globeNodeIdToLatLon( nid, latMin, latMax, lonMin, lonMax );
QgsChunkNodeId cid1( nid.d + 1, nid.x * 2, nid.y * 2 );
QgsChunkNodeId cid2( nid.d + 1, nid.x * 2 + 1, nid.y * 2 );
QgsChunkNodeId cid3( nid.d + 1, nid.x * 2, nid.y * 2 + 1 );
QgsChunkNodeId cid4( nid.d + 1, nid.x * 2 + 1, nid.y * 2 + 1 );
double latMin, latMax, lonMin, lonMax;
globeNodeIdToLatLon( nid, latMin, latMax, lonMin, lonMax );
QgsChunkNodeId cid1( nid.d + 1, nid.x * 2, nid.y * 2 );
QgsChunkNodeId cid2( nid.d + 1, nid.x * 2 + 1, nid.y * 2 );
QgsChunkNodeId cid3( nid.d + 1, nid.x * 2, nid.y * 2 + 1 );
QgsChunkNodeId cid4( nid.d + 1, nid.x * 2 + 1, nid.y * 2 + 1 );
double d1 = mDistanceArea.measureLine( QgsPointXY( lonMin, latMin ), QgsPointXY( lonMin + ( lonMax - lonMin ) / 2, latMin ) );
double d2 = mDistanceArea.measureLine( QgsPointXY( lonMin, latMin ), QgsPointXY( lonMin, latMin + ( latMax - latMin ) / 2 ) );
float error = static_cast<float>( std::max( d1, d2 ) ) / static_cast<float>( mMapSettings->terrainSettings()->mapTileResolution() );
double d1 = mDistanceArea.measureLine( QgsPointXY( lonMin, latMin ), QgsPointXY( lonMin + ( lonMax - lonMin ) / 2, latMin ) );
double d2 = mDistanceArea.measureLine( QgsPointXY( lonMin, latMin ), QgsPointXY( lonMin, latMin + ( latMax - latMin ) / 2 ) );
float error = static_cast<float>( std::max( d1, d2 ) ) / static_cast<float>( mMapSettings->terrainSettings()->mapTileResolution() );
children << new QgsChunkNode( cid1, globeNodeIdToBox3D( cid1, mGlobeCrsToLatLon ), error, node )
<< new QgsChunkNode( cid2, globeNodeIdToBox3D( cid2, mGlobeCrsToLatLon ), error, node )
<< new QgsChunkNode( cid3, globeNodeIdToBox3D( cid3, mGlobeCrsToLatLon ), error, node )
<< new QgsChunkNode( cid4, globeNodeIdToBox3D( cid4, mGlobeCrsToLatLon ), error, node );
}
return children;
}
private:
Qgs3DMapSettings *mMapSettings = nullptr;
QgsTerrainTextureGenerator *mTextureGenerator = nullptr; // owned by the factory
QgsDistanceArea mDistanceArea;
QgsCoordinateTransform mGlobeCrsToLatLon;
double mRadiusX, mRadiusY, mRadiusZ;
};
children << new QgsChunkNode( cid1, globeNodeIdToBox3D( cid1, mGlobeCrsToLatLon ), error, node )
<< new QgsChunkNode( cid2, globeNodeIdToBox3D( cid2, mGlobeCrsToLatLon ), error, node )
<< new QgsChunkNode( cid3, globeNodeIdToBox3D( cid3, mGlobeCrsToLatLon ), error, node )
<< new QgsChunkNode( cid4, globeNodeIdToBox3D( cid4, mGlobeCrsToLatLon ), error, node );
}
return children;
}
// ---------------
//! Handles asynchronous updates of globe's map images when layers change
class QgsGlobeMapUpdateJob : public QgsChunkQueueJob
QgsGlobeMapUpdateJob::QgsGlobeMapUpdateJob( QgsTerrainTextureGenerator *textureGenerator, QgsChunkNode *node )
: QgsChunkQueueJob( node )
, mTextureGenerator( textureGenerator )
{
public:
QgsGlobeMapUpdateJob( QgsTerrainTextureGenerator *textureGenerator, QgsChunkNode *node )
: QgsChunkQueueJob( node )
, mTextureGenerator( textureGenerator )
// extract our terrain texture image from the 3D entity
QVector<QgsGlobeMaterial *> materials = node->entity()->componentsOfType<QgsGlobeMaterial>();
Q_ASSERT( materials.count() == 1 );
QVector<Qt3DRender::QAbstractTextureImage *> texImages = materials[0]->texture()->textureImages();
Q_ASSERT( texImages.count() == 1 );
QgsTerrainTextureImage *terrainTexImage = qobject_cast<QgsTerrainTextureImage *>( texImages[0] );
Q_ASSERT( terrainTexImage );
connect( textureGenerator, &QgsTerrainTextureGenerator::tileReady, this, [this, terrainTexImage]( int jobId, const QImage &image ) {
if ( mJobId == jobId )
{
// extract our terrain texture image from the 3D entity
QVector<QgsGlobeMaterial *> materials = node->entity()->componentsOfType<QgsGlobeMaterial>();
Q_ASSERT( materials.count() == 1 );
QVector<Qt3DRender::QAbstractTextureImage *> texImages = materials[0]->texture()->textureImages();
Q_ASSERT( texImages.count() == 1 );
QgsTerrainTextureImage *terrainTexImage = qobject_cast<QgsTerrainTextureImage *>( texImages[0] );
Q_ASSERT( terrainTexImage );
connect( textureGenerator, &QgsTerrainTextureGenerator::tileReady, this, [this, terrainTexImage]( int jobId, const QImage &image ) {
if ( mJobId == jobId )
{
terrainTexImage->setImage( image );
mJobId = -1;
emit finished();
}
} );
mJobId = textureGenerator->render( terrainTexImage->imageExtent(), node->tileId(), terrainTexImage->imageDebugText() );
terrainTexImage->setImage( image );
mJobId = -1;
emit finished();
}
} );
mJobId = textureGenerator->render( terrainTexImage->imageExtent(), node->tileId(), terrainTexImage->imageDebugText() );
}
void cancel() override
{
if ( mJobId != -1 )
mTextureGenerator->cancelJob( mJobId );
}
private:
QgsTerrainTextureGenerator *mTextureGenerator = nullptr;
int mJobId;
};
void QgsGlobeMapUpdateJob::cancel()
{
if ( mJobId != -1 )
mTextureGenerator->cancelJob( mJobId );
}
// ---------------

View File

@ -34,9 +34,66 @@
#include "qgschunkedentity.h"
#include "qgschunkloader.h"
#include "qgscoordinatetransform.h"
#include "qgsdistancearea.h"
#include <QImage>
class QgsMapLayer;
class QgsGlobeMapUpdateJobFactory;
class QgsTerrainTextureGenerator;
class QgsGlobeChunkLoader : public QgsChunkLoader
{
Q_OBJECT
public:
QgsGlobeChunkLoader( QgsChunkNode *node, QgsTerrainTextureGenerator *textureGenerator, const QgsCoordinateTransform &globeCrsToLatLon );
Qt3DCore::QEntity *createEntity( Qt3DCore::QEntity *parent ) override;
private:
QgsTerrainTextureGenerator *mTextureGenerator;
QgsCoordinateTransform mGlobeCrsToLatLon;
int mJobId;
QImage mTexture;
};
//! Handles asynchronous updates of globe's map images when layers change
class QgsGlobeMapUpdateJob : public QgsChunkQueueJob
{
Q_OBJECT
public:
QgsGlobeMapUpdateJob( QgsTerrainTextureGenerator *textureGenerator, QgsChunkNode *node );
void cancel() override;
private:
QgsTerrainTextureGenerator *mTextureGenerator = nullptr;
int mJobId;
};
class QgsGlobeChunkLoaderFactory : public QgsChunkLoaderFactory
{
Q_OBJECT
public:
QgsGlobeChunkLoaderFactory( Qgs3DMapSettings *mapSettings );
~QgsGlobeChunkLoaderFactory();
QgsChunkLoader *createChunkLoader( QgsChunkNode *node ) const override;
QgsChunkNode *createRootNode() const override;
QVector<QgsChunkNode *> createChildren( QgsChunkNode *node ) const override;
private:
Qgs3DMapSettings *mMapSettings = nullptr;
QgsTerrainTextureGenerator *mTextureGenerator = nullptr; // owned by the factory
QgsDistanceArea mDistanceArea;
QgsCoordinateTransform mGlobeCrsToLatLon;
double mRadiusX, mRadiusY, mRadiusZ;
};
/**
* 3D chunked entity implementation to generate globe mesh with constant elevation