QGIS API Documentation 3.43.0-Master (4da382ed187)
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 "qgsabstract3dengine.h"
22#include "qgsgeotransform.h"
23#include "qgslinevertexdata_p.h"
24#include "qgslinematerial_p.h"
25#include "qgsvertexid.h"
26#include "qgssymbollayer.h"
27#include "qgs3dmapsettings.h"
28#include "qgs3dutils.h"
29#include "qgslinestring.h"
30#include "qgsmessagelog.h"
31#include "qgspolygon.h"
32#include "qgssymbollayerutils.h"
34#include "qgstessellator.h"
35
36#include <Qt3DCore/QEntity>
37
38#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
39#include <Qt3DRender/QAttribute>
40#include <Qt3DRender/QBuffer>
41#include <Qt3DRender/QGeometry>
42#else
43#include <Qt3DCore/QAttribute>
44#include <Qt3DCore/QBuffer>
45#include <Qt3DCore/QGeometry>
46#endif
47
48#include <Qt3DRender/QGeometryRenderer>
49#include <QColor>
50
51
53
54
55QgsRubberBand3D::QgsRubberBand3D( Qgs3DMapSettings &map, QgsAbstract3DEngine *engine, Qt3DCore::QEntity *parentEntity, const Qgis::GeometryType geometryType )
56 : mMapSettings( &map )
57 , mEngine( engine )
58 , mGeometryType( geometryType )
59{
60 switch ( mGeometryType )
61 {
63 setupMarker( parentEntity );
64 break;
66 setupLine( parentEntity, engine );
67 setupMarker( parentEntity );
68 break;
70 setupMarker( parentEntity );
71 setupLine( parentEntity, engine );
72 setupPolygon( parentEntity );
73 break;
76 QgsDebugError( "Unknown GeometryType used in QgsRubberband3D" );
77 break;
78 }
79}
80
81void QgsRubberBand3D::setupMarker( Qt3DCore::QEntity *parentEntity )
82{
83 mMarkerEntity = new Qt3DCore::QEntity( parentEntity );
84 mMarkerGeometry = new QgsBillboardGeometry();
85 mMarkerGeometryRenderer = new Qt3DRender::QGeometryRenderer;
86 mMarkerGeometryRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::Points );
87 mMarkerGeometryRenderer->setGeometry( mMarkerGeometry );
88 mMarkerGeometryRenderer->setVertexCount( mMarkerGeometry->count() );
89
90 setMarkerType( mMarkerType );
91 mMarkerEntity->addComponent( mMarkerGeometryRenderer );
92
93 mMarkerTransform = new QgsGeoTransform;
94 mMarkerTransform->setOrigin( mMapSettings->origin() );
95 mMarkerEntity->addComponent( mMarkerTransform );
96}
97
98void QgsRubberBand3D::setupLine( Qt3DCore::QEntity *parentEntity, QgsAbstract3DEngine *engine )
99{
100 mLineEntity = new Qt3DCore::QEntity( parentEntity );
101
102 QgsLineVertexData dummyLineData;
103 mLineGeometry = dummyLineData.createGeometry( mLineEntity );
104
105 Q_ASSERT( mLineGeometry->attributes().count() == 2 );
106 mPositionAttribute = mLineGeometry->attributes().at( 0 );
107 mIndexAttribute = mLineGeometry->attributes().at( 1 );
108
109 mLineGeometryRenderer = new Qt3DRender::QGeometryRenderer;
110 mLineGeometryRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::LineStripAdjacency );
111 mLineGeometryRenderer->setGeometry( mLineGeometry );
112 mLineGeometryRenderer->setPrimitiveRestartEnabled( true );
113 mLineGeometryRenderer->setRestartIndexValue( 0 );
114
115 mLineEntity->addComponent( mLineGeometryRenderer );
116
117 mLineMaterial = new QgsLineMaterial;
118 mLineMaterial->setLineWidth( mWidth );
119 mLineMaterial->setLineColor( mColor );
120
121 QObject::connect( engine, &QgsAbstract3DEngine::sizeChanged, mLineMaterial, [this, engine] {
122 mLineMaterial->setViewportSize( engine->size() );
123 } );
124 mLineMaterial->setViewportSize( engine->size() );
125
126 mLineEntity->addComponent( mLineMaterial );
127
128 mLineTransform = new QgsGeoTransform( mLineEntity );
129 mLineTransform->setOrigin( mMapSettings->origin() );
130 mLineEntity->addComponent( mLineTransform );
131}
132
133void QgsRubberBand3D::setupPolygon( Qt3DCore::QEntity *parentEntity )
134{
135 mPolygonEntity = new Qt3DCore::QEntity( parentEntity );
136
137 mPolygonGeometry = new QgsTessellatedPolygonGeometry();
138
139 Qt3DRender::QGeometryRenderer *polygonGeometryRenderer = new Qt3DRender::QGeometryRenderer;
140 polygonGeometryRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::Triangles );
141 polygonGeometryRenderer->setGeometry( mPolygonGeometry );
142 mPolygonEntity->addComponent( polygonGeometryRenderer );
143
144 QgsPhongMaterialSettings polygonMaterialSettings = QgsPhongMaterialSettings();
145 polygonMaterialSettings.setAmbient( mColor );
146 polygonMaterialSettings.setDiffuse( mColor );
147 polygonMaterialSettings.setOpacity( DEFAULT_POLYGON_OPACITY );
148 mPolygonMaterial = polygonMaterialSettings.toMaterial( QgsMaterialSettingsRenderingTechnique::Triangles, QgsMaterialContext() );
149 mPolygonEntity->addComponent( mPolygonMaterial );
150
151 mPolygonTransform = new QgsGeoTransform;
152 mPolygonTransform->setOrigin( mMapSettings->origin() );
153 mPolygonEntity->addComponent( mPolygonTransform );
154}
155
156void QgsRubberBand3D::removePoint( int index )
157{
158 if ( QgsPolygon *polygon = qgsgeometry_cast<QgsPolygon *>( mGeometry.get() ) )
159 {
160 QgsLineString *lineString = qgsgeometry_cast<QgsLineString *>( polygon->exteriorRing() );
161 const int vertexIndex = index < 0 ? lineString->numPoints() - 1 + index : index;
162 lineString->deleteVertex( QgsVertexId( 0, 0, vertexIndex ) );
163
164 if ( lineString->numPoints() < 3 )
165 {
166 mGeometry.set( new QgsLineString( *lineString ) );
167 }
168 }
169 else if ( QgsLineString *lineString = qgsgeometry_cast<QgsLineString *>( mGeometry.get() ) )
170 {
171 const int vertexIndex = index < 0 ? lineString->numPoints() + index : index;
172 lineString->deleteVertex( QgsVertexId( 0, 0, vertexIndex ) );
173 }
174 else
175 {
176 return;
177 }
178
179 updateGeometry();
180}
181
182QgsRubberBand3D::~QgsRubberBand3D()
183{
184 if ( mPolygonEntity )
185 delete mPolygonEntity;
186 if ( mLineEntity )
187 delete mLineEntity;
188 if ( mMarkerEntity )
189 delete mMarkerEntity;
190}
191
192
193float QgsRubberBand3D::width() const
194{
195 return mWidth;
196}
197
198void QgsRubberBand3D::setWidth( float width )
199{
200 const bool isLineOrPolygon = mGeometryType == Qgis::GeometryType::Line || mGeometryType == Qgis::GeometryType::Polygon;
201 mWidth = width;
202
203 if ( isLineOrPolygon && mEdgesEnabled )
204 {
205 // when highlighting lines, the vertex markers should be wider
206 mLineMaterial->setLineWidth( width );
207 width *= 3;
208 }
209
210 mMarkerSymbol->setSize( width );
211 updateMarkerMaterial();
212}
213
214QColor QgsRubberBand3D::color() const
215{
216 return mColor;
217}
218
219void QgsRubberBand3D::setColor( const QColor color )
220{
221 const bool isLineOrPolygon = mGeometryType == Qgis::GeometryType::Line || mGeometryType == Qgis::GeometryType::Polygon;
222 mColor = color;
223
224 if ( mEdgesEnabled && isLineOrPolygon )
225 {
226 mLineMaterial->setLineColor( color );
227 }
228
229 if ( isLineOrPolygon )
230 {
231 mMarkerSymbol->setColor( color.lighter( 130 ) );
232 }
233 else
234 {
235 mMarkerSymbol->setColor( color );
236 }
237
238 if ( mMarkerSymbol->symbolLayerCount() > 0 && mMarkerSymbol->symbolLayer( 0 )->layerType() == QLatin1String( "SimpleMarker" ) && !mOutlineColor.value() )
239 {
240 mMarkerSymbol->symbolLayer( 0 )->setStrokeColor( color );
241 }
242 updateMarkerMaterial();
243
244 if ( mGeometryType == Qgis::GeometryType::Polygon )
245 {
246 if ( mPolygonMaterial )
247 mPolygonEntity->removeComponent( mPolygonMaterial );
248
249 if ( mPolygonFillEnabled )
250 {
251 QgsPhongMaterialSettings polygonMaterialSettings;
252 polygonMaterialSettings.setAmbient( mColor );
253 polygonMaterialSettings.setDiffuse( mColor );
254 polygonMaterialSettings.setOpacity( DEFAULT_POLYGON_OPACITY );
255 mPolygonMaterial = polygonMaterialSettings.toMaterial( QgsMaterialSettingsRenderingTechnique::Triangles, QgsMaterialContext() );
256 mPolygonEntity->addComponent( mPolygonMaterial );
257 }
258 }
259}
260
261QColor QgsRubberBand3D::outlineColor() const
262{
263 return mOutlineColor;
264}
265
266void QgsRubberBand3D::setOutlineColor( const QColor color )
267{
268 mOutlineColor = color;
269
270 if ( mMarkerSymbol->symbolLayerCount() > 0 && mMarkerSymbol->symbolLayer( 0 )->layerType() == QLatin1String( "SimpleMarker" ) )
271 {
272 mMarkerSymbol->symbolLayer( 0 )->setStrokeColor( color );
273 }
274 updateMarkerMaterial();
275}
276
277void QgsRubberBand3D::setMarkerType( const MarkerType marker )
278{
279 mMarkerType = marker;
280
281 const bool lineOrPolygon = mGeometryType == Qgis::GeometryType::Line || mGeometryType == Qgis::GeometryType::Polygon;
282
283 const QVariantMap props {
284 { QStringLiteral( "color" ), lineOrPolygon ? mColor.lighter( 130 ).name() : mColor.name() },
285 { QStringLiteral( "size_unit" ), QStringLiteral( "pixel" ) },
286 { QStringLiteral( "size" ), QString::number( lineOrPolygon ? mWidth * 3.f : mWidth ) },
287 { QStringLiteral( "outline_color" ), mOutlineColor.value() ? mOutlineColor.name() : mColor.name() },
288 { QStringLiteral( "outline_style" ), QgsSymbolLayerUtils::encodePenStyle( mMarkerOutlineStyle ) },
289 { QStringLiteral( "outline_width" ), QString::number( lineOrPolygon ? 0.5 : 1 ) },
290 { QStringLiteral( "name" ), mMarkerType == Square ? QStringLiteral( "square" ) : QStringLiteral( "circle" ) }
291 };
292
293 mMarkerSymbol = QgsMarkerSymbol::createSimple( props );
294 updateMarkerMaterial();
295}
296
297QgsRubberBand3D::MarkerType QgsRubberBand3D::markerType() const
298{
299 return mMarkerType;
300}
301
302void QgsRubberBand3D::setMarkerOutlineStyle( const Qt::PenStyle style )
303{
304 mMarkerOutlineStyle = style;
305 setMarkerType( markerType() );
306}
307
308Qt::PenStyle QgsRubberBand3D::markerOutlineStyle() const
309{
310 return mMarkerOutlineStyle;
311}
312
313void QgsRubberBand3D::setMarkersEnabled( const bool enable )
314{
315 mMarkerEnabled = enable;
316 updateMarkerMaterial();
317}
318
319bool QgsRubberBand3D::hasMarkersEnabled() const
320{
321 return mMarkerEnabled;
322}
323
324void QgsRubberBand3D::setEdgesEnabled( const bool enable )
325{
326 mEdgesEnabled = enable;
327 setColor( mColor );
328}
329
330bool QgsRubberBand3D::hasEdgesEnabled() const
331{
332 return mEdgesEnabled;
333}
334
335void QgsRubberBand3D::setFillEnabled( const bool enable )
336{
337 mPolygonFillEnabled = enable;
338 setColor( mColor );
339}
340
341bool QgsRubberBand3D::hasFillEnabled() const
342{
343 return mPolygonFillEnabled;
344}
345
346void QgsRubberBand3D::reset()
347{
348 mGeometry.set( nullptr );
349 updateGeometry();
350}
351
352void QgsRubberBand3D::addPoint( const QgsPoint &pt )
353{
354 if ( QgsPolygon *polygon = qgsgeometry_cast<QgsPolygon *>( mGeometry.get() ) )
355 {
356 QgsLineString *exteriorRing = qgsgeometry_cast<QgsLineString *>( polygon->exteriorRing() );
357 const int lastVertexIndex = exteriorRing->numPoints() - 1;
358 exteriorRing->insertVertex( QgsVertexId( 0, 0, lastVertexIndex ), pt );
359 }
360 else if ( QgsLineString *lineString = qgsgeometry_cast<QgsLineString *>( mGeometry.get() ) )
361 {
362 lineString->addVertex( pt );
363 // transform linestring to polygon if we have enough vertices
364 if ( mGeometryType == Qgis::GeometryType::Polygon && lineString->numPoints() >= 3 )
365 {
366 mGeometry.set( new QgsPolygon( lineString->clone() ) );
367 }
368 }
369 else if ( !mGeometry.constGet() )
370 {
371 mGeometry.set( new QgsLineString( QVector<QgsPoint> { pt } ) );
372 }
373
374 updateGeometry();
375}
376
377void QgsRubberBand3D::setGeometry( const QgsGeometry &geometry )
378{
379 mGeometry = geometry;
380 mGeometryType = geometry.type();
381
382 updateGeometry();
383}
384
385void QgsRubberBand3D::removeLastPoint()
386{
387 removePoint( -1 );
388}
389
390void QgsRubberBand3D::removePenultimatePoint()
391{
392 removePoint( -2 );
393}
394
395void QgsRubberBand3D::moveLastPoint( const QgsPoint &pt )
396{
397 if ( QgsPolygon *polygon = qgsgeometry_cast<QgsPolygon *>( mGeometry.get() ) )
398 {
399 QgsLineString *lineString = qgsgeometry_cast<QgsLineString *>( polygon->exteriorRing() );
400 const int lastVertexIndex = lineString->numPoints() - 2;
401 lineString->moveVertex( QgsVertexId( 0, 0, lastVertexIndex ), pt );
402 }
403 else if ( QgsLineString *lineString = qgsgeometry_cast<QgsLineString *>( mGeometry.get() ) )
404 {
405 const int lastVertexIndex = lineString->numPoints() - 1;
406 lineString->moveVertex( QgsVertexId( 0, 0, lastVertexIndex ), pt );
407 }
408 else
409 {
410 return;
411 }
412
413 updateGeometry();
414}
415
416void QgsRubberBand3D::updateGeometry()
417{
418 // figure out a reasonable origin for the coordinates to keep them as small as possible
419 const QgsBox3D box = mGeometry.constGet()->boundingBox3D();
420 const QgsVector3D dataOrigin = box.isNull() ? mMapSettings->origin() : box.center();
421
422 QgsLineVertexData lineData;
423 lineData.withAdjacency = true;
424 lineData.geocentricCoordinates = mMapSettings->sceneMode() == Qgis::SceneMode::Globe;
426 if ( const QgsPolygon *polygon = qgsgeometry_cast<const QgsPolygon *>( mGeometry.constGet() ) )
427 {
428 std::unique_ptr< QgsLineString > lineString( qgsgeometry_cast<QgsLineString *>( polygon->exteriorRing()->clone() ) );
429 const int lastVertexIndex = lineString->numPoints() - 1;
430 lineString->deleteVertex( QgsVertexId( 0, 0, lastVertexIndex ) );
431 lineData.addLineString( *lineString, 0, true );
432 }
433 else if ( const QgsLineString *lineString = qgsgeometry_cast<const QgsLineString *>( mGeometry.constGet() ) )
434 {
435 lineData.addLineString( *lineString, 0, false );
436 }
437
438
439 if ( mEdgesEnabled && ( mGeometryType == Qgis::GeometryType::Line || mGeometryType == Qgis::GeometryType::Polygon ) )
440 {
441 mPositionAttribute->buffer()->setData( lineData.createVertexBuffer() );
442 mIndexAttribute->buffer()->setData( lineData.createIndexBuffer() );
443 mLineGeometryRenderer->setVertexCount( lineData.indexes.count() );
444 mLineTransform->setGeoTranslation( dataOrigin );
445 }
446
447 // first entry is empty for primitive restart
448 lineData.vertices.pop_front();
449
450 // we may not want a marker on the last point as it's tracked by the mouse cursor
451 if ( mHideLastMarker && !lineData.vertices.isEmpty() )
452 lineData.vertices.pop_back();
453
454 mMarkerGeometry->setPoints( lineData.vertices );
455 mMarkerGeometryRenderer->setVertexCount( lineData.vertices.count() );
456 mMarkerTransform->setGeoTranslation( dataOrigin );
457
458 if ( mGeometryType == Qgis::GeometryType::Polygon )
459 {
460 if ( const QgsPolygon *polygon = qgsgeometry_cast<const QgsPolygon *>( mGeometry.constGet() ) )
461 {
462 // TODO: tessellator should handle origins with non-zero Z to make
463 // things work well in large scenes
464 const QgsVector3D polygonOrigin( mMapSettings->origin().x(), mMapSettings->origin().y(), 0 );
465 QgsTessellator tessellator( polygonOrigin.x(), polygonOrigin.y(), true );
466 tessellator.setOutputZUp( true );
467 tessellator.addPolygon( *polygon, 0 );
468 if ( !tessellator.error().isEmpty() )
469 {
470 QgsMessageLog::logMessage( tessellator.error(), QObject::tr( "3D" ) );
471 }
472 // extract vertex buffer data from tessellator
473 const QByteArray data( reinterpret_cast<const char *>( tessellator.data().constData() ), static_cast<int>( tessellator.data().count() * sizeof( float ) ) );
474 const int vertexCount = data.count() / tessellator.stride();
475 mPolygonGeometry->setData( data, vertexCount, QVector<QgsFeatureId>(), QVector<uint>() );
476 mPolygonTransform->setGeoTranslation( polygonOrigin );
477 }
478 else
479 {
480 mPolygonGeometry->setData( QByteArray(), 0, QVector<QgsFeatureId>(), QVector<uint>() );
481 }
482 }
483}
484
485void QgsRubberBand3D::updateMarkerMaterial()
486{
487 if ( mMarkerEnabled )
488 {
489 mMarkerMaterial = new QgsPoint3DBillboardMaterial();
490 mMarkerMaterial->setTexture2DFromSymbol( mMarkerSymbol.get(), Qgs3DRenderContext::fromMapSettings( mMapSettings ) );
491 mMarkerEntity->addComponent( mMarkerMaterial );
492
493 //TODO: QgsAbstract3DEngine::sizeChanged should have const QSize &size param
494 QObject::connect( mEngine, &QgsAbstract3DEngine::sizeChanged, mMarkerMaterial, [this] {
495 mMarkerMaterial->setViewportSize( mEngine->size() );
496 } );
497 mMarkerMaterial->setViewportSize( mEngine->size() );
498 }
499 else
500 {
501 mMarkerEntity->removeComponent( mMarkerMaterial );
502 QObject::disconnect( mEngine, nullptr, mMarkerMaterial, nullptr );
503 }
504}
@ 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.
@ Globe
Scene is represented as a globe using a geocentric CRS.
Definition of the world.
static Qgs3DRenderContext fromMapSettings(const Qgs3DMapSettings *mapSettings)
Creates an initialized Qgs3DRenderContext instance from given Qgs3DMapSettings.
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 3-dimensional box composed of x, y, z coordinates.
Definition qgsbox3d.h:43
QgsVector3D center() const
Returns the center of the box as a vector.
Definition qgsbox3d.cpp:118
bool isNull() const
Test if the box is null (holding no spatial information).
Definition qgsbox3d.cpp:310
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.
A 3D vector (similar to QVector3D) with the difference that it uses double precision instead of singl...
Definition qgsvector3d.h:30
@ 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