QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
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#include "qgspolygon.h"
42
43#include "qgslinevertexdata_p.h"
44#include "qgslinematerial_p.h"
45
47
48
49class QgsPolygon3DSymbolHandler : public QgsFeature3DHandler
50{
51 public:
52 QgsPolygon3DSymbolHandler( const QgsPolygon3DSymbol *symbol, const QgsFeatureIds &selectedIds )
53 : mSymbol( static_cast< QgsPolygon3DSymbol *>( symbol->clone() ) )
54 , mSelectedIds( selectedIds ) {}
55
56 bool prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames ) override;
57 void processFeature( const QgsFeature &f, const Qgs3DRenderContext &context ) override;
58 void finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context ) override;
59
60 private:
61
63 struct PolygonData
64 {
65 std::unique_ptr<QgsTessellator> tessellator;
66 QVector<QgsFeatureId> triangleIndexFids;
67 QVector<uint> triangleIndexStartingIndices;
68 QByteArray materialDataDefined;
69 };
70
71 void processPolygon( QgsPolygon *polyClone, QgsFeatureId fid, float height, float extrusionHeight, const Qgs3DRenderContext &context, PolygonData &out );
72 void processMaterialDatadefined( uint verticesCount, const QgsExpressionContext &context, PolygonData &out );
73 void makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, PolygonData &out, bool selected );
74 Qt3DRender::QMaterial *material( const QgsPolygon3DSymbol *symbol, bool isSelected, const Qgs3DRenderContext &context ) const;
75
76 // input specific for this class
77 std::unique_ptr< QgsPolygon3DSymbol > mSymbol;
78 // inputs - generic
79 QgsFeatureIds mSelectedIds;
80
81 // outputs
82 PolygonData outNormal;
83 PolygonData outSelected;
84
85 QgsLineVertexData outEdges;
86};
87
88
89bool QgsPolygon3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames )
90{
91 outEdges.withAdjacency = true;
92 outEdges.init( mSymbol->altitudeClamping(), mSymbol->altitudeBinding(), 0, &context.map() );
93
94 const QgsPhongTexturedMaterialSettings *texturedMaterialSettings = dynamic_cast< const QgsPhongTexturedMaterialSettings * >( mSymbol->material() );
95
96 outNormal.tessellator.reset( new QgsTessellator( context.map().origin().x(), context.map().origin().y(), true, mSymbol->invertNormals(), mSymbol->addBackFaces(), false,
97 texturedMaterialSettings && texturedMaterialSettings->requiresTextureCoordinates(),
98 mSymbol->renderedFacade(),
99 texturedMaterialSettings ? texturedMaterialSettings->textureRotation() : 0 ) );
100 outSelected.tessellator.reset( new QgsTessellator( context.map().origin().x(), context.map().origin().y(), true, mSymbol->invertNormals(),
101 mSymbol->addBackFaces(), false,
102 texturedMaterialSettings && texturedMaterialSettings->requiresTextureCoordinates(),
103 mSymbol->renderedFacade(),
104 texturedMaterialSettings ? texturedMaterialSettings->textureRotation() : 0 ) );
105
106 QSet<QString> attrs = mSymbol->dataDefinedProperties().referencedFields( context.expressionContext() );
107 attributeNames.unite( attrs );
108 attrs = mSymbol->material()->dataDefinedProperties().referencedFields( context.expressionContext() );
109 attributeNames.unite( attrs );
110 return true;
111}
112
113void QgsPolygon3DSymbolHandler::processPolygon( QgsPolygon *polyClone, QgsFeatureId fid, float height, float extrusionHeight, const Qgs3DRenderContext &context, PolygonData &out )
114{
115 const uint oldVerticesCount = out.tessellator->dataVerticesCount();
116 if ( mSymbol->edgesEnabled() )
117 {
118 // add edges before the polygon gets the Z values modified because addLineString() does its own altitude handling
119 outEdges.addLineString( *static_cast<const QgsLineString *>( polyClone->exteriorRing() ), height );
120 for ( int i = 0; i < polyClone->numInteriorRings(); ++i )
121 outEdges.addLineString( *static_cast<const QgsLineString *>( polyClone->interiorRing( i ) ), height );
122
123 if ( extrusionHeight )
124 {
125 // add roof and wall edges
126 const QgsLineString *exterior = static_cast<const QgsLineString *>( polyClone->exteriorRing() );
127 outEdges.addLineString( *exterior, extrusionHeight + height );
128 outEdges.addVerticalLines( *exterior, extrusionHeight, height );
129 for ( int i = 0; i < polyClone->numInteriorRings(); ++i )
130 {
131 const QgsLineString *interior = static_cast<const QgsLineString *>( polyClone->interiorRing( i ) );
132 outEdges.addLineString( *interior, extrusionHeight + height );
133 outEdges.addVerticalLines( *interior, extrusionHeight, height );
134 }
135 }
136 }
137
138 Qgs3DUtils::clampAltitudes( polyClone, mSymbol->altitudeClamping(), mSymbol->altitudeBinding(), height, context.map() );
139
140 Q_ASSERT( out.tessellator->dataVerticesCount() % 3 == 0 );
141 const uint startingTriangleIndex = static_cast<uint>( out.tessellator->dataVerticesCount() / 3 );
142 out.triangleIndexStartingIndices.append( startingTriangleIndex );
143 out.triangleIndexFids.append( fid );
144 out.tessellator->addPolygon( *polyClone, extrusionHeight );
145 delete polyClone;
146
147 if ( mSymbol->material()->dataDefinedProperties().hasActiveProperties() )
148 processMaterialDatadefined( out.tessellator->dataVerticesCount() - oldVerticesCount, context.expressionContext(), out );
149}
150
151void QgsPolygon3DSymbolHandler::processMaterialDatadefined( uint verticesCount, const QgsExpressionContext &context, QgsPolygon3DSymbolHandler::PolygonData &out )
152{
153 const QByteArray bytes = mSymbol->material()->dataDefinedVertexColorsAsByte( context );
154 out.materialDataDefined.append( bytes.repeated( verticesCount ) );
155}
156
157void QgsPolygon3DSymbolHandler::processFeature( const QgsFeature &f, const Qgs3DRenderContext &context )
158{
159 if ( f.geometry().isNull() )
160 return;
161
162 PolygonData &out = mSelectedIds.contains( f.id() ) ? outSelected : outNormal;
163
164 QgsGeometry geom = f.geometry();
166
167 // segmentize curved geometries if necessary
169 {
170 geom = QgsGeometry( g->segmentize() );
171 g = geom.constGet()->simplifiedTypeRef();
172 }
173
174 const QgsPropertyCollection &ddp = mSymbol->dataDefinedProperties();
175 const bool hasDDHeight = ddp.isActive( QgsAbstract3DSymbol::PropertyHeight );
176 const bool hasDDExtrusion = ddp.isActive( QgsAbstract3DSymbol::PropertyExtrusionHeight );
177
178 float height = mSymbol->height();
179 float extrusionHeight = mSymbol->extrusionHeight();
180 if ( hasDDHeight )
181 height = ddp.valueAsDouble( QgsAbstract3DSymbol::PropertyHeight, context.expressionContext(), height );
182 if ( hasDDExtrusion )
183 extrusionHeight = ddp.valueAsDouble( QgsAbstract3DSymbol::PropertyExtrusionHeight, context.expressionContext(), extrusionHeight );
184
185 if ( const QgsPolygon *poly = qgsgeometry_cast< const QgsPolygon *>( g ) )
186 {
187 QgsPolygon *polyClone = poly->clone();
188 processPolygon( polyClone, f.id(), height, extrusionHeight, context, out );
189 }
190 else if ( const QgsMultiPolygon *mpoly = qgsgeometry_cast< const QgsMultiPolygon *>( g ) )
191 {
192 for ( int i = 0; i < mpoly->numGeometries(); ++i )
193 {
194 QgsPolygon *polyClone = static_cast< const QgsPolygon *>( mpoly->polygonN( i ) )->clone();
195 processPolygon( polyClone, f.id(), height, extrusionHeight, context, out );
196 }
197 }
198 else if ( const QgsGeometryCollection *gc = qgsgeometry_cast< const QgsGeometryCollection *>( g ) )
199 {
200 for ( int i = 0; i < gc->numGeometries(); ++i )
201 {
202 const QgsAbstractGeometry *g2 = gc->geometryN( i );
204 {
205 QgsPolygon *polyClone = static_cast< const QgsPolygon *>( g2 )->clone();
206 processPolygon( polyClone, f.id(), height, extrusionHeight, context, out );
207 }
208 }
209 }
210 else
211 qWarning() << "not a polygon";
212
213 mFeatureCount++;
214}
215
216void 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
250void 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
285static 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
298static 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
313Qt3DRender::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
331namespace 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: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:164
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:166
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