Skip to content
Permalink
Browse files

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.
  • Loading branch information
wonder-sk committed Oct 26, 2017
1 parent ba75123 commit 8c325206fc977ace4c9be99085349a10edb03a5c
@@ -23,6 +23,7 @@

QgsDemTerrainGenerator::QgsDemTerrainGenerator()
: mResolution( 16 )
, mSkirtHeight( 10.f )
{
}

@@ -47,6 +48,7 @@ QgsTerrainGenerator *QgsDemTerrainGenerator::clone() const
QgsDemTerrainGenerator *cloned = new QgsDemTerrainGenerator;
cloned->mLayer = mLayer;
cloned->mResolution = mResolution;
cloned->mSkirtHeight = mSkirtHeight;
cloned->updateGenerator();
return cloned;
}
@@ -71,12 +73,14 @@ void QgsDemTerrainGenerator::writeXml( QDomElement &elem ) const
{
elem.setAttribute( "layer", mLayer.layerId );
elem.setAttribute( "resolution", mResolution );
elem.setAttribute( "skirt-height", mSkirtHeight );
}

void QgsDemTerrainGenerator::readXml( const QDomElement &elem )
{
mLayer = QgsMapLayerRef( elem.attribute( "layer" ) );
mResolution = elem.attribute( "resolution" ).toInt();
mSkirtHeight = elem.attribute( "skirt-height" ).toFloat();
}

void QgsDemTerrainGenerator::resolveReferences( const QgsProject &project )
@@ -49,6 +49,11 @@ class _3D_EXPORT QgsDemTerrainGenerator : public QgsTerrainGenerator
//! Returns resolution of the generator (how many elevation samples on one side of a terrain tile)
int resolution() const { return mResolution; }

//! Sets skirt height (in world units). Skirts at the edges of terrain tiles help hide cracks between adjacent tiles.
void setSkirtHeight( float skirtHeight ) { mSkirtHeight = skirtHeight; }
//! Returns skirt height (in world units). Skirts at the edges of terrain tiles help hide cracks between adjacent tiles.
float skirtHeight() const { return mSkirtHeight; }

//! Returns height map generator object - takes care of extraction of elevations from the layer)
QgsDemHeightMapGenerator *heightMapGenerator() { return mHeightMapGenerator; }

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


@@ -24,14 +24,15 @@
using namespace Qt3DRender;


