QGIS API Documentation 3.99.0-Master (2fe06baccd8)
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 "qgs3drendercontext.h"
19#include "qgs3dutils.h"
20#include "qgsgeotransform.h"
21#include "qgslinematerial_p.h"
22#include "qgslinestring.h"
23#include "qgslinevertexdata_p.h"
24#include "qgsmessagelog.h"
25#include "qgsmultipolygon.h"
27#include "qgspolygon.h"
28#include "qgspolygon3dsymbol.h"
31#include "qgstessellator.h"
32#include "qgsvectorlayer.h"
33
34#include <Qt3DExtras/QDiffuseMapMaterial>
35#include <Qt3DExtras/QPhongMaterial>
36#include <Qt3DRender/QAbstractTextureImage>
37#include <Qt3DRender/QCullFace>
38#include <Qt3DRender/QEffect>
39#include <Qt3DRender/QGeometryRenderer>
40#include <Qt3DRender/QTechnique>
41#include <Qt3DRender/QTexture>
42
44
45
46class QgsPolygon3DSymbolHandler : public QgsFeature3DHandler
47{
48 public:
49 QgsPolygon3DSymbolHandler( const QgsPolygon3DSymbol *symbol, const QgsFeatureIds &selectedIds )
50 : mSymbol( static_cast<QgsPolygon3DSymbol *>( symbol->clone() ) )
51 , mSelectedIds( selectedIds ) {}
52
53 bool prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames, const QgsVector3D &chunkOrigin ) override;
54 void processFeature( const QgsFeature &f, const Qgs3DRenderContext &context ) override;
55 void finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context ) override;
56
57 private:
59 struct PolygonData
60 {
61 std::unique_ptr<QgsTessellator> tessellator;
62 QVector<QgsFeatureId> triangleIndexFids;
63 QVector<uint> triangleIndexStartingIndices;
64 QByteArray materialDataDefined;
65 };
66
67 void processPolygon( const QgsPolygon *poly, QgsFeatureId fid, float offset, float extrusionHeight, const Qgs3DRenderContext &context, PolygonData &out );
68 void processMaterialDatadefined( uint verticesCount, const QgsExpressionContext &context, PolygonData &out );
69 void makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, PolygonData &out, bool selected );
70 QgsMaterial *material( const QgsPolygon3DSymbol *symbol, bool isSelected, const Qgs3DRenderContext &context ) const;
71
72 // input specific for this class
73 std::unique_ptr<QgsPolygon3DSymbol> mSymbol;
74 // inputs - generic
75 QgsFeatureIds mSelectedIds;
76
78 QgsVector3D mChunkOrigin;
79
80 // outputs
81 PolygonData outNormal;
82 PolygonData outSelected;
83
84 QgsLineVertexData outEdges;
85};
86
87
88bool QgsPolygon3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames, const QgsVector3D &chunkOrigin )
89{
90 outEdges.withAdjacency = true;
91 outEdges.init( mSymbol->altitudeClamping(), mSymbol->altitudeBinding(), 0, context, chunkOrigin );
92
93 mChunkOrigin = chunkOrigin;
94
95 const QgsPhongTexturedMaterialSettings *texturedMaterialSettings = dynamic_cast<const QgsPhongTexturedMaterialSettings *>( mSymbol->materialSettings() );
96
97 auto tessellator = std::make_unique<QgsTessellator>();
98 tessellator->setOrigin( chunkOrigin );
99 tessellator->setAddNormals( true );
100 tessellator->setInvertNormals( mSymbol->invertNormals() );
101 tessellator->setBackFacesEnabled( mSymbol->addBackFaces() );
102 tessellator->setOutputZUp( true );
103 tessellator->setExtrusionFaces( mSymbol->extrusionFaces() );
104 tessellator->setTextureRotation( texturedMaterialSettings ? static_cast<float>( texturedMaterialSettings->textureRotation() ) : 0.f );
105 tessellator->setAddTextureUVs( texturedMaterialSettings && texturedMaterialSettings->requiresTextureCoordinates() );
106 tessellator->setOutputZUp( true );
107
108 outNormal.tessellator = std::move( tessellator );
109
110 tessellator = std::make_unique<QgsTessellator>();
111 tessellator->setOrigin( chunkOrigin );
112 tessellator->setAddNormals( true );
113 tessellator->setInvertNormals( mSymbol->invertNormals() );
114 tessellator->setBackFacesEnabled( mSymbol->addBackFaces() );
115 tessellator->setOutputZUp( true );
116 tessellator->setExtrusionFaces( mSymbol->extrusionFaces() );
117 tessellator->setTextureRotation( texturedMaterialSettings ? static_cast<float>( texturedMaterialSettings->textureRotation() ) : 0.f );
118 tessellator->setAddTextureUVs( texturedMaterialSettings && texturedMaterialSettings->requiresTextureCoordinates() );
119 tessellator->setOutputZUp( true );
120
121 outSelected.tessellator = std::move( tessellator );
122
123 QSet<QString> attrs = mSymbol->dataDefinedProperties().referencedFields( context.expressionContext() );
124 attributeNames.unite( attrs );
125 attrs = mSymbol->materialSettings()->dataDefinedProperties().referencedFields( context.expressionContext() );
126 attributeNames.unite( attrs );
127 return true;
128}
129
130void QgsPolygon3DSymbolHandler::processPolygon( const QgsPolygon *poly, QgsFeatureId fid, float offset, float extrusionHeight, const Qgs3DRenderContext &context, PolygonData &out )
131{
132 std::unique_ptr<QgsPolygon> polyClone( poly->clone() );
133
134 const uint oldVerticesCount = out.tessellator->dataVerticesCount();
135 if ( mSymbol->edgesEnabled() )
136 {
137 // add edges before the polygon gets the Z values modified because addLineString() does its own altitude handling
138 outEdges.addLineString( *static_cast<const QgsLineString *>( polyClone->exteriorRing() ), offset );
139 for ( int i = 0; i < polyClone->numInteriorRings(); ++i )
140 outEdges.addLineString( *static_cast<const QgsLineString *>( polyClone->interiorRing( i ) ), offset );
141
142 if ( extrusionHeight )
143 {
144 // add roof and wall edges
145 const QgsLineString *exterior = static_cast<const QgsLineString *>( polyClone->exteriorRing() );
146 outEdges.addLineString( *exterior, extrusionHeight + offset );
147 outEdges.addVerticalLines( *exterior, extrusionHeight, offset );
148 for ( int i = 0; i < polyClone->numInteriorRings(); ++i )
149 {
150 const QgsLineString *interior = static_cast<const QgsLineString *>( polyClone->interiorRing( i ) );
151 outEdges.addLineString( *interior, extrusionHeight + offset );
152 outEdges.addVerticalLines( *interior, extrusionHeight, offset );
153 }
154 }
155 }
156
157 Qgs3DUtils::clampAltitudes( polyClone.get(), mSymbol->altitudeClamping(), mSymbol->altitudeBinding(), offset, context );
158
159 Q_ASSERT( out.tessellator->dataVerticesCount() % 3 == 0 );
160 const uint startingTriangleIndex = static_cast<uint>( out.tessellator->dataVerticesCount() / 3 );
161 out.triangleIndexStartingIndices.append( startingTriangleIndex );
162 out.triangleIndexFids.append( fid );
163 out.tessellator->addPolygon( *polyClone, extrusionHeight );
164 if ( !out.tessellator->error().isEmpty() )
165 {
166 QgsMessageLog::logMessage( out.tessellator->error(), QObject::tr( "3D" ) );
167 }
168
169 if ( mSymbol->materialSettings()->dataDefinedProperties().hasActiveProperties() )
170 processMaterialDatadefined( out.tessellator->dataVerticesCount() - oldVerticesCount, context.expressionContext(), out );
171}
172
173void QgsPolygon3DSymbolHandler::processMaterialDatadefined( uint verticesCount, const QgsExpressionContext &context, QgsPolygon3DSymbolHandler::PolygonData &out )
174{
175 const QByteArray bytes = mSymbol->materialSettings()->dataDefinedVertexColorsAsByte( context );
176 out.materialDataDefined.append( bytes.repeated( verticesCount ) );
177}
178
179void QgsPolygon3DSymbolHandler::processFeature( const QgsFeature &f, const Qgs3DRenderContext &context )
180{
181 if ( f.geometry().isNull() )
182 return;
183
184 PolygonData &out = mSelectedIds.contains( f.id() ) ? outSelected : outNormal;
185
186 QgsGeometry geom = f.geometry();
188
189 // segmentize curved geometries if necessary
191 {
192 geom = QgsGeometry( g->segmentize() );
193 g = geom.constGet()->simplifiedTypeRef();
194 }
195
196 const QgsPropertyCollection &ddp = mSymbol->dataDefinedProperties();
197 const bool hasDDHeight = ddp.isActive( QgsAbstract3DSymbol::Property::Height );
198 const bool hasDDExtrusion = ddp.isActive( QgsAbstract3DSymbol::Property::ExtrusionHeight );
199
200 float offset = mSymbol->offset();
201 float extrusionHeight = mSymbol->extrusionHeight();
202 if ( hasDDHeight )
203 offset = static_cast<float>( ddp.valueAsDouble( QgsAbstract3DSymbol::Property::Height, context.expressionContext(), offset ) );
204 if ( hasDDExtrusion )
205 extrusionHeight = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::ExtrusionHeight, context.expressionContext(), extrusionHeight );
206
207 if ( const QgsPolygon *poly = qgsgeometry_cast<const QgsPolygon *>( g ) )
208 {
209 processPolygon( poly, f.id(), offset, extrusionHeight, context, out );
210 }
211 else if ( const QgsMultiPolygon *mpoly = qgsgeometry_cast<const QgsMultiPolygon *>( g ) )
212 {
213 for ( int i = 0; i < mpoly->numGeometries(); ++i )
214 {
215 const QgsPolygon *poly = mpoly->polygonN( i );
216 processPolygon( poly, f.id(), offset, extrusionHeight, context, out );
217 }
218 }
220 {
221 for ( int i = 0; i < gc->numGeometries(); ++i )
222 {
223 const QgsAbstractGeometry *g2 = gc->geometryN( i );
225 {
226 const QgsPolygon *poly = static_cast<const QgsPolygon *>( g2 );
227 processPolygon( poly, f.id(), offset, extrusionHeight, context, out );
228 }
229 }
230 }
231 else if ( const QgsPolyhedralSurface *polySurface = qgsgeometry_cast<const QgsPolyhedralSurface *>( g ) )
232 {
233 for ( int i = 0; i < polySurface->numPatches(); ++i )
234 {
235 const QgsPolygon *poly = polySurface->patchN( i );
236 processPolygon( poly, f.id(), offset, extrusionHeight, context, out );
237 }
238 }
239 else
240 qWarning() << "not a polygon";
241
242 mFeatureCount++;
243}
244
245void QgsPolygon3DSymbolHandler::finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context )
246{
247 // create entity for selected and not selected
248 makeEntity( parent, context, outNormal, false );
249 makeEntity( parent, context, outSelected, true );
250
251 mZMin = std::min( outNormal.tessellator->zMinimum(), outSelected.tessellator->zMinimum() );
252 mZMax = std::max( outNormal.tessellator->zMaximum(), outSelected.tessellator->zMaximum() );
253
254 // add entity for edges
255 if ( mSymbol->edgesEnabled() && !outEdges.indexes.isEmpty() )
256 {
257 QgsLineMaterial *mat = new QgsLineMaterial;
258 mat->setLineColor( mSymbol->edgeColor() );
259 mat->setLineWidth( mSymbol->edgeWidth() );
260
261 Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
262 entity->setObjectName( parent->objectName() + "_EDGES" );
263
264 // geometry renderer
265 Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer;
266 renderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::LineStripAdjacency );
267 renderer->setGeometry( outEdges.createGeometry( entity ) );
268 renderer->setVertexCount( outEdges.indexes.count() );
269 renderer->setPrimitiveRestartEnabled( true );
270 renderer->setRestartIndexValue( 0 );
271
272 // add transform (our geometry has coordinates relative to mChunkOrigin)
273 QgsGeoTransform *tr = new QgsGeoTransform;
274 tr->setGeoTranslation( mChunkOrigin );
275
276 // make entity
277 entity->addComponent( renderer );
278 entity->addComponent( mat );
279 entity->addComponent( tr );
280 entity->setParent( parent );
281 }
282}
283
284
285void QgsPolygon3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, PolygonData &polyData, bool selected )
286{
287 if ( polyData.tessellator->dataVerticesCount() == 0 )
288 return; // nothing to show - no need to create the entity
289
290 QgsMaterial *mat = material( mSymbol.get(), selected, context );
291
292 // extract vertex buffer data from tessellator
293 const QByteArray data( reinterpret_cast<const char *>( polyData.tessellator->data().constData() ), static_cast<int>( polyData.tessellator->data().count() * sizeof( float ) ) );
294 const int nVerts = data.count() / polyData.tessellator->stride();
295
296 const QgsPhongTexturedMaterialSettings *texturedMaterialSettings = dynamic_cast<const QgsPhongTexturedMaterialSettings *>( mSymbol->materialSettings() );
297
298 QgsTessellatedPolygonGeometry *geometry = new QgsTessellatedPolygonGeometry( true, mSymbol->invertNormals(), mSymbol->addBackFaces(), texturedMaterialSettings && texturedMaterialSettings->requiresTextureCoordinates() );
299
300 geometry->setData( data, nVerts, polyData.triangleIndexFids, polyData.triangleIndexStartingIndices );
301
302 if ( mSymbol->materialSettings()->dataDefinedProperties().hasActiveProperties() )
303 mSymbol->materialSettings()->applyDataDefinedToGeometry( geometry, nVerts, polyData.materialDataDefined );
304
305 Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer;
306 renderer->setGeometry( geometry );
307
308 // add transform (our geometry has coordinates relative to mChunkOrigin)
309 QgsGeoTransform *tr = new QgsGeoTransform;
310 tr->setGeoTranslation( mChunkOrigin );
311
312 // make entity
313 Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
314 entity->setObjectName( parent->objectName() + "_CHUNK_MESH" );
315 entity->addComponent( renderer );
316 entity->addComponent( mat );
317 entity->addComponent( tr );
318 entity->setParent( parent );
319
320 if ( !selected )
321 renderer->setProperty( Qgs3DTypes::PROP_NAME_3D_RENDERER_FLAG, Qgs3DTypes::Main3DRenderer ); // temporary measure to distinguish between "selected" and "main"
322
323 // cppcheck wrongly believes entity will leak
324 // cppcheck-suppress memleak
325}
326
327// front/back side culling
328static void applyCullingMode( Qgs3DTypes::CullingMode cullingMode, QgsMaterial *material )
329{
330 const auto techniques = material->effect()->techniques();
331 for ( auto tit = techniques.constBegin(); tit != techniques.constEnd(); ++tit )
332 {
333 auto renderPasses = ( *tit )->renderPasses();
334 for ( auto rpit = renderPasses.begin(); rpit != renderPasses.end(); ++rpit )
335 {
336 Qt3DRender::QCullFace *cullFace = new Qt3DRender::QCullFace;
337 cullFace->setMode( Qgs3DUtils::qt3DcullingMode( cullingMode ) );
338 ( *rpit )->addRenderState( cullFace );
339 }
340 }
341}
342
343QgsMaterial *QgsPolygon3DSymbolHandler::material( const QgsPolygon3DSymbol *symbol, bool isSelected, const Qgs3DRenderContext &context ) const
344{
345 QgsMaterialContext materialContext;
346 materialContext.setIsSelected( isSelected );
347 materialContext.setSelectionColor( context.selectionColor() );
348
349 const bool dataDefined = mSymbol->materialSettings()->dataDefinedProperties().hasActiveProperties();
351 applyCullingMode( symbol->cullingMode(), material );
352 return material;
353}
354
355
356// --------------
357
358
359namespace Qgs3DSymbolImpl
360{
361
362
363 QgsFeature3DHandler *handlerForPolygon3DSymbol( QgsVectorLayer *layer, const QgsAbstract3DSymbol *symbol )
364 {
365 const QgsPolygon3DSymbol *polygonSymbol = dynamic_cast<const QgsPolygon3DSymbol *>( symbol );
366 if ( !polygonSymbol )
367 return nullptr;
368
369 return new QgsPolygon3DSymbolHandler( polygonSymbol, layer->selectedFeatureIds() );
370 }
371} // namespace Qgs3DSymbolImpl
372
@ Polygon
Polygon.
Definition qgis.h:281
Rendering context for preparation of 3D entities.
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).
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.
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.
Context settings for a material.
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.
Base class for all materials used within QGIS 3D views.
Definition qgsmaterial.h:39
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE())
Adds a message to the log instance (and creates it if necessary).
Multi polygon geometry collection.
A Phong shading model with diffuse texture map.
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.
3D symbol that draws polygon geometries as planar polygons, optionally extruded (with added walls).
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 an integer key value.
bool isActive(int key) const final
Returns true if the collection contains an active property with the specified key.
Qt3DRender::QGeometry subclass that represents polygons tessellated into 3D geometry.
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.
A 3D vector (similar to QVector3D) with the difference that it uses double precision instead of singl...
Definition qgsvector3d.h:30
Q_INVOKABLE const QgsFeatureIds & selectedFeatureIds() const
Returns a list of the selected features IDs in this layer.
static Q_INVOKABLE 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.
@ Triangles
Triangle based rendering (default).
@ TrianglesDataDefined
Triangle based rendering with possibility of datadefined color.
T qgsgeometry_cast(QgsAbstractGeometry *geom)
QSet< QgsFeatureId > QgsFeatureIds
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features