QGIS API Documentation 3.43.0-Master (c67cf405802)
qgsrubberband3d.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsrubberband3d.cpp
3 --------------------------------------
4 Date : June 2021
5 Copyright : (C) 2021 by Martin Dobias
6 Email : wonder dot sk at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgsrubberband3d.h"
17
20#include "qgsmarkersymbol.h"
21#include "qgswindow3dengine.h"
22#include "qgslinevertexdata_p.h"
23#include "qgslinematerial_p.h"
24#include "qgsvertexid.h"
25#include "qgssymbollayer.h"
26#include "qgs3dmapsettings.h"
27#include "qgs3dutils.h"
28#include "qgslinestring.h"
29#include "qgsmessagelog.h"
30#include "qgspolygon.h"
31#include "qgssymbollayerutils.h"
33#include "qgstessellator.h"
34
35#include <Qt3DCore/QEntity>
36
37#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
38#include <Qt3DRender/QAttribute>
39#include <Qt3DRender/QBuffer>
40#include <Qt3DRender/QGeometry>
41#else
42#include <Qt3DCore/QAttribute>
43#include <Qt3DCore/QBuffer>
44#include <Qt3DCore/QGeometry>
45#endif
46
47#include <Qt3DRender/QGeometryRenderer>
48#include <QColor>
49
50
52
53
54QgsRubberBand3D::QgsRubberBand3D( Qgs3DMapSettings &map, QgsAbstract3DEngine *engine, Qt3DCore::QEntity *parentEntity, const Qgis::GeometryType geometryType )
55 : mMapSettings( &map )
56 , mEngine( engine )
57 , mGeometryType( geometryType )
58{
59 switch ( mGeometryType )
60 {
62 setupMarker( parentEntity );
63 break;
65 setupLine( parentEntity, engine );
66 setupMarker( parentEntity );
67 break;
69 setupMarker( parentEntity );
70 setupLine( parentEntity, engine );
71 setupPolygon( parentEntity );
72 break;
75 QgsDebugError( "Unknown GeometryType used in QgsRubberband3D" );
76 break;
77 }
78}
79
80void QgsRubberBand3D::setupMarker( Qt3DCore::QEntity *parentEntity )
81{
82 mMarkerEntity = new Qt3DCore::QEntity( parentEntity );
83 mMarkerGeometry = new QgsBillboardGeometry();
84 mMarkerGeometryRenderer = new Qt3DRender::QGeometryRenderer;
85 mMarkerGeometryRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::Points );
86 mMarkerGeometryRenderer->setGeometry( mMarkerGeometry );
87 mMarkerGeometryRenderer->setVertexCount( mMarkerGeometry->count() );
88
89 setMarkerType( mMarkerType );
90 mMarkerEntity->addComponent( mMarkerGeometryRenderer );
91}
92
93void QgsRubberBand3D::setupLine( Qt3DCore::QEntity *parentEntity, QgsAbstract3DEngine *engine )
94{
95 mLineEntity = new Qt3DCore::QEntity( parentEntity );
96
97 QgsLineVertexData dummyLineData;
98 mLineGeometry = dummyLineData.createGeometry( mLineEntity );
99
100 Q_ASSERT( mLineGeometry->attributes().count() == 2 );
101 mPositionAttribute = mLineGeometry->attributes().at( 0 );
102 mIndexAttribute = mLineGeometry->attributes().at( 1 );
103
104 mLineGeometryRenderer = new Qt3DRender::QGeometryRenderer;
105 mLineGeometryRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::LineStripAdjacency );
106 mLineGeometryRenderer->setGeometry( mLineGeometry );
107 mLineGeometryRenderer->setPrimitiveRestartEnabled( true );
108 mLineGeometryRenderer->setRestartIndexValue( 0 );
109
110 mLineEntity->addComponent( mLineGeometryRenderer );
111
112 mLineMaterial = new QgsLineMaterial;
113 mLineMaterial->setLineWidth( mWidth );
114 mLineMaterial->setLineColor( mColor );
115
116 QObject::connect( engine, &QgsAbstract3DEngine::sizeChanged, mLineMaterial, [this, engine] {
117 mLineMaterial->setViewportSize( engine->size() );
118 } );
119 mLineMaterial->setViewportSize( engine->size() );
120
121 mLineEntity->addComponent( mLineMaterial );
122}
123
124void QgsRubberBand3D::setupPolygon( Qt3DCore::QEntity *parentEntity )
125{
126 mPolygonEntity = new Qt3DCore::QEntity( parentEntity );
127
128 mPolygonGeometry = new QgsTessellatedPolygonGeometry();
129
130 Qt3DRender::QGeometryRenderer *polygonGeometryRenderer = new Qt3DRender::QGeometryRenderer;
131 polygonGeometryRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::Triangles );
132 polygonGeometryRenderer->setGeometry( mPolygonGeometry );
133 mPolygonEntity->addComponent( polygonGeometryRenderer );
134
135 QgsPhongMaterialSettings polygonMaterialSettings = QgsPhongMaterialSettings();
136 polygonMaterialSettings.setAmbient( mColor );
137 polygonMaterialSettings.setDiffuse( mColor );
138 polygonMaterialSettings.setOpacity( DEFAULT_POLYGON_OPACITY );
139 mPolygonMaterial = polygonMaterialSettings.toMaterial( QgsMaterialSettingsRenderingTechnique::Triangles, QgsMaterialContext() );
140 mPolygonEntity->addComponent( mPolygonMaterial );
141}
142
143void QgsRubberBand3D::removePoint( int index )
144{
145 if ( QgsPolygon *polygon = qgsgeometry_cast<QgsPolygon *>( mGeometry.get() ) )
146 {
147 QgsLineString *lineString = qgsgeometry_cast<QgsLineString *>( polygon->exteriorRing() );
148 const int vertexIndex = index < 0 ? lineString->numPoints() - 1 + index : index;
149 lineString->deleteVertex( QgsVertexId( 0, 0, vertexIndex ) );
150
151 if ( lineString->numPoints() < 3 )
152 {
153 mGeometry.set( new QgsLineString( *lineString ) );
154 }
155 }
156 else if ( QgsLineString *lineString = qgsgeometry_cast<QgsLineString *>( mGeometry.get() ) )
157 {
158 const int vertexIndex = index < 0 ? lineString->numPoints() + index : index;
159 lineString->deleteVertex( QgsVertexId( 0, 0, vertexIndex ) );
160 }
161 else
162 {
163 return;
164 }
165
166 updateGeometry();
167}
168
169QgsRubberBand3D::~QgsRubberBand3D()
170{
171 if ( mPolygonEntity )
172 delete mPolygonEntity;
173 if ( mLineEntity )
174 delete mLineEntity;
175 if ( mMarkerEntity )
176 delete mMarkerEntity;
177}
178
179
180float QgsRubberBand3D::width() const
181{
182 return mWidth;
183}
184
185void QgsRubberBand3D::setWidth( float width )
186{
187 const bool isLineOrPolygon = mGeometryType == Qgis::GeometryType::Line || mGeometryType == Qgis::GeometryType::Polygon;
188 mWidth = width;
189
190 if ( isLineOrPolygon && mEdgesEnabled )
191 {
192 // when highlighting lines, the vertex markers should be wider
193 mLineMaterial->setLineWidth( width );
194 width *= 3;
195 }
196
197 mMarkerSymbol->setSize( width );
198 updateMarkerMaterial();
199}
200
201QColor QgsRubberBand3D::color() const
202{
203 return mColor;
204}
205
206void QgsRubberBand3D::setColor( const QColor color )
207{
208 const bool isLineOrPolygon = mGeometryType == Qgis::GeometryType::Line || mGeometryType == Qgis::GeometryType::Polygon;
209 mColor = color;
210
211 if ( mEdgesEnabled && isLineOrPolygon )
212 {
213 mLineMaterial->setLineColor( color );
214 }
215
216 if ( isLineOrPolygon )
217 {
218 mMarkerSymbol->setColor( color.lighter( 130 ) );
219 }
220 else
221 {
222 mMarkerSymbol->setColor( color );
223 }
224
225 if ( mMarkerSymbol->symbolLayerCount() > 0 && mMarkerSymbol->symbolLayer( 0 )->layerType() == QLatin1String( "SimpleMarker" ) && !mOutlineColor.value() )
226 {
227 mMarkerSymbol->symbolLayer( 0 )->setStrokeColor( color );
228 }
229 updateMarkerMaterial();
230
231 if ( mGeometryType == Qgis::GeometryType::Polygon )
232 {
233 if ( mPolygonMaterial )
234 mPolygonEntity->removeComponent( mPolygonMaterial );
235
236 if ( mPolygonFillEnabled )
237 {
238 QgsPhongMaterialSettings polygonMaterialSettings;
239 polygonMaterialSettings.setAmbient( mColor );
240 polygonMaterialSettings.setDiffuse( mColor );
241 polygonMaterialSettings.setOpacity( DEFAULT_POLYGON_OPACITY );
242 mPolygonMaterial = polygonMaterialSettings.toMaterial( QgsMaterialSettingsRenderingTechnique::Triangles, QgsMaterialContext() );
243 mPolygonEntity->addComponent( mPolygonMaterial );
244 }
245 }
246}
247
248QColor QgsRubberBand3D::outlineColor() const
249{
250 return mOutlineColor;
251}
252
253void QgsRubberBand3D::setOutlineColor( const QColor color )
254{
255 mOutlineColor = color;
256
257 if ( mMarkerSymbol->symbolLayerCount() > 0 && mMarkerSymbol->symbolLayer( 0 )->layerType() == QLatin1String( "SimpleMarker" ) )
258 {
259 mMarkerSymbol->symbolLayer( 0 )->setStrokeColor( color );
260 }
261 updateMarkerMaterial();
262}
263
264void QgsRubberBand3D::setMarkerType( const MarkerType marker )
265{
266 mMarkerType = marker;
267
268 const bool lineOrPolygon = mGeometryType == Qgis::GeometryType::Line || mGeometryType == Qgis::GeometryType::Polygon;
269
270 const QVariantMap props {
271 { QStringLiteral( "color" ), lineOrPolygon ? mColor.lighter( 130 ).name() : mColor.name() },
272 { QStringLiteral( "size_unit" ), QStringLiteral( "pixel" ) },
273 { QStringLiteral( "size" ), QString::number( lineOrPolygon ? mWidth * 3.f : mWidth ) },
274 { QStringLiteral( "outline_color" ), mOutlineColor.value() ? mOutlineColor.name() : mColor.name() },
275 { QStringLiteral( "outline_style" ), QgsSymbolLayerUtils::encodePenStyle( mMarkerOutlineStyle ) },
276 { QStringLiteral( "outline_width" ), QString::number( lineOrPolygon ? 0.5 : 1 ) },
277 { QStringLiteral( "name" ), mMarkerType == Square ? QStringLiteral( "square" ) : QStringLiteral( "circle" ) }
278 };
279
280 mMarkerSymbol = QgsMarkerSymbol::createSimple( props );
281 updateMarkerMaterial();
282}
283
284QgsRubberBand3D::MarkerType QgsRubberBand3D::markerType() const
285{
286 return mMarkerType;
287}
288
289void QgsRubberBand3D::setMarkerOutlineStyle( const Qt::PenStyle style )
290{
291 mMarkerOutlineStyle = style;
292 setMarkerType( markerType() );
293}
294
295Qt::PenStyle QgsRubberBand3D::markerOutlineStyle() const
296{
297 return mMarkerOutlineStyle;
298}
299
300void QgsRubberBand3D::setMarkersEnabled( const bool enable )
301{
302 mMarkerEnabled = enable;
303 updateMarkerMaterial();
304}
305
306bool QgsRubberBand3D::hasMarkersEnabled() const
307{
308 return mMarkerEnabled;
309}
310
311void QgsRubberBand3D::setEdgesEnabled( const bool enable )
312{
313 mEdgesEnabled = enable;
314 setColor( mColor );
315}
316
317bool QgsRubberBand3D::hasEdgesEnabled() const
318{
319 return mEdgesEnabled;
320}
321
322void QgsRubberBand3D::setFillEnabled( const bool enable )
323{
324 mPolygonFillEnabled = enable;
325 setColor( mColor );
326}
327
328bool QgsRubberBand3D::hasFillEnabled() const
329{
330 return mPolygonFillEnabled;
331}
332
333void QgsRubberBand3D::reset()
334{
335 mGeometry.set( nullptr );
336 updateGeometry();
337}
338
339void QgsRubberBand3D::addPoint( const QgsPoint &pt )
340{
341 if ( QgsPolygon *polygon = qgsgeometry_cast<QgsPolygon *>( mGeometry.get() ) )
342 {
343 QgsLineString *exteriorRing = qgsgeometry_cast<QgsLineString *>( polygon->exteriorRing() );
344 const int lastVertexIndex = exteriorRing->numPoints() - 1;
345 exteriorRing->insertVertex( QgsVertexId( 0, 0, lastVertexIndex ), pt );
346 }
347 else if ( QgsLineString *lineString = qgsgeometry_cast<QgsLineString *>( mGeometry.get() ) )
348 {
349 lineString->addVertex( pt );
350 // transform linestring to polygon if we have enough vertices
351 if ( mGeometryType == Qgis::GeometryType::Polygon && lineString->numPoints() >= 3 )
352 {
353 mGeometry.set( new QgsPolygon( lineString->clone() ) );
354 }
355 }
356 else if ( !mGeometry.constGet() )
357 {
358 mGeometry.set( new QgsLineString( QVector<QgsPoint> { pt } ) );
359 }
360
361 updateGeometry();
362}
363
364void QgsRubberBand3D::setGeometry( const QgsGeometry &geometry )
365{
366 mGeometry = geometry;
367 mGeometryType = geometry.type();
368
369 updateGeometry();
370}
371
372void QgsRubberBand3D::removeLastPoint()
373{
374 removePoint( -1 );
375}
376
377void QgsRubberBand3D::removePenultimatePoint()
378{
379 removePoint( -2 );
380}
381
382void QgsRubberBand3D::moveLastPoint( const QgsPoint &pt )
383{
384 if ( QgsPolygon *polygon = qgsgeometry_cast<QgsPolygon *>( mGeometry.get() ) )
385 {
386 QgsLineString *lineString = qgsgeometry_cast<QgsLineString *>( polygon->exteriorRing() );
387 const int lastVertexIndex = lineString->numPoints() - 2;
388 lineString->moveVertex( QgsVertexId( 0, 0, lastVertexIndex ), pt );
389 }
390 else if ( QgsLineString *lineString = qgsgeometry_cast<QgsLineString *>( mGeometry.get() ) )
391 {
392 const int lastVertexIndex = lineString->numPoints() - 1;
393 lineString->moveVertex( QgsVertexId( 0, 0, lastVertexIndex ), pt );
394 }
395 else
396 {
397 return;
398 }
399
400 updateGeometry();
401}
402
403void QgsRubberBand3D::updateGeometry()
404{
405 QgsLineVertexData lineData;
406 lineData.withAdjacency = true;
408 if ( const QgsPolygon *polygon = qgsgeometry_cast<const QgsPolygon *>( mGeometry.constGet() ) )
409 {
410 std::unique_ptr< QgsLineString > lineString( qgsgeometry_cast<QgsLineString *>( polygon->exteriorRing()->clone() ) );
411 const int lastVertexIndex = lineString->numPoints() - 1;
412 lineString->deleteVertex( QgsVertexId( 0, 0, lastVertexIndex ) );
413 lineData.addLineString( *lineString, 0, true );
414 }
415 else if ( const QgsLineString *lineString = qgsgeometry_cast<const QgsLineString *>( mGeometry.constGet() ) )
416 {
417 lineData.addLineString( *lineString, 0, false );
418 }
419
420
421 if ( mEdgesEnabled && ( mGeometryType == Qgis::GeometryType::Line || mGeometryType == Qgis::GeometryType::Polygon ) )
422 {
423 mPositionAttribute->buffer()->setData( lineData.createVertexBuffer() );
424 mIndexAttribute->buffer()->setData( lineData.createIndexBuffer() );
425 mLineGeometryRenderer->setVertexCount( lineData.indexes.count() );
426 }
427
428 // first entry is empty for primitive restart
429 lineData.vertices.pop_front();
430
431 // we may not want a marker on the last point as it's tracked by the mouse cursor
432 if ( mHideLastMarker && !lineData.vertices.isEmpty() )
433 lineData.vertices.pop_back();
434
435 mMarkerGeometry->setPoints( lineData.vertices );
436 mMarkerGeometryRenderer->setVertexCount( lineData.vertices.count() );
437
438
439 if ( mGeometryType == Qgis::GeometryType::Polygon )
440 {
441 if ( const QgsPolygon *polygon = qgsgeometry_cast<const QgsPolygon *>( mGeometry.constGet() ) )
442 {
443 QgsTessellator tessellator( mMapSettings->origin().x(), mMapSettings->origin().y(), true );
444 tessellator.setOutputZUp( true );
445 tessellator.addPolygon( *polygon, 0 );
446 if ( !tessellator.error().isEmpty() )
447 {
448 QgsMessageLog::logMessage( tessellator.error(), QObject::tr( "3D" ) );
449 }
450 // extract vertex buffer data from tessellator
451 const QByteArray data( reinterpret_cast<const char *>( tessellator.data().constData() ), static_cast<int>( tessellator.data().count() * sizeof( float ) ) );
452 const int vertexCount = data.count() / tessellator.stride();
453 mPolygonGeometry->setData( data, vertexCount, QVector<QgsFeatureId>(), QVector<uint>() );
454 }
455 else
456 {
457 mPolygonGeometry->setData( QByteArray(), 0, QVector<QgsFeatureId>(), QVector<uint>() );
458 }
459 }
460}
461
462void QgsRubberBand3D::updateMarkerMaterial()
463{
464 if ( mMarkerEnabled )
465 {
466 mMarkerMaterial = new QgsPoint3DBillboardMaterial();
467 mMarkerMaterial->setTexture2DFromSymbol( mMarkerSymbol.get(), Qgs3DRenderContext::fromMapSettings( mMapSettings ) );
468 mMarkerEntity->addComponent( mMarkerMaterial );
469
470 //TODO: QgsAbstract3DEngine::sizeChanged should have const QSize &size param
471 QObject::connect( mEngine, &QgsAbstract3DEngine::sizeChanged, mMarkerMaterial, [this] {
472 mMarkerMaterial->setViewportSize( mEngine->size() );
473 } );
474 mMarkerMaterial->setViewportSize( mEngine->size() );
475 }
476 else
477 {
478 mMarkerEntity->removeComponent( mMarkerMaterial );
479 QObject::disconnect( mEngine, nullptr, mMarkerMaterial, nullptr );
480 }
481}
@ Absolute
Elevation is taken directly from feature and is independent of terrain height (final elevation = feat...
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
Definition qgis.h:337
@ Polygon
Polygons.
@ Unknown
Unknown types.
@ Null
No geometry.
@ Vertex
Clamp every vertex of feature.
Definition of the world.
static Qgs3DRenderContext fromMapSettings(const Qgs3DMapSettings *mapSettings)
Creates an initialized Qgs3DRenderContext instance from given Qgs3DMapSettings.
QgsVector3D origin() const
Returns coordinates in map CRS at which 3D scene has origin (0,0,0)
Base class for 3D engine implementation.
void sizeChanged()
Emitted after a call to setSize()
virtual QSize size() const =0
Returns size of the engine's rendering area in pixels.
Geometry of the billboard rendering for points in 3D map view.
A geometry is the spatial representation of a feature.
Qgis::GeometryType type
Line string geometry type, with support for z-dimension and m-values.
bool moveVertex(QgsVertexId position, const QgsPoint &newPos) override
Moves a vertex within the geometry.
int numPoints() const override
Returns the number of points in the curve.
bool deleteVertex(QgsVertexId position) override
Deletes a vertex within the geometry.
bool insertVertex(QgsVertexId position, const QgsPoint &vertex) override
Inserts a vertex into the geometry.
void addVertex(const QgsPoint &pt)
Adds a new vertex to the end of the line string.
QgsLineString * clone() const override
Clones the geometry by performing a deep copy.
static std::unique_ptr< QgsMarkerSymbol > createSimple(const QVariantMap &properties)
Create a marker symbol with one symbol layer: SimpleMarker with specified properties.
Context settings for a material.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE())
Adds a message to the log instance (and creates it if necessary).
Basic shading material used for rendering based on the Phong shading model with three color component...
void setOpacity(double opacity)
Sets opacity of the surface.
QgsMaterial * toMaterial(QgsMaterialSettingsRenderingTechnique technique, const QgsMaterialContext &context) const override
Creates a new QgsMaterial object representing the material settings.
void setDiffuse(const QColor &diffuse)
Sets diffuse color component.
void setAmbient(const QColor &ambient)
Sets ambient color component.
Material of the billboard rendering for points in 3D map view.
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:49
Polygon geometry type.
Definition qgspolygon.h:33
static QString encodePenStyle(Qt::PenStyle style)
Qt3DRender::QGeometry subclass that represents polygons tessellated into 3D geometry.
Tessellates polygons into triangles.
@ Triangles
Triangle based rendering (default)
#define QgsDebugError(str)
Definition qgslogger.h:40
Utility class for identifying a unique vertex within a geometry.
Definition qgsvertexid.h:30