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