Skip to content

Commit

Permalink
Add support for polygon geometry & dashed outline in QgsRubberBand3D
Browse files Browse the repository at this point in the history
  • Loading branch information
Withalion committed Jan 31, 2025
1 parent 1cf9734 commit 0eb820d
Show file tree
Hide file tree
Showing 3 changed files with 250 additions and 74 deletions.
9 changes: 9 additions & 0 deletions src/3d/qgsframegraph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -478,10 +478,19 @@ Qt3DRender::QFrameGraphNode *QgsFrameGraph::constructRubberBandsPass()
mRubberBandsLayerFilter = new Qt3DRender::QLayerFilter( mRubberBandsCameraSelector );
mRubberBandsLayerFilter->addLayer( mRubberBandsLayer );

Qt3DRender::QBlendEquationArguments *blendState = new Qt3DRender::QBlendEquationArguments;
blendState->setSourceRgb( Qt3DRender::QBlendEquationArguments::SourceAlpha );
blendState->setDestinationRgb( Qt3DRender::QBlendEquationArguments::OneMinusSourceAlpha );

Qt3DRender::QBlendEquation *blendEquation = new Qt3DRender::QBlendEquation;
blendEquation->setBlendFunction( Qt3DRender::QBlendEquation::Add );

mRubberBandsStateSet = new Qt3DRender::QRenderStateSet( mRubberBandsLayerFilter );
Qt3DRender::QDepthTest *depthTest = new Qt3DRender::QDepthTest;
depthTest->setDepthFunction( Qt3DRender::QDepthTest::Always );
mRubberBandsStateSet->addRenderState( depthTest );
mRubberBandsStateSet->addRenderState( blendState );
mRubberBandsStateSet->addRenderState( blendEquation );

// Here we attach our drawings to the render target also used by forward pass.
// This is kind of okay, but as a result, post-processing effects get applied
Expand Down
257 changes: 192 additions & 65 deletions src/3d/qgsrubberband3d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,51 +38,46 @@
#include <Qt3DCore/QGeometry>
#endif

#include "qgs3dutils.h"
#include "qgsmessagelog.h"
#include "qgstessellatedpolygongeometry.h"

#include <Qt3DRender/QGeometryRenderer>
#include <QColor>


/// @cond PRIVATE


QgsRubberBand3D::QgsRubberBand3D( Qgs3DMapSettings &map, QgsWindow3DEngine *engine, Qt3DCore::QEntity *parentEntity, Qgis::GeometryType geometryType )
QgsRubberBand3D::QgsRubberBand3D( Qgs3DMapSettings &map, QgsWindow3DEngine *engine, Qt3DCore::QEntity *parentEntity, const Qgis::GeometryType geometryType, const bool isOutlineDashed )
: mMapSettings( &map )
, mEngine( engine )
, mGeometryType( geometryType )
, mIsOutlineDashed( isOutlineDashed )
{
if ( mGeometryType == Qgis::GeometryType::Line || mGeometryType == Qgis::GeometryType::Polygon )
switch ( mGeometryType )
{
// Rubberband line
mLineEntity = new Qt3DCore::QEntity( parentEntity );

QgsLineVertexData dummyLineData;
mGeometry = dummyLineData.createGeometry( mLineEntity );

Q_ASSERT( mGeometry->attributes().count() == 2 );
mPositionAttribute = mGeometry->attributes().at( 0 );
mIndexAttribute = mGeometry->attributes().at( 1 );

mLineGeomRenderer = new Qt3DRender::QGeometryRenderer;
mLineGeomRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::LineStripAdjacency );
mLineGeomRenderer->setGeometry( mGeometry );
mLineGeomRenderer->setPrimitiveRestartEnabled( true );
mLineGeomRenderer->setRestartIndexValue( 0 );

mLineEntity->addComponent( mLineGeomRenderer );

mLineMaterial = new QgsLineMaterial;
mLineMaterial->setLineWidth( mWidth );
mLineMaterial->setLineColor( mColor );

QObject::connect( engine, &QgsAbstract3DEngine::sizeChanged, mLineMaterial, [this, engine] {
mLineMaterial->setViewportSize( engine->size() );
} );
mLineMaterial->setViewportSize( engine->size() );

mLineEntity->addComponent( mLineMaterial );
case Qgis::GeometryType::Point:
setupMarker( parentEntity );
break;
case Qgis::GeometryType::Line:
setupLine( parentEntity, engine );
setupMarker( parentEntity );
break;
case Qgis::GeometryType::Polygon:
setupMarker( parentEntity );
setupLine( parentEntity, engine );
setupPolygon( parentEntity );
break;
case Qgis::GeometryType::Null:
case Qgis::GeometryType::Unknown:
QgsMessageLog::logMessage( QObject::tr( "Unknown GeometryType used in QgsRubberband3D" ), QObject::tr( "3D" ) );
break;
}
}

