mirror of
https://github.com/qgis/QGIS.git
synced 2025-02-25 00:58:06 -05:00
[3d] Respect no-data values from DEMs in terrain generator (fixes #17558)
This commit is contained in:
parent
1ddcac501c
commit
2f19d62997
@ -304,6 +304,9 @@ void QgsChunkedEntity::requestResidency( QgsChunkNode *node )
|
|||||||
}
|
}
|
||||||
else if ( node->state() == QgsChunkNode::Skeleton )
|
else if ( node->state() == QgsChunkNode::Skeleton )
|
||||||
{
|
{
|
||||||
|
if ( !node->hasData() )
|
||||||
|
return; // no need to load (we already tried but got nothing back)
|
||||||
|
|
||||||
// add to the loading queue
|
// add to the loading queue
|
||||||
QgsChunkListEntry *entry = new QgsChunkListEntry( node );
|
QgsChunkListEntry *entry = new QgsChunkListEntry( node );
|
||||||
node->setQueuedForLoad( entry );
|
node->setQueuedForLoad( entry );
|
||||||
@ -332,10 +335,18 @@ void QgsChunkedEntity::onActiveJobFinished()
|
|||||||
// mark as loaded + create entity
|
// mark as loaded + create entity
|
||||||
Qt3DCore::QEntity *entity = node->loader()->createEntity( this );
|
Qt3DCore::QEntity *entity = node->loader()->createEntity( this );
|
||||||
|
|
||||||
// load into node (should be in main thread again)
|
if ( entity )
|
||||||
node->setLoaded( entity );
|
{
|
||||||
|
// load into node (should be in main thread again)
|
||||||
|
node->setLoaded( entity );
|
||||||
|
|
||||||
mReplacementQueue->insertFirst( node->replacementQueueEntry() );
|
mReplacementQueue->insertFirst( node->replacementQueueEntry() );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
node->setHasData( false );
|
||||||
|
node->cancelLoading();
|
||||||
|
}
|
||||||
|
|
||||||
// now we need an update!
|
// now we need an update!
|
||||||
mNeedsUpdate = true;
|
mNeedsUpdate = true;
|
||||||
|
@ -60,7 +60,7 @@ bool QgsChunkNode::allChildChunksResident( const QTime ¤tTime ) const
|
|||||||
{
|
{
|
||||||
if ( !mChildren[i] )
|
if ( !mChildren[i] )
|
||||||
return false; // not even a skeleton
|
return false; // not even a skeleton
|
||||||
if ( !mChildren[i]->mEntity )
|
if ( mChildren[i]->mHasData && !mChildren[i]->mEntity )
|
||||||
return false; // no there yet
|
return false; // no there yet
|
||||||
Q_UNUSED( currentTime ); // seems we do not need this extra time (it just brings extra problems)
|
Q_UNUSED( currentTime ); // seems we do not need this extra time (it just brings extra problems)
|
||||||
//if (children[i]->entityCreatedTime.msecsTo(currentTime) < 100)
|
//if (children[i]->entityCreatedTime.msecsTo(currentTime) < 100)
|
||||||
|
@ -172,6 +172,11 @@ class QgsChunkNode
|
|||||||
//! called when bounding box
|
//! called when bounding box
|
||||||
void setExactBbox( const QgsAABB &box );
|
void setExactBbox( const QgsAABB &box );
|
||||||
|
|
||||||
|
//! Sets whether the node has any data to be displayed. Can be used to set to false after load returned no data
|
||||||
|
void setHasData( bool hasData ) { mHasData = hasData; }
|
||||||
|
//! Returns whether the node has any data to be displayed. If not, it will be kept as a skeleton node and will not get loaded anymore
|
||||||
|
bool hasData() const { return mHasData; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QgsAABB mBbox; //!< Bounding box in world coordinates
|
QgsAABB mBbox; //!< Bounding box in world coordinates
|
||||||
float mError; //!< Error of the node in world coordinates
|
float mError; //!< Error of the node in world coordinates
|
||||||
@ -193,6 +198,7 @@ class QgsChunkNode
|
|||||||
QgsChunkQueueJob *mUpdater; //!< Object that does update of the chunk (not null <=> Updating state)
|
QgsChunkQueueJob *mUpdater; //!< Object that does update of the chunk (not null <=> Updating state)
|
||||||
|
|
||||||
QTime mEntityCreatedTime;
|
QTime mEntityCreatedTime;
|
||||||
|
bool mHasData = true; //!< Whether there are (will be) any data in this node (or any descentants) and so whether it makes sense to load this node
|
||||||
};
|
};
|
||||||
|
|
||||||
/// @endcond
|
/// @endcond
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
#include <Qt3DRender/qbuffer.h>
|
#include <Qt3DRender/qbuffer.h>
|
||||||
#include <Qt3DRender/qbufferdatagenerator.h>
|
#include <Qt3DRender/qbufferdatagenerator.h>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
///@cond PRIVATE
|
///@cond PRIVATE
|
||||||
|
|
||||||
@ -51,6 +52,10 @@ static QByteArray createPlaneVertexData( int res, float skirtHeight, const QByte
|
|||||||
const float du = 1.0 / ( resolution.width() - 1 );
|
const float du = 1.0 / ( resolution.width() - 1 );
|
||||||
const float dv = 1.0 / ( resolution.height() - 1 );
|
const float dv = 1.0 / ( resolution.height() - 1 );
|
||||||
|
|
||||||
|
// the height of vertices with no-data value... the value should not really matter
|
||||||
|
// as we do not create valid triangles that would use such vertices
|
||||||
|
const float noDataHeight = 0;
|
||||||
|
|
||||||
// Iterate over z
|
// Iterate over z
|
||||||
for ( int j = -1; j <= resolution.height(); ++j )
|
for ( int j = -1; j <= resolution.height(); ++j )
|
||||||
{
|
{
|
||||||
@ -71,6 +76,9 @@ static QByteArray createPlaneVertexData( int res, float skirtHeight, const QByte
|
|||||||
else
|
else
|
||||||
height = zData[ jBound * resolution.width() + iBound ] - skirtHeight;
|
height = zData[ jBound * resolution.width() + iBound ] - skirtHeight;
|
||||||
|
|
||||||
|
if ( std::isnan( height ) )
|
||||||
|
height = noDataHeight;
|
||||||
|
|
||||||
// position
|
// position
|
||||||
*fptr++ = x;
|
*fptr++ = x;
|
||||||
*fptr++ = height;
|
*fptr++ = height;
|
||||||
@ -91,8 +99,23 @@ static QByteArray createPlaneVertexData( int res, float skirtHeight, const QByte
|
|||||||
return bufferBytes;
|
return bufferBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline int ijToHeightMapIndex( int i, int j, int numVerticesX, int numVerticesZ )
|
||||||
|
{
|
||||||
|
i = qBound( 1, i, numVerticesX - 1 ) - 1;
|
||||||
|
j = qBound( 1, j, numVerticesZ - 1 ) - 1;
|
||||||
|
return j * ( numVerticesX - 2 ) + i;
|
||||||
|
}
|
||||||
|
|
||||||
static QByteArray createPlaneIndexData( int res )
|
|
||||||
|
static bool hasNoData( int i, int j, const float *heightMap, int numVerticesX, int numVerticesZ )
|
||||||
|
{
|
||||||
|
return std::isnan( heightMap[ ijToHeightMapIndex( i, j, numVerticesX, numVerticesZ ) ] ) ||
|
||||||
|
std::isnan( heightMap[ ijToHeightMapIndex( i + 1, j, numVerticesX, numVerticesZ ) ] ) ||
|
||||||
|
std::isnan( heightMap[ ijToHeightMapIndex( i, j + 1, numVerticesX, numVerticesZ ) ] ) ||
|
||||||
|
std::isnan( heightMap[ ijToHeightMapIndex( i + 1, j + 1, numVerticesX, numVerticesZ ) ] );
|
||||||
|
}
|
||||||
|
|
||||||
|
static QByteArray createPlaneIndexData( int res, const QByteArray &heightMap )
|
||||||
{
|
{
|
||||||
QSize resolution( res, res );
|
QSize resolution( res, res );
|
||||||
int numVerticesX = resolution.width() + 2;
|
int numVerticesX = resolution.width() + 2;
|
||||||
@ -106,6 +129,8 @@ static QByteArray createPlaneIndexData( int res )
|
|||||||
indexBytes.resize( indices * sizeof( quint32 ) );
|
indexBytes.resize( indices * sizeof( quint32 ) );
|
||||||
quint32 *indexPtr = reinterpret_cast<quint32 *>( indexBytes.data() );
|
quint32 *indexPtr = reinterpret_cast<quint32 *>( indexBytes.data() );
|
||||||
|
|
||||||
|
const float *heightMapFloat = reinterpret_cast<const float *>( heightMap.constData() );
|
||||||
|
|
||||||
// Iterate over z
|
// Iterate over z
|
||||||
for ( int j = 0; j < numVerticesZ - 1; ++j )
|
for ( int j = 0; j < numVerticesZ - 1; ++j )
|
||||||
{
|
{
|
||||||
@ -115,6 +140,20 @@ static QByteArray createPlaneIndexData( int res )
|
|||||||
// Iterate over x
|
// Iterate over x
|
||||||
for ( int i = 0; i < numVerticesX - 1; ++i )
|
for ( int i = 0; i < numVerticesX - 1; ++i )
|
||||||
{
|
{
|
||||||
|
if ( hasNoData( i, j, heightMapFloat, numVerticesX, numVerticesZ ) )
|
||||||
|
{
|
||||||
|
// at least one corner of the quad has no-data value
|
||||||
|
// so let's make two invalid triangles
|
||||||
|
*indexPtr++ = rowStartIndex + i;
|
||||||
|
*indexPtr++ = rowStartIndex + i;
|
||||||
|
*indexPtr++ = rowStartIndex + i;
|
||||||
|
|
||||||
|
*indexPtr++ = rowStartIndex + i;
|
||||||
|
*indexPtr++ = rowStartIndex + i;
|
||||||
|
*indexPtr++ = rowStartIndex + i;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Split quad into two triangles
|
// Split quad into two triangles
|
||||||
*indexPtr++ = rowStartIndex + i;
|
*indexPtr++ = rowStartIndex + i;
|
||||||
*indexPtr++ = nextRowStartIndex + i;
|
*indexPtr++ = nextRowStartIndex + i;
|
||||||
@ -169,13 +208,14 @@ class PlaneVertexBufferFunctor : public QBufferDataGenerator
|
|||||||
class PlaneIndexBufferFunctor : public QBufferDataGenerator
|
class PlaneIndexBufferFunctor : public QBufferDataGenerator
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit PlaneIndexBufferFunctor( int resolution )
|
explicit PlaneIndexBufferFunctor( int resolution, const QByteArray &heightMap )
|
||||||
: mResolution( resolution )
|
: mResolution( resolution )
|
||||||
|
, mHeightMap( heightMap )
|
||||||
{}
|
{}
|
||||||
|
|
||||||
QByteArray operator()() final
|
QByteArray operator()() final
|
||||||
{
|
{
|
||||||
return createPlaneIndexData( mResolution );
|
return createPlaneIndexData( mResolution, mHeightMap );
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator ==( const QBufferDataGenerator &other ) const final
|
bool operator ==( const QBufferDataGenerator &other ) const final
|
||||||
@ -190,6 +230,7 @@ class PlaneIndexBufferFunctor : public QBufferDataGenerator
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
int mResolution;
|
int mResolution;
|
||||||
|
QByteArray mHeightMap;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -254,7 +295,7 @@ void DemTerrainTileGeometry::init()
|
|||||||
mIndexAttribute->setCount( faces * 3 );
|
mIndexAttribute->setCount( faces * 3 );
|
||||||
|
|
||||||
mVertexBuffer->setDataGenerator( QSharedPointer<PlaneVertexBufferFunctor>::create( mResolution, mSkirtHeight, mHeightMap ) );
|
mVertexBuffer->setDataGenerator( QSharedPointer<PlaneVertexBufferFunctor>::create( mResolution, mSkirtHeight, mHeightMap ) );
|
||||||
mIndexBuffer->setDataGenerator( QSharedPointer<PlaneIndexBufferFunctor>::create( mResolution ) );
|
mIndexBuffer->setDataGenerator( QSharedPointer<PlaneIndexBufferFunctor>::create( mResolution, mHeightMap ) );
|
||||||
|
|
||||||
addAttribute( mPositionAttribute );
|
addAttribute( mPositionAttribute );
|
||||||
addAttribute( mTexCoordAttribute );
|
addAttribute( mTexCoordAttribute );
|
||||||
|
@ -31,14 +31,23 @@ static void _heightMapMinMax( const QByteArray &heightMap, float &zMin, float &z
|
|||||||
{
|
{
|
||||||
const float *zBits = ( const float * ) heightMap.constData();
|
const float *zBits = ( const float * ) heightMap.constData();
|
||||||
int zCount = heightMap.count() / sizeof( float );
|
int zCount = heightMap.count() / sizeof( float );
|
||||||
zMin = zBits[0];
|
bool first = true;
|
||||||
zMax = zBits[0];
|
|
||||||
for ( int i = 0; i < zCount; ++i )
|
for ( int i = 0; i < zCount; ++i )
|
||||||
{
|
{
|
||||||
float z = zBits[i];
|
float z = zBits[i];
|
||||||
|
if ( std::isnan( z ) )
|
||||||
|
continue;
|
||||||
|
if ( first )
|
||||||
|
{
|
||||||
|
zMin = zMax = z;
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
zMin = qMin( zMin, z );
|
zMin = qMin( zMin, z );
|
||||||
zMax = qMax( zMax, z );
|
zMax = qMax( zMax, z );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( first )
|
||||||
|
zMin = zMax = std::numeric_limits<float>::quiet_NaN();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -59,6 +68,15 @@ QgsDemTerrainTileLoader::QgsDemTerrainTileLoader( QgsTerrainEntity *terrain, Qgs
|
|||||||
|
|
||||||
Qt3DCore::QEntity *QgsDemTerrainTileLoader::createEntity( Qt3DCore::QEntity *parent )
|
Qt3DCore::QEntity *QgsDemTerrainTileLoader::createEntity( Qt3DCore::QEntity *parent )
|
||||||
{
|
{
|
||||||
|
float zMin, zMax;
|
||||||
|
_heightMapMinMax( mHeightMap, zMin, zMax );
|
||||||
|
|
||||||
|
if ( std::isnan( zMin ) || std::isnan( zMax ) )
|
||||||
|
{
|
||||||
|
// no data available for this tile
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
QgsTerrainTileEntity *entity = new QgsTerrainTileEntity;
|
QgsTerrainTileEntity *entity = new QgsTerrainTileEntity;
|
||||||
|
|
||||||
// create geometry renderer
|
// create geometry renderer
|
||||||
@ -77,9 +95,6 @@ Qt3DCore::QEntity *QgsDemTerrainTileLoader::createEntity( Qt3DCore::QEntity *par
|
|||||||
transform = new Qt3DCore::QTransform();
|
transform = new Qt3DCore::QTransform();
|
||||||
entity->addComponent( transform );
|
entity->addComponent( transform );
|
||||||
|
|
||||||
float zMin, zMax;
|
|
||||||
_heightMapMinMax( mHeightMap, zMin, zMax );
|
|
||||||
|
|
||||||
const Qgs3DMapSettings &map = terrain()->map3D();
|
const Qgs3DMapSettings &map = terrain()->map3D();
|
||||||
QgsRectangle extent = map.terrainGenerator()->tilingScheme().tileToExtent( mNode->tileX(), mNode->tileY(), mNode->tileZ() ); //node->extent;
|
QgsRectangle extent = map.terrainGenerator()->tilingScheme().tileToExtent( mNode->tileX(), mNode->tileY(), mNode->tileZ() ); //node->extent;
|
||||||
double x0 = extent.xMinimum() - map.origin().x();
|
double x0 = extent.xMinimum() - map.origin().x();
|
||||||
@ -156,6 +171,20 @@ static QByteArray _readDtmData( QgsRasterDataProvider *provider, const QgsRectan
|
|||||||
block->convert( Qgis::Float32 ); // currently we expect just floats
|
block->convert( Qgis::Float32 ); // currently we expect just floats
|
||||||
data = block->data();
|
data = block->data();
|
||||||
data.detach(); // this should make a deep copy
|
data.detach(); // this should make a deep copy
|
||||||
|
|
||||||
|
if ( block->hasNoData() )
|
||||||
|
{
|
||||||
|
// turn all no-data values into NaN in the output array
|
||||||
|
float *floatData = reinterpret_cast<float *>( data.data() );
|
||||||
|
Q_ASSERT( data.count() % sizeof( float ) == 0 );
|
||||||
|
int count = data.count() / sizeof( float );
|
||||||
|
for ( int i = 0; i < count; ++i )
|
||||||
|
{
|
||||||
|
if ( block->isNoData( i ) )
|
||||||
|
floatData[i] = std::numeric_limits<float>::quiet_NaN();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
delete block;
|
delete block;
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user