Skip to content

Commit 8c32520

Browse files
committed
Add skirts to hide cracks between adjacent terrain tiles
This adds user configurable skirt height to DEM terrain generator. Skirts are vertical walls at the edges of each terrain tile that make cracks (discontinuities) much less apparent. If there are visible cracks in terrain in the 3D view, try increasing skirt height. The ideal skirt height depends on the scale of the map and range of altitudes.
1 parent ba75123 commit 8c32520

8 files changed

+117
-38
lines changed

src/3d/terrain/qgsdemterraingenerator.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
QgsDemTerrainGenerator::QgsDemTerrainGenerator()
2525
: mResolution( 16 )
26+
, mSkirtHeight( 10.f )
2627
{
2728
}
2829

@@ -47,6 +48,7 @@ QgsTerrainGenerator *QgsDemTerrainGenerator::clone() const
4748
QgsDemTerrainGenerator *cloned = new QgsDemTerrainGenerator;
4849
cloned->mLayer = mLayer;
4950
cloned->mResolution = mResolution;
51+
cloned->mSkirtHeight = mSkirtHeight;
5052
cloned->updateGenerator();
5153
return cloned;
5254
}
@@ -71,12 +73,14 @@ void QgsDemTerrainGenerator::writeXml( QDomElement &elem ) const
7173
{
7274
elem.setAttribute( "layer", mLayer.layerId );
7375
elem.setAttribute( "resolution", mResolution );
76+
elem.setAttribute( "skirt-height", mSkirtHeight );
7477
}
7578

7679
void QgsDemTerrainGenerator::readXml( const QDomElement &elem )
7780
{
7881
mLayer = QgsMapLayerRef( elem.attribute( "layer" ) );
7982
mResolution = elem.attribute( "resolution" ).toInt();
83+
mSkirtHeight = elem.attribute( "skirt-height" ).toFloat();
8084
}
8185

8286
void QgsDemTerrainGenerator::resolveReferences( const QgsProject &project )

src/3d/terrain/qgsdemterraingenerator.h

+7
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ class _3D_EXPORT QgsDemTerrainGenerator : public QgsTerrainGenerator
4949
//! Returns resolution of the generator (how many elevation samples on one side of a terrain tile)
5050
int resolution() const { return mResolution; }
5151

52+
//! Sets skirt height (in world units). Skirts at the edges of terrain tiles help hide cracks between adjacent tiles.
53+
void setSkirtHeight( float skirtHeight ) { mSkirtHeight = skirtHeight; }
54+
//! Returns skirt height (in world units). Skirts at the edges of terrain tiles help hide cracks between adjacent tiles.
55+
float skirtHeight() const { return mSkirtHeight; }
56+
5257
//! Returns height map generator object - takes care of extraction of elevations from the layer)
5358
QgsDemHeightMapGenerator *heightMapGenerator() { return mHeightMapGenerator; }
5459

@@ -71,6 +76,8 @@ class _3D_EXPORT QgsDemTerrainGenerator : public QgsTerrainGenerator
7176
QgsMapLayerRef mLayer;
7277
//! how many vertices to place on one side of the tile
7378
int mResolution;
79+
//! height of the "skirts" at the edges of tiles to hide cracks between adjacent cracks
80+
float mSkirtHeight;
7481
};
7582

7683

src/3d/terrain/qgsdemterraintilegeometry_p.cpp

+40-21
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,15 @@
2424
using namespace Qt3DRender;
2525

2626

