Skip to content

Commit 2f19d62

Browse files
committedDec 21, 2017
[3d] Respect no-data values from DEMs in terrain generator (fixes #17558)
1 parent 1ddcac5 commit 2f19d62

File tree

5 files changed

+100
-13
lines changed

5 files changed

+100
-13
lines changed
 

‎src/3d/chunks/qgschunkedentity_p.cpp

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,9 @@ void QgsChunkedEntity::requestResidency( QgsChunkNode *node )
304304
}
305305
else if ( node->state() == QgsChunkNode::Skeleton )
306306
{
307+
if ( !node->hasData() )
308+
return; // no need to load (we already tried but got nothing back)
309+
307310
// add to the loading queue
308311
QgsChunkListEntry *entry = new QgsChunkListEntry( node );
309312
node->setQueuedForLoad( entry );
@@ -332,10 +335,18 @@ void QgsChunkedEntity::onActiveJobFinished()
332335
// mark as loaded + create entity
333336
Qt3DCore::QEntity *entity = node->loader()->createEntity( this );
334337

335-
// load into node (should be in main thread again)
336-
node->setLoaded( entity );
338+
if ( entity )
339+
{
340+
// load into node (should be in main thread again)
341+
node->setLoaded( entity );
337342

338-
mReplacementQueue->insertFirst( node->replacementQueueEntry() );
343+
mReplacementQueue->insertFirst( node->replacementQueueEntry() );
344+
}
345+
else
346+
{
347+
node->setHasData( false );
348+
node->cancelLoading();
349+
}
339350

340351
// now we need an update!
341352
mNeedsUpdate = true;

‎src/3d/chunks/qgschunknode_p.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ bool QgsChunkNode::allChildChunksResident( const QTime &currentTime ) const
6060
{
6161
if ( !mChildren[i] )
6262
return false; // not even a skeleton
63-
if ( !mChildren[i]->mEntity )
63+
if ( mChildren[i]->mHasData && !mChildren[i]->mEntity )
6464
return false; // no there yet
6565
Q_UNUSED( currentTime ); // seems we do not need this extra time (it just brings extra problems)
6666
//if (children[i]->entityCreatedTime.msecsTo(currentTime) < 100)

‎src/3d/chunks/qgschunknode_p.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,11 @@ class QgsChunkNode
172172
//! called when bounding box
173173
void setExactBbox( const QgsAABB &box );
174174

175+
//! Sets whether the node has any data to be displayed. Can be used to set to false after load returned no data
176+
void setHasData( bool hasData ) { mHasData = hasData; }
177+
//! 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
178+
bool hasData() const { return mHasData; }
179+
175180
private:
176181
QgsAABB mBbox; //!< Bounding box in world coordinates
177182
float mError; //!< Error of the node in world coordinates
@@ -193,6 +198,7 @@ class QgsChunkNode
193198
QgsChunkQueueJob *mUpdater; //!< Object that does update of the chunk (not null <=> Updating state)
194199

195200
QTime mEntityCreatedTime;
201+
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
196202
};
197203

198204
/// @endcond

‎src/3d/terrain/qgsdemterraintilegeometry_p.cpp

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <Qt3DRender/qbuffer.h>
1919
#include <Qt3DRender/qbufferdatagenerator.h>
2020
#include <limits>
21+
#include <cmath>
2122

2223
///@cond PRIVATE
2324

@@ -51,6 +52,10 @@ static QByteArray createPlaneVertexData( int res, float skirtHeight, const QByte
5152
const float du = 1.0 / ( resolution.width() - 1 );
5253
const float dv = 1.0 / ( resolution.height() - 1 );
5354

55+
// the height of vertices with no-data value... the value should not really matter
56+
// as we do not create valid triangles that would use such vertices
57+
const float noDataHeight = 0;
58+
5459
// Iterate over z
5560
for ( int j = -1; j <= resolution.height(); ++j )
5661
{
@@ -71,6 +76,9 @@ static QByteArray createPlaneVertexData( int res, float skirtHeight, const QByte
7176
else
7277
height = zData[ jBound * resolution.width() + iBound ] - skirtHeight;
7378

79+
if ( std::isnan( height ) )
80+
height = noDataHeight;
81+
7482
// position
7583
*fptr++ = x;
7684
*fptr++ = height;
@@ -91,8 +99,23 @@ static QByteArray createPlaneVertexData( int res, float skirtHeight, const QByte
9199
return bufferBytes;
92100
}
93101

102+
inline int ijToHeightMapIndex( int i, int j, int numVerticesX, int numVerticesZ )
103+
{
104+
i = qBound( 1, i, numVerticesX - 1 ) - 1;
105+
j = qBound( 1, j, numVerticesZ - 1 ) - 1;
106+
return j * ( numVerticesX - 2 ) + i;
107+
}
108+
109+
110+
static bool hasNoData( int i, int j, const float *heightMap, int numVerticesX, int numVerticesZ )
111+
{
112+
return std::isnan( heightMap[ ijToHeightMapIndex( i, j, numVerticesX, numVerticesZ ) ] ) ||
113+
std::isnan( heightMap[ ijToHeightMapIndex( i + 1, j, numVerticesX, numVerticesZ ) ] ) ||
114+
std::isnan( heightMap[ ijToHeightMapIndex( i, j + 1, numVerticesX, numVerticesZ ) ] ) ||
115+
std::isnan( heightMap[ ijToHeightMapIndex( i + 1, j + 1, numVerticesX, numVerticesZ ) ] );
116+
}
94117

95-
static QByteArray createPlaneIndexData( int res )
118+
static QByteArray createPlaneIndexData( int res, const QByteArray &heightMap )
96119
{
97120
QSize resolution( res, res );
98121
int numVerticesX = resolution.width() + 2;
@@ -106,6 +129,8 @@ static QByteArray createPlaneIndexData( int res )
106129
indexBytes.resize( indices * sizeof( quint32 ) );
107130
quint32 *indexPtr = reinterpret_cast<quint32 *>( indexBytes.data() );
108131

132+
const float *heightMapFloat = reinterpret_cast<const float *>( heightMap.constData() );
133+
109134
// Iterate over z
110135
for ( int j = 0; j < numVerticesZ - 1; ++j )
111136
{
@@ -115,6 +140,20 @@ static QByteArray createPlaneIndexData( int res )
115140
// Iterate over x
116141
for ( int i = 0; i < numVerticesX - 1; ++i )
117142
{
143+
if ( hasNoData( i, j, heightMapFloat, numVerticesX, numVerticesZ ) )
144+
{
145+
// at least one corner of the quad has no-data value
146+
// so let's make two invalid triangles
147+
*indexPtr++ = rowStartIndex + i;
148+
*indexPtr++ = rowStartIndex + i;
149+
*indexPtr++ = rowStartIndex + i;
150+
151+
*indexPtr++ = rowStartIndex + i;
152+
*indexPtr++ = rowStartIndex + i;
153+
*indexPtr++ = rowStartIndex + i;
154+
continue;
155+
}
156+
118157
// Split quad into two triangles
119158
*indexPtr++ = rowStartIndex + i;
120159
*indexPtr++ = nextRowStartIndex + i;
@@ -169,13 +208,14 @@ class PlaneVertexBufferFunctor : public QBufferDataGenerator
169208
class PlaneIndexBufferFunctor : public QBufferDataGenerator
170209
{
171210
public:
172-
explicit PlaneIndexBufferFunctor( int resolution )
211+
explicit PlaneIndexBufferFunctor( int resolution, const QByteArray &heightMap )
173212
: mResolution( resolution )
213+
, mHeightMap( heightMap )
174214
{}
175215

176216
QByteArray operator()() final
177217
{
178-
return createPlaneIndexData( mResolution );
218+
return createPlaneIndexData( mResolution, mHeightMap );
179219
}
180220

181221
bool operator ==( const QBufferDataGenerator &other ) const final
@@ -190,6 +230,7 @@ class PlaneIndexBufferFunctor : public QBufferDataGenerator
190230

191231
private:
192232
int mResolution;
233+
QByteArray mHeightMap;
193234
};
194235

195236

@@ -254,7 +295,7 @@ void DemTerrainTileGeometry::init()
254295
mIndexAttribute->setCount( faces * 3 );
255296

256297
mVertexBuffer->setDataGenerator( QSharedPointer<PlaneVertexBufferFunctor>::create( mResolution, mSkirtHeight, mHeightMap ) );
257-
mIndexBuffer->setDataGenerator( QSharedPointer<PlaneIndexBufferFunctor>::create( mResolution ) );
298+
mIndexBuffer->setDataGenerator( QSharedPointer<PlaneIndexBufferFunctor>::create( mResolution, mHeightMap ) );
258299

259300
addAttribute( mPositionAttribute );
260301
addAttribute( mTexCoordAttribute );

‎src/3d/terrain/qgsdemterraintileloader_p.cpp

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,23 @@ static void _heightMapMinMax( const QByteArray &heightMap, float &zMin, float &z
3131
{
3232
const float *zBits = ( const float * ) heightMap.constData();
3333
int zCount = heightMap.count() / sizeof( float );
34-
zMin = zBits[0];
35-
zMax = zBits[0];
34+
bool first = true;
3635
for ( int i = 0; i < zCount; ++i )
3736
{
3837
float z = zBits[i];
38+
if ( std::isnan( z ) )
39+
continue;
40+
if ( first )
41+
{
42+
zMin = zMax = z;
43+
first = false;
44+
}
3945
zMin = qMin( zMin, z );
4046
zMax = qMax( zMax, z );
4147
}
48+
49+
if ( first )
50+
zMin = zMax = std::numeric_limits<float>::quiet_NaN();
4251
}
4352

4453

@@ -59,6 +68,15 @@ QgsDemTerrainTileLoader::QgsDemTerrainTileLoader( QgsTerrainEntity *terrain, Qgs
5968

6069
Qt3DCore::QEntity *QgsDemTerrainTileLoader::createEntity( Qt3DCore::QEntity *parent )
6170
{
71+
float zMin, zMax;
72+
_heightMapMinMax( mHeightMap, zMin, zMax );
73+
74+
if ( std::isnan( zMin ) || std::isnan( zMax ) )
75+
{
76+
// no data available for this tile
77+
return nullptr;
78+
}
79+
6280
QgsTerrainTileEntity *entity = new QgsTerrainTileEntity;
6381

6482
// create geometry renderer
@@ -77,9 +95,6 @@ Qt3DCore::QEntity *QgsDemTerrainTileLoader::createEntity( Qt3DCore::QEntity *par
7795
transform = new Qt3DCore::QTransform();
7896
entity->addComponent( transform );
7997

80-
float zMin, zMax;
81-
_heightMapMinMax( mHeightMap, zMin, zMax );
82-
8398
const Qgs3DMapSettings &map = terrain()->map3D();
8499
QgsRectangle extent = map.terrainGenerator()->tilingScheme().tileToExtent( mNode->tileX(), mNode->tileY(), mNode->tileZ() ); //node->extent;
85100
double x0 = extent.xMinimum() - map.origin().x();
@@ -156,6 +171,20 @@ static QByteArray _readDtmData( QgsRasterDataProvider *provider, const QgsRectan
156171
block->convert( Qgis::Float32 ); // currently we expect just floats
157172
data = block->data();
158173
data.detach(); // this should make a deep copy
174+
175+
if ( block->hasNoData() )
176+
{
177+
// turn all no-data values into NaN in the output array
178+
float *floatData = reinterpret_cast<float *>( data.data() );
179+
Q_ASSERT( data.count() % sizeof( float ) == 0 );
180+
int count = data.count() / sizeof( float );
181+
for ( int i = 0; i < count; ++i )
182+
{
183+
if ( block->isNoData( i ) )
184+
floatData[i] = std::numeric_limits<float>::quiet_NaN();
185+
}
186+
}
187+
159188
delete block;
160189
}
161190
return data;

0 commit comments

Comments
 (0)
Please sign in to comment.