Skip to content

Commit

Permalink
[3d] Fix missing rubber bands when measuring (fixes #34630)
Browse files Browse the repository at this point in the history
This fix also introduces a helper class QgsRubberBand3D to handle rubber bands
in 3D, similar to how QgsRubberBand is used in 2D map canvas.

Avoids addition of a temporary vector layer only used for measurement purposes,
which was confusing. Also fixes unreported crash when user removed the measurement
layer after it was added and the tool was used again.
  • Loading branch information
wonder-sk authored and nyalldawson committed Jun 18, 2021
1 parent c62c133 commit de79264
Show file tree
Hide file tree
Showing 5 changed files with 241 additions and 79 deletions.
1 change: 1 addition & 0 deletions src/3d/CMakeLists.txt
Expand Up @@ -37,6 +37,7 @@ set(QGIS_3D_SRCS
qgspreviewquad.cpp
qgsshadowsettings.cpp
qgscolorramptexture.cpp
qgsrubberband3d.cpp

qgspointcloudlayer3drenderer.cpp
qgspointcloudlayerchunkloader_p.cpp
Expand Down
121 changes: 121 additions & 0 deletions src/3d/qgsrubberband3d.cpp
@@ -0,0 +1,121 @@
/***************************************************************************
qgsrubberband3d.cpp
--------------------------------------
Date : June 2021
Copyright : (C) 2021 by Martin Dobias
Email : wonder dot sk at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#include "qgsrubberband3d.h"

#include "qgslinevertexdata_p.h"
#include "qgsabstractmaterialsettings.h"
#include "qgslinematerial_p.h"
#include "qgsphongmaterialsettings.h"

#include "qgslinestring.h"

#include <Qt3DCore/QEntity>
#include <Qt3DRender/QAttribute>
#include <Qt3DRender/QBuffer>
#include <Qt3DRender/QGeometry>
#include <Qt3DRender/QGeometryRenderer>
#include <Qt3DRender/QMaterial>


/// @cond PRIVATE


QgsRubberBand3D::QgsRubberBand3D( Qgs3DMapSettings &map, Qt3DCore::QEntity *parentEntity )
{
mMapSettings = &map;

mEntity = new Qt3DCore::QEntity( parentEntity );

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

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

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

mEntity->addComponent( mGeomRenderer );

mLineMaterial = new QgsLineMaterial;
mLineMaterial->setLineWidth( 3 );
mLineMaterial->setLineColor( Qt::red );
mLineMaterial->setViewportSize( QSize( 200, 200 ) ); // TODO: based on viewport size!

mEntity->addComponent( mLineMaterial );
}

QgsRubberBand3D::~QgsRubberBand3D()
{
delete mEntity;
}

float QgsRubberBand3D::width() const
{
return mLineMaterial->lineWidth();
}

void QgsRubberBand3D::setWidth( float width )
{
mLineMaterial->setLineWidth( width );
}

QColor QgsRubberBand3D::color() const
{
return mLineMaterial->lineColor();
}

void QgsRubberBand3D::setColor( QColor color )
{
mLineMaterial->setLineColor( color );
}

void QgsRubberBand3D::reset()
{
mLineString.clear();
updateGeometry();
}

void QgsRubberBand3D::addPoint( const QgsPoint &pt )
{
mLineString.addVertex( pt );
updateGeometry();
}

void QgsRubberBand3D::removeLastPoint()
{
int lastVertexIndex = mLineString.numPoints() - 1;
mLineString.deleteVertex( QgsVertexId( 0, 0, lastVertexIndex ) );
updateGeometry();
}

void QgsRubberBand3D::updateGeometry()
{
QgsLineVertexData lineData;
lineData.withAdjacency = true;
lineData.init( Qgs3DTypes::AltClampAbsolute, Qgs3DTypes::AltBindVertex, 0, mMapSettings );
lineData.addLineString( mLineString );

mPositionAttribute->buffer()->setData( lineData.createVertexBuffer() );
mIndexAttribute->buffer()->setData( lineData.createIndexBuffer() );
mGeomRenderer->setVertexCount( lineData.indexes.count() );
}

/// @endcond
101 changes: 101 additions & 0 deletions src/3d/qgsrubberband3d.h
@@ -0,0 +1,101 @@
/***************************************************************************
qgsrubberband3d.h
--------------------------------------
Date : June 2021
Copyright : (C) 2021 by Martin Dobias
Email : wonder dot sk at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#ifndef QGSRUBBERBAND3D_H
#define QGSRUBBERBAND3D_H

#include "qgis_3d.h"

#define SIP_NO_FILE

/// @cond PRIVATE

//
// W A R N I N G
// -------------
//
// This file is not part of the QGIS API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//

#include "qgslinestring.h"

class QgsLineMaterial;
class Qgs3DMapSettings;

namespace Qt3DCore
{
class QEntity;
}
namespace Qt3DRender
{
class QGeometry;
class QGeometryRenderer;
class QBuffer;
class QMaterial;
class QAttribute;
}

/**
* \ingroup 3d
* Rubber band implementation for use in 3D map views.
*
* Coordinates are expected to be passed in map CRS and the 3D entity generated by this
* class will be attached to the parentEntity given in the constructor (normally this
* should be the root entity, i.e. map scene object).
*
* \note Currently only supports linestring geometry.
* \since QGIS 3.20
*/
class _3D_EXPORT QgsRubberBand3D
{
public:
QgsRubberBand3D( Qgs3DMapSettings &map, Qt3DCore::QEntity *parentEntity );
~QgsRubberBand3D();

float width() const;
void setWidth( float width );

QColor color() const;
void setColor( QColor color );

void reset();

void addPoint( const QgsPoint &pt );

void removeLastPoint();

private:
void updateGeometry();

private:
QgsLineString mLineString;

Qgs3DMapSettings *mMapSettings = nullptr; // not owned

Qt3DCore::QEntity *mEntity = nullptr; // owned by parentEntity (from constructor)

// all these are owned by mEntity
Qt3DRender::QGeometryRenderer *mGeomRenderer = nullptr;
Qt3DRender::QGeometry *mGeometry = nullptr;
Qt3DRender::QAttribute *mPositionAttribute = nullptr;
Qt3DRender::QAttribute *mIndexAttribute = nullptr;
QgsLineMaterial *mLineMaterial = nullptr;
};

/// @endcond

#endif // QGSRUBBERBAND3D_H
85 changes: 15 additions & 70 deletions src/app/3d/qgs3dmaptoolmeasureline.cpp
Expand Up @@ -30,6 +30,7 @@
#include "qgsvectorlayer3drenderer.h"
#include "qgsmaplayer.h"
#include "qgs3dmeasuredialog.h"
#include "qgsrubberband3d.h"

#include "qgs3dmapscenepickhandler.h"

Expand Down Expand Up @@ -59,6 +60,7 @@ Qgs3DMapToolMeasureLine::Qgs3DMapToolMeasureLine( Qgs3DMapCanvas *canvas )

// Update scale if the terrain vertical scale changed
connect( canvas, &Qgs3DMapCanvas::mapSettingsChanged, this, &Qgs3DMapToolMeasureLine::onMapSettingsChanged );

}

Qgs3DMapToolMeasureLine::~Qgs3DMapToolMeasureLine() = default;
Expand All @@ -72,29 +74,15 @@ void Qgs3DMapToolMeasureLine::activate()

mCanvas->scene()->registerPickHandler( mPickHandler.get() );

mRubberBand.reset( new QgsRubberBand3D( *mCanvas->map(), mCanvas->scene() ) );

if ( mIsAlreadyActivated )
{
restart();
updateSettings();
}
else
{
QgsLineString *measurementLine = new QgsLineString();
measurementLine->addZValue();

QgsFeature measurementFeature( QgsFeatureId( 1 ) );
measurementFeature.setGeometry( QgsGeometry( measurementLine ) );

// Initialize the line layer
QString mapCRS = mCanvas->map()->crs().authid();
mMeasurementLayer = new QgsVectorLayer( QStringLiteral( "LineStringZ?crs=" ) + mapCRS, QStringLiteral( "Measurement" ), QStringLiteral( "memory" ) );
QgsProject::instance()->addMapLayer( mMeasurementLayer );

// Add feature to layer
mMeasurementLayer->startEditing();
mMeasurementLayer->addFeature( measurementFeature );
mMeasurementLayer->commitChanges();

// Set style
updateSettings();
mIsAlreadyActivated = true;
Expand All @@ -114,6 +102,8 @@ void Qgs3DMapToolMeasureLine::deactivate()

mCanvas->scene()->unregisterPickHandler( mPickHandler.get() );

mRubberBand.reset();

// Hide dialog
mDialog->hide();
}
Expand All @@ -128,9 +118,6 @@ void Qgs3DMapToolMeasureLine::onMapSettingsChanged()
if ( !mIsAlreadyActivated )
return;
connect( mCanvas->scene(), &Qgs3DMapScene::terrainEntityChanged, this, &Qgs3DMapToolMeasureLine::onTerrainEntityChanged );

// Update scale if the terrain vertical scale changed
connect( mCanvas->map(), &Qgs3DMapSettings::terrainVerticalScaleChanged, this, &Qgs3DMapToolMeasureLine::updateMeasurementLayer );
}

