QGIS API Documentation 3.41.0-Master (cea29feecf2)
Loading...
Searching...
No Matches
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"
21#include "qgs3drendercontext.h"
22#include "qgs3dutils.h"
23#include "qgstessellator.h"
25
26#include <Qt3DExtras/QPhongMaterial>
27
28#include <Qt3DExtras/QDiffuseMapMaterial>
29#include <Qt3DRender/QAbstractTextureImage>
30#include <Qt3DRender/QTexture>
31
32#include <Qt3DRender/QEffect>
33#include <Qt3DRender/QTechnique>
34#include <Qt3DRender/QCullFace>
35#include <Qt3DRender/QGeometryRenderer>
36
37#include "qgsvectorlayer.h"
38#include "qgslinestring.h"
39#include "qgsmultipolygon.h"
40#include "qgspolygon.h"
41#include "qgsmessagelog.h"
42#include "qgsgeotransform.h"
43
44#include "qgslinevertexdata_p.h"
45#include "qgslinematerial_p.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, const QgsVector3D &chunkOrigin ) override;
58 void processFeature( const QgsFeature &f, const Qgs3DRenderContext &context ) override;
59 void finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context ) override;
60
61 private:
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( const QgsPolygon *poly, QgsFeatureId fid, float offset, 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 QgsMaterial *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
82 QgsVector3D mChunkOrigin;
83
84 // outputs
85 PolygonData outNormal;
86 PolygonData outSelected;
87
88 QgsLineVertexData outEdges;
89};
90
91
92bool QgsPolygon3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames, const QgsVector3D &chunkOrigin )
93{
94 outEdges.withAdjacency = true;
95 outEdges.init( mSymbol->altitudeClamping(), mSymbol->altitudeBinding(), 0, context, chunkOrigin );
96
97 mChunkOrigin = chunkOrigin;
98
99 const QgsPhongTexturedMaterialSettings *texturedMaterialSettings = dynamic_cast<const QgsPhongTexturedMaterialSettings *>( mSymbol->materialSettings() );
100
101 outNormal.tessellator.reset( new QgsTessellator( chunkOrigin.x(), chunkOrigin.y(), true, mSymbol->invertNormals(), mSymbol->addBackFaces(), false, texturedMaterialSettings && texturedMaterialSettings->requiresTextureCoordinates(), mSymbol->renderedFacade(), texturedMaterialSettings ? texturedMaterialSettings->textureRotation() : 0 ) );
102 outSelected.tessellator.reset( new QgsTessellator( chunkOrigin.x(), chunkOrigin.y(), true, mSymbol->invertNormals(), mSymbol->addBackFaces(), false, texturedMaterialSettings && texturedMaterialSettings->requiresTextureCoordinates(), mSymbol->renderedFacade(), texturedMaterialSettings ? texturedMaterialSettings->textureRotation() : 0 ) );
103
104 outNormal.tessellator->setOutputZUp( true );
105 outSelected.tessellator->setOutputZUp( true );
106
107 QSet<QString> attrs = mSymbol->dataDefinedProperties().referencedFields( context.expressionContext() );
108 attributeNames.unite( attrs );
109 attrs = mSymbol->materialSettings()->dataDefinedProperties().referencedFields( context.expressionContext() );
110 attributeNames.unite( attrs );
111 return true;
112}
113
114void QgsPolygon3DSymbolHandler::processPolygon( const QgsPolygon *poly, QgsFeatureId fid, float offset, float extrusionHeight, const Qgs3DRenderContext &context, PolygonData &out )
115{
116 std::unique_ptr<QgsPolygon> polyClone( poly->clone() );
117
118 const uint oldVerticesCount = out.tessellator->dataVerticesCount();
119 if ( mSymbol->edgesEnabled() )
120 {
121 // add edges before the polygon gets the Z values modified because addLineString() does its own altitude handling
122 outEdges.addLineString( *static_cast<const QgsLineString *>( polyClone->exteriorRing() ), offset );
123 for ( int i = 0; i < polyClone->numInteriorRings(); ++i )
124 outEdges.addLineString( *static_cast<const QgsLineString *>( polyClone->interiorRing( i ) ), offset );
125
126 if ( extrusionHeight )
127 {
128 // add roof and wall edges
129 const QgsLineString *exterior = static_cast<const QgsLineString *>( polyClone->exteriorRing() );
130 outEdges.addLineString( *exterior, extrusionHeight + offset );
131 outEdges.addVerticalLines( *exterior, extrusionHeight, offset );
132 for ( int i = 0; i < polyClone->numInteriorRings(); ++i )
133 {
134 const QgsLineString *interior = static_cast<const QgsLineString *>( polyClone->interiorRing( i ) );
135 outEdges.addLineString( *interior, extrusionHeight + offset );
136 outEdges.addVerticalLines( *interior, extrusionHeight, offset );
137 }
138 }
139 }
140
141 Qgs3DUtils::clampAltitudes( polyClone.get(), mSymbol->altitudeClamping(), mSymbol->altitudeBinding(), offset, context );
142
143 Q_ASSERT( out.tessellator->dataVerticesCount() % 3 == 0 );
144 const uint startingTriangleIndex = static_cast<uint>( out.tessellator->dataVerticesCount() / 3 );
145 out.triangleIndexStartingIndices.append( startingTriangleIndex );
146 out.triangleIndexFids.append( fid );
147 out.tessellator->addPolygon( *polyClone, extrusionHeight );
148 if ( !out.tessellator->error().isEmpty() )
149 {
150 QgsMessageLog::logMessage( out.tessellator->error(), QObject::tr( "3D" ) );
151 }
152
153 if ( mSymbol->materialSettings()->dataDefinedProperties().hasActiveProperties() )
154 processMaterialDatadefined( out.tessellator->dataVerticesCount() - oldVerticesCount, context.expressionContext(), out );
155}
156
157void QgsPolygon3DSymbolHandler::processMaterialDatadefined( uint verticesCount, const QgsExpressionContext &context, QgsPolygon3DSymbolHandler::PolygonData &out )
158{
159 const QByteArray bytes = mSymbol->materialSettings()->dataDefinedVertexColorsAsByte( context );
160 out.materialDataDefined.append( bytes.repeated( verticesCount ) );
161}
162
163void QgsPolygon3DSymbolHandler::processFeature( const QgsFeature &f, const Qgs3DRenderContext &context )
164{
165 if ( f.geometry().isNull() )
166 return;
167
168 PolygonData &out = mSelectedIds.contains( f.id() ) ? outSelected : outNormal;
169
170 QgsGeometry geom = f.geometry();
172
173 // segmentize curved geometries if necessary
175 {
176 geom = QgsGeometry( g->segmentize() );
177 g = geom.constGet()->simplifiedTypeRef();
178 }
179
180 const QgsPropertyCollection &ddp = mSymbol->dataDefinedProperties();
181 const bool hasDDHeight = ddp.isActive( QgsAbstract3DSymbol::Property::Height );
182 const bool hasDDExtrusion = ddp.isActive( QgsAbstract3DSymbol::Property::ExtrusionHeight );
183
184 float offset = mSymbol->offset();
185 float extrusionHeight = mSymbol->extrusionHeight();
186 if ( hasDDHeight )
187 offset = static_cast<float>( ddp.valueAsDouble( QgsAbstract3DSymbol::Property::Height, context.expressionContext(), offset ) );
188 if ( hasDDExtrusion )
189 extrusionHeight = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::ExtrusionHeight, context.expressionContext(), extrusionHeight );
190
191 if ( const QgsPolygon *poly = qgsgeometry_cast<const QgsPolygon *>( g ) )
192 {
193 processPolygon( poly, f.id(), offset, extrusionHeight, context, out );
194 }
195 else if ( const QgsMultiPolygon *mpoly = qgsgeometry_cast<const QgsMultiPolygon *>( g ) )
196 {
197 for ( int i = 0; i < mpoly->numGeometries(); ++i )
198 {
199 const QgsPolygon *poly = mpoly->polygonN( i );
200 processPolygon( poly, f.id(), offset, extrusionHeight, context, out );
201 }
202 }
203 else if ( const QgsGeometryCollection *gc = qgsgeometry_cast<const QgsGeometryCollection *>( g ) )
204 {
205 for ( int i = 0; i < gc->numGeometries(); ++i )
206 {
207 const QgsAbstractGeometry *g2 = gc->geometryN( i );
209 {
210 const QgsPolygon *poly = static_cast<const QgsPolygon *>( g2 );
211 processPolygon( poly, f.id(), offset, extrusionHeight, context, out );
212 }
213 }
214 }
215 else if ( const QgsPolyhedralSurface *polySurface = qgsgeometry_cast<const QgsPolyhedralSurface *>( g ) )
216 {
217 for ( int i = 0; i < polySurface->numPatches(); ++i )
218 {
219 const QgsPolygon *poly = polySurface->patchN( i );
220 processPolygon( poly, f.id(), offset, extrusionHeight, context, out );
221 }
222 }
223 else
224 qWarning() << "not a polygon";
225
226 mFeatureCount++;
227}
228
229void QgsPolygon3DSymbolHandler::finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context )
230{
231 // create entity for selected and not selected
232 makeEntity( parent, context, outNormal, false );
233 makeEntity( parent, context, outSelected, true );
234
235 mZMin = std::min( outNormal.tessellator->zMinimum(), outSelected.tessellator->zMinimum() );
236 mZMax = std::max( outNormal.tessellator->zMaximum(), outSelected.tessellator->zMaximum() );
237
238 // add entity for edges
239 if ( mSymbol->edgesEnabled() && !outEdges.indexes.isEmpty() )
240 {
241 QgsLineMaterial *mat = new QgsLineMaterial;
242 mat->setLineColor( mSymbol->edgeColor() );
243 mat->setLineWidth( mSymbol->edgeWidth() );
244
245 Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
246 entity->setObjectName( parent->objectName() + "_EDGES" );
247
248 // geometry renderer
249 Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer;
250 renderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::LineStripAdjacency );
251 renderer->setGeometry( outEdges.createGeometry( entity ) );
252 renderer->setVertexCount( outEdges.indexes.count() );
253 renderer->setPrimitiveRestartEnabled( true );
254 renderer->setRestartIndexValue( 0 );
255
256 // add transform (our geometry has coordinates relative to mChunkOrigin)
257 QgsGeoTransform *tr = new QgsGeoTransform;
258 tr->setGeoTranslation( mChunkOrigin );
259
260 // make entity
261 entity->addComponent( renderer );
262 entity->addComponent( mat );
263 entity->addComponent( tr );
264 entity->setParent( parent );
265 }
266}
267
268
269void QgsPolygon3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, PolygonData &polyData, bool selected )
270{
271 if ( polyData.tessellator->dataVerticesCount() == 0 )
272 return; // nothing to show - no need to create the entity
273
274 QgsMaterial *mat = material( mSymbol.get(), selected, context );
275
276 // extract vertex buffer data from tessellator
277 const QByteArray data( reinterpret_cast<const char *>( polyData.tessellator->data().constData() ), static_cast<int>( polyData.tessellator->data().count() * sizeof( float ) ) );
278 const int nVerts = data.count() / polyData.tessellator->stride();
279
280 const QgsPhongTexturedMaterialSettings *texturedMaterialSettings = dynamic_cast<const QgsPhongTexturedMaterialSettings *>( mSymbol->materialSettings() );
281
282 QgsTessellatedPolygonGeometry *geometry = new QgsTessellatedPolygonGeometry( true, mSymbol->invertNormals(), mSymbol->addBackFaces(), texturedMaterialSettings && texturedMaterialSettings->requiresTextureCoordinates() );
283
284 geometry->setData( data, nVerts, polyData.triangleIndexFids, polyData.triangleIndexStartingIndices );
285
286 if ( mSymbol->materialSettings()->dataDefinedProperties().hasActiveProperties() )
287 mSymbol->materialSettings()->applyDataDefinedToGeometry( geometry, nVerts, polyData.materialDataDefined );
288
289 Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer;
290 renderer->setGeometry( geometry );
291
292 // add transform (our geometry has coordinates relative to mChunkOrigin)
293 QgsGeoTransform *tr = new QgsGeoTransform;
294 tr->setGeoTranslation( mChunkOrigin );
295
296 // make entity
297 Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
298 entity->setObjectName( parent->objectName() + "_CHUNK_MESH" );
299 entity->addComponent( renderer );
300 entity->addComponent( mat );
301 entity->addComponent( tr );
302 entity->setParent( parent );
303
304 if ( !selected )
305 renderer->setProperty( Qgs3DTypes::PROP_NAME_3D_RENDERER_FLAG, Qgs3DTypes::Main3DRenderer ); // temporary measure to distinguish between "selected" and "main"
306
307 // cppcheck wrongly believes entity will leak
308 // cppcheck-suppress memleak
309}
310
311// front/back side culling
312static void applyCullingMode( Qgs3DTypes::CullingMode cullingMode, QgsMaterial *material )
313{
314 const auto techniques = material->effect()->techniques();
315 for ( auto tit = techniques.constBegin(); tit != techniques.constEnd(); ++tit )
316 {
317 auto renderPasses = ( *tit )->renderPasses();
318 for ( auto rpit = renderPasses.begin(); rpit != renderPasses.end(); ++rpit )
319 {
320 Qt3DRender::QCullFace *cullFace = new Qt3DRender::QCullFace;
321 cullFace->setMode( Qgs3DUtils::qt3DcullingMode( cullingMode ) );
322 ( *rpit )->addRenderState( cullFace );
323 }
324 }
325}
326
327QgsMaterial *QgsPolygon3DSymbolHandler::material( const QgsPolygon3DSymbol *symbol, bool isSelected, const Qgs3DRenderContext &context ) const
328{
329 QgsMaterialContext materialContext;
330 materialContext.setIsSelected( isSelected );
331 materialContext.setSelectionColor( context.selectionColor() );
332
333 const bool dataDefined = mSymbol->materialSettings()->dataDefinedProperties().hasActiveProperties();
335 applyCullingMode( symbol->cullingMode(), material );
336 return material;
337}
338
339
340// --------------
341
342
343namespace Qgs3DSymbolImpl
344{
345
346
347 QgsFeature3DHandler *handlerForPolygon3DSymbol( QgsVectorLayer *layer, const QgsAbstract3DSymbol *symbol )
348 {
349 const QgsPolygon3DSymbol *polygonSymbol = dynamic_cast<const QgsPolygon3DSymbol *>( symbol );
350 if ( !polygonSymbol )
351 return nullptr;
352
353 return new QgsPolygon3DSymbolHandler( polygonSymbol, layer->selectedFeatureIds() );
354 }
355} // namespace Qgs3DSymbolImpl
356
@ Polygon
Polygon.
QColor selectionColor() const
Returns color used for selected features.
QgsExpressionContext & expressionContext()
Gets the expression context.
@ Main3DRenderer
Renderer for normal entities.
Definition qgs3dtypes.h:48
static const char * PROP_NAME_3D_RENDERER_FLAG
Qt property name to hold the 3D geometry renderer flag.
Definition qgs3dtypes.h:43
CullingMode
Triangle culling mode.
Definition qgs3dtypes.h:35
static Qt3DRender::QCullFace::CullingMode qt3DcullingMode(Qgs3DTypes::CullingMode mode)
Converts Qgs3DTypes::CullingMode mode into its Qt3D equivalent.
static void clampAltitudes(QgsLineString *lineString, Qgis::AltitudeClamping altClamp, Qgis::AltitudeBinding altBind, const QgsPoint &centroid, float offset, const Qgs3DRenderContext &context)
Clamps altitude of vertices of a linestring according to the settings.
@ ExtrusionHeight
Extrusion height (zero means no extrusion)
@ Height
Height (altitude)
Abstract base class for all geometries.
virtual const QgsAbstractGeometry * simplifiedTypeRef() const
Returns a reference to the simplest lossless representation of this geometry, e.g.
virtual QgsAbstractGeometry * segmentize(double tolerance=M_PI/180., SegmentationToleranceType toleranceType=MaximumAngle) const
Returns a version of the geometry without curves.
Qgis::WkbType wkbType() const
Returns the WKB type of the geometry.
virtual QgsMaterial * toMaterial(QgsMaterialSettingsRenderingTechnique technique, const QgsMaterialContext &context) const =0
Creates a new QgsMaterial 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.
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:58
QgsFeatureId id
Definition qgsfeature.h:66
QgsGeometry geometry
Definition qgsfeature.h:69
A geometry is the spatial representation of a feature.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
Line string geometry type, with support for z-dimension and m-values.
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.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
Multi polygon geometry collection.
bool requiresTextureCoordinates() const
Returns true if the material requires texture coordinates to be generated during triangulation....
double textureRotation() const
Returns the texture rotation, in degrees.
QgsAbstractMaterialSettings * materialSettings() const
Returns material settings used for shading of the symbol.
Qgs3DTypes::CullingMode cullingMode() const
Returns front/back culling mode.
Polygon geometry type.
Definition qgspolygon.h:33
QgsPolygon * clone() const override
Clones the geometry by performing a deep copy.
Polyhedral surface geometry type.
A grouped map of multiple QgsProperty objects, each referenced by a integer key value.
bool isActive(int key) const final
Returns true if the collection contains an active property with the specified key.
QSet< QString > referencedFields(const QgsExpressionContext &context=QgsExpressionContext(), bool ignoreContext=false) const final
Returns the set of any fields referenced by the active properties from the collection.
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.
Class for storage of 3D vectors similar to QVector3D, with the difference that it uses double precisi...
Definition qgsvector3d.h:31
double y() const
Returns Y coordinate.
Definition qgsvector3d.h:50
double x() const
Returns X coordinate.
Definition qgsvector3d.h:48
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(Qgis::WkbType type)
Returns true if the WKB type is a curved type or can contain curved geometries.
static Qgis::WkbType flatType(Qgis::WkbType type)
Returns the flat type for a WKB type.
QgsMaterialSettingsRenderingTechnique
Material rendering techniques 3.
@ Triangles
Triangle based rendering (default)
@ TrianglesDataDefined
Triangle based rendering with possibility of datadefined color.
QSet< QgsFeatureId > QgsFeatureIds
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features