// Rubberband vertex markers
void QgsRubberBand3D::setupMarker( Qt3DCore::QEntity *parentEntity )
{
mMarkerEntity = new Qt3DCore::QEntity( parentEntity );
mMarkerGeometry = new QgsBillboardGeometry();
mMarkerGeometryRenderer = new Qt3DRender::QGeometryRenderer;
Expand All @@ -94,11 +89,63 @@ QgsRubberBand3D::QgsRubberBand3D( Qgs3DMapSettings &map, QgsWindow3DEngine *engi
mMarkerEntity->addComponent( mMarkerGeometryRenderer );
}

void QgsRubberBand3D::setupLine( Qt3DCore::QEntity *parentEntity, QgsWindow3DEngine *engine )
{
mLineEntity = new Qt3DCore::QEntity( parentEntity );

QgsLineVertexData dummyLineData;
mLineGeometry = dummyLineData.createGeometry( mLineEntity );

Q_ASSERT( mLineGeometry->attributes().count() == 2 );
mPositionAttribute = mLineGeometry->attributes().at( 0 );
mIndexAttribute = mLineGeometry->attributes().at( 1 );

mLineGeometryRenderer = new Qt3DRender::QGeometryRenderer;
mLineGeometryRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::LineStripAdjacency );
mLineGeometryRenderer->setGeometry( mLineGeometry );
mLineGeometryRenderer->setPrimitiveRestartEnabled( true );
mLineGeometryRenderer->setRestartIndexValue( 0 );

mLineEntity->addComponent( mLineGeometryRenderer );

mLineMaterial = new QgsLineMaterial;
mLineMaterial->setLineWidth( mWidth );
mLineMaterial->setLineColor( mColor );

QObject::connect( engine, &QgsAbstract3DEngine::sizeChanged, mLineMaterial, [this, engine] {
mLineMaterial->setViewportSize( engine->size() );
} );
mLineMaterial->setViewportSize( engine->size() );

mLineEntity->addComponent( mLineMaterial );
}

void QgsRubberBand3D::setupPolygon( Qt3DCore::QEntity *parentEntity )
{
mPolygonEntity = new Qt3DCore::QEntity( parentEntity );

mPolygonGeometry = new QgsTessellatedPolygonGeometry();

mPolygonGeometryRenderer = new Qt3DRender::QGeometryRenderer;
mPolygonGeometryRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::Triangles );
mPolygonGeometryRenderer->setGeometry( mPolygonGeometry );
mPolygonEntity->addComponent( mPolygonGeometryRenderer );

mPolygonMaterial = new QgsPhongMaterialSettings();
mPolygonMaterial->setAmbient( mColor );
mPolygonMaterial->setDiffuse( mColor );
mPolygonMaterial->setOpacity( 0.25 );
mPolygonEntity->addComponent( mPolygonMaterial->toMaterial( QgsMaterialSettingsRenderingTechnique::Triangles, QgsMaterialContext() ) );
}

QgsRubberBand3D::~QgsRubberBand3D()
{
if ( mPolygonEntity )
mPolygonEntity->deleteLater();
if ( mLineEntity )
mLineEntity->deleteLater();
mMarkerEntity->deleteLater();
if ( mMarkerEntity )
mMarkerEntity->deleteLater();
}

float QgsRubberBand3D::width() const
Expand All @@ -110,15 +157,18 @@ void QgsRubberBand3D::setWidth( float width )
{
mWidth = width;

if ( mGeometryType == Qgis::GeometryType::Line || mGeometryType == Qgis::GeometryType::Polygon )
if ( mMarkerType != None )
{
// when highlighting lines, the vertex markers should be wider
mLineMaterial->setLineWidth( width );
width *= 3;
if ( mGeometryType == Qgis::GeometryType::Line || mGeometryType == Qgis::GeometryType::Polygon )
{
// when highlighting lines, the vertex markers should be wider
mLineMaterial->setLineWidth( width );
width *= 3;
}

mMarkerSymbol->setSize( width );
updateMarkerMaterial();
}

mMarkerSymbol->setSize( width );
updateMarkerMaterial();
}