27-
static QByteArray createPlaneVertexData( int res, const QByteArray &heights )
27+
static QByteArray createPlaneVertexData( int res, float skirtHeight, const QByteArray &heights )
2828
{
2929
Q_ASSERT( res >= 2 );
3030
Q_ASSERT( heights.count() == res * res * ( int )sizeof( float ) );
3131

32-
const float *zBits = ( const float * ) heights.constData();
32+
const float *zData = ( const float * ) heights.constData();
33+
const float *zBits = zData;
3334

34-
const int nVerts = res * res;
35+
const int nVerts = ( res + 2 ) * ( res + 2 );
3536

3637
// Populate a buffer with the interleaved per-vertex data with
3738
// vec3 pos, vec2 texCoord, vec3 normal, vec4 tangent
@@ -51,26 +52,35 @@ static QByteArray createPlaneVertexData( int res, const QByteArray &heights )
5152
const float dv = 1.0 / ( resolution.height() - 1 );
5253

5354
// Iterate over z
54-
for ( int j = 0; j < resolution.height(); ++j )
55+
for ( int j = -1; j <= resolution.height(); ++j )
5556
{
56-
const float z = z0 + static_cast<float>( j ) * dz;
57-
const float v = static_cast<float>( j ) * dv;
57+
int jBound = qBound( 0, j, resolution.height() - 1 );
58+
const float z = z0 + static_cast<float>( jBound ) * dz;
59+
const float v = static_cast<float>( jBound ) * dv;
5860

5961
// Iterate over x
60-
for ( int i = 0; i < resolution.width(); ++i )
62+
for ( int i = -1; i <= resolution.width(); ++i )
6163
{
62-
const float x = x0 + static_cast<float>( i ) * dx;
63-
const float u = static_cast<float>( i ) * du;
64+
int iBound = qBound( 0, i, resolution.width() - 1 );
65+
const float x = x0 + static_cast<float>( iBound ) * dx;
66+
const float u = static_cast<float>( iBound ) * du;
67+
68+
float height;
69+
if ( i == iBound && j == jBound )
70+
height = *zBits++;
71+
else
72+
height = zData[ jBound * resolution.width() + iBound ] - skirtHeight;
6473

6574
// position
6675
*fptr++ = x;
67-
*fptr++ = *zBits++;
76+
*fptr++ = height;
6877
*fptr++ = z;
6978

7079
// texture coordinates
7180
*fptr++ = u;
7281
*fptr++ = v;
7382

83+
// TODO: compute correct normals based on neighboring pixels
7484
// normal
7585
*fptr++ = 0.0f;
7686
*fptr++ = 1.0f;
@@ -85,22 +95,25 @@ static QByteArray createPlaneVertexData( int res, const QByteArray &heights )
8595
static QByteArray createPlaneIndexData( int res )
8696
{
8797
QSize resolution( res, res );
98+
int numVerticesX = resolution.width() + 2;
99+
int numVerticesZ = resolution.height() + 2;
100+
88101
// Create the index data. 2 triangles per rectangular face
89-
const int faces = 2 * ( resolution.width() - 1 ) * ( resolution.height() - 1 );
102+
const int faces = 2 * ( numVerticesX - 1 ) * ( numVerticesZ - 1 );
90103
const quint32 indices = 3 * faces;
91104
Q_ASSERT( indices < std::numeric_limits<quint32>::max() );
92105
QByteArray indexBytes;
93106
indexBytes.resize( indices * sizeof( quint32 ) );
94107
quint32 *indexPtr = reinterpret_cast<quint32 *>( indexBytes.data() );
95108

96109
// Iterate over z
97-
for ( int j = 0; j < resolution.height() - 1; ++j )
110+
for ( int j = 0; j < numVerticesZ - 1; ++j )
98111
{
99-
const int rowStartIndex = j * resolution.width();
100-
const int nextRowStartIndex = ( j + 1 ) * resolution.width();
112+
const int rowStartIndex = j * numVerticesX;
113+
const int nextRowStartIndex = ( j + 1 ) * numVerticesX;
101114

102115
// Iterate over x
103-
for ( int i = 0; i < resolution.width() - 1; ++i )
116+
for ( int i = 0; i < numVerticesX - 1; ++i )
104117
{
105118
// Split quad into two triangles
106119
*indexPtr++ = rowStartIndex + i;
@@ -122,23 +135,25 @@ static QByteArray createPlaneIndexData( int res )
122135
class PlaneVertexBufferFunctor : public QBufferDataGenerator
123136
{
124137
public:
125-
explicit PlaneVertexBufferFunctor( int resolution, const QByteArray &heightMap )
138+
explicit PlaneVertexBufferFunctor( int resolution, float skirtHeight, const QByteArray &heightMap )
126139
: mResolution( resolution )
140+
, mSkirtHeight( skirtHeight )
127141
, mHeightMap( heightMap )
128142
{}
129143

130144
~PlaneVertexBufferFunctor() {}
131145

132146
QByteArray operator()() final
133147
{
134-
return createPlaneVertexData( mResolution, mHeightMap );
148+
return createPlaneVertexData( mResolution, mSkirtHeight, mHeightMap );
135149
}
136150

137151
bool operator ==( const QBufferDataGenerator &other ) const final
138152
{
139153
const PlaneVertexBufferFunctor *otherFunctor = functor_cast<PlaneVertexBufferFunctor>( &other );
140154
if ( otherFunctor != nullptr )
141155
return ( otherFunctor->mResolution == mResolution &&
156+
otherFunctor->mSkirtHeight == mSkirtHeight &&
142157
otherFunctor->mHeightMap == mHeightMap );
143158
return false;
144159
}
@@ -147,6 +162,7 @@ class PlaneVertexBufferFunctor : public QBufferDataGenerator
147162

148163
private:
149164
int mResolution;
165+
float mSkirtHeight;
150166
QByteArray mHeightMap;
151167
};
152168

@@ -184,9 +200,10 @@ class PlaneIndexBufferFunctor : public QBufferDataGenerator
184200
// ------------
185201

186202

187-
DemTerrainTileGeometry::DemTerrainTileGeometry( int resolution, const QByteArray &heightMap, DemTerrainTileGeometry::QNode *parent )
203+
DemTerrainTileGeometry::DemTerrainTileGeometry( int resolution, float skirtHeight, const QByteArray &heightMap, DemTerrainTileGeometry::QNode *parent )
188204
: QGeometry( parent )
189205
, mResolution( resolution )
206+
, mSkirtHeight( skirtHeight )
190207
, mHeightMap( heightMap )
191208
{
192209
init();
@@ -201,9 +218,11 @@ void DemTerrainTileGeometry::init()
201218
mVertexBuffer = new Qt3DRender::QBuffer( Qt3DRender::QBuffer::VertexBuffer, this );
202219
mIndexBuffer = new Qt3DRender::QBuffer( Qt3DRender::QBuffer::IndexBuffer, this );
203220

204-
const int nVerts = mResolution * mResolution;
221+
int nVertsX = mResolution + 2;
222+
int nVertsZ = mResolution + 2;
223+
const int nVerts = nVertsX * nVertsZ;
205224
const int stride = ( 3 + 2 + 3 ) * sizeof( float );
206-
const int faces = 2 * ( mResolution - 1 ) * ( mResolution - 1 );
225+
const int faces = 2 * ( nVertsX - 1 ) * ( nVertsZ - 1 );
207226

208227
mPositionAttribute->setName( QAttribute::defaultPositionAttributeName() );
209228
mPositionAttribute->setVertexBaseType( QAttribute::Float );
@@ -238,7 +257,7 @@ void DemTerrainTileGeometry::init()
238257
// Each primitive has 3 vertives
239258
mIndexAttribute->setCount( faces * 3 );
240259

241-
mVertexBuffer->setDataGenerator( QSharedPointer<PlaneVertexBufferFunctor>::create( mResolution, mHeightMap ) );
260+
mVertexBuffer->setDataGenerator( QSharedPointer<PlaneVertexBufferFunctor>::create( mResolution, mSkirtHeight, mHeightMap ) );
242261
mIndexBuffer->setDataGenerator( QSharedPointer<PlaneIndexBufferFunctor>::create( mResolution ) );
243262

244263
addAttribute( mPositionAttribute );

src/3d/terrain/qgsdemterraintilegeometry_p.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,14 @@ class DemTerrainTileGeometry : public Qt3DRender::QGeometry
5555
* Constructs a terrain tile geometry. Resolution is the number of vertices on one side of the tile,
5656
* heightMap is array of float values with one height value for each vertex
5757
*/
58-
explicit DemTerrainTileGeometry( int resolution, const QByteArray &heightMap, QNode *parent = nullptr );
58+
explicit DemTerrainTileGeometry( int resolution, float skirtHeight, const QByteArray &heightMap, QNode *parent = nullptr );
5959
~DemTerrainTileGeometry() = default;
6060

6161
private:
6262
void init();
6363

6464
int mResolution;
65+
float mSkirtHeight;
6566
QByteArray mHeightMap;
6667
Qt3DRender::QAttribute *mPositionAttribute = nullptr;
6768
Qt3DRender::QAttribute *mNormalAttribute = nullptr;

src/3d/terrain/qgsdemterraintileloader_p.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ QgsDemTerrainTileLoader::QgsDemTerrainTileLoader( QgsTerrainEntity *terrain, Qgs
5454
connect( generator->heightMapGenerator(), &QgsDemHeightMapGenerator::heightMapReady, this, &QgsDemTerrainTileLoader::onHeightMapReady );
5555
mHeightMapJobId = generator->heightMapGenerator()->render( node->tileX(), node->tileY(), node->tileZ() );
5656
mResolution = generator->heightMapGenerator()->resolution();
57+
mSkirtHeight = generator->skirtHeight();
5758
}
5859

5960
QgsDemTerrainTileLoader::~QgsDemTerrainTileLoader()
@@ -67,7 +68,7 @@ Qt3DCore::QEntity *QgsDemTerrainTileLoader::createEntity( Qt3DCore::QEntity *par
6768
// create geometry renderer
6869

6970
Qt3DRender::QGeometryRenderer *mesh = new Qt3DRender::QGeometryRenderer;
70-
mesh->setGeometry( new DemTerrainTileGeometry( mResolution, mHeightMap, mesh ) );
71+
mesh->setGeometry( new DemTerrainTileGeometry( mResolution, mSkirtHeight, mHeightMap, mesh ) );
7172
entity->addComponent( mesh ); // takes ownership if the component has no parent
7273

7374
// create material

src/3d/terrain/qgsdemterraintileloader_p.h

+1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ class QgsDemTerrainTileLoader : public QgsTerrainTileLoader
6161
int mHeightMapJobId;
6262
QByteArray mHeightMap;
6363
int mResolution;
64+
float mSkirtHeight;
6465
};
6566

6667

src/app/3d/qgs3dmapconfigwidget.cpp

+22-6
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,15 @@ Qgs3DMapConfigWidget::Qgs3DMapConfigWidget( Qgs3DMapSettings *map, QgsMapCanvas
4242
{
4343
QgsDemTerrainGenerator *demTerrainGen = static_cast<QgsDemTerrainGenerator *>( terrainGen );
4444
spinTerrainResolution->setValue( demTerrainGen->resolution() );
45+
spinTerrainSkirtHeight->setValue( demTerrainGen->skirtHeight() );
4546
cboTerrainLayer->setLayer( demTerrainGen->layer() );
4647
}
4748
else
4849
{
4950
cboTerrainLayer->setLayer( nullptr );
5051
spinTerrainResolution->setEnabled( false );
5152
spinTerrainResolution->setValue( 16 );
53+
spinTerrainSkirtHeight->setValue( 10 );
5254
}
5355

5456
spinTerrainScale->setValue( mMap->terrainVerticalScale() );
@@ -74,13 +76,27 @@ void Qgs3DMapConfigWidget::apply()
7476
{
7577
QgsRasterLayer *demLayer = qobject_cast<QgsRasterLayer *>( cboTerrainLayer->currentLayer() );
7678

77-
// TODO: what if just changing generator's properties
78-
if ( demLayer && mMap->terrainGenerator()->type() != QgsTerrainGenerator::Dem )
79+
if ( demLayer )
7980
{
80-
QgsDemTerrainGenerator *demTerrainGen = new QgsDemTerrainGenerator;
81-
demTerrainGen->setLayer( demLayer );
82-
demTerrainGen->setResolution( spinTerrainResolution->value() );
83-
mMap->setTerrainGenerator( demTerrainGen );
81+
bool tGenNeedsUpdate = true;
82+
if ( mMap->terrainGenerator()->type() == QgsTerrainGenerator::Dem )
83+
{
84+
// if we already have a DEM terrain generator, check whether there was actually any change
85+
QgsDemTerrainGenerator *oldDemTerrainGen = static_cast<QgsDemTerrainGenerator *>( mMap->terrainGenerator() );
86+
if ( oldDemTerrainGen->layer() == demLayer &&
87+
oldDemTerrainGen->resolution() == spinTerrainResolution->value() &&
88+
oldDemTerrainGen->skirtHeight() == spinTerrainSkirtHeight->value() )
89+
tGenNeedsUpdate = false;
90+
}
91+
92+
if ( tGenNeedsUpdate )
93+
{
94+
QgsDemTerrainGenerator *demTerrainGen = new QgsDemTerrainGenerator;
95+
demTerrainGen->setLayer( demLayer );
96+
demTerrainGen->setResolution( spinTerrainResolution->value() );
97+
demTerrainGen->setSkirtHeight( spinTerrainSkirtHeight->value() );
98+
mMap->setTerrainGenerator( demTerrainGen );
99+
}
84100
}
85101
else if ( !demLayer && mMap->terrainGenerator()->type() != QgsTerrainGenerator::Flat )
86102
{

0 commit comments

Comments
 (0)