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