QGIS API Documentation 3.27.0-Master (f261cc1f8b)
qgsline3dsymbol_p.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsline3dsymbol_p.cpp
3 --------------------------------------
4 Date : July 2017
5 Copyright : (C) 2017 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 "qgsline3dsymbol_p.h"
17
18#include "qgsline3dsymbol.h"
19#include "qgslinematerial_p.h"
20#include "qgslinevertexdata_p.h"
22#include "qgstessellator.h"
23#include "qgs3dmapsettings.h"
24//#include "qgsterraingenerator.h"
25#include "qgs3dutils.h"
26
27#include "qgsvectorlayer.h"
28#include "qgsmultilinestring.h"
29#include "qgsmultipolygon.h"
30#include "qgsgeos.h"
32
34
35#include <Qt3DExtras/QPhongMaterial>
36#include <Qt3DRender/QAttribute>
37#include <Qt3DRender/QBuffer>
38#include <Qt3DRender/QGeometryRenderer>
39
41
42// -----------
43
44
45class QgsBufferedLine3DSymbolHandler : public QgsFeature3DHandler
46{
47 public:
48 QgsBufferedLine3DSymbolHandler( const QgsLine3DSymbol *symbol, const QgsFeatureIds &selectedIds )
49 : mSymbol( static_cast< QgsLine3DSymbol *>( symbol->clone() ) )
50 , mSelectedIds( selectedIds ) {}
51
52 bool prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames ) override;
53 void processFeature( const QgsFeature &feature, const Qgs3DRenderContext &context ) override;
54 void finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context ) override;
55
56 private:
57
59 struct LineData
60 {
61 std::unique_ptr<QgsTessellator> tessellator;
62 QVector<QgsFeatureId> triangleIndexFids;
63 QVector<uint> triangleIndexStartingIndices;
64 };
65
66 void processPolygon( QgsPolygon *polyBuffered, QgsFeatureId fid, float height, float extrusionHeight, const Qgs3DRenderContext &context, LineData &out );
67
68 void makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, LineData &out, bool selected );
69
70 // input specific for this class
71 std::unique_ptr< QgsLine3DSymbol > mSymbol;
72 // inputs - generic
73 QgsFeatureIds mSelectedIds;
74
75 // outputs
76 LineData outNormal;
77 LineData outSelected;
78};
79
80
81
82bool QgsBufferedLine3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames )
83{
84 Q_UNUSED( attributeNames )
85
86 const QgsPhongTexturedMaterialSettings *texturedMaterialSettings = dynamic_cast< const QgsPhongTexturedMaterialSettings * >( mSymbol->material() );
87
88 outNormal.tessellator.reset( new QgsTessellator( context.map().origin().x(), context.map().origin().y(), true,
89 false, false, false, texturedMaterialSettings ? texturedMaterialSettings->requiresTextureCoordinates() : false,
90 3,
91 texturedMaterialSettings ? texturedMaterialSettings->textureRotation() : 0 ) );
92 outSelected.tessellator.reset( new QgsTessellator( context.map().origin().x(), context.map().origin().y(), true,
93 false, false, false, texturedMaterialSettings ? texturedMaterialSettings->requiresTextureCoordinates() : false,
94 3,
95 texturedMaterialSettings ? texturedMaterialSettings->textureRotation() : 0 ) );
96
97 return true;
98}
99
100void QgsBufferedLine3DSymbolHandler::processFeature( const QgsFeature &f, const Qgs3DRenderContext &context )
101{
102 if ( f.geometry().isNull() )
103 return;
104
105 LineData &out = mSelectedIds.contains( f.id() ) ? outSelected : outNormal;
106
107 QgsGeometry geom = f.geometry();
109
110 // segmentize curved geometries if necessary
112 {
113 geom = QgsGeometry( g->segmentize() );
114 g = geom.constGet()->simplifiedTypeRef();
115 }
116
117 // TODO: configurable
118 const int nSegments = 4;
119 const Qgis::EndCapStyle endCapStyle = Qgis::EndCapStyle::Round;
120 const Qgis::JoinStyle joinStyle = Qgis::JoinStyle::Round;
121 const double mitreLimit = 0;
122
123 const QgsGeos engine( g );
124
125 double width = mSymbol->width();
126 if ( qgsDoubleNear( width, 0 ) )
127 {
128 // a zero-width buffered line should be treated like a "wall" or "fence" -- we fake this by bumping the width to a very tiny amount,
129 // so that we get a very narrow polygon shape to work with...
130 width = 0.001;
131 }
132
133 QgsAbstractGeometry *buffered = engine.buffer( width / 2., nSegments, endCapStyle, joinStyle, mitreLimit ); // factory
134 if ( !buffered )
135 return;
136
137 if ( QgsWkbTypes::flatType( buffered->wkbType() ) == QgsWkbTypes::Polygon )
138 {
139 QgsPolygon *polyBuffered = static_cast<QgsPolygon *>( buffered );
140 processPolygon( polyBuffered, f.id(), mSymbol->height(), mSymbol->extrusionHeight(), context, out );
141 }
142 else if ( QgsWkbTypes::flatType( buffered->wkbType() ) == QgsWkbTypes::MultiPolygon )
143 {
144 QgsMultiPolygon *mpolyBuffered = static_cast<QgsMultiPolygon *>( buffered );
145 for ( int i = 0; i < mpolyBuffered->numGeometries(); ++i )
146 {
147 QgsPolygon *polyBuffered = static_cast<QgsPolygon *>( mpolyBuffered->polygonN( i ) )->clone(); // need to clone individual geometry parts
148 processPolygon( polyBuffered, f.id(), mSymbol->height(), mSymbol->extrusionHeight(), context, out );
149 }
150 delete buffered;
151 }
152 mFeatureCount++;
153}
154
155void QgsBufferedLine3DSymbolHandler::processPolygon( QgsPolygon *polyBuffered, QgsFeatureId fid, float height, float extrusionHeight, const Qgs3DRenderContext &context, LineData &out )
156{
157 Qgs3DUtils::clampAltitudes( polyBuffered, mSymbol->altitudeClamping(), mSymbol->altitudeBinding(), height, context.map() );
158
159 Q_ASSERT( out.tessellator->dataVerticesCount() % 3 == 0 );
160 const uint startingTriangleIndex = static_cast<uint>( out.tessellator->dataVerticesCount() / 3 );
161 out.triangleIndexStartingIndices.append( startingTriangleIndex );
162 out.triangleIndexFids.append( fid );
163 out.tessellator->addPolygon( *polyBuffered, extrusionHeight );
164 delete polyBuffered;
165}
166
167void QgsBufferedLine3DSymbolHandler::finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context )
168{
169 // create entity for selected and not selected
170 makeEntity( parent, context, outNormal, false );
171 makeEntity( parent, context, outSelected, true );
172
173 mZMin = std::min( outNormal.tessellator->zMinimum(), outSelected.tessellator->zMinimum() );
174 mZMax = std::max( outNormal.tessellator->zMaximum(), outSelected.tessellator->zMaximum() );
175}
176
177
178void QgsBufferedLine3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, LineData &out, bool selected )
179{
180 if ( out.tessellator->dataVerticesCount() == 0 )
181 return; // nothing to show - no need to create the entity
182
183 QgsMaterialContext materialContext;
184 materialContext.setIsSelected( selected );
185 materialContext.setSelectionColor( context.map().selectionColor() );
186 Qt3DRender::QMaterial *mat = mSymbol->material()->toMaterial( QgsMaterialSettingsRenderingTechnique::Triangles, materialContext );
187
188 // extract vertex buffer data from tessellator
189 const QByteArray data( ( const char * )out.tessellator->data().constData(), out.tessellator->data().count() * sizeof( float ) );
190 const int nVerts = data.count() / out.tessellator->stride();
191
192 const QgsPhongTexturedMaterialSettings *texturedMaterialSettings = dynamic_cast< const QgsPhongTexturedMaterialSettings * >( mSymbol->material() );
193
194 QgsTessellatedPolygonGeometry *geometry = new QgsTessellatedPolygonGeometry( true, false, false,
195 texturedMaterialSettings ? texturedMaterialSettings->requiresTextureCoordinates() : false );
196 geometry->setData( data, nVerts, out.triangleIndexFids, out.triangleIndexStartingIndices );
197
198 Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer;
199 renderer->setGeometry( geometry );
200
201 // make entity
202 Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
203 entity->addComponent( renderer );
204 entity->addComponent( mat );
205 entity->setParent( parent );
206
207 if ( !selected )
208 entity->findChild<Qt3DRender::QGeometryRenderer *>()->setObjectName( QStringLiteral( "main" ) ); // temporary measure to distinguish between "selected" and "main"
209
210 // cppcheck wrongly believes entity will leak
211 // cppcheck-suppress memleak
212}
213
214
215// --------------
216
217
218class QgsSimpleLine3DSymbolHandler : public QgsFeature3DHandler
219{
220 public:
221 QgsSimpleLine3DSymbolHandler( const QgsLine3DSymbol *symbol, const QgsFeatureIds &selectedIds )
222 : mSymbol( static_cast< QgsLine3DSymbol *>( symbol->clone() ) )
223 , mSelectedIds( selectedIds )
224 {
225 }
226
227 bool prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames ) override;
228 void processFeature( const QgsFeature &feature, const Qgs3DRenderContext &context ) override;
229 void finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context ) override;
230
231 private:
232
233 void makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, QgsLineVertexData &out, bool selected );
234 Qt3DExtras::QPhongMaterial *material( const QgsLine3DSymbol &symbol ) const;
235
236 // input specific for this class
237 std::unique_ptr< QgsLine3DSymbol > mSymbol;
238 // inputs - generic
239 QgsFeatureIds mSelectedIds;
240
241 // outputs
242 QgsLineVertexData outNormal;
243 QgsLineVertexData outSelected;
244};
245
246
247
248bool QgsSimpleLine3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames )
249{
250 Q_UNUSED( attributeNames )
251
252 outNormal.init( mSymbol->altitudeClamping(), mSymbol->altitudeBinding(), mSymbol->height(), &context.map() );
253 outSelected.init( mSymbol->altitudeClamping(), mSymbol->altitudeBinding(), mSymbol->height(), &context.map() );
254
255 return true;
256}
257
258void QgsSimpleLine3DSymbolHandler::processFeature( const QgsFeature &f, const Qgs3DRenderContext &context )
259{
260 Q_UNUSED( context )
261 if ( f.geometry().isNull() )
262 return;
263
264 QgsLineVertexData &out = mSelectedIds.contains( f.id() ) ? outSelected : outNormal;
265
266 const QgsGeometry geom = f.geometry();
267 const QgsAbstractGeometry *g = geom.constGet();
268 if ( const QgsLineString *ls = qgsgeometry_cast<const QgsLineString *>( g ) )
269 {
270 out.addLineString( *ls );
271 }
272 else if ( const QgsMultiLineString *mls = qgsgeometry_cast<const QgsMultiLineString *>( g ) )
273 {
274 for ( int nGeom = 0; nGeom < mls->numGeometries(); ++nGeom )
275 {
276 const QgsLineString *ls = mls->lineStringN( nGeom );
277 out.addLineString( *ls );
278 }
279 }
280 mFeatureCount++;
281}
282
283void QgsSimpleLine3DSymbolHandler::finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context )
284{
285 // create entity for selected and not selected
286 makeEntity( parent, context, outNormal, false );
287 makeEntity( parent, context, outSelected, true );
288
289 updateZRangeFromPositions( outNormal.vertices );
290 updateZRangeFromPositions( outSelected.vertices );
291}
292
293
294void QgsSimpleLine3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, QgsLineVertexData &out, bool selected )
295{
296 if ( out.indexes.isEmpty() )
297 return;
298
299 // material (only ambient color is used for the color)
300
301 QgsMaterialContext materialContext;
302 materialContext.setIsSelected( selected );
303 materialContext.setSelectionColor( context.map().selectionColor() );
304 Qt3DRender::QMaterial *mat = mSymbol->material()->toMaterial( QgsMaterialSettingsRenderingTechnique::Lines, materialContext );
305
306 // geometry renderer
307
308 Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
309
310 Qt3DRender::QGeometry *geom = out.createGeometry( entity );
311
312 Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer;
313 renderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::LineStrip );
314 renderer->setGeometry( geom );
315 renderer->setVertexCount( out.indexes.count() );
316 renderer->setPrimitiveRestartEnabled( true );
317 renderer->setRestartIndexValue( 0 );
318
319 // make entity
320 entity->addComponent( renderer );
321 entity->addComponent( mat );
322 entity->setParent( parent );
323}
324
325
326
327// --------------
328
329
330class QgsThickLine3DSymbolHandler : public QgsFeature3DHandler
331{
332 public:
333 QgsThickLine3DSymbolHandler( const QgsLine3DSymbol *symbol, const QgsFeatureIds &selectedIds )
334 : mSymbol( static_cast< QgsLine3DSymbol * >( symbol->clone() ) )
335 , mSelectedIds( selectedIds )
336 {
337 }
338
339 bool prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames ) override;
340 void processFeature( const QgsFeature &feature, const Qgs3DRenderContext &context ) override;
341 void finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context ) override;
342
343 private:
344
345
346 void makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, QgsLineVertexData &out, bool selected );
347 Qt3DExtras::QPhongMaterial *material( const QgsLine3DSymbol &symbol ) const;
348
349 // input specific for this class
350 std::unique_ptr< QgsLine3DSymbol > mSymbol;
351 // inputs - generic
352 QgsFeatureIds mSelectedIds;
353
354 // outputs
355 QgsLineVertexData outNormal;
356 QgsLineVertexData outSelected;
357};
358
359
360
361bool QgsThickLine3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames )
362{
363 Q_UNUSED( attributeNames )
364
365 outNormal.withAdjacency = true;
366 outSelected.withAdjacency = true;
367 outNormal.init( mSymbol->altitudeClamping(), mSymbol->altitudeBinding(), mSymbol->height(), &context.map() );
368 outSelected.init( mSymbol->altitudeClamping(), mSymbol->altitudeBinding(), mSymbol->height(), &context.map() );
369
370 return true;
371}
372
373void QgsThickLine3DSymbolHandler::processFeature( const QgsFeature &f, const Qgs3DRenderContext &context )
374{
375 Q_UNUSED( context )
376 if ( f.geometry().isNull() )
377 return;
378
379 QgsLineVertexData &out = mSelectedIds.contains( f.id() ) ? outSelected : outNormal;
380
381 QgsGeometry geom = f.geometry();
383
384 // segmentize curved geometries if necessary
386 {
387 geom = QgsGeometry( g->segmentize() );
388 g = geom.constGet()->simplifiedTypeRef();
389 }
390
391 if ( const QgsLineString *ls = qgsgeometry_cast<const QgsLineString *>( g ) )
392 {
393 out.addLineString( *ls );
394 }
395 else if ( const QgsMultiLineString *mls = qgsgeometry_cast<const QgsMultiLineString *>( g ) )
396 {
397 for ( int nGeom = 0; nGeom < mls->numGeometries(); ++nGeom )
398 {
399 const QgsLineString *ls = mls->lineStringN( nGeom );
400 out.addLineString( *ls );
401 }
402 }
403 mFeatureCount++;
404}
405
406void QgsThickLine3DSymbolHandler::finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context )
407{
408 // create entity for selected and not selected
409 makeEntity( parent, context, outNormal, false );
410 makeEntity( parent, context, outSelected, true );
411
412 updateZRangeFromPositions( outNormal.vertices );
413 updateZRangeFromPositions( outSelected.vertices );
414}
415
416
417void QgsThickLine3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, QgsLineVertexData &out, bool selected )
418{
419 if ( out.indexes.isEmpty() )
420 return;
421
422 // material (only ambient color is used for the color)
423 QgsMaterialContext materialContext;
424 materialContext.setIsSelected( selected );
425 materialContext.setSelectionColor( context.map().selectionColor() );
426 Qt3DRender::QMaterial *mat = mSymbol->material()->toMaterial( QgsMaterialSettingsRenderingTechnique::Lines, materialContext );
427 if ( !mat )
428 {
429 const QgsSimpleLineMaterialSettings defaultMaterial;
430 mat = defaultMaterial.toMaterial( QgsMaterialSettingsRenderingTechnique::Lines, materialContext );
431 }
432
433 if ( QgsLineMaterial *lineMaterial = dynamic_cast< QgsLineMaterial * >( mat ) )
434 lineMaterial->setLineWidth( mSymbol->width() );
435
436 Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
437
438 // geometry renderer
439 Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer;
440 renderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::LineStripAdjacency );
441 renderer->setGeometry( out.createGeometry( entity ) );
442 renderer->setVertexCount( out.indexes.count() );
443 renderer->setPrimitiveRestartEnabled( true );
444 renderer->setRestartIndexValue( 0 );
445
446 // make entity
447 entity->addComponent( renderer );
448 entity->addComponent( mat );
449 entity->setParent( parent );
450}
451
452
453// --------------
454
455
456namespace Qgs3DSymbolImpl
457{
458
459 QgsFeature3DHandler *handlerForLine3DSymbol( QgsVectorLayer *layer, const QgsAbstract3DSymbol *symbol )
460 {
461 const QgsLine3DSymbol *lineSymbol = dynamic_cast< const QgsLine3DSymbol * >( symbol );
462 if ( !lineSymbol )
463 return nullptr;
464
465 if ( lineSymbol->renderAsSimpleLines() )
466 return new QgsThickLine3DSymbolHandler( lineSymbol, layer->selectedFeatureIds() );
467 //return new QgsSimpleLine3DSymbolHandler( symbol, layer->selectedFeatureIds() );
468 else
469 return new QgsBufferedLine3DSymbolHandler( lineSymbol, layer->selectedFeatureIds() );
470 }
471
472 Qt3DCore::QEntity *entityForLine3DSymbol( const Qgs3DMapSettings &map, QgsVectorLayer *layer, const QgsLine3DSymbol &symbol )
473 {
474 QgsFeature3DHandler *handler = handlerForLine3DSymbol( layer, &symbol );
475 Qt3DCore::QEntity *e = entityFromHandler( handler, map, layer );
476 delete handler;
477 return e;
478 }
479}
480
JoinStyle
Join styles for buffers.
Definition: qgis.h:1013
EndCapStyle
End cap styles for buffers.
Definition: qgis.h:1000
static void clampAltitudes(QgsLineString *lineString, Qgis::AltitudeClamping altClamp, Qgis::AltitudeBinding altBind, const QgsPoint &centroid, float height, const Qgs3DMapSettings &map)
Clamps altitude of vertices of a linestring according to the settings.
Definition: qgs3dutils.cpp:351
Abstract base class for all geometries.
virtual QgsAbstractGeometry * segmentize(double tolerance=M_PI/180., SegmentationToleranceType toleranceType=MaximumAngle) const
Returns a version of the geometry without curves.
virtual const QgsAbstractGeometry * simplifiedTypeRef() const SIP_HOLDGIL
Returns a reference to the simplest lossless representation of this geometry, e.g.
QgsWkbTypes::Type wkbType() const SIP_HOLDGIL
Returns the WKB type of the geometry.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
QgsGeometry geometry
Definition: qgsfeature.h:67
Q_GADGET QgsFeatureId id
Definition: qgsfeature.h:64
int numGeometries() const SIP_HOLDGIL
Returns the number of geometries within the collection.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:125
const QgsAbstractGeometry * constGet() const SIP_HOLDGIL
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
Q_GADGET bool isNull
Definition: qgsgeometry.h:127
Does vector analysis using the geos library and handles import, export, exception handling*.
Definition: qgsgeos.h:104
bool renderAsSimpleLines() const
Returns whether the renderer will render data with simple lines (otherwise it uses buffer)
Line string geometry type, with support for z-dimension and m-values.
Definition: qgslinestring.h:45
void setIsSelected(bool isSelected)
Sets whether the material should represent a selected state.
void setSelectionColor(const QColor &color)
Sets the color for representing materials in a selected state.
Multi line string geometry collection.
Multi polygon geometry collection.
QgsPolygon * polygonN(int index)
Returns the polygon with the specified index.
bool requiresTextureCoordinates() const
Returns true if the material requires texture coordinates to be generated during triangulation....
float textureRotation() const
Returns the texture rotation, in degrees.
Polygon geometry type.
Definition: qgspolygon.h:34
Qt3DRender::QMaterial * toMaterial(QgsMaterialSettingsRenderingTechnique technique, const QgsMaterialContext &context) const override
Creates a new QMaterial object representing the material settings.
void setData(const QByteArray &vertexBufferData, int vertexCount, const QVector< QgsFeatureId > &triangleIndexFids, const QVector< uint > &triangleIndexStartingIndices)
Initializes vertex buffer (and other members) from data that were already tessellated.
Class that takes care of tessellation of polygons into triangles.
Represents a vector layer which manages a vector based data sets.
Q_INVOKABLE const QgsFeatureIds & selectedFeatureIds() const
Returns a list of the selected features IDs in this layer.
static bool isCurvedType(Type type) SIP_HOLDGIL
Returns true if the WKB type is a curved type or can contain curved geometries.
Definition: qgswkbtypes.h:911
static Type flatType(Type type) SIP_HOLDGIL
Returns the flat type for a WKB type.
Definition: qgswkbtypes.h:732
@ Triangles
Triangle based rendering (default)
@ Lines
Line based rendering, requires line data.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:2396
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:37
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
Definition: qgsfeatureid.h:28