Skip to content

Commit

Permalink
[FEATURE] Simple rendering of 3D linestrings
Browse files Browse the repository at this point in the history
This mode of 3D line rendering will use OpenGL line rendering
instead of buffering lines into polygons and rendering them as meshes.

The advantage is that the 3D lines do not loose their Z coordinate
which is the case currently with "ordinary" 3D rendering after buffering.

The disadvantage is that the lines cannot be wide (supported in Qt3D only
since 5.10, but even then their rendering won't have nice joins/caps)
and only ambient color is used from the material.
  • Loading branch information
wonder-sk committed Jul 30, 2018
1 parent 2e97b7b commit 6482a3b
Show file tree
Hide file tree
Showing 9 changed files with 153 additions and 1 deletion.
14 changes: 14 additions & 0 deletions src/3d/qgs3dutils.cpp
Expand Up @@ -149,6 +149,20 @@ Qt3DRender::QCullFace::CullingMode Qgs3DUtils::cullingModeFromString( const QStr
return Qt3DRender::QCullFace::NoCulling;
}

float Qgs3DUtils::clampAltitude( const QgsPoint &p, AltitudeClamping altClamp, AltitudeBinding altBind, float height, const QgsPoint &centroid, const Qgs3DMapSettings &map )
{
float terrainZ = 0;
if ( altClamp == AltClampRelative || altClamp == AltClampTerrain )
{
QgsPointXY pt = altBind == AltBindVertex ? p : centroid;
terrainZ = map.terrainGenerator()->heightAt( pt.x(), pt.y(), map );
}

float geomZ = altClamp == AltClampAbsolute || altClamp == AltClampRelative ? p.z() : 0;

float z = ( terrainZ + geomZ ) * map.terrainVerticalScale() + height;
return z;
}

void Qgs3DUtils::clampAltitudes( QgsLineString *lineString, AltitudeClamping altClamp, AltitudeBinding altBind, const QgsPoint &centroid, float height, const Qgs3DMapSettings &map )
{
Expand Down
2 changes: 2 additions & 0 deletions src/3d/qgs3dutils.h
Expand Up @@ -81,6 +81,8 @@ class _3D_EXPORT Qgs3DUtils
//! Converts a string to a value from CullingMode enum
static Qt3DRender::QCullFace::CullingMode cullingModeFromString( const QString &str );

//! Clamps altitude of a vertex according to the settings, returns Z value
static float clampAltitude( const QgsPoint &p, AltitudeClamping altClamp, AltitudeBinding altBind, float height, const QgsPoint &centroid, const Qgs3DMapSettings &map );
//! Clamps altitude of vertices of a linestring according to the settings
static void clampAltitudes( QgsLineString *lineString, AltitudeClamping altClamp, AltitudeBinding altBind, const QgsPoint &centroid, float height, const Qgs3DMapSettings &map );
//! Clamps altitude of vertices of a polygon according to the settings
Expand Down
2 changes: 2 additions & 0 deletions src/3d/symbols/qgsline3dsymbol.cpp
Expand Up @@ -31,6 +31,7 @@ void QgsLine3DSymbol::writeXml( QDomElement &elem, const QgsReadWriteContext &co
elemDataProperties.setAttribute( QStringLiteral( "alt-binding" ), Qgs3DUtils::altBindingToString( mAltBinding ) );
elemDataProperties.setAttribute( QStringLiteral( "height" ), mHeight );
elemDataProperties.setAttribute( QStringLiteral( "extrusion-height" ), mExtrusionHeight );
elemDataProperties.setAttribute( QStringLiteral( "simple-lines" ), mRenderAsSimpleLines ? "1" : "0" );
elemDataProperties.setAttribute( QStringLiteral( "width" ), mWidth );
elem.appendChild( elemDataProperties );

Expand All @@ -49,6 +50,7 @@ void QgsLine3DSymbol::readXml( const QDomElement &elem, const QgsReadWriteContex
mHeight = elemDataProperties.attribute( QStringLiteral( "height" ) ).toFloat();
mExtrusionHeight = elemDataProperties.attribute( QStringLiteral( "extrusion-height" ) ).toFloat();
mWidth = elemDataProperties.attribute( QStringLiteral( "width" ) ).toFloat();
mRenderAsSimpleLines = elemDataProperties.attribute( QStringLiteral( "simple-lines" ), "0" ).toInt();

QDomElement elemMaterial = elem.firstChildElement( QStringLiteral( "material" ) );
mMaterial.readXml( elemMaterial );
Expand Down
6 changes: 6 additions & 0 deletions src/3d/symbols/qgsline3dsymbol.h
Expand Up @@ -65,6 +65,11 @@ class _3D_EXPORT QgsLine3DSymbol : public QgsAbstract3DSymbol
//! Sets extrusion height (in map units)
void setExtrusionHeight( float extrusionHeight ) { mExtrusionHeight = extrusionHeight; }

//! Returns whether the renderer will render data with simple lines (otherwise it uses buffer)
bool renderAsSimpleLines() const { return mRenderAsSimpleLines; }
//! Sets whether the renderer will render data with simple lines (otherwise it uses buffer)
void setRenderAsSimpleLines( bool enabled ) { mRenderAsSimpleLines = enabled; }

//! Returns material used for shading of the symbol
QgsPhongMaterialSettings material() const { return mMaterial; }
//! Sets material used for shading of the symbol
Expand All @@ -79,6 +84,7 @@ class _3D_EXPORT QgsLine3DSymbol : public QgsAbstract3DSymbol
float mWidth = 2.0f; //!< Line width (horizontally)
float mHeight = 0.0f; //!< Base height of polygons
float mExtrusionHeight = 0.0f; //!< How much to extrude (0 means no walls)
bool mRenderAsSimpleLines = false; //!< Whether to render data with simple lines (otherwise it uses buffer)
QgsPhongMaterialSettings mMaterial; //!< Defines appearance of objects
};

Expand Down
107 changes: 106 additions & 1 deletion src/3d/symbols/qgsline3dsymbol_p.cpp
Expand Up @@ -22,9 +22,13 @@
#include "qgs3dutils.h"

#include "qgsvectorlayer.h"
#include "qgsmultilinestring.h"
#include "qgsmultipolygon.h"
#include "qgsgeos.h"

#include <Qt3DRender/QAttribute>
#include <Qt3DRender/QBuffer>

/// @cond PRIVATE

QgsLine3DSymbolEntity::QgsLine3DSymbolEntity( const Qgs3DMapSettings &map, QgsVectorLayer *layer, const QgsLine3DSymbol &symbol, Qt3DCore::QNode *parent )
Expand Down Expand Up @@ -88,7 +92,7 @@ void QgsLine3DSymbolEntity::addEntityForNotSelectedLines( const Qgs3DMapSettings
QgsLine3DSymbolEntityNode::QgsLine3DSymbolEntityNode( const Qgs3DMapSettings &map, QgsVectorLayer *layer, const QgsLine3DSymbol &symbol, const QgsFeatureRequest &req, Qt3DCore::QNode *parent )
: Qt3DCore::QEntity( parent )
{
addComponent( renderer( map, symbol, layer, req ) );
addComponent( symbol.renderAsSimpleLines() ? rendererSimple( map, symbol, layer, req ) : renderer( map, symbol, layer, req ) );
}

Qt3DRender::QGeometryRenderer *QgsLine3DSymbolEntityNode::renderer( const Qgs3DMapSettings &map, const QgsLine3DSymbol &symbol, const QgsVectorLayer *layer, const QgsFeatureRequest &request )
Expand Down Expand Up @@ -150,4 +154,105 @@ Qt3DRender::QGeometryRenderer *QgsLine3DSymbolEntityNode::renderer( const Qgs3DM
return renderer;
}


Qt3DRender::QGeometryRenderer *QgsLine3DSymbolEntityNode::rendererSimple( const Qgs3DMapSettings &map, const QgsLine3DSymbol &symbol, const QgsVectorLayer *layer, const QgsFeatureRequest &request )
{
QVector<QVector3D> vertices;
vertices << QVector3D(); // the first index is invalid, we use it for primitive restart
QVector<unsigned int> indexes;

QgsPoint centroid;
QgsPointXY origin( map.origin().x(), map.origin().y() );
QgsFeature f;
QgsFeatureIterator fi = layer->getFeatures( request );
while ( fi.nextFeature( f ) )
{
if ( f.geometry().isNull() )
continue;

if ( symbol.altitudeBinding() == AltBindCentroid )
centroid = QgsPoint( f.geometry().centroid().asPoint() );

QgsGeometry geom = f.geometry();
const QgsAbstractGeometry *g = geom.constGet();
if ( QgsLineString *ls = qgsgeometry_cast<QgsLineString *>( g ) )
{
for ( int i = 0; i < ls->vertexCount(); ++i )
{
QgsPoint p = ls->vertexAt( QgsVertexId( 0, 0, i ) );
float z = Qgs3DUtils::clampAltitude( p, symbol.altitudeClamping(), symbol.altitudeBinding(), symbol.height(), centroid, map );
vertices << QVector3D( p.x() - map.origin().x(), z, p.y() - map.origin().y() );
indexes << vertices.count() - 1;
}
}
else if ( const QgsMultiLineString *mls = qgsgeometry_cast<const QgsMultiLineString *>( g ) )
{
for ( int nGeom = 0; nGeom < mls->numGeometries(); ++nGeom )
{
const QgsLineString *ls = qgsgeometry_cast<const QgsLineString *>( mls->geometryN( nGeom ) );
for ( int i = 0; i < ls->vertexCount(); ++i )
{
QgsPoint p = ls->vertexAt( QgsVertexId( 0, 0, i ) );
float z = Qgs3DUtils::clampAltitude( p, symbol.altitudeClamping(), symbol.altitudeBinding(), symbol.height(), centroid, map );
vertices << QVector3D( p.x() - map.origin().x(), z, p.y() - map.origin().y() );
indexes << vertices.count() - 1;
}
indexes << 0; // add primitive restart
}
}

indexes << 0; // add primitive restart
}

QByteArray vertexBufferData;
vertexBufferData.resize( vertices.size() * 3 * sizeof( float ) );
float *rawVertexArray = reinterpret_cast<float *>( vertexBufferData.data() );
int idx = 0;
for ( const auto &v : vertices )
{
rawVertexArray[idx++] = v.x();
rawVertexArray[idx++] = v.y();
rawVertexArray[idx++] = v.z();
}

QByteArray indexBufferData;
indexBufferData.resize( indexes.size() * sizeof( int ) );
unsigned int *rawIndexArray = reinterpret_cast<unsigned int *>( indexBufferData.data() );
idx = 0;
for ( unsigned int indexVal : indexes )
{
rawIndexArray[idx++] = indexVal;
}

Qt3DRender::QBuffer *vertexBuffer = new Qt3DRender::QBuffer( Qt3DRender::QBuffer::VertexBuffer, this );
vertexBuffer->setData( vertexBufferData );

Qt3DRender::QBuffer *indexBuffer = new Qt3DRender::QBuffer( Qt3DRender::QBuffer::IndexBuffer, this );
indexBuffer->setData( indexBufferData );

Qt3DRender::QAttribute *positionAttribute = new Qt3DRender::QAttribute( this );
positionAttribute->setAttributeType( Qt3DRender::QAttribute::VertexAttribute );
positionAttribute->setBuffer( vertexBuffer );
positionAttribute->setVertexBaseType( Qt3DRender::QAttribute::Float );
positionAttribute->setVertexSize( 3 );
positionAttribute->setName( Qt3DRender::QAttribute::defaultPositionAttributeName() );

Qt3DRender::QAttribute *indexAttribute = new Qt3DRender::QAttribute( this );
indexAttribute->setAttributeType( Qt3DRender::QAttribute::IndexAttribute );
indexAttribute->setBuffer( indexBuffer );
indexAttribute->setVertexBaseType( Qt3DRender::QAttribute::UnsignedInt );

Qt3DRender::QGeometry *geom = new Qt3DRender::QGeometry;
geom->addAttribute( positionAttribute );
geom->addAttribute( indexAttribute );

Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer;
renderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::LineStrip );
renderer->setGeometry( geom );
renderer->setVertexCount( vertices.count() );
renderer->setPrimitiveRestartEnabled( true );
renderer->setRestartIndexValue( 0 );
return renderer;
}

/// @endcond
1 change: 1 addition & 0 deletions src/3d/symbols/qgsline3dsymbol_p.h
Expand Up @@ -59,6 +59,7 @@ class QgsLine3DSymbolEntityNode : public Qt3DCore::QEntity

private:
Qt3DRender::QGeometryRenderer *renderer( const Qgs3DMapSettings &map, const QgsLine3DSymbol &symbol, const QgsVectorLayer *layer, const QgsFeatureRequest &req );
Qt3DRender::QGeometryRenderer *rendererSimple( const Qgs3DMapSettings &map, const QgsLine3DSymbol &symbol, const QgsVectorLayer *layer, const QgsFeatureRequest &request );

QgsTessellatedPolygonGeometry *mGeometry = nullptr;
};
Expand Down
12 changes: 12 additions & 0 deletions src/app/3d/qgsline3dsymbolwidget.cpp
Expand Up @@ -34,6 +34,8 @@ QgsLine3DSymbolWidget::QgsLine3DSymbolWidget( QWidget *parent )
connect( spinExtrusion, static_cast<void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLine3DSymbolWidget::changed );
connect( cboAltClamping, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsLine3DSymbolWidget::changed );
connect( cboAltBinding, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsLine3DSymbolWidget::changed );
connect( chkSimpleLines, &QCheckBox::clicked, this, &QgsLine3DSymbolWidget::changed );
connect( chkSimpleLines, &QCheckBox::clicked, this, &QgsLine3DSymbolWidget::updateGuiState );
connect( widgetMaterial, &QgsPhongMaterialWidget::changed, this, &QgsLine3DSymbolWidget::changed );
}

Expand All @@ -44,7 +46,9 @@ void QgsLine3DSymbolWidget::setSymbol( const QgsLine3DSymbol &symbol )
spinExtrusion->setValue( symbol.extrusionHeight() );
cboAltClamping->setCurrentIndex( ( int ) symbol.altitudeClamping() );
cboAltBinding->setCurrentIndex( ( int ) symbol.altitudeBinding() );
chkSimpleLines->setChecked( symbol.renderAsSimpleLines() );
widgetMaterial->setMaterial( symbol.material() );
updateGuiState();
}

QgsLine3DSymbol QgsLine3DSymbolWidget::symbol() const
Expand All @@ -55,6 +59,14 @@ QgsLine3DSymbol QgsLine3DSymbolWidget::symbol() const
sym.setExtrusionHeight( spinExtrusion->value() );
sym.setAltitudeClamping( ( AltitudeClamping ) cboAltClamping->currentIndex() );
sym.setAltitudeBinding( ( AltitudeBinding ) cboAltBinding->currentIndex() );
sym.setRenderAsSimpleLines( chkSimpleLines->isChecked() );
sym.setMaterial( widgetMaterial->material() );
return sym;
}

void QgsLine3DSymbolWidget::updateGuiState()
{
bool simple = chkSimpleLines->isChecked();
spinWidth->setEnabled( !simple );
spinExtrusion->setEnabled( !simple );
}
3 changes: 3 additions & 0 deletions src/app/3d/qgsline3dsymbolwidget.h
Expand Up @@ -32,6 +32,9 @@ class QgsLine3DSymbolWidget : public QWidget, private Ui::Line3DSymbolWidget
void setSymbol( const QgsLine3DSymbol &symbol );
QgsLine3DSymbol symbol() const;

private slots:
void updateGuiState();

signals:
void changed();

Expand Down
7 changes: 7 additions & 0 deletions src/ui/3d/line3dsymbolwidget.ui
Expand Up @@ -110,6 +110,13 @@
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="chkSimpleLines">
<property name="text">
<string>Render as simple 3D lines</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
Expand Down

0 comments on commit 6482a3b

Please sign in to comment.