QGIS API Documentation 4.3.0-Master (90cb4ecf9ef)
Loading...
Searching...
No Matches
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
18#include "qgs3d.h"
19#include "qgs3dmapscene.h"
20#include "qgs3dmapsettings.h"
21#include "qgs3dutils.h"
22#include "qgsabstract3dengine.h"
24#include "qgsframegraph.h"
25#include "qgsgeotransform.h"
26#include "qgslinematerial_p.h"
27#include "qgslinestring.h"
28#include "qgslinevertexdata_p.h"
29#include "qgsmarkersymbol.h"
31#include "qgsmessagelog.h"
33#include "qgspolygon.h"
35#include "qgssymbollayer.h"
36#include "qgssymbollayerutils.h"
38#include "qgstessellator.h"
39#include "qgsunlitmaterial.h"
40#include "qgsvertexid.h"
41
42#include <QColor>
43#include <QString>
44#include <Qt3DCore/QAttribute>
45#include <Qt3DCore/QBuffer>
46#include <Qt3DCore/QEntity>
47#include <Qt3DCore/QGeometry>
48#include <Qt3DRender/QGeometryRenderer>
49
50using namespace Qt::StringLiterals;
51
53
54
55QgsRubberBand3D::QgsRubberBand3D( Qgs3DMapScene *scene, const Qgis::GeometryType geometryType )
56 : mMapSettings( scene->mapSettings() )
57 , mEngine( scene->engine() )
58 , mGeometryType( geometryType )
59{
60 Qt3DCore::QEntity *parentEntity = mEngine->frameGraph()->rubberBandRenderView().rubberBandEntity();
61
62 switch ( mGeometryType )
63 {
65 setupMarker( parentEntity );
66 break;
68 setupLine( parentEntity );
69 setupMarker( parentEntity );
70 break;
72 setupMarker( parentEntity );
73 setupLine( parentEntity );
74 setupPolygon( parentEntity );
75 break;
78 QgsDebugError( "Unknown GeometryType used in QgsRubberband3D" );
79 break;
80 }
81}
82
83void QgsRubberBand3D::setupMarker( Qt3DCore::QEntity *parentEntity )
84{
85 mMarkerEntity = make_qobject_unique<Qt3DCore::QEntity>( parentEntity );
86 mMarkerGeometry = new QgsBillboardGeometry();
87 mMarkerGeometryRenderer = new Qt3DRender::QGeometryRenderer;
88 mMarkerGeometryRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::TriangleStrip );
89 mMarkerGeometryRenderer->setGeometry( mMarkerGeometry );
90 mMarkerGeometryRenderer->setVertexCount( 4 );
91
92 setMarkerType( mMarkerType );
93 mMarkerEntity->addComponent( mMarkerGeometryRenderer );
94
95 mMarkerTransform = new QgsGeoTransform;
96 mMarkerTransform->setOrigin( mMapSettings->origin() );
97 mMarkerEntity->addComponent( mMarkerTransform );
98}
99
100void QgsRubberBand3D::setupLine( Qt3DCore::QEntity *parentEntity )
101{
102 mLineEntity = make_qobject_unique<Qt3DCore::QEntity>( parentEntity );
103
104 QgsLineVertexData dummyLineData;
105 mLineGeometry = dummyLineData.createGeometry( mLineEntity );
106
107 Q_ASSERT( mLineGeometry->attributes().count() == 2 );
108 mPositionAttribute = mLineGeometry->attributes().at( 0 );
109 mIndexAttribute = mLineGeometry->attributes().at( 1 );
110
111 mLineGeometryRenderer = new Qt3DRender::QGeometryRenderer;
112 mLineGeometryRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::LineStripAdjacency );
113 mLineGeometryRenderer->setGeometry( mLineGeometry );
114 mLineGeometryRenderer->setPrimitiveRestartEnabled( true );
115 mLineGeometryRenderer->setRestartIndexValue( 0 );
116
117 mLineEntity->addComponent( mLineGeometryRenderer );
118
119 mLineMaterial = new QgsLineMaterial;
120 mLineMaterial->setLineWidth( mWidth );
121 mLineMaterial->setLineColor( mColor );
122
123 QObject::connect( mEngine, &QgsAbstract3DEngine::sizeChanged, mLineMaterial, [this] { mLineMaterial->setViewportSize( mEngine->size() ); } );
124 mLineMaterial->setViewportSize( mEngine->size() );
125
126 mLineEntity->addComponent( mLineMaterial );
127
128 mLineTransform = new QgsGeoTransform;
129 mLineTransform->setOrigin( mMapSettings->origin() );
130 mLineEntity->addComponent( mLineTransform );
131}
132
133void QgsRubberBand3D::setupPolygon( Qt3DCore::QEntity *parentEntity )
134{
135 mPolygonEntity = make_qobject_unique<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 QColor color = mColor;
145 color.setAlphaF( DEFAULT_POLYGON_OPACITY );
146 mPolygonMaterial = new QgsUnlitMaterial();
147 mPolygonMaterial->setColor( color );
148
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 {
186 mPolygonEntity.reset();
187 }
188 if ( mLineEntity )
189 {
190 mLineEntity.reset();
191 }
192 if ( mMarkerEntity )
193 {
194 mMarkerEntity.reset();
195 }
196}
197
198
199float QgsRubberBand3D::width() const
200{
201 return mWidth;
202}
203
204void QgsRubberBand3D::setWidth( float width )
205{
206 const bool isLineOrPolygon = mGeometryType == Qgis::GeometryType::Line || mGeometryType == Qgis::GeometryType::Polygon;
207 mWidth = width;
208
209 if ( isLineOrPolygon && mEdgesEnabled )
210 {
211 // when highlighting lines, the vertex markers should be wider
212 mLineMaterial->setLineWidth( width );
213 width *= 3;
214 }
215
216 mMarkerSymbol->setSize( width );
217 updateMarkerMaterial();
218}
219
220QColor QgsRubberBand3D::color() const
221{
222 return mColor;
223}
224
225void QgsRubberBand3D::setColor( const QColor color )
226{
227 const bool isLineOrPolygon = mGeometryType == Qgis::GeometryType::Line || mGeometryType == Qgis::GeometryType::Polygon;
228 mColor = color;
229
230 if ( mEdgesEnabled && isLineOrPolygon )
231 {
232 mLineMaterial->setLineColor( color );
233 }
234
235 if ( isLineOrPolygon )
236 {
237 mMarkerSymbol->setColor( color.lighter( 130 ) );
238 }
239 else
240 {
241 mMarkerSymbol->setColor( color );
242 }
243
244 if ( mMarkerSymbol->symbolLayerCount() > 0 && mMarkerSymbol->symbolLayer( 0 )->layerType() == "SimpleMarker"_L1 && !mOutlineColor.value() )
245 {
246 mMarkerSymbol->symbolLayer( 0 )->setStrokeColor( color );
247 }
248 updateMarkerMaterial();
249
250 if ( mGeometryType == Qgis::GeometryType::Polygon )
251 {
252 if ( mPolygonMaterial )
253 mPolygonEntity->removeComponent( mPolygonMaterial );
254
255 if ( mPolygonFillEnabled )
256 {
257 QColor color = mColor;
258 color.setAlphaF( DEFAULT_POLYGON_OPACITY );
259 mPolygonMaterial = new QgsUnlitMaterial();
260 mPolygonMaterial->setColor( color );
261 mPolygonEntity->addComponent( mPolygonMaterial );
262 }
263 }
264}
265
266QColor QgsRubberBand3D::outlineColor() const
267{
268 return mOutlineColor;
269}
270
271void QgsRubberBand3D::setOutlineColor( const QColor color )
272{
273 mOutlineColor = color;
274
275 if ( mMarkerSymbol->symbolLayerCount() > 0 && mMarkerSymbol->symbolLayer( 0 )->layerType() == "SimpleMarker"_L1 )
276 {
277 mMarkerSymbol->symbolLayer( 0 )->setStrokeColor( color );
278 }
279 updateMarkerMaterial();
280}
281
282void QgsRubberBand3D::setMarkerType( const MarkerType marker )
283{
284 mMarkerType = marker;
285
286 const bool lineOrPolygon = mGeometryType == Qgis::GeometryType::Line || mGeometryType == Qgis::GeometryType::Polygon;
287
288 const QVariantMap props {
289 { u"color"_s, lineOrPolygon ? mColor.lighter( 130 ).name() : mColor.name() },
290 { u"size_unit"_s, u"pixel"_s },
291 { u"size"_s, QString::number( lineOrPolygon ? mWidth * 3.f : mWidth ) },
292 { u"outline_color"_s, mOutlineColor.value() ? mOutlineColor.name() : mColor.name() },
293 { u"outline_style"_s, QgsSymbolLayerUtils::encodePenStyle( mMarkerOutlineStyle ) },
294 { u"outline_width"_s, QString::number( lineOrPolygon ? 0.5 : 1 ) },
295 { u"name"_s, mMarkerType == Square ? u"square"_s : u"circle"_s }
296 };
297
298 mMarkerSymbol = QgsMarkerSymbol::createSimple( props );
299 updateMarkerMaterial();
300}
301
302QgsRubberBand3D::MarkerType QgsRubberBand3D::markerType() const
303{
304 return mMarkerType;
305}
306
307void QgsRubberBand3D::setMarkerOutlineStyle( const Qt::PenStyle style )
308{
309 mMarkerOutlineStyle = style;
310 setMarkerType( markerType() );
311}
312
313Qt::PenStyle QgsRubberBand3D::markerOutlineStyle() const
314{
315 return mMarkerOutlineStyle;
316}
317
318void QgsRubberBand3D::setMarkersEnabled( const bool enable )
319{
320 mMarkerEnabled = enable;
321 updateMarkerMaterial();
322}
323
324bool QgsRubberBand3D::hasMarkersEnabled() const
325{
326 return mMarkerEnabled;
327}
328
329void QgsRubberBand3D::setEdgesEnabled( const bool enable )
330{
331 mEdgesEnabled = enable;
332 setColor( mColor );
333}
334
335bool QgsRubberBand3D::hasEdgesEnabled() const
336{
337 return mEdgesEnabled;
338}
339
340void QgsRubberBand3D::setFillEnabled( const bool enable )
341{
342 mPolygonFillEnabled = enable;
343 setColor( mColor );
344}
345
346bool QgsRubberBand3D::hasFillEnabled() const
347{
348 return mPolygonFillEnabled;
349}
350
351void QgsRubberBand3D::reset()
352{
353 mGeometry.set( nullptr );
354 updateGeometry();
355}
356
357void QgsRubberBand3D::addPoint( const QgsPoint &pt )
358{
359 if ( QgsPolygon *polygon = qgsgeometry_cast<QgsPolygon *>( mGeometry.get() ) )
360 {
361 QgsLineString *exteriorRing = qgsgeometry_cast<QgsLineString *>( polygon->exteriorRing() );
362 const int lastVertexIndex = exteriorRing->numPoints() - 1;
363 exteriorRing->insertVertex( QgsVertexId( 0, 0, lastVertexIndex ), pt );
364 }
365 else if ( QgsLineString *lineString = qgsgeometry_cast<QgsLineString *>( mGeometry.get() ) )
366 {
367 lineString->addVertex( pt );
368 // transform linestring to polygon if we have enough vertices
369 if ( mGeometryType == Qgis::GeometryType::Polygon && lineString->numPoints() >= 3 )
370 {
371 mGeometry.set( new QgsPolygon( lineString->clone() ) );
372 }
373 }
374 else if ( !mGeometry.constGet() )
375 {
376 mGeometry.set( new QgsLineString( QVector<QgsPoint> { pt } ) );
377 }
378
379 updateGeometry();
380}
381
382void QgsRubberBand3D::setGeometry( const QgsGeometry &geometry )
383{
384 mGeometry = geometry;
385 mGeometryType = geometry.type();
386
387 updateGeometry();
388}
389
390void QgsRubberBand3D::removeLastPoint()
391{
392 removePoint( -1 );
393}
394
395void QgsRubberBand3D::removePenultimatePoint()
396{
397 removePoint( -2 );
398}
399
400void QgsRubberBand3D::moveLastPoint( const QgsPoint &pt )
401{
402 if ( QgsPolygon *polygon = qgsgeometry_cast<QgsPolygon *>( mGeometry.get() ) )
403 {
404 QgsLineString *lineString = qgsgeometry_cast<QgsLineString *>( polygon->exteriorRing() );
405 const int lastVertexIndex = lineString->numPoints() - 2;
406 lineString->moveVertex( QgsVertexId( 0, 0, lastVertexIndex ), pt );
407 }
408 else if ( QgsLineString *lineString = qgsgeometry_cast<QgsLineString *>( mGeometry.get() ) )
409 {
410 const int lastVertexIndex = lineString->numPoints() - 1;
411 lineString->moveVertex( QgsVertexId( 0, 0, lastVertexIndex ), pt );
412 }
413 else
414 {
415 return;
416 }
417
418 updateGeometry();
419}
420
421void QgsRubberBand3D::updateGeometry()
422{
423 // figure out a reasonable origin for the coordinates to keep them as small as possible
424 const QgsBox3D box = mGeometry.constGet() ? mGeometry.constGet()->boundingBox3D() : QgsBox3D();
425 const QgsVector3D dataOrigin = box.isNull() ? mMapSettings->origin() : box.center();
426
427 QgsLineVertexData lineData;
428 lineData.withAdjacency = true;
429 lineData.geocentricCoordinates = mMapSettings->sceneMode() == Qgis::SceneMode::Globe;
431 if ( const QgsPolygon *polygon = qgsgeometry_cast<const QgsPolygon *>( mGeometry.constGet() ) )
432 {
433 std::unique_ptr< QgsLineString > lineString( qgsgeometry_cast<QgsLineString *>( polygon->exteriorRing()->clone() ) );
434 const int lastVertexIndex = lineString->numPoints() - 1;
435 lineString->deleteVertex( QgsVertexId( 0, 0, lastVertexIndex ) );
436 lineData.addLineString( *lineString, 0, true );
437 }
438 else if ( const QgsLineString *lineString = qgsgeometry_cast<const QgsLineString *>( mGeometry.constGet() ) )
439 {
440 lineData.addLineString( *lineString, 0, false );
441 }
442
443
444 if ( mEdgesEnabled && ( mGeometryType == Qgis::GeometryType::Line || mGeometryType == Qgis::GeometryType::Polygon ) )
445 {
446 mPositionAttribute->buffer()->setData( lineData.createVertexBuffer() );
447 mIndexAttribute->buffer()->setData( lineData.createIndexBuffer() );
448 mLineGeometryRenderer->setVertexCount( static_cast<int>( lineData.indexes.count() ) );
449 mLineTransform->setGeoTranslation( dataOrigin );
450 }
451
452 // first entry is empty for primitive restart
453 lineData.vertices.pop_front();
454
455 // we may not want a marker on the last point as it's tracked by the mouse cursor
456 if ( mHideLastMarker && !lineData.vertices.isEmpty() )
457 lineData.vertices.pop_back();
458
459 mMarkerGeometry->setPositions( lineData.vertices );
460 mMarkerGeometryRenderer->setVertexCount( 4 );
461 mMarkerGeometryRenderer->setInstanceCount( lineData.vertices.count() );
462 mMarkerTransform->setGeoTranslation( dataOrigin );
463
464 if ( mGeometryType == Qgis::GeometryType::Polygon )
465 {
466 if ( const QgsPolygon *polygon = qgsgeometry_cast<const QgsPolygon *>( mGeometry.constGet() ) )
467 {
468 QgsTessellator tessellator;
469 tessellator.setOrigin( mMapSettings->origin() );
470 tessellator.setAddNormals( true );
471 tessellator.addPolygon( *polygon, 0 );
472 if ( !tessellator.error().isEmpty() )
473 {
474 QgsMessageLog::logMessage( tessellator.error(), QObject::tr( "3D" ) );
475 }
476 // extract vertex buffer data from tessellator
477 const QByteArray vertexBuffer = tessellator.vertexBuffer();
478 const QByteArray indexBuffer = tessellator.indexBuffer();
479 const size_t vertexCount = tessellator.uniqueVertexCount();
480 const size_t indexCount = tessellator.dataVerticesCount();
481
482 mPolygonGeometry->setVertexBufferData( vertexBuffer, vertexCount, QVector<QgsFeatureId>(), QVector<uint>() );
483 mPolygonGeometry->setIndexBufferData( indexBuffer, indexCount );
484 mPolygonTransform->setGeoTranslation( mMapSettings->origin() );
485 }
486 else
487 {
488 mPolygonGeometry->setVertexBufferData( QByteArray(), 0, QVector<QgsFeatureId>(), QVector<uint>() );
489 mPolygonGeometry->setIndexBufferData( QByteArray(), 0 );
490 }
491 }
492}
493
494void QgsRubberBand3D::updateMarkerMaterial()
495{
496 if ( mMarkerEnabled )
497 {
498 if ( !mMarkerMaterial )
499 {
500 mMarkerMaterial = new QgsPoint3DBillboardMaterial();
501 mMarkerEntity->addComponent( mMarkerMaterial );
502 //TODO: QgsAbstract3DEngine::sizeChanged should have const QSize &size param
503 QObject::connect( mEngine, &QgsAbstract3DEngine::sizeChanged, mMarkerMaterial, [material = mMarkerMaterial, engine = mEngine] { material->setViewportSize( engine->size() ); } );
504 }
505
506 mMarkerMaterial->setTexture2DFromSymbol( mMarkerSymbol.get(), Qgs3DRenderContext::fromMapSettings( mMapSettings ) );
507 mMarkerMaterial->setViewportSize( mEngine->size() );
508 }
509 else if ( !mMarkerEnabled && mMarkerMaterial )
510 {
511 mMarkerEntity->removeComponent( mMarkerMaterial );
512 mMarkerMaterial->setParent( static_cast<Qt3DCore::QEntity *>( nullptr ) );
513 mMarkerMaterial->deleteLater();
514 mMarkerMaterial = nullptr;
515 }
516}
@ Absolute
Elevation is taken directly from feature and is independent of terrain height (final elevation = feat...
Definition qgis.h:4202
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
Definition qgis.h:379
@ Point
Points.
Definition qgis.h:380
@ Line
Lines.
Definition qgis.h:381
@ Polygon
Polygons.
Definition qgis.h:382
@ Unknown
Unknown types.
Definition qgis.h:383
@ Null
No geometry.
Definition qgis.h:384
@ Vertex
Clamp every vertex of feature.
Definition qgis.h:4215
@ Globe
Scene is represented as a globe using a geocentric CRS.
Definition qgis.h:4538
Entity that encapsulates our 3D scene - contains all other entities (such as terrain) as children.
static Qgs3DRenderContext fromMapSettings(const Qgs3DMapSettings *mapSettings)
Creates an initialized Qgs3DRenderContext instance from given Qgs3DMapSettings.
void sizeChanged()
Emitted after a call to setSize().
Geometry of the billboard rendering for points in 3D map view.
A 3-dimensional box composed of x, y, z coordinates.
Definition qgsbox3d.h:45
QgsVector3D center() const
Returns the center of the box as a vector.
Definition qgsbox3d.cpp:124
bool isNull() const
Test if the box is null (holding no spatial information).
Definition qgsbox3d.cpp:311
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 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.
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(), Qgis::StringFormat format=Qgis::StringFormat::PlainText)
Adds a message to the log instance (and creates it if necessary).
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:53
Polygon geometry type.
Definition qgspolygon.h:37
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.
static QString encodePenStyle(Qt::PenStyle style)
Qt3DRender::QGeometry subclass that represents polygons tessellated into 3D geometry.
Tessellates polygons into triangles.
void setOrigin(const QgsVector3D &origin)
Sets the origin point of the map.
QByteArray vertexBuffer() const
Returns vertex buffer for the generated points.
void addPolygon(const QgsPolygon &polygon, float extrusionHeight)
Tessellates a triangle and adds its vertex entries to the output data array.
int uniqueVertexCount() const
Returns unique vertex count.
QByteArray indexBuffer() const
Returns index buffer for the generated points.
QString error() const
Returns a descriptive error string if the tessellation failed.
int dataVerticesCount() const
Returns the number of vertices stored in the output data array.
void setAddNormals(bool addNormals)
Sets whether normals should be added to the output data.
A 3D vector (similar to QVector3D) with the difference that it uses double precision instead of singl...
Definition qgsvector3d.h:33
T qgsgeometry_cast(QgsAbstractGeometry *geom)
#define QgsDebugError(str)
Definition qgslogger.h:71
constexpr QObjectUniquePtr< Tp > make_qobject_unique(Args &&...args)
Create an object owned by a QObjectUniquePtr.
Utility class for identifying a unique vertex within a geometry.
Definition qgsvertexid.h:35