QGIS API Documentation 3.27.0-Master (9c08adf5ef)
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
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
50class 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
90bool 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
114void 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
152void 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
158void 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();
167
168 // segmentize curved geometries if necessary
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 mFeatureCount++;
215}
216
217void QgsPolygon3DSymbolHandler::finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context )
218{
219 // create entity for selected and not selected
220 makeEntity( parent, context, outNormal, false );
221 makeEntity( parent, context, outSelected, true );
222
223 mZMin = std::min( outNormal.tessellator->zMinimum(), outSelected.tessellator->zMinimum() );
224 mZMax = std::max( outNormal.tessellator->zMaximum(), outSelected.tessellator->zMaximum() );
225
226 // add entity for edges
227 if ( mSymbol->edgesEnabled() && !outEdges.indexes.isEmpty() )
228 {
229 QgsLineMaterial *mat = new QgsLineMaterial;
230 mat->setLineColor( mSymbol->edgeColor() );
231 mat->setLineWidth( mSymbol->edgeWidth() );
232
233 Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
234
235 // geometry renderer
236 Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer;
237 renderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::LineStripAdjacency );
238 renderer->setGeometry( outEdges.createGeometry( entity ) );
239 renderer->setVertexCount( outEdges.indexes.count() );
240 renderer->setPrimitiveRestartEnabled( true );
241 renderer->setRestartIndexValue( 0 );
242
243 // make entity
244 entity->addComponent( renderer );
245 entity->addComponent( mat );
246 entity->setParent( parent );
247 }
248}
249
250
251void QgsPolygon3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, PolygonData &out, bool selected )
252{
253 if ( out.tessellator->dataVerticesCount() == 0 )
254 return; // nothing to show - no need to create the entity
255
256 Qt3DRender::QMaterial *mat = material( mSymbol.get(), selected, context );
257
258 // extract vertex buffer data from tessellator
259 const QByteArray data( ( const char * )out.tessellator->data().constData(), out.tessellator->data().count() * sizeof( float ) );
260 const int nVerts = data.count() / out.tessellator->stride();
261
262 const QgsPhongTexturedMaterialSettings *texturedMaterialSettings = dynamic_cast< const QgsPhongTexturedMaterialSettings * >( mSymbol->material() );
263
264 QgsTessellatedPolygonGeometry *geometry = new QgsTessellatedPolygonGeometry( true, mSymbol->invertNormals(), mSymbol->addBackFaces(),
265 texturedMaterialSettings && texturedMaterialSettings->requiresTextureCoordinates() );
266 geometry->setData( data, nVerts, out.triangleIndexFids, out.triangleIndexStartingIndices );
267 if ( mSymbol->material()->dataDefinedProperties().hasActiveProperties() )
268 mSymbol->material()->applyDataDefinedToGeometry( geometry, nVerts, out.materialDataDefined );
269
270 Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer;
271 renderer->setGeometry( geometry );
272
273 // make entity
274 Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
275 entity->addComponent( renderer );
276 entity->addComponent( mat );
277 entity->setParent( parent );
278
279 if ( !selected )
280 entity->findChild<Qt3DRender::QGeometryRenderer *>()->setObjectName( QStringLiteral( "main" ) ); // temporary measure to distinguish between "selected" and "main"
281// cppcheck wrongly believes entity will leak
282// cppcheck-suppress memleak
283}
284
285
286static Qt3DRender::QCullFace::CullingMode _qt3DcullingMode( Qgs3DTypes::CullingMode mode )
287{
288 switch ( mode )
289 {
290 case Qgs3DTypes::NoCulling: return Qt3DRender::QCullFace::NoCulling;
291 case Qgs3DTypes::Front: return Qt3DRender::QCullFace::Front;
292 case Qgs3DTypes::Back: return Qt3DRender::QCullFace::Back;
293 case Qgs3DTypes::FrontAndBack: return Qt3DRender::QCullFace::FrontAndBack;
294 }
295 return Qt3DRender::QCullFace::NoCulling;
296}
297
298// front/back side culling
299static void applyCullingMode( Qgs3DTypes::CullingMode cullingMode, Qt3DRender::QMaterial *material )
300{
301 const auto techniques = material->effect()->techniques();
302 for ( auto tit = techniques.constBegin(); tit != techniques.constEnd(); ++tit )
303 {
304 auto renderPasses = ( *tit )->renderPasses();
305 for ( auto rpit = renderPasses.begin(); rpit != renderPasses.end(); ++rpit )
306 {
307 Qt3DRender::QCullFace *cullFace = new Qt3DRender::QCullFace;
308 cullFace->setMode( _qt3DcullingMode( cullingMode ) );
309 ( *rpit )->addRenderState( cullFace );
310 }
311 }
312}
313
314Qt3DRender::QMaterial *QgsPolygon3DSymbolHandler::material( const QgsPolygon3DSymbol *symbol, bool isSelected, const Qgs3DRenderContext &context ) const
315{
316 QgsMaterialContext materialContext;
317 materialContext.setIsSelected( isSelected );
318 materialContext.setSelectionColor( context.map().selectionColor() );
319
320 const bool dataDefined = mSymbol->material()->dataDefinedProperties().hasActiveProperties();
321 Qt3DRender::QMaterial *material = symbol->material()->toMaterial( dataDefined ?
323 materialContext );
324 applyCullingMode( symbol->cullingMode(), material );
325 return material;
326}
327
328
329// --------------
330
331
332namespace Qgs3DSymbolImpl
333{
334
335
336 QgsFeature3DHandler *handlerForPolygon3DSymbol( QgsVectorLayer *layer, const QgsAbstract3DSymbol *symbol )
337 {
338 const QgsPolygon3DSymbol *polygonSymbol = dynamic_cast< const QgsPolygon3DSymbol * >( symbol );
339 if ( !polygonSymbol )
340 return nullptr;
341
342 return new QgsPolygon3DSymbolHandler( polygonSymbol, layer->selectedFeatureIds() );
343 }
344
345 Qt3DCore::QEntity *entityForPolygon3DSymbol( const Qgs3DMapSettings &map, QgsVectorLayer *layer, const QgsPolygon3DSymbol &symbol )
346 {
347 QgsFeature3DHandler *handler = handlerForPolygon3DSymbol( layer, &symbol );
348 Qt3DCore::QEntity *e = entityFromHandler( handler, map, layer );
349 delete handler;
350 return e;
351 }
352
353}
354
CullingMode
Triangle culling mode.
Definition: qgs3dtypes.h:36
@ FrontAndBack
Will not render anything.
Definition: qgs3dtypes.h:40
@ NoCulling
Will render both front and back faces of triangles.
Definition: qgs3dtypes.h:37
@ Front
Will render only back faces of triangles.
Definition: qgs3dtypes.h:38
@ Back
Will render only front faces of triangles (recommended when input data are consistent)
Definition: qgs3dtypes.h:39
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
@ 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: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 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