QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
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 
45 class 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 
82 bool 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 
100 void 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();
108 
109  // segmentize curved geometries if necessary
110  if ( QgsWkbTypes::isCurvedType( geom.constGet()->wkbType() ) )
111  geom = QgsGeometry( geom.constGet()->segmentize() );
112 
113  const QgsAbstractGeometry *g = geom.constGet();
114 
115  // TODO: configurable
116  const int nSegments = 4;
119  const double mitreLimit = 0;
120 
121  QgsGeos engine( g );
122 
123  double width = mSymbol->width();
124  if ( qgsDoubleNear( width, 0 ) )
125  {
126  // 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,
127  // so that we get a very narrow polygon shape to work with...
128  width = 0.001;
129  }
130 
131  QgsAbstractGeometry *buffered = engine.buffer( width / 2., nSegments, endCapStyle, joinStyle, mitreLimit ); // factory
132  if ( !buffered )
133  return;
134 
135  if ( QgsWkbTypes::flatType( buffered->wkbType() ) == QgsWkbTypes::Polygon )
136  {
137  QgsPolygon *polyBuffered = static_cast<QgsPolygon *>( buffered );
138  processPolygon( polyBuffered, f.id(), mSymbol->height(), mSymbol->extrusionHeight(), context, out );
139  }
140  else if ( QgsWkbTypes::flatType( buffered->wkbType() ) == QgsWkbTypes::MultiPolygon )
141  {
142  QgsMultiPolygon *mpolyBuffered = static_cast<QgsMultiPolygon *>( buffered );
143  for ( int i = 0; i < mpolyBuffered->numGeometries(); ++i )
144  {
145  QgsPolygon *polyBuffered = static_cast<QgsPolygon *>( mpolyBuffered->polygonN( i ) )->clone(); // need to clone individual geometry parts
146  processPolygon( polyBuffered, f.id(), mSymbol->height(), mSymbol->extrusionHeight(), context, out );
147  }
148  delete buffered;
149  }
150 }
151 
152 void QgsBufferedLine3DSymbolHandler::processPolygon( QgsPolygon *polyBuffered, QgsFeatureId fid, float height, float extrusionHeight, const Qgs3DRenderContext &context, LineData &out )
153 {
154  Qgs3DUtils::clampAltitudes( polyBuffered, mSymbol->altitudeClamping(), mSymbol->altitudeBinding(), height, context.map() );
155 
156  Q_ASSERT( out.tessellator->dataVerticesCount() % 3 == 0 );
157  uint startingTriangleIndex = static_cast<uint>( out.tessellator->dataVerticesCount() / 3 );
158  out.triangleIndexStartingIndices.append( startingTriangleIndex );
159  out.triangleIndexFids.append( fid );
160  out.tessellator->addPolygon( *polyBuffered, extrusionHeight );
161  delete polyBuffered;
162 }
163 
164 void QgsBufferedLine3DSymbolHandler::finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context )
165 {
166  // create entity for selected and not selected
167  makeEntity( parent, context, outNormal, false );
168  makeEntity( parent, context, outSelected, true );
169 
170  mZMin = std::min( outNormal.tessellator->zMinimum(), outSelected.tessellator->zMinimum() );
171  mZMax = std::max( outNormal.tessellator->zMaximum(), outSelected.tessellator->zMaximum() );
172 }
173 
174 
175 void QgsBufferedLine3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, LineData &out, bool selected )
176 {
177  if ( out.tessellator->dataVerticesCount() == 0 )
178  return; // nothing to show - no need to create the entity
179 
180  QgsMaterialContext materialContext;
181  materialContext.setIsSelected( selected );
182  materialContext.setSelectionColor( context.map().selectionColor() );
183  Qt3DRender::QMaterial *mat = mSymbol->material()->toMaterial( QgsMaterialSettingsRenderingTechnique::Triangles, materialContext );
184 
185  // extract vertex buffer data from tessellator
186  QByteArray data( ( const char * )out.tessellator->data().constData(), out.tessellator->data().count() * sizeof( float ) );
187  int nVerts = data.count() / out.tessellator->stride();
188 
189  const QgsPhongTexturedMaterialSettings *texturedMaterialSettings = dynamic_cast< const QgsPhongTexturedMaterialSettings * >( mSymbol->material() );
190 
191  QgsTessellatedPolygonGeometry *geometry = new QgsTessellatedPolygonGeometry( true, false, false,
192  texturedMaterialSettings ? texturedMaterialSettings->requiresTextureCoordinates() : false );
193  geometry->setData( data, nVerts, out.triangleIndexFids, out.triangleIndexStartingIndices );
194 
195  Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer;
196  renderer->setGeometry( geometry );
197 
198  // make entity
199  Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
200  entity->addComponent( renderer );
201  entity->addComponent( mat );
202  entity->setParent( parent );
203 
204  if ( !selected )
205  entity->findChild<Qt3DRender::QGeometryRenderer *>()->setObjectName( QStringLiteral( "main" ) ); // temporary measure to distinguish between "selected" and "main"
206 
207  // cppcheck wrongly believes entity will leak
208  // cppcheck-suppress memleak
209 }
210 
211 
212 // --------------
213 
214 
215 class QgsSimpleLine3DSymbolHandler : public QgsFeature3DHandler
216 {
217  public:
218  QgsSimpleLine3DSymbolHandler( const QgsLine3DSymbol *symbol, const QgsFeatureIds &selectedIds )
219  : mSymbol( static_cast< QgsLine3DSymbol *>( symbol->clone() ) )
220  , mSelectedIds( selectedIds )
221  {
222  }
223 
224  bool prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames ) override;
225  void processFeature( const QgsFeature &feature, const Qgs3DRenderContext &context ) override;
226  void finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context ) override;
227 
228  private:
229 
230  void makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, QgsLineVertexData &out, bool selected );
231  Qt3DExtras::QPhongMaterial *material( const QgsLine3DSymbol &symbol ) const;
232 
233  // input specific for this class
234  std::unique_ptr< QgsLine3DSymbol > mSymbol;
235  // inputs - generic
236  QgsFeatureIds mSelectedIds;
237 
238  // outputs
239  QgsLineVertexData outNormal;
240  QgsLineVertexData outSelected;
241 };
242 
243 
244 
245 bool QgsSimpleLine3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames )
246 {
247  Q_UNUSED( attributeNames )
248 
249  outNormal.init( mSymbol->altitudeClamping(), mSymbol->altitudeBinding(), mSymbol->height(), &context.map() );
250  outSelected.init( mSymbol->altitudeClamping(), mSymbol->altitudeBinding(), mSymbol->height(), &context.map() );
251 
252  return true;
253 }
254 
255 void QgsSimpleLine3DSymbolHandler::processFeature( const QgsFeature &f, const Qgs3DRenderContext &context )
256 {
257  Q_UNUSED( context )
258  if ( f.geometry().isNull() )
259  return;
260 
261  QgsLineVertexData &out = mSelectedIds.contains( f.id() ) ? outSelected : outNormal;
262 
263  QgsGeometry geom = f.geometry();
264  const QgsAbstractGeometry *g = geom.constGet();
265  if ( const QgsLineString *ls = qgsgeometry_cast<const QgsLineString *>( g ) )
266  {
267  out.addLineString( *ls );
268  }
269  else if ( const QgsMultiLineString *mls = qgsgeometry_cast<const QgsMultiLineString *>( g ) )
270  {
271  for ( int nGeom = 0; nGeom < mls->numGeometries(); ++nGeom )
272  {
273  const QgsLineString *ls = mls->lineStringN( nGeom );
274  out.addLineString( *ls );
275  }
276  }
277 }
278 
279 void QgsSimpleLine3DSymbolHandler::finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context )
280 {
281  // create entity for selected and not selected
282  makeEntity( parent, context, outNormal, false );
283  makeEntity( parent, context, outSelected, true );
284 
285  updateZRangeFromPositions( outNormal.vertices );
286  updateZRangeFromPositions( outSelected.vertices );
287 }
288 
289 
290 void QgsSimpleLine3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, QgsLineVertexData &out, bool selected )
291 {
292  if ( out.indexes.isEmpty() )
293  return;
294 
295  // material (only ambient color is used for the color)
296 
297  QgsMaterialContext materialContext;
298  materialContext.setIsSelected( selected );
299  materialContext.setSelectionColor( context.map().selectionColor() );
300  Qt3DRender::QMaterial *mat = mSymbol->material()->toMaterial( QgsMaterialSettingsRenderingTechnique::Lines, materialContext );
301 
302  // geometry renderer
303 
304  Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
305 
306  Qt3DRender::QGeometry *geom = out.createGeometry( entity );
307 
308  Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer;
309  renderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::LineStrip );
310  renderer->setGeometry( geom );
311  renderer->setVertexCount( out.indexes.count() );
312  renderer->setPrimitiveRestartEnabled( true );
313  renderer->setRestartIndexValue( 0 );
314 
315  // make entity
316  entity->addComponent( renderer );
317  entity->addComponent( mat );
318  entity->setParent( parent );
319 }
320 
321 
322 
323 // --------------
324 
325 
326 class QgsThickLine3DSymbolHandler : public QgsFeature3DHandler
327 {
328  public:
329  QgsThickLine3DSymbolHandler( const QgsLine3DSymbol *symbol, const QgsFeatureIds &selectedIds )
330  : mSymbol( static_cast< QgsLine3DSymbol * >( symbol->clone() ) )
331  , mSelectedIds( selectedIds )
332  {
333  }
334 
335  bool prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames ) override;
336  void processFeature( const QgsFeature &feature, const Qgs3DRenderContext &context ) override;
337  void finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context ) override;
338 
339  private:
340 
341 
342  void makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, QgsLineVertexData &out, bool selected );
343  Qt3DExtras::QPhongMaterial *material( const QgsLine3DSymbol &symbol ) const;
344 
345  // input specific for this class
346  std::unique_ptr< QgsLine3DSymbol > mSymbol;
347  // inputs - generic
348  QgsFeatureIds mSelectedIds;
349 
350  // outputs
351  QgsLineVertexData outNormal;
352  QgsLineVertexData outSelected;
353 };
354 
355 
356 
357 bool QgsThickLine3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames )
358 {
359  Q_UNUSED( attributeNames )
360 
361  outNormal.withAdjacency = true;
362  outSelected.withAdjacency = true;
363  outNormal.init( mSymbol->altitudeClamping(), mSymbol->altitudeBinding(), mSymbol->height(), &context.map() );
364  outSelected.init( mSymbol->altitudeClamping(), mSymbol->altitudeBinding(), mSymbol->height(), &context.map() );
365 
366  return true;
367 }
368 
369 void QgsThickLine3DSymbolHandler::processFeature( const QgsFeature &f, const Qgs3DRenderContext &context )
370 {
371  Q_UNUSED( context )
372  if ( f.geometry().isNull() )
373  return;
374 
375  QgsLineVertexData &out = mSelectedIds.contains( f.id() ) ? outSelected : outNormal;
376 
377  QgsGeometry geom = f.geometry();
378  const QgsAbstractGeometry *g = geom.constGet();
379  if ( const QgsLineString *ls = qgsgeometry_cast<const QgsLineString *>( g ) )
380  {
381  out.addLineString( *ls );
382  }
383  else if ( const QgsMultiLineString *mls = qgsgeometry_cast<const QgsMultiLineString *>( g ) )
384  {
385  for ( int nGeom = 0; nGeom < mls->numGeometries(); ++nGeom )
386  {
387  const QgsLineString *ls = mls->lineStringN( nGeom );
388  out.addLineString( *ls );
389  }
390  }
391 }
392 
393 void QgsThickLine3DSymbolHandler::finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context )
394 {
395  // create entity for selected and not selected
396  makeEntity( parent, context, outNormal, false );
397  makeEntity( parent, context, outSelected, true );
398 
399  updateZRangeFromPositions( outNormal.vertices );
400  updateZRangeFromPositions( outSelected.vertices );
401 }
402 
403 
404 void QgsThickLine3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, QgsLineVertexData &out, bool selected )
405 {
406  if ( out.indexes.isEmpty() )
407  return;
408 
409  // material (only ambient color is used for the color)
410  QgsMaterialContext materialContext;
411  materialContext.setIsSelected( selected );
412  materialContext.setSelectionColor( context.map().selectionColor() );
413  Qt3DRender::QMaterial *mat = mSymbol->material()->toMaterial( QgsMaterialSettingsRenderingTechnique::Lines, materialContext );
414  if ( !mat )
415  {
416  QgsSimpleLineMaterialSettings defaultMaterial;
417  mat = defaultMaterial.toMaterial( QgsMaterialSettingsRenderingTechnique::Lines, materialContext );
418  }
419 
420  if ( QgsLineMaterial *lineMaterial = dynamic_cast< QgsLineMaterial * >( mat ) )
421  lineMaterial->setLineWidth( mSymbol->width() );
422 
423  Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
424 
425  // geometry renderer
426  Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer;
427  renderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::LineStripAdjacency );
428  renderer->setGeometry( out.createGeometry( entity ) );
429  renderer->setVertexCount( out.indexes.count() );
430  renderer->setPrimitiveRestartEnabled( true );
431  renderer->setRestartIndexValue( 0 );
432 
433  // make entity
434  entity->addComponent( renderer );
435  entity->addComponent( mat );
436  entity->setParent( parent );
437 }
438 
439 
440 // --------------
441 
442 
443 namespace Qgs3DSymbolImpl
444 {
445 
446  QgsFeature3DHandler *handlerForLine3DSymbol( QgsVectorLayer *layer, const QgsAbstract3DSymbol *symbol )
447  {
448  const QgsLine3DSymbol *lineSymbol = dynamic_cast< const QgsLine3DSymbol * >( symbol );
449  if ( !lineSymbol )
450  return nullptr;
451 
452  if ( lineSymbol->renderAsSimpleLines() )
453  return new QgsThickLine3DSymbolHandler( lineSymbol, layer->selectedFeatureIds() );
454  //return new QgsSimpleLine3DSymbolHandler( symbol, layer->selectedFeatureIds() );
455  else
456  return new QgsBufferedLine3DSymbolHandler( lineSymbol, layer->selectedFeatureIds() );
457  }
458 
459  Qt3DCore::QEntity *entityForLine3DSymbol( const Qgs3DMapSettings &map, QgsVectorLayer *layer, const QgsLine3DSymbol &symbol )
460  {
461  QgsFeature3DHandler *handler = handlerForLine3DSymbol( layer, &symbol );
462  Qt3DCore::QEntity *e = entityFromHandler( handler, map, layer );
463  delete handler;
464  return e;
465  }
466 }
467 
static void clampAltitudes(QgsLineString *lineString, Qgs3DTypes::AltitudeClamping altClamp, Qgs3DTypes::AltitudeBinding altBind, const QgsPoint &centroid, float height, const Qgs3DMapSettings &map)
Clamps altitude of vertices of a linestring according to the settings.
Definition: qgs3dutils.cpp:263
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.
QgsWkbTypes::Type wkbType() const SIP_HOLDGIL
Returns the WKB type of the geometry.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
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:124
JoinStyle
Join styles for buffers.
Definition: qgsgeometry.h:1149
@ JoinStyleRound
Use rounded joins.
Definition: qgsgeometry.h:1150
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:126
EndCapStyle
End cap styles for buffers.
Definition: qgsgeometry.h:1140
@ CapRound
Round cap.
Definition: qgsgeometry.h:1141
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:44
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:881
static Type flatType(Type type) SIP_HOLDGIL
Returns the flat type for a WKB type.
Definition: qgswkbtypes.h:702
@ 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:316
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