QGIS API Documentation  3.8.0-Zanzibar (11aff65)
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 
23 #include <Qt3DCore/QTransform>
24 #include <Qt3DExtras/QPhongMaterial>
25 #include <Qt3DRender/QEffect>
26 #include <Qt3DRender/QTechnique>
27 #include <Qt3DRender/QCullFace>
28 #include <Qt3DRender/QGeometryRenderer>
29 
30 #include "qgsvectorlayer.h"
31 #include "qgslinestring.h"
32 #include "qgsmultipolygon.h"
33 
34 #include "qgslinevertexdata_p.h"
35 #include "qgslinematerial_p.h"
36 
38 
39 
40 class QgsPolygon3DSymbolHandler : public QgsFeature3DHandler
41 {
42  public:
43  QgsPolygon3DSymbolHandler( const QgsPolygon3DSymbol &symbol, const QgsFeatureIds &selectedIds )
44  : mSymbol( symbol ), mSelectedIds( selectedIds ) {}
45 
46  bool prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames ) override;
47  void processFeature( QgsFeature &feature, const Qgs3DRenderContext &context ) override;
48  void finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context ) override;
49 
50  private:
51 
53  struct PolygonData
54  {
55  QList<QgsPolygon *> polygons;
56  QList<QgsFeatureId> fids;
57  QList<float> extrusionHeightPerPolygon; // will stay empty if not needed per polygon
58  };
59 
60  void processPolygon( QgsPolygon *polyClone, QgsFeatureId fid, float height, bool hasDDExtrusion, float extrusionHeight, const Qgs3DRenderContext &context, PolygonData &out );
61  void makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, PolygonData &out, bool selected );
62  Qt3DExtras::QPhongMaterial *material( const QgsPolygon3DSymbol &symbol ) const;
63 
64  // input specific for this class
65  const QgsPolygon3DSymbol &mSymbol;
66  // inputs - generic
67  QgsFeatureIds mSelectedIds;
68 
69  // outputs
70  PolygonData outNormal;
71  PolygonData outSelected;
72 
73  QgsLineVertexData outEdges;
74 };
75 
76 
77 bool QgsPolygon3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames )
78 {
79  outEdges.withAdjacency = true;
80  outEdges.init( mSymbol.altitudeClamping(), mSymbol.altitudeBinding(), mSymbol.height(), &context.map() );
81 
82  QSet<QString> attrs = mSymbol.dataDefinedProperties().referencedFields( context.expressionContext() );
83  attributeNames.unite( attrs );
84  return true;
85 }
86 
87 void QgsPolygon3DSymbolHandler::processPolygon( QgsPolygon *polyClone, QgsFeatureId fid, float height, bool hasDDExtrusion, float extrusionHeight, const Qgs3DRenderContext &context, PolygonData &out )
88 {
89  if ( mSymbol.edgesEnabled() )
90  {
91  // add edges before the polygon gets the Z values modified because addLineString() does its own altitude handling
92  outEdges.addLineString( *static_cast<const QgsLineString *>( polyClone->exteriorRing() ) );
93  for ( int i = 0; i < polyClone->numInteriorRings(); ++i )
94  outEdges.addLineString( *static_cast<const QgsLineString *>( polyClone->interiorRing( i ) ) );
95 
96  if ( extrusionHeight )
97  {
98  // add roof and wall edges
99  const QgsLineString *exterior = static_cast<const QgsLineString *>( polyClone->exteriorRing() );
100  outEdges.addLineString( *exterior, extrusionHeight );
101  outEdges.addVerticalLines( *exterior, extrusionHeight );
102  for ( int i = 0; i < polyClone->numInteriorRings(); ++i )
103  {
104  const QgsLineString *interior = static_cast<const QgsLineString *>( polyClone->interiorRing( i ) );
105  outEdges.addLineString( *interior, extrusionHeight );
106  outEdges.addVerticalLines( *interior, extrusionHeight );
107  }
108  }
109  }
110 
111  Qgs3DUtils::clampAltitudes( polyClone, mSymbol.altitudeClamping(), mSymbol.altitudeBinding(), height, context.map() );
112  out.polygons.append( polyClone );
113  out.fids.append( fid );
114  if ( hasDDExtrusion )
115  out.extrusionHeightPerPolygon.append( extrusionHeight );
116 }
117 
118 void QgsPolygon3DSymbolHandler::processFeature( QgsFeature &f, const Qgs3DRenderContext &context )
119 {
120  if ( f.geometry().isNull() )
121  return;
122 
123  PolygonData &out = mSelectedIds.contains( f.id() ) ? outSelected : outNormal;
124 
125  QgsGeometry geom = f.geometry();
126 
127  // segmentize curved geometries if necessary
128  if ( QgsWkbTypes::isCurvedType( geom.constGet()->wkbType() ) )
129  geom = QgsGeometry( geom.constGet()->segmentize() );
130 
131  const QgsAbstractGeometry *g = geom.constGet();
132 
133  const QgsPropertyCollection &ddp = mSymbol.dataDefinedProperties();
134  bool hasDDHeight = ddp.isActive( QgsAbstract3DSymbol::PropertyHeight );
135  bool hasDDExtrusion = ddp.isActive( QgsAbstract3DSymbol::PropertyExtrusionHeight );
136 
137  float height = mSymbol.height();
138  float extrusionHeight = mSymbol.extrusionHeight();
139  if ( hasDDHeight )
140  height = ddp.valueAsDouble( QgsAbstract3DSymbol::PropertyHeight, context.expressionContext(), height );
141  if ( hasDDExtrusion )
142  extrusionHeight = ddp.valueAsDouble( QgsAbstract3DSymbol::PropertyExtrusionHeight, context.expressionContext(), extrusionHeight );
143 
144  if ( const QgsPolygon *poly = qgsgeometry_cast< const QgsPolygon *>( g ) )
145  {
146  QgsPolygon *polyClone = poly->clone();
147  processPolygon( polyClone, f.id(), height, hasDDExtrusion, extrusionHeight, context, out );
148  }
149  else if ( const QgsMultiPolygon *mpoly = qgsgeometry_cast< const QgsMultiPolygon *>( g ) )
150  {
151  for ( int i = 0; i < mpoly->numGeometries(); ++i )
152  {
153  const QgsAbstractGeometry *g2 = mpoly->geometryN( i );
154  Q_ASSERT( QgsWkbTypes::flatType( g2->wkbType() ) == QgsWkbTypes::Polygon );
155  QgsPolygon *polyClone = static_cast< const QgsPolygon *>( g2 )->clone();
156  processPolygon( polyClone, f.id(), height, hasDDExtrusion, extrusionHeight, context, out );
157  }
158  }
159  else if ( const QgsGeometryCollection *gc = qgsgeometry_cast< const QgsGeometryCollection *>( g ) )
160  {
161  for ( int i = 0; i < gc->numGeometries(); ++i )
162  {
163  const QgsAbstractGeometry *g2 = gc->geometryN( i );
165  {
166  QgsPolygon *polyClone = static_cast< const QgsPolygon *>( g2 )->clone();
167  processPolygon( polyClone, f.id(), height, hasDDExtrusion, extrusionHeight, context, out );
168  }
169  }
170  }
171  else
172  qDebug() << "not a polygon";
173 }
174 
175 
176 void QgsPolygon3DSymbolHandler::finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context )
177 {
178  // create entity for selected and not selected
179  makeEntity( parent, context, outNormal, false );
180  makeEntity( parent, context, outSelected, true );
181 
182  // add entity for edges
183  if ( mSymbol.edgesEnabled() && !outEdges.indexes.isEmpty() )
184  {
185  QgsLineMaterial *mat = new QgsLineMaterial;
186  mat->setLineColor( mSymbol.edgeColor() );
187  mat->setLineWidth( mSymbol.edgeWidth() );
188 
189  Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
190 
191  // geometry renderer
192  Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer;
193  renderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::LineStripAdjacency );
194  renderer->setGeometry( outEdges.createGeometry( entity ) );
195  renderer->setVertexCount( outEdges.indexes.count() );
196  renderer->setPrimitiveRestartEnabled( true );
197  renderer->setRestartIndexValue( 0 );
198 
199  // make entity
200  entity->addComponent( renderer );
201  entity->addComponent( mat );
202  entity->setParent( parent );
203  }
204 }
205 
206 
207 void QgsPolygon3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, PolygonData &out, bool selected )
208 {
209  if ( out.polygons.isEmpty() )
210  return; // nothing to show - no need to create the entity
211 
212  Qt3DExtras::QPhongMaterial *mat = material( mSymbol );
213  if ( selected )
214  {
215  // update the material with selection colors
216  mat->setDiffuse( context.map().selectionColor() );
217  mat->setAmbient( context.map().selectionColor().darker() );
218  }
219 
220  QgsPointXY origin( context.map().origin().x(), context.map().origin().y() );
222  geometry->setInvertNormals( mSymbol.invertNormals() );
223  geometry->setAddBackFaces( mSymbol.addBackFaces() );
224  geometry->setPolygons( out.polygons, out.fids, origin, mSymbol.extrusionHeight(), out.extrusionHeightPerPolygon );
225 
226  Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer;
227  renderer->setGeometry( geometry );
228 
229  // make entity
230  Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
231  entity->addComponent( renderer );
232  entity->addComponent( mat );
233  entity->setParent( parent );
234 
235  if ( !selected )
236  entity->findChild<Qt3DRender::QGeometryRenderer *>()->setObjectName( QStringLiteral( "main" ) ); // temporary measure to distinguish between "selected" and "main"
237 }
238 
239 
240 static Qt3DRender::QCullFace::CullingMode _qt3DcullingMode( Qgs3DTypes::CullingMode mode )
241 {
242  switch ( mode )
243  {
244  case Qgs3DTypes::NoCulling: return Qt3DRender::QCullFace::NoCulling;
245  case Qgs3DTypes::Front: return Qt3DRender::QCullFace::Front;
246  case Qgs3DTypes::Back: return Qt3DRender::QCullFace::Back;
247  case Qgs3DTypes::FrontAndBack: return Qt3DRender::QCullFace::FrontAndBack;
248  }
249  return Qt3DRender::QCullFace::NoCulling;
250 }
251 
252 Qt3DExtras::QPhongMaterial *QgsPolygon3DSymbolHandler::material( const QgsPolygon3DSymbol &symbol ) const
253 {
254  Qt3DExtras::QPhongMaterial *material = new Qt3DExtras::QPhongMaterial;
255 
256  // front/back side culling
257  auto techniques = material->effect()->techniques();
258  for ( auto tit = techniques.constBegin(); tit != techniques.constEnd(); ++tit )
259  {
260  auto renderPasses = ( *tit )->renderPasses();
261  for ( auto rpit = renderPasses.begin(); rpit != renderPasses.end(); ++rpit )
262  {
263  Qt3DRender::QCullFace *cullFace = new Qt3DRender::QCullFace;
264  cullFace->setMode( _qt3DcullingMode( symbol.cullingMode() ) );
265  ( *rpit )->addRenderState( cullFace );
266  }
267  }
268 
269  material->setAmbient( symbol.material().ambient() );
270  material->setDiffuse( symbol.material().diffuse() );
271  material->setSpecular( symbol.material().specular() );
272  material->setShininess( symbol.material().shininess() );
273  return material;
274 }
275 
276 
277 // --------------
278 
279 
280 namespace Qgs3DSymbolImpl
281 {
282 
283 
284  QgsFeature3DHandler *handlerForPolygon3DSymbol( QgsVectorLayer *layer, const QgsPolygon3DSymbol &symbol )
285  {
286  return new QgsPolygon3DSymbolHandler( symbol, layer->selectedFeatureIds() );
287  }
288 
289  Qt3DCore::QEntity *entityForPolygon3DSymbol( const Qgs3DMapSettings &map, QgsVectorLayer *layer, const QgsPolygon3DSymbol &symbol )
290  {
291  QgsFeature3DHandler *handler = handlerForPolygon3DSymbol( layer, symbol );
292  Qt3DCore::QEntity *e = entityFromHandler( handler, map, layer );
293  delete handler;
294  return e;
295  }
296 
297 }
298 
QgsFeatureId id
Definition: qgsfeature.h:64
float shininess() const
Returns shininess of the surface.
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:34
void setInvertNormals(bool invert)
Sets whether the normals of triangles will be inverted (useful for fixing clockwise / counter-clockwi...
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...
QColor specular() const
Returns specular color component.
A class to represent a 2D point.
Definition: qgspointxy.h:43
const QgsCurve * interiorRing(int i) const
Retrieves an interior ring from the curve polygon.
qint64 QgsFeatureId
Definition: qgsfeatureid.h:25
3 3D symbol that draws polygon geometries as planar polygons, optionally extruded (with added walls)...
CullingMode
Triangle culling mode.
Definition: qgs3dtypes.h:49
QgsPropertyCollection & dataDefinedProperties()
Returns a reference to the symbol layer&#39;s property collection, used for data defined overrides...
Extrusion height (zero means no extrusion)
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:111
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:55
Will not render anything.
Definition: qgs3dtypes.h:54
QgsPhongMaterialSettings material() const
Returns material used for shading of the symbol.
3 Definition of the world
bool isActive(int key) const override
Returns true if the collection contains an active property with the specified key.
int numInteriorRings() const
Returns the number of interior rings contained with the curve polygon.
Will render only front faces of triangles (recommended when input data are consistent) ...
Definition: qgs3dtypes.h:53
const QgsFeatureIds & selectedFeatureIds() const
Returns a list of the selected features IDs in this layer.
Will render only back faces of triangles.
Definition: qgs3dtypes.h:52
Will render both front and back faces of triangles.
Definition: qgs3dtypes.h:51
Geometry collection.
3 Class derived from Qt3DRender::QGeometry that represents polygons tessellated into 3D geometry...
Abstract base class for all geometries.
QgsWkbTypes::Type wkbType() const
Returns the WKB type of the geometry.
Qgs3DTypes::CullingMode cullingMode() const
Returns front/back culling mode.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
QSet< QString > referencedFields(const QgsExpressionContext &context=QgsExpressionContext()) const override
Returns the set of any fields referenced by the active properties from the collection.
Multi polygon geometry collection.
static bool isCurvedType(Type type)
Returns true if the WKB type is a curved type or can contain curved geometries.
Definition: qgswkbtypes.h:609
Line string geometry type, with support for z-dimension and m-values.
Definition: qgslinestring.h:43
QgsPolygon * clone() const override
Clones the geometry by performing a deep copy.
Definition: qgspolygon.cpp:42
QColor ambient() const
Returns ambient color component.
A grouped map of multiple QgsProperty objects, each referenced by a integer key value.
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:267
QColor diffuse() const
Returns diffuse color component.
QgsGeometry geometry
Definition: qgsfeature.h:67
Polygon geometry type.
Definition: qgspolygon.h:31
const QgsCurve * exteriorRing() const
Returns the curve polygon&#39;s exterior ring.
Represents a vector layer which manages a vector based data sets.
static Type flatType(Type type)
Returns the flat type for a WKB type.
Definition: qgswkbtypes.h:430
virtual QgsAbstractGeometry * segmentize(double tolerance=M_PI/180., SegmentationToleranceType toleranceType=MaximumAngle) const
Returns a version of the geometry without curves.