static QByteArray createPlaneVertexData( int res, const QByteArray &heights )
static QByteArray createPlaneVertexData( int res, float skirtHeight, const QByteArray &heights )
{
Q_ASSERT( res >= 2 );
Q_ASSERT( heights.count() == res * res * ( int )sizeof( float ) );

const float *zBits = ( const float * ) heights.constData();
const float *zData = ( const float * ) heights.constData();
const float *zBits = zData;

const int nVerts = res * res;
const int nVerts = ( res + 2 ) * ( res + 2 );

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

// Iterate over z
for ( int j = 0; j < resolution.height(); ++j )
for ( int j = -1; j <= resolution.height(); ++j )
{
const float z = z0 + static_cast<float>( j ) * dz;
const float v = static_cast<float>( j ) * dv;
int jBound = qBound( 0, j, resolution.height() - 1 );
const float z = z0 + static_cast<float>( jBound ) * dz;
const float v = static_cast<float>( jBound ) * dv;

// Iterate over x
for ( int i = 0; i < resolution.width(); ++i )
for ( int i = -1; i <= resolution.width(); ++i )
{
const float x = x0 + static_cast<float>( i ) * dx;
const float u = static_cast<float>( i ) * du;
int iBound = qBound( 0, i, resolution.width() - 1 );
const float x = x0 + static_cast<float>( iBound ) * dx;
const float u = static_cast<float>( iBound ) * du;

float height;
if ( i == iBound && j == jBound )
height = *zBits++;
else
height = zData[ jBound * resolution.width() + iBound ] - skirtHeight;

// position
*fptr++ = x;
*fptr++ = *zBits++;
*fptr++ = height;
*fptr++ = z;

// texture coordinates
*fptr++ = u;
*fptr++ = v;

// TODO: compute correct normals based on neighboring pixels
// normal
*fptr++ = 0.0f;
*fptr++ = 1.0f;
@@ -85,22 +95,25 @@ static QByteArray createPlaneVertexData( int res, const QByteArray &heights )
static QByteArray createPlaneIndexData( int res )
{
QSize resolution( res, res );
int numVerticesX = resolution.width() + 2;
int numVerticesZ = resolution.height() + 2;

// Create the index data. 2 triangles per rectangular face
const int faces = 2 * ( resolution.width() - 1 ) * ( resolution.height() - 1 );
const int faces = 2 * ( numVerticesX - 1 ) * ( numVerticesZ - 1 );
const quint32 indices = 3 * faces;
Q_ASSERT( indices < std::numeric_limits<quint32>::max() );
QByteArray indexBytes;
indexBytes.resize( indices * sizeof( quint32 ) );
quint32 *indexPtr = reinterpret_cast<quint32 *>( indexBytes.data() );

// Iterate over z
for ( int j = 0; j < resolution.height() - 1; ++j )
for ( int j = 0; j < numVerticesZ - 1; ++j )
{
const int rowStartIndex = j * resolution.width();
const int nextRowStartIndex = ( j + 1 ) * resolution.width();
const int rowStartIndex = j * numVerticesX;
const int nextRowStartIndex = ( j + 1 ) * numVerticesX;

// Iterate over x
for ( int i = 0; i < resolution.width() - 1; ++i )
for ( int i = 0; i < numVerticesX - 1; ++i )
{
// Split quad into two triangles
*indexPtr++ = rowStartIndex + i;
@@ -122,23 +135,25 @@ static QByteArray createPlaneIndexData( int res )
class PlaneVertexBufferFunctor : public QBufferDataGenerator
{
public:
explicit PlaneVertexBufferFunctor( int resolution, const QByteArray &heightMap )
explicit PlaneVertexBufferFunctor( int resolution, float skirtHeight, const QByteArray &heightMap )
: mResolution( resolution )
, mSkirtHeight( skirtHeight )
, mHeightMap( heightMap )
{}

~PlaneVertexBufferFunctor() {}

QByteArray operator()() final
{
return createPlaneVertexData( mResolution, mHeightMap );
return createPlaneVertexData( mResolution, mSkirtHeight, mHeightMap );
}

bool operator ==( const QBufferDataGenerator &other ) const final
{
const PlaneVertexBufferFunctor *otherFunctor = functor_cast<PlaneVertexBufferFunctor>( &other );
if ( otherFunctor != nullptr )
return ( otherFunctor->mResolution == mResolution &&
otherFunctor->mSkirtHeight == mSkirtHeight &&
otherFunctor->mHeightMap == mHeightMap );
return false;
}
@@ -147,6 +162,7 @@ class PlaneVertexBufferFunctor : public QBufferDataGenerator

private:
int mResolution;
float mSkirtHeight;
QByteArray mHeightMap;
};

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


DemTerrainTileGeometry::DemTerrainTileGeometry( int resolution, const QByteArray &heightMap, DemTerrainTileGeometry::QNode *parent )
DemTerrainTileGeometry::DemTerrainTileGeometry( int resolution, float skirtHeight, const QByteArray &heightMap, DemTerrainTileGeometry::QNode *parent )
: QGeometry( parent )
, mResolution( resolution )
, mSkirtHeight( skirtHeight )
, mHeightMap( heightMap )
{
init();
@@ -201,9 +218,11 @@ void DemTerrainTileGeometry::init()
mVertexBuffer = new Qt3DRender::QBuffer( Qt3DRender::QBuffer::VertexBuffer, this );
mIndexBuffer = new Qt3DRender::QBuffer( Qt3DRender::QBuffer::IndexBuffer, this );

const int nVerts = mResolution * mResolution;
int nVertsX = mResolution + 2;
int nVertsZ = mResolution + 2;
const int nVerts = nVertsX * nVertsZ;
const int stride = ( 3 + 2 + 3 ) * sizeof( float );
const int faces = 2 * ( mResolution - 1 ) * ( mResolution - 1 );
const int faces = 2 * ( nVertsX - 1 ) * ( nVertsZ - 1 );

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

mVertexBuffer->setDataGenerator( QSharedPointer<PlaneVertexBufferFunctor>::create( mResolution, mHeightMap ) );
mVertexBuffer->setDataGenerator( QSharedPointer<PlaneVertexBufferFunctor>::create( mResolution, mSkirtHeight, mHeightMap ) );
mIndexBuffer->setDataGenerator( QSharedPointer<PlaneIndexBufferFunctor>::create( mResolution ) );

addAttribute( mPositionAttribute );
@@ -55,13 +55,14 @@ class DemTerrainTileGeometry : public Qt3DRender::QGeometry
* Constructs a terrain tile geometry. Resolution is the number of vertices on one side of the tile,
* heightMap is array of float values with one height value for each vertex
*/
explicit DemTerrainTileGeometry( int resolution, const QByteArray &heightMap, QNode *parent = nullptr );
explicit DemTerrainTileGeometry( int resolution, float skirtHeight, const QByteArray &heightMap, QNode *parent = nullptr );
~DemTerrainTileGeometry() = default;

private:
void init();

int mResolution;
float mSkirtHeight;
QByteArray mHeightMap;
Qt3DRender::QAttribute *mPositionAttribute = nullptr;
Qt3DRender::QAttribute *mNormalAttribute = nullptr;
@@ -54,6 +54,7 @@ QgsDemTerrainTileLoader::QgsDemTerrainTileLoader( QgsTerrainEntity *terrain, Qgs
connect( generator->heightMapGenerator(), &QgsDemHeightMapGenerator::heightMapReady, this, &QgsDemTerrainTileLoader::onHeightMapReady );
mHeightMapJobId = generator->heightMapGenerator()->render( node->tileX(), node->tileY(), node->tileZ() );
mResolution = generator->heightMapGenerator()->resolution();
mSkirtHeight = generator->skirtHeight();
}

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

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

// create material
@@ -61,6 +61,7 @@ class QgsDemTerrainTileLoader : public QgsTerrainTileLoader
int mHeightMapJobId;
QByteArray mHeightMap;
int mResolution;
float mSkirtHeight;
};


@@ -42,13 +42,15 @@ Qgs3DMapConfigWidget::Qgs3DMapConfigWidget( Qgs3DMapSettings *map, QgsMapCanvas
{
QgsDemTerrainGenerator *demTerrainGen = static_cast<QgsDemTerrainGenerator *>( terrainGen );
spinTerrainResolution->setValue( demTerrainGen->resolution() );
spinTerrainSkirtHeight->setValue( demTerrainGen->skirtHeight() );
cboTerrainLayer->setLayer( demTerrainGen->layer() );
}
else
{
cboTerrainLayer->setLayer( nullptr );
spinTerrainResolution->setEnabled( false );
spinTerrainResolution->setValue( 16 );
spinTerrainSkirtHeight->setValue( 10 );
}

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

// TODO: what if just changing generator's properties
if ( demLayer && mMap->terrainGenerator()->type() != QgsTerrainGenerator::Dem )
if ( demLayer )
{
QgsDemTerrainGenerator *demTerrainGen = new QgsDemTerrainGenerator;
demTerrainGen->setLayer( demLayer );
demTerrainGen->setResolution( spinTerrainResolution->value() );
mMap->setTerrainGenerator( demTerrainGen );
bool tGenNeedsUpdate = true;
if ( mMap->terrainGenerator()->type() == QgsTerrainGenerator::Dem )
{
// if we already have a DEM terrain generator, check whether there was actually any change
QgsDemTerrainGenerator *oldDemTerrainGen = static_cast<QgsDemTerrainGenerator *>( mMap->terrainGenerator() );
if ( oldDemTerrainGen->layer() == demLayer &&
oldDemTerrainGen->resolution() == spinTerrainResolution->value() &&
oldDemTerrainGen->skirtHeight() == spinTerrainSkirtHeight->value() )
tGenNeedsUpdate = false;
}

if ( tGenNeedsUpdate )
{
QgsDemTerrainGenerator *demTerrainGen = new QgsDemTerrainGenerator;
demTerrainGen->setLayer( demLayer );
demTerrainGen->setResolution( spinTerrainResolution->value() );
demTerrainGen->setSkirtHeight( spinTerrainSkirtHeight->value() );
mMap->setTerrainGenerator( demTerrainGen );
}
}
else if ( !demLayer && mMap->terrainGenerator()->type() != QgsTerrainGenerator::Flat )
{

0 comments on commit 8c32520

Please sign in to comment.
You can’t perform that action at this time.