void Qgs3DMapToolMeasureLine::onTerrainPicked( Qt3DRender::QPickEvent *event )
Expand Down Expand Up @@ -178,62 +165,17 @@ void Qgs3DMapToolMeasureLine::handleClick( Qt3DRender::QPickEvent *event, const
}
}

void Qgs3DMapToolMeasureLine::updateMeasurementLayer()
{
if ( !mMeasurementLayer )
return;
double verticalScale = canvas()->map()->terrainVerticalScale();
QgsLineString *line;
if ( verticalScale != 1.0 )
{
QVector<QgsPoint> descaledPoints;
QVector<QgsPoint>::const_iterator it;
QgsPoint point;
for ( it = mPoints.constBegin(); it != mPoints.constEnd(); ++it )
{
point = *it;
descaledPoints.append(
QgsPoint( it->x(), it->y(), it->z() / verticalScale )
);
}
line = new QgsLineString( descaledPoints );
}
else
{
line = new QgsLineString( mPoints );
}
QgsGeometry lineGeometry( line );

QgsGeometryMap geometryMap;
geometryMap.insert( 1, lineGeometry );
mMeasurementLayer->dataProvider()->changeGeometryValues( geometryMap );
mMeasurementLayer->reload();
mCanvas->map()->setRenderers( QList<QgsAbstract3DRenderer *>() << mMeasurementLayer->renderer3D()->clone() );
}