QColor QgsRubberBand3D::color() const
Expand All @@ -130,19 +180,46 @@ void QgsRubberBand3D::setColor( QColor color )
{
mColor = color;

if ( mGeometryType == Qgis::GeometryType::Line || mGeometryType == Qgis::GeometryType::Polygon )
if ( mMarkerType != None )
{
mLineMaterial->setLineColor( color );
mMarkerSymbol->setColor( color.lighter( 130 ) );
if ( mGeometryType == Qgis::GeometryType::Line || mGeometryType == Qgis::GeometryType::Polygon )
{
mLineMaterial->setLineColor( color );
mMarkerSymbol->setColor( color.lighter( 130 ) );
}
else
{
mMarkerSymbol->setColor( color );
}

if ( mMarkerSymbol->symbolLayerCount() > 0 && mMarkerSymbol->symbolLayer( 0 )->layerType() == QLatin1String( "SimpleMarker" ) && !mOutlineColor.value() )
{
mMarkerSymbol->symbolLayer( 0 )->setStrokeColor( color );
}
updateMarkerMaterial();
}
else

if ( mGeometryType == Qgis::GeometryType::Polygon )
{
mMarkerSymbol->setColor( color );
mPolygonEntity->removeComponent( mPolygonMaterial->toMaterial( QgsMaterialSettingsRenderingTechnique::Triangles, QgsMaterialContext() ) );
mPolygonMaterial->setAmbient( mColor );
mPolygonMaterial->setDiffuse( mColor );
mPolygonEntity->addComponent( mPolygonMaterial->toMaterial( QgsMaterialSettingsRenderingTechnique::Triangles, QgsMaterialContext() ) );
}
}

QColor QgsRubberBand3D::outlineColor() const
{
return mOutlineColor;
}

void QgsRubberBand3D::setOutlineColor( const QColor color )
{
mOutlineColor = color;

if ( mMarkerSymbol->symbolLayerCount() > 0 && mMarkerSymbol->symbolLayer( 0 )->layerType() == QLatin1String( "SimpleMarker" ) )
{
static_cast<QgsMarkerSymbolLayer *>( mMarkerSymbol->symbolLayer( 0 ) )->setStrokeColor( color );
mMarkerSymbol->symbolLayer( 0 )->setStrokeColor( color );
}
updateMarkerMaterial();
}
Expand All @@ -157,7 +234,8 @@ void QgsRubberBand3D::setMarkerType( MarkerType marker )
{ QStringLiteral( "color" ), lineOrPolygon ? mColor.lighter( 130 ).name() : mColor.name() },
{ QStringLiteral( "size_unit" ), QStringLiteral( "pixel" ) },
{ QStringLiteral( "size" ), QString::number( lineOrPolygon ? mWidth * 3.f : mWidth ) },
{ QStringLiteral( "outline_color" ), mColor.name() },
{ QStringLiteral( "outline_color" ), mOutlineColor.value() ? mOutlineColor.name() : mColor.name() },
{ QStringLiteral( "outline_style" ), mIsOutlineDashed ? QStringLiteral( "dot" ) : QStringLiteral( "solid" ) },
{ QStringLiteral( "outline_width" ), QString::number( lineOrPolygon ? 0.5 : 1 ) },
{ QStringLiteral( "name" ), mMarkerType == Square ? QStringLiteral( "square" ) : QStringLiteral( "circle" ) }
};
Expand All @@ -174,59 +252,108 @@ QgsRubberBand3D::MarkerType QgsRubberBand3D::markerType() const
void QgsRubberBand3D::reset()
{
mLineString.clear();
mPolygon.clear();
updateGeometry();
}

void QgsRubberBand3D::addPoint( const QgsPoint &pt )
{
mLineString.addVertex( pt );
if ( mGeometryType == Qgis::GeometryType::Polygon && mLineString.numPoints() >= 3 )
mPolygon = QgsPolygon( new QgsLineString( mLineString ) );
else
mPolygon.clear();

updateGeometry();
}

void QgsRubberBand3D::setPoints( const QgsLineString &points )
{
mLineString = points;
if ( mGeometryType == Qgis::GeometryType::Polygon && mLineString.numPoints() >= 3 )
mPolygon = QgsPolygon( new QgsLineString( mLineString ) );
else
mPolygon.clear();

updateGeometry();
}

void QgsRubberBand3D::setPolygon( const QgsPolygon &polygon )
{
mPolygon = polygon;
if ( mMarkerType != None )
{
const QgsLineString *exteriorRing = dynamic_cast<QgsLineString *>( mPolygon.exteriorRing() );
mLineString = *exteriorRing;
}

updateGeometry();
}

