QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
qgspolygon3dsymbol_p.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgspolygon3dsymbol_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 "qgspolygon3dsymbol_p.h"
17 
18 #include "qgspolygon3dsymbol.h"
20 #include "qgs3dmapsettings.h"
21 #include "qgs3dutils.h"
22 #include "qgstessellator.h"
24 
25 #include <Qt3DCore/QTransform>
26 #include <Qt3DRender/QMaterial>
27 #include <Qt3DExtras/QPhongMaterial>
28 
29 #include <Qt3DExtras/QDiffuseMapMaterial>
30 #include <Qt3DRender/QAbstractTextureImage>
31 #include <Qt3DRender/QTexture>
32 
33 #include <Qt3DRender/QEffect>
34 #include <Qt3DRender/QTechnique>
35 #include <Qt3DRender/QCullFace>
36 #include <Qt3DRender/QGeometryRenderer>
37 
38 #include "qgsvectorlayer.h"
39 #include "qgslinestring.h"
40 #include "qgsmultipolygon.h"
41 
42 #include "qgslinevertexdata_p.h"
43 #include "qgslinematerial_p.h"
44 
45 #include "qgsimagetexture.h"
46 
48 
49 
50 class QgsPolygon3DSymbolHandler : public QgsFeature3DHandler
51 {
52  public:
53  QgsPolygon3DSymbolHandler( const QgsPolygon3DSymbol *symbol, const QgsFeatureIds &selectedIds )
54  : mSymbol( static_cast< QgsPolygon3DSymbol *>( symbol->clone() ) )
55  , mSelectedIds( selectedIds ) {}
56 
57  bool prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames ) override;
58  void processFeature( const QgsFeature &f, const Qgs3DRenderContext &context ) override;
59  void finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context ) override;
60 
61  private:
62 
64  struct PolygonData
65  {
66  std::unique_ptr<QgsTessellator> tessellator;
67  QVector<QgsFeatureId> triangleIndexFids;
68  QVector<uint> triangleIndexStartingIndices;
69  QByteArray materialDataDefined;
70  };
71 
72  void processPolygon( QgsPolygon *polyClone, QgsFeatureId fid, float height, float extrusionHeight, const Qgs3DRenderContext &context, PolygonData &out );
73  void processMaterialDatadefined( uint verticesCount, const QgsExpressionContext &context, PolygonData &out );
74  void makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, PolygonData &out, bool selected );
75  Qt3DRender::QMaterial *material( const QgsPolygon3DSymbol *symbol, bool isSelected, const Qgs3DRenderContext &context ) const;
76 
77  // input specific for this class
78  std::unique_ptr< QgsPolygon3DSymbol > mSymbol;
79  // inputs - generic
80  QgsFeatureIds mSelectedIds;
81 
82  // outputs
83  PolygonData outNormal;
84  PolygonData outSelected;
85 
86  QgsLineVertexData outEdges;
87 };
88 
89 
90 bool QgsPolygon3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames )
91 {
92  outEdges.withAdjacency = true;
93  outEdges.init( mSymbol->altitudeClamping(), mSymbol->altitudeBinding(), 0, &context.map() );
94 
95  const QgsPhongTexturedMaterialSettings *texturedMaterialSettings = dynamic_cast< const QgsPhongTexturedMaterialSettings * >( mSymbol->material() );
96 
97  outNormal.tessellator.reset( new QgsTessellator( context.map().origin().x(), context.map().origin().y(), true, mSymbol->invertNormals(), mSymbol->addBackFaces(), false,
98  texturedMaterialSettings && texturedMaterialSettings->requiresTextureCoordinates(),
99  mSymbol->renderedFacade(),
100  texturedMaterialSettings ? texturedMaterialSettings->textureRotation() : 0 ) );
101  outSelected.tessellator.reset( new QgsTessellator( context.map().origin().x(), context.map().origin().y(), true, mSymbol->invertNormals(),
102  mSymbol->addBackFaces(), false,
103  texturedMaterialSettings && texturedMaterialSettings->requiresTextureCoordinates(),
104  mSymbol->renderedFacade(),
105  texturedMaterialSettings ? texturedMaterialSettings->textureRotation() : 0 ) );
106 
107  QSet<QString> attrs = mSymbol->dataDefinedProperties().referencedFields( context.expressionContext() );
108  attributeNames.unite( attrs );
109  attrs = mSymbol->material()->dataDefinedProperties().referencedFields( context.expressionContext() );
110  attributeNames.unite( attrs );
111  return true;
112 }
113 
114 void QgsPolygon3DSymbolHandler::processPolygon( QgsPolygon *polyClone, QgsFeatureId fid, float height, float extrusionHeight, const Qgs3DRenderContext &context, PolygonData &out )
115 {
116  const uint oldVerticesCount = out.tessellator->dataVerticesCount();
117  if ( mSymbol->edgesEnabled() )
118  {
119  // add edges before the polygon gets the Z values modified because addLineString() does its own altitude handling
120  outEdges.addLineString( *static_cast<const QgsLineString *>( polyClone->exteriorRing() ), height );
121  for ( int i = 0; i < polyClone->numInteriorRings(); ++i )
122  outEdges.addLineString( *static_cast<const QgsLineString *>( polyClone->interiorRing( i ) ), height );
123 
124  if ( extrusionHeight )
125  {
126  // add roof and wall edges
127  const QgsLineString *exterior = static_cast<const QgsLineString *>( polyClone->exteriorRing() );
128  outEdges.addLineString( *exterior, extrusionHeight + height );
129  outEdges.addVerticalLines( *exterior, extrusionHeight, height );
130  for ( int i = 0; i < polyClone->numInteriorRings(); ++i )
131  {
132  const QgsLineString *interior = static_cast<const QgsLineString *>( polyClone->interiorRing( i ) );
133  outEdges.addLineString( *interior, extrusionHeight + height );
134  outEdges.addVerticalLines( *interior, extrusionHeight, height );
135  }
136  }
137  }
138 
139  Qgs3DUtils::clampAltitudes( polyClone, mSymbol->altitudeClamping(), mSymbol->altitudeBinding(), height, context.map() );
140 
141  Q_ASSERT( out.tessellator->dataVerticesCount() % 3 == 0 );
142  const uint startingTriangleIndex = static_cast<uint>( out.tessellator->dataVerticesCount() / 3 );
143  out.triangleIndexStartingIndices.append( startingTriangleIndex );
144  out.triangleIndexFids.append( fid );
145  out.tessellator->addPolygon( *polyClone, extrusionHeight );
146  delete polyClone;
147 
148  if ( mSymbol->material()->dataDefinedProperties().hasActiveProperties() )
149  processMaterialDatadefined( out.tessellator->dataVerticesCount() - oldVerticesCount, context.expressionContext(), out );
150 }
151 
152 void QgsPolygon3DSymbolHandler::processMaterialDatadefined( uint verticesCount, const QgsExpressionContext &context, QgsPolygon3DSymbolHandler::PolygonData &out )
153 {
154  const QByteArray bytes = mSymbol->material()->dataDefinedVertexColorsAsByte( context );
155  out.materialDataDefined.append( bytes.repeated( verticesCount ) );
156 }
157 
158 void QgsPolygon3DSymbolHandler::processFeature( const QgsFeature &f, const Qgs3DRenderContext &context )
159 {
160  if ( f.geometry().isNull() )
161  return;
162 
163  PolygonData &out = mSelectedIds.contains( f.id() ) ? outSelected : outNormal;
164 
165  QgsGeometry geom = f.geometry();
166  const QgsAbstractGeometry *g = geom.constGet()->simplifiedTypeRef();
167 
168  // segmentize curved geometries if necessary
169  if ( QgsWkbTypes::isCurvedType( g->wkbType() ) )
170  {
171  geom = QgsGeometry( g->segmentize() );
172  g = geom.constGet()->simplifiedTypeRef();
173  }
174 
175  const QgsPropertyCollection &ddp = mSymbol->dataDefinedProperties();
176  const bool hasDDHeight = ddp.isActive( QgsAbstract3DSymbol::PropertyHeight );
177  const bool hasDDExtrusion = ddp.isActive( QgsAbstract3DSymbol::PropertyExtrusionHeight );
178 
179  float height = mSymbol->height();
180  float extrusionHeight = mSymbol->extrusionHeight();
181  if ( hasDDHeight )
182  height = ddp.valueAsDouble( QgsAbstract3DSymbol::PropertyHeight, context.expressionContext(), height );
183  if ( hasDDExtrusion )
184  extrusionHeight = ddp.valueAsDouble( QgsAbstract3DSymbol::PropertyExtrusionHeight, context.expressionContext(), extrusionHeight );
185 
186  if ( const QgsPolygon *poly = qgsgeometry_cast< const QgsPolygon *>( g ) )
187  {
188  QgsPolygon *polyClone = poly->clone();
189  processPolygon( polyClone, f.id(), height, extrusionHeight, context, out );
190  }
191  else if ( const QgsMultiPolygon *mpoly = qgsgeometry_cast< const QgsMultiPolygon *>( g ) )
192  {
193  for ( int i = 0; i < mpoly->numGeometries(); ++i )
194  {
195  QgsPolygon *polyClone = static_cast< const QgsPolygon *>( mpoly->polygonN( i ) )->clone();
196  processPolygon( polyClone, f.id(), height, extrusionHeight, context, out );
197  }
198  }
199  else if ( const QgsGeometryCollection *gc = qgsgeometry_cast< const QgsGeometryCollection *>( g ) )
200  {
201  for ( int i = 0; i < gc->numGeometries(); ++i )
202  {
203  const QgsAbstractGeometry *g2 = gc->geometryN( i );
205  {
206  QgsPolygon *polyClone = static_cast< const QgsPolygon *>( g2 )->clone();
207  processPolygon( polyClone, f.id(), height, extrusionHeight, context, out );
208  }
209  }
210  }
211  else
212  qWarning() << "not a polygon";
213 }
214 
215 
216 void QgsPolygon3DSymbolHandler::finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context )
217 {
218  // create entity for selected and not selected
219  makeEntity( parent, context, outNormal, false );
220  makeEntity( parent, context, outSelected, true );
221 
222  mZMin = std::min( outNormal.tessellator->zMinimum(), outSelected.tessellator->zMinimum() );
223  mZMax = std::max( outNormal.tessellator->zMaximum(), outSelected.tessellator->zMaximum() );
224 
225  // add entity for edges
226  if ( mSymbol->edgesEnabled() && !outEdges.indexes.isEmpty() )
227  {
228  QgsLineMaterial *mat = new QgsLineMaterial;
229  mat->setLineColor( mSymbol->edgeColor() );
230  mat->setLineWidth( mSymbol->edgeWidth() );
231 
232  Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
233 
234  // geometry renderer
235  Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer;
236  renderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::LineStripAdjacency );
237  renderer->setGeometry( outEdges.createGeometry( entity ) );
238  renderer->setVertexCount( outEdges.indexes.count() );
239  renderer->setPrimitiveRestartEnabled( true );
240  renderer->setRestartIndexValue( 0 );
241 
242  // make entity
243  entity->addComponent( renderer );
244  entity->addComponent( mat );
245  entity->setParent( parent );
246  }
247 }
248 
249 
250 void QgsPolygon3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, PolygonData &out, bool selected )
251 {
252  if ( out.tessellator->dataVerticesCount() == 0 )
253  return; // nothing to show - no need to create the entity
254 
255  Qt3DRender::QMaterial *mat = material( mSymbol.get(), selected, context );
256 
257  // extract vertex buffer data from tessellator
258  const QByteArray data( ( const char * )out.tessellator->data().constData(), out.tessellator->data().count() * sizeof( float ) );
259  const int nVerts = data.count() / out.tessellator->stride();
260 
261  const QgsPhongTexturedMaterialSettings *texturedMaterialSettings = dynamic_cast< const QgsPhongTexturedMaterialSettings * >( mSymbol->material() );
262 
263  QgsTessellatedPolygonGeometry *geometry = new QgsTessellatedPolygonGeometry( true, mSymbol->invertNormals(), mSymbol->addBackFaces(),
264  texturedMaterialSettings && texturedMaterialSettings->requiresTextureCoordinates() );
265  geometry->setData( data, nVerts, out.triangleIndexFids, out.triangleIndexStartingIndices );
266  if ( mSymbol->material()->dataDefinedProperties().hasActiveProperties() )
267  mSymbol->material()->applyDataDefinedToGeometry( geometry, nVerts, out.materialDataDefined );
268 
269  Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer;
270  renderer->setGeometry( geometry );
271 
272  // make entity
273  Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
274  entity->addComponent( renderer );
275  entity->addComponent( mat );
276  entity->setParent( parent );
277 
278  if ( !selected )
279  entity->findChild<Qt3DRender::QGeometryRenderer *>()->setObjectName( QStringLiteral( "main" ) ); // temporary measure to distinguish between "selected" and "main"
280 // cppcheck wrongly believes entity will leak
281 // cppcheck-suppress memleak
282 }
283 
284 
285 static Qt3DRender::QCullFace::CullingMode _qt3DcullingMode( Qgs3DTypes::CullingMode mode )
286 {
287  switch ( mode )
288  {
289  case Qgs3DTypes::NoCulling: return Qt3DRender::QCullFace::NoCulling;
290  case Qgs3DTypes::Front: return Qt3DRender::QCullFace::Front;
291  case Qgs3DTypes::Back: return Qt3DRender::QCullFace::Back;
292  case Qgs3DTypes::FrontAndBack: return Qt3DRender::QCullFace::FrontAndBack;
293  }
294  return Qt3DRender::QCullFace::NoCulling;
295 }
296 
297 // front/back side culling
298 static void applyCullingMode( Qgs3DTypes::CullingMode cullingMode, Qt3DRender::QMaterial *material )
299 {
300  const auto techniques = material->effect()->techniques();
301  for ( auto tit = techniques.constBegin(); tit != techniques.constEnd(); ++tit )
302  {
303  auto renderPasses = ( *tit )->renderPasses();
304  for ( auto rpit = renderPasses.begin(); rpit != renderPasses.end(); ++rpit )
305  {
306  Qt3DRender::QCullFace *cullFace = new Qt3DRender::QCullFace;
307  cullFace->setMode( _qt3DcullingMode( cullingMode ) );
308  ( *rpit )->addRenderState( cullFace );
309  }
310  }
311 }
312 
313 Qt3DRender::QMaterial *QgsPolygon3DSymbolHandler::material( const QgsPolygon3DSymbol *symbol, bool isSelected, const Qgs3DRenderContext &context ) const
314 {
315  QgsMaterialContext materialContext;
316  materialContext.setIsSelected( isSelected );
317  materialContext.setSelectionColor( context.map().selectionColor() );
318 
319  const bool dataDefined = mSymbol->material()->dataDefinedProperties().hasActiveProperties();
320  Qt3DRender::QMaterial *material = symbol->material()->toMaterial( dataDefined ?
322  materialContext );
323  applyCullingMode( symbol->cullingMode(), material );
324  return material;
325 }
326 
327 
328 // --------------
329 
330 
331 namespace Qgs3DSymbolImpl
332 {
333 
334 
335  QgsFeature3DHandler *handlerForPolygon3DSymbol( QgsVectorLayer *layer, const QgsAbstract3DSymbol *symbol )
336  {
337  const QgsPolygon3DSymbol *polygonSymbol = dynamic_cast< const QgsPolygon3DSymbol * >( symbol );
338  if ( !polygonSymbol )
339  return nullptr;
340 
341  return new QgsPolygon3DSymbolHandler( polygonSymbol, layer->selectedFeatureIds() );
342  }
343 
344  Qt3DCore::QEntity *entityForPolygon3DSymbol( const Qgs3DMapSettings &map, QgsVectorLayer *layer, const QgsPolygon3DSymbol &symbol )
345  {
346  QgsFeature3DHandler *handler = handlerForPolygon3DSymbol( layer, &symbol );
347  Qt3DCore::QEntity *e = entityFromHandler( handler, map, layer );
348  delete handler;
349  return e;
350  }
351 
352 }
353 
CullingMode
Triangle culling mode.
Definition: qgs3dtypes.h:50
@ FrontAndBack
Will not render anything.
Definition: qgs3dtypes.h:54
@ NoCulling
Will render both front and back faces of triangles.
Definition: qgs3dtypes.h:51
@ Front
Will render only back faces of triangles.
Definition: qgs3dtypes.h:52
@ Back
Will render only front faces of triangles (recommended when input data are consistent)
Definition: qgs3dtypes.h:53
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:270
@ PropertyHeight
Height (altitude)
@ PropertyExtrusionHeight
Extrusion height (zero means no extrusion)
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.
virtual Qt3DRender::QMaterial * toMaterial(QgsMaterialSettingsRenderingTechnique technique, const QgsMaterialContext &context) const =0
Creates a new QMaterial object representing the material settings.
QgsPropertyCollection dataDefinedProperties() const
Returns the symbol material property collection, used for data defined overrides.
double valueAsDouble(int key, const QgsExpressionContext &context, double defaultValue=0.0, bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a double.
const QgsCurve * interiorRing(int i) const SIP_HOLDGIL
Retrieves an interior ring from the curve polygon.
const QgsCurve * exteriorRing() const SIP_HOLDGIL
Returns the curve polygon's exterior ring.
int numInteriorRings() const SIP_HOLDGIL
Returns the number of interior rings contained with the curve polygon.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
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
Geometry 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
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 polygon geometry collection.
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.
Qgs3DTypes::CullingMode cullingMode() const
Returns front/back culling mode.
QgsAbstractMaterialSettings * material() const
Returns material used for shading of the symbol.
Polygon geometry type.
Definition: qgspolygon.h:34
QgsPolygon * clone() const override
Clones the geometry by performing a deep copy.
Definition: qgspolygon.cpp:54
A grouped map of multiple QgsProperty objects, each referenced by a integer key value.
QSet< QString > referencedFields(const QgsExpressionContext &context=QgsExpressionContext(), bool ignoreContext=false) const override
Returns the set of any fields referenced by the active properties from the collection.
bool isActive(int key) const override
Returns true if the collection contains an active property with the specified key.
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)
@ TrianglesDataDefined
Triangle based rendering with possibility of datadefined color.
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