void Qgs3DMapToolMeasureLine::updateSettings()
{
if ( !mMeasurementLayer )
return;
// Line style
QgsLine3DSymbol *lineSymbol = new QgsLine3DSymbol;
lineSymbol->setRenderAsSimpleLines( true );
lineSymbol->setWidth( 4 );
lineSymbol->setAltitudeClamping( Qgs3DTypes::AltClampAbsolute );

std::unique_ptr< QgsPhongMaterialSettings > phongMaterial = std::make_unique< QgsPhongMaterialSettings >();
QgsSettings settings;
int myRed = settings.value( QStringLiteral( "qgis/default_measure_color_red" ), 222 ).toInt();
int myGreen = settings.value( QStringLiteral( "qgis/default_measure_color_green" ), 155 ).toInt();
int myBlue = settings.value( QStringLiteral( "qgis/default_measure_color_blue" ), 67 ).toInt();
phongMaterial->setAmbient( QColor( myRed, myGreen, myBlue ) );
lineSymbol->setMaterial( phongMaterial.release() );

// Set renderer
QgsVectorLayer3DRenderer *lineSymbolRenderer = new QgsVectorLayer3DRenderer( lineSymbol );
mMeasurementLayer->setRenderer3D( lineSymbolRenderer );
lineSymbolRenderer->setLayer( mMeasurementLayer );
mCanvas->map()->setRenderers( QList<QgsAbstract3DRenderer *>() << mMeasurementLayer->renderer3D()->clone() );

mRubberBand->setWidth( 3 );
mRubberBand->setColor( QColor( myRed, myGreen, myBlue ) );
}

void Qgs3DMapToolMeasureLine::addPoint( const QgsPoint &point )
Expand All @@ -247,16 +189,18 @@ void Qgs3DMapToolMeasureLine::addPoint( const QgsPoint &point )
QgsPoint addedPoint( point );

mPoints.append( addedPoint );
updateMeasurementLayer();
mDialog->addPoint();

mRubberBand->addPoint( QgsPoint( point.x(), point.y(), point.z() / canvas()->map()->terrainVerticalScale() ) );
}

void Qgs3DMapToolMeasureLine::restart()
{
mPoints.clear();
mDone = true;
updateMeasurementLayer();
mDialog->resetTable();

mRubberBand->reset();
}

void Qgs3DMapToolMeasureLine::undo()
Expand All @@ -274,8 +218,9 @@ void Qgs3DMapToolMeasureLine::undo()
else
{
mPoints.removeLast();
updateMeasurementLayer();
mDialog->removeLastPoint();

mRubberBand->removeLastPoint();
}
}

Expand Down

0 comments on commit de79264

Please sign in to comment.