QGIS API Documentation 4.3.0-Master (2b7e6c9893e)
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 mMarkerGeometryRenderer->setInstanceCount( 0 );
92
93 setMarkerType( mMarkerType );
94 mMarkerEntity->addComponent( mMarkerGeometryRenderer );
95
96 mMarkerTransform = new QgsGeoTransform;
97 mMarkerTransform->setOrigin( mMapSettings->origin() );
98 mMarkerEntity->addComponent( mMarkerTransform );
99}
100
101void QgsRubberBand3D::setupLine( Qt3DCore::QEntity *parentEntity )
102{
103 mLineEntity = make_qobject_unique<Qt3DCore::QEntity>( parentEntity );
104
105 QgsLineVertexData dummyLineData;
106 mLineGeometry = dummyLineData.createGeometry( mLineEntity );
107
108 Q_ASSERT( mLineGeometry->attributes().count() == 2 );
109 mPositionAttribute = mLineGeometry->attributes().at( 0 );
110 mIndexAttribute = mLineGeometry->attributes().at( 1 );
111
112 mLineGeometryRenderer = new Qt3DRender::QGeometryRenderer;
113 mLineGeometryRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::LineStripAdjacency );
114 mLineGeometryRenderer->setGeometry( mLineGeometry );
115 mLineGeometryRenderer->setPrimitiveRestartEnabled( true );
116 mLineGeometryRenderer->setRestartIndexValue( 0 );
117
118 mLineEntity->addComponent( mLineGeometryRenderer );
119
120 mLineMaterial = new QgsLineMaterial;
121 mLineMaterial->setLineWidth( mWidth );
122 mLineMaterial->setLineColor( mColor );
123
124 QObject::connect( mEngine, &QgsAbstract3DEngine::sizeChanged, mLineMaterial, [this] { mLineMaterial->setViewportSize( mEngine->size() ); } );
125 mLineMaterial->setViewportSize( mEngine->size() );
126
127 mLineEntity->addComponent( mLineMaterial );
128
129 mLineTransform = new QgsGeoTransform;
130 mLineTransform->setOrigin( mMapSettings->origin() );
131 mLineEntity->addComponent( mLineTransform );
132}
133
134void QgsRubberBand3D::setupPolygon( Qt3DCore::QEntity *parentEntity )
135{
136 mPolygonEntity = make_qobject_unique<Qt3DCore::QEntity>( parentEntity );
137
138 mPolygonGeometry = new QgsTessellatedPolygonGeometry();
139
140 Qt3DRender::QGeometryRenderer *polygonGeometryRenderer = new Qt3DRender::QGeometryRenderer;
141 polygonGeometryRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::Triangles );
142 polygonGeometryRenderer->setGeometry( mPolygonGeometry );
143 mPolygonEntity->addComponent( polygonGeometryRenderer );
144
145 QColor color = mColor;
146 color.setAlphaF( DEFAULT_POLYGON_OPACITY );
147 mPolygonMaterial = new QgsUnlitMaterial();
148 mPolygonMaterial->setColor( color );
149
150 mPolygonEntity->addComponent( mPolygonMaterial );
151
152 mPolygonTransform = new QgsGeoTransform;
153 mPolygonTransform->setOrigin( mMapSettings->origin() );
154 mPolygonEntity->addComponent( mPolygonTransform );
155}
156
157void QgsRubberBand3D::removePoint( int index )
158{
159 if ( QgsPolygon *polygon = qgsgeometry_cast<QgsPolygon *>( mGeometry.get() ) )
160 {
161 QgsLineString *lineString = qgsgeometry_cast<QgsLineString *>( polygon->exteriorRing() );
162 const int vertexIndex = index < 0 ? lineString->numPoints() - 1 + index : index;
163 lineString->deleteVertex( QgsVertexId( 0, 0, vertexIndex ) );
164
165 if ( lineString->numPoints() < 3 )
166 {
167 mGeometry.set( new QgsLineString( *lineString ) );
168 }
169 }
170 else if ( QgsLineString *lineString = qgsgeometry_cast<QgsLineString *>( mGeometry.get() ) )
171 {
172 const int vertexIndex = index < 0 ? lineString->numPoints() + index : index;
173 lineString->deleteVertex( QgsVertexId( 0, 0, vertexIndex ) );
174 }
175 else
176 {
177 return;
178 }
179
180 updateGeometry();
181}
182
183QgsRubberBand3D::~QgsRubberBand3D()
184{
185 if ( mPolygonEntity )
186 {
187 mPolygonEntity.reset();
188 }
189 if ( mLineEntity )
190 {
191 mLineEntity.reset();
192 }
193 if ( mMarkerEntity )
194 {
195 mMarkerEntity.reset();
196 }
197}
198
199
200float QgsRubberBand3D::width() const
201{
202 return mWidth;
203}
204
205void QgsRubberBand3D::setWidth( float width )
206{
207 const bool isLineOrPolygon = mGeometryType == Qgis::GeometryType::Line || mGeometryType == Qgis::GeometryType::Polygon;
208 mWidth = width;
209
210 if ( isLineOrPolygon && mEdgesEnabled )
211 {
212 // when highlighting lines, the vertex markers should be wider
213 mLineMaterial->setLineWidth( width );
214 width *= 3;
215 }
216
217 mMarkerSymbol->setSize( width );
218 updateMarkerMaterial();
219}
220
221QColor QgsRubberBand3D::color() const
222{
223 return mColor;
224}
225
226void QgsRubberBand3D::setColor( const QColor color )
227{
228 const bool isLineOrPolygon = mGeometryType == Qgis::GeometryType::Line || mGeometryType == Qgis::GeometryType::Polygon;
229 mColor = color;
230
231 if ( mEdgesEnabled && isLineOrPolygon )
232 {
233 mLineMaterial->setLineColor( color );
234 }
235
236 if ( isLineOrPolygon )
237 {
238 mMarkerSymbol->setColor( color.lighter( 130 ) );
239 }
240 else
241 {
242 mMarkerSymbol->setColor( color );
243 }
244
245 if ( mMarkerSymbol->symbolLayerCount() > 0 && mMarkerSymbol->symbolLayer( 0 )->layerType() == "SimpleMarker"_L1 && !mOutlineColor.value() )
246 {
247 mMarkerSymbol->symbolLayer( 0 )->setStrokeColor( color );
248 }
249 updateMarkerMaterial();
250
251 if ( mGeometryType == Qgis::GeometryType::Polygon )
252 {
253 if ( mPolygonMaterial )
254 mPolygonEntity->removeComponent( mPolygonMaterial );
255
256 if ( mPolygonFillEnabled )
257 {
258 QColor color = mColor;
259 color.setAlphaF( DEFAULT_POLYGON_OPACITY );
260 mPolygonMaterial = new QgsUnlitMaterial();
261 mPolygonMaterial->setColor( color );
262 mPolygonEntity->addComponent( mPolygonMaterial );
263 }
264 }
265}
266
267QColor QgsRubberBand3D::outlineColor() const
268{
269 return mOutlineColor;
270}
271
272void QgsRubberBand3D::setOutlineColor( const QColor color )
273{
274 mOutlineColor = color;
275
276 if ( mMarkerSymbol->symbolLayerCount() > 0 && mMarkerSymbol->symbolLayer( 0 )->layerType() == "SimpleMarker"_L1 )
277 {
278 mMarkerSymbol->symbolLayer( 0 )->setStrokeColor( color );
279 }
280 updateMarkerMaterial();
281}
282
283void QgsRubberBand3D::setMarkerType( const MarkerType marker )
284{
285 mMarkerType = marker;
286
287 const bool lineOrPolygon = mGeometryType == Qgis::GeometryType::Line || mGeometryType == Qgis::GeometryType::Polygon;
288
289 const QVariantMap props {
290 { u"color"_s, lineOrPolygon ? mColor.lighter( 130 ).name() : mColor.name() },
291 { u"size_unit"_s, u"pixel"_s },
292 { u"size"_s, QString::number( lineOrPolygon ? mWidth * 3.f : mWidth ) },
293 { u"outline_color"_s, mOutlineColor.value() ? mOutlineColor.name() : mColor.name() },
294 { u"outline_style"_s, QgsSymbolLayerUtils::encodePenStyle( mMarkerOutlineStyle ) },
295 { u"outline_width"_s, QString::number( lineOrPolygon ? 0.5 : 1 ) },
296 { u"name"_s, mMarkerType == Square ? u"square"_s : u"circle"_s }
297 };
298
299 mMarkerSymbol = QgsMarkerSymbol::createSimple( props );
300 updateMarkerMaterial();
301}
302
303QgsRubberBand3D::MarkerType QgsRubberBand3D::markerType() const
304{
305 return mMarkerType;
306}
307
308void QgsRubberBand3D::setMarkerOutlineStyle( const Qt::PenStyle style )
309{
310 mMarkerOutlineStyle = style;
311 setMarkerType( markerType() );
312}
313
314Qt::PenStyle QgsRubberBand3D::markerOutlineStyle() const
315{
316 return mMarkerOutlineStyle;
317}
318
319void QgsRubberBand3D::setMarkersEnabled( const bool enable )
320{
321 mMarkerEnabled = enable;
322 updateMarkerMaterial();
323}
324
325bool QgsRubberBand3D::hasMarkersEnabled() const
326{
327 return mMarkerEnabled;
328}
329
330void QgsRubberBand3D::setEdgesEnabled( const bool enable )
331{
332 mEdgesEnabled = enable;
333 setColor( mColor );
334}
335
336bool QgsRubberBand3D::hasEdgesEnabled() const
337{
338 return mEdgesEnabled;
339}
340
341void QgsRubberBand3D::setFillEnabled( const bool enable )
342{
343 mPolygonFillEnabled = enable;
344 setColor( mColor );
345}
346
347bool QgsRubberBand3D::hasFillEnabled() const
348{
349 return mPolygonFillEnabled;
350}
351
352void QgsRubberBand3D::reset()
353{
354 mGeometry.set( nullptr );
355 updateGeometry();
356}
357
358void QgsRubberBand3D::addPoint( const QgsPoint &pt )
359{
360 if ( QgsPolygon *polygon = qgsgeometry_cast<QgsPolygon *>( mGeometry.get() ) )
361 {
362 QgsLineString *exteriorRing = qgsgeometry_cast<QgsLineString *>( polygon->exteriorRing() );
363 const int lastVertexIndex = exteriorRing->numPoints() - 1;
364 exteriorRing->insertVertex( QgsVertexId( 0, 0, lastVertexIndex ), pt );
365 }
366 else if ( QgsLineString *lineString = qgsgeometry_cast<QgsLineString *>( mGeometry.get() ) )
367 {
368 lineString->addVertex( pt );
369 // transform linestring to polygon if we have enough vertices
370 if ( mGeometryType == Qgis::GeometryType::Polygon && lineString->numPoints() >= 3 )
371 {
372 mGeometry.set( new QgsPolygon( lineString->clone() ) );
373 }
374 }
375 else if ( !mGeometry.constGet() )
376 {
377 mGeometry.set( new QgsLineString( QVector<QgsPoint> { pt } ) );
378 }
379
380 updateGeometry();
381}
382
383void QgsRubberBand3D::setGeometry( const QgsGeometry &geometry )
384{
385 mGeometry = geometry;
386 mGeometryType = geometry.type();
387
388 updateGeometry();
389}
390
391void QgsRubberBand3D::removeLastPoint()
392{
393 removePoint( -1 );
394}
395
396void QgsRubberBand3D::removePenultimatePoint()
397{
398 removePoint( -2 );
399}
400
401void QgsRubberBand3D::moveLastPoint( const QgsPoint &pt )
402{
403 if ( QgsPolygon *polygon = qgsgeometry_cast<QgsPolygon *>( mGeometry.get() ) )
404 {
405 QgsLineString *lineString = qgsgeometry_cast<QgsLineString *>( polygon->exteriorRing() );
406 const int lastVertexIndex = lineString->numPoints() - 2;
407 lineString->moveVertex( QgsVertexId( 0, 0, lastVertexIndex ), pt );
408 }
409 else if ( QgsLineString *lineString = qgsgeometry_cast<QgsLineString *>( mGeometry.get() ) )
410 {
411 const int lastVertexIndex = lineString->numPoints() - 1;
412 lineString->moveVertex( QgsVertexId( 0, 0, lastVertexIndex ), pt );
413 }
414 else
415 {
416 return;
417 }
418
419 updateGeometry();
420}
421
422void QgsRubberBand3D::updateGeometry()
423{
424 // figure out a reasonable origin for the coordinates to keep them as small as possible
425 const QgsBox3D box = mGeometry.constGet() ? mGeometry.constGet()->boundingBox3D() : QgsBox3D();
426 const QgsVector3D dataOrigin = box.isNull() ? mMapSettings->origin() : box.center();
427
428 QgsLineVertexData lineData;
429 lineData.withAdjacency = true;
430 lineData.geocentricCoordinates = mMapSettings->sceneMode() == Qgis::SceneMode::Globe;
432 if ( const QgsPolygon *polygon = qgsgeometry_cast<const QgsPolygon *>( mGeometry.constGet() ) )
433 {
434 std::unique_ptr< QgsLineString > lineString( qgsgeometry_cast<QgsLineString *>( polygon->exteriorRing()->clone() ) );
435 const int lastVertexIndex = lineString->numPoints() - 1;
436 lineString->deleteVertex( QgsVertexId( 0, 0, lastVertexIndex ) );
437 lineData.addLineString( *lineString, 0, true );
438 }
439 else if ( const QgsLineString *lineString = qgsgeometry_cast<const QgsLineString *>( mGeometry.constGet() ) )
440 {
441 lineData.addLineString( *lineString, 0, false );
442 }
443
444
445 if ( mEdgesEnabled && ( mGeometryType == Qgis::GeometryType::Line || mGeometryType == Qgis::GeometryType::Polygon ) )
446 {
447 mPositionAttribute->buffer()->setData( lineData.createVertexBuffer() );
448 mIndexAttribute->buffer()->setData( lineData.createIndexBuffer() );
449 mLineGeometryRenderer->setVertexCount( static_cast<int>( lineData.indexes.count() ) );
450 mLineTransform->setGeoTranslation( dataOrigin );
451 }
452
453 // first entry is empty for primitive restart
454 lineData.vertices.pop_front();
455
456 // we may not want a marker on the last point as it's tracked by the mouse cursor
457 if ( mHideLastMarker && !lineData.vertices.isEmpty() )
458 lineData.vertices.pop_back();
459
460 mMarkerGeometry->setPositions( lineData.vertices );
461 mMarkerGeometryRenderer->setVertexCount( 4 );
462 mMarkerGeometryRenderer->setInstanceCount( lineData.vertices.count() );
463 mMarkerTransform->setGeoTranslation( dataOrigin );
464
465 if ( mGeometryType == Qgis::GeometryType::Polygon )
466 {
467 if ( const QgsPolygon *polygon = qgsgeometry_cast<const QgsPolygon *>( mGeometry.constGet() ) )
468 {
469 QgsTessellator tessellator;
470 tessellator.setOrigin( mMapSettings->origin() );
471 tessellator.setAddNormals( true );
472 tessellator.addPolygon( *polygon, 0 );
473 if ( !tessellator.error().isEmpty() )
474 {
475 QgsMessageLog::logMessage( tessellator.error(), QObject::tr( "3D" ) );
476 }
477 // extract vertex buffer data from tessellator
478 const QByteArray vertexBuffer = tessellator.vertexBuffer();
479 const QByteArray indexBuffer = tessellator.indexBuffer();
480 const size_t vertexCount = tessellator.uniqueVertexCount();
481 const size_t indexCount = tessellator.dataVerticesCount();
482
483 mPolygonGeometry->setVertexBufferData( vertexBuffer, vertexCount, QVector<QgsFeatureId>(), QVector<uint>() );
484 mPolygonGeometry->setIndexBufferData( indexBuffer, indexCount );
485 mPolygonTransform->setGeoTranslation( mMapSettings->origin() );
486 }
487 else
488 {
489 mPolygonGeometry->setVertexBufferData( QByteArray(), 0, QVector<QgsFeatureId>(), QVector<uint>() );
490 mPolygonGeometry->setIndexBufferData( QByteArray(), 0 );
491 }
492 }
493}
494
495void QgsRubberBand3D::updateMarkerMaterial()
496{
497 if ( mMarkerEnabled )
498 {
499 if ( !mMarkerMaterial )
500 {
501 mMarkerMaterial = new QgsPoint3DBillboardMaterial();
502 mMarkerEntity->addComponent( mMarkerMaterial );
503 //TODO: QgsAbstract3DEngine::sizeChanged should have const QSize &size param
504 QObject::connect( mEngine, &QgsAbstract3DEngine::sizeChanged, mMarkerMaterial, [material = mMarkerMaterial, engine = mEngine] { material->setViewportSize( engine->size() ); } );
505 }
506
507 mMarkerMaterial->setTexture2DFromSymbol( mMarkerSymbol.get(), Qgs3DRenderContext::fromMapSettings( mMapSettings ) );
508 mMarkerMaterial->setViewportSize( mEngine->size() );
509 }
510 else if ( !mMarkerEnabled && mMarkerMaterial )
511 {
512 mMarkerEntity->removeComponent( mMarkerMaterial );
513 mMarkerMaterial->setParent( static_cast<Qt3DCore::QEntity *>( nullptr ) );
514 mMarkerMaterial->deleteLater();
515 mMarkerMaterial = nullptr;
516 }
517}
@ Absolute
Elevation is taken directly from feature and is independent of terrain height (final elevation = feat...
Definition qgis.h:4204
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:4217
@ Globe
Scene is represented as a globe using a geocentric CRS.
Definition qgis.h:4540
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