void QgsRubberBand3D::removeLastPoint()
{
const int lastVertexIndex = mLineString.numPoints() - 1;
mLineString.deleteVertex( QgsVertexId( 0, 0, lastVertexIndex ) );
if ( mGeometryType == Qgis::GeometryType::Polygon && mLineString.numPoints() >= 3 )
mPolygon = QgsPolygon( new QgsLineString( mLineString ) );
else
mPolygon.clear();
updateGeometry();
}

void QgsRubberBand3D::moveLastPoint( const QgsPoint &pt )
{
const int lastVertexIndex = mLineString.numPoints() - 1;
mLineString.moveVertex( QgsVertexId( 0, 0, lastVertexIndex ), pt );
if ( mGeometryType == Qgis::GeometryType::Polygon && mLineString.numPoints() >= 3 )
mPolygon = QgsPolygon( new QgsLineString( mLineString ) );
else
mPolygon.clear();
updateGeometry();
}

void QgsRubberBand3D::updateGeometry()
{
QgsLineVertexData lineData;
lineData.withAdjacency = true;
lineData.init( Qgis::AltitudeClamping::Absolute, Qgis::AltitudeBinding::Vertex, 0, Qgs3DRenderContext::fromMapSettings( mMapSettings ), mMapSettings->origin() );
const bool closed = mGeometryType == Qgis::GeometryType::Polygon;
lineData.addLineString( mLineString, 0, closed );

if ( mGeometryType == Qgis::GeometryType::Line || mGeometryType == Qgis::GeometryType::Polygon )
if ( mMarkerType != None )
{
mPositionAttribute->buffer()->setData( lineData.createVertexBuffer() );
mIndexAttribute->buffer()->setData( lineData.createIndexBuffer() );
mLineGeomRenderer->setVertexCount( lineData.indexes.count() );
QgsLineVertexData lineData;
lineData.withAdjacency = true;
lineData.init( Qgis::AltitudeClamping::Absolute, Qgis::AltitudeBinding::Vertex, 0, Qgs3DRenderContext::fromMapSettings( mMapSettings ), mMapSettings->origin() );
const bool closed = mGeometryType == Qgis::GeometryType::Polygon;
lineData.addLineString( mLineString, 0, closed );

if ( mGeometryType == Qgis::GeometryType::Line || mGeometryType == Qgis::GeometryType::Polygon )
{
mPositionAttribute->buffer()->setData( lineData.createVertexBuffer() );
mIndexAttribute->buffer()->setData( lineData.createIndexBuffer() );
mLineGeometryRenderer->setVertexCount( lineData.indexes.count() );
}

// first entry is empty for primitive restart
lineData.vertices.pop_front();

// we may not want a marker on the last point as it's tracked by the mouse cursor
if ( mHideLastMarker && !lineData.vertices.isEmpty() )
lineData.vertices.pop_back();

mMarkerGeometry->setPoints( lineData.vertices );
mMarkerGeometryRenderer->setVertexCount( lineData.vertices.count() );
}

// first entry is empty for primitive restart
lineData.vertices.pop_front();

// we may not want a marker on the last point as it's tracked by the mouse cursor
if ( mHideLastMarker && !lineData.vertices.isEmpty() )
lineData.vertices.pop_back();

mMarkerGeometry->setPoints( lineData.vertices );
mMarkerGeometryRenderer->setVertexCount( lineData.vertices.count() );
if ( mGeometryType == Qgis::GeometryType::Polygon )
{
QgsTessellator tessellator( mMapSettings->origin().x(), mMapSettings->origin().y(), true );
tessellator.setOutputZUp( true );
tessellator.addPolygon( mPolygon, 0 );
if ( !tessellator.error().isEmpty() )
{
QgsMessageLog::logMessage( tessellator.error(), QObject::tr( "3D" ) );
}
// extract vertex buffer data from tessellator
const QByteArray data( reinterpret_cast<const char *>( tessellator.data().constData() ), static_cast<int>( tessellator.data().count() * sizeof( float ) ) );
const int vertexCount = data.count() / tessellator.stride();
mPolygonGeometry->setData( data, vertexCount, QVector<QgsFeatureId>() << -10, QVector<uint>() << 0 );
}
}

void QgsRubberBand3D::updateMarkerMaterial()
Expand Down
Loading

0 comments on commit 0eb820d

Please sign in to comment.