QGIS API Documentation 3.41.0-Master (45a0abf3bec)
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 <Qt3DCore/QTransform>
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#include "qgsmessagelog.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:
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( const QgsPolygon *poly, QgsFeatureId fid, float offset, 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 QgsMaterial *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
83 QgsVector3D mChunkOrigin;
84
85 // outputs
86 PolygonData outNormal;
87 PolygonData outSelected;
88
89 QgsLineVertexData outEdges;
90};
91
92
93bool QgsPolygon3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames, const QgsVector3D &chunkOrigin )
94{
95 outEdges.withAdjacency = true;
96 outEdges.init( mSymbol->altitudeClamping(), mSymbol->altitudeBinding(), 0, context, chunkOrigin );
97
98 mChunkOrigin = chunkOrigin;
99
100 const QgsPhongTexturedMaterialSettings *texturedMaterialSettings = dynamic_cast< const QgsPhongTexturedMaterialSettings * >( mSymbol->materialSettings() );
101
102 outNormal.tessellator.reset( new QgsTessellator( chunkOrigin.x(), chunkOrigin.y(), true, mSymbol->invertNormals(), mSymbol->addBackFaces(), false,
103 texturedMaterialSettings && texturedMaterialSettings->requiresTextureCoordinates(),
104 mSymbol->renderedFacade(),
105 texturedMaterialSettings ? texturedMaterialSettings->textureRotation() : 0 ) );
106 outSelected.tessellator.reset( new QgsTessellator( chunkOrigin.x(), chunkOrigin.y(), true, mSymbol->invertNormals(),
107 mSymbol->addBackFaces(), false,
108 texturedMaterialSettings && texturedMaterialSettings->requiresTextureCoordinates(),
109 mSymbol->renderedFacade(),
110 texturedMaterialSettings ? texturedMaterialSettings->textureRotation() : 0 ) );
111
112 outNormal.tessellator->setOutputZUp( true );
113 outSelected.tessellator->setOutputZUp( true );
114
115 QSet<QString> attrs = mSymbol->dataDefinedProperties().referencedFields( context.expressionContext() );
116 attributeNames.unite( attrs );
117 attrs = mSymbol->materialSettings()->dataDefinedProperties().referencedFields( context.expressionContext() );
118 attributeNames.unite( attrs );
119 return true;
120}
121
122void QgsPolygon3DSymbolHandler::processPolygon( const QgsPolygon *poly, QgsFeatureId fid, float offset, float extrusionHeight, const Qgs3DRenderContext &context, PolygonData &out )
123{
124 std::unique_ptr< QgsPolygon > polyClone( poly->clone() );
125
126 const uint oldVerticesCount = out.tessellator->dataVerticesCount();
127 if ( mSymbol->edgesEnabled() )
128 {
129 // add edges before the polygon gets the Z values modified because addLineString() does its own altitude handling
130 outEdges.addLineString( *static_cast<const QgsLineString *>( polyClone->exteriorRing() ), offset );
131 for ( int i = 0; i < polyClone->numInteriorRings(); ++i )
132 outEdges.addLineString( *static_cast<const QgsLineString *>( polyClone->interiorRing( i ) ), offset );
133
134 if ( extrusionHeight )
135 {
136 // add roof and wall edges
137 const QgsLineString *exterior = static_cast<const QgsLineString *>( polyClone->exteriorRing() );
138 outEdges.addLineString( *exterior, extrusionHeight + offset );
139 outEdges.addVerticalLines( *exterior, extrusionHeight, offset );
140 for ( int i = 0; i < polyClone->numInteriorRings(); ++i )
141 {
142 const QgsLineString *interior = static_cast<const QgsLineString *>( polyClone->interiorRing( i ) );
143 outEdges.addLineString( *interior, extrusionHeight + offset );
144 outEdges.addVerticalLines( *interior, extrusionHeight, offset );
145 }
146 }
147 }
148
149 Qgs3DUtils::clampAltitudes( polyClone.get(), mSymbol->altitudeClamping(), mSymbol->altitudeBinding(), offset, context );
150
151 Q_ASSERT( out.tessellator->dataVerticesCount() % 3 == 0 );
152 const uint startingTriangleIndex = static_cast<uint>( out.tessellator->dataVerticesCount() / 3 );
153 out.triangleIndexStartingIndices.append( startingTriangleIndex );
154 out.triangleIndexFids.append( fid );
155 out.tessellator->addPolygon( *polyClone, extrusionHeight );
156 if ( !out.tessellator->error().isEmpty() )
157 {
158 QgsMessageLog::logMessage( out.tessellator->error(), QObject::tr( "3D" ) );
159 }
160
161 if ( mSymbol->materialSettings()->dataDefinedProperties().hasActiveProperties() )
162 processMaterialDatadefined( out.tessellator->dataVerticesCount() - oldVerticesCount, context.expressionContext(), out );
163}
164
165void QgsPolygon3DSymbolHandler::processMaterialDatadefined( uint verticesCount, const QgsExpressionContext &context, QgsPolygon3DSymbolHandler::PolygonData &out )
166{
167 const QByteArray bytes = mSymbol->materialSettings()->dataDefinedVertexColorsAsByte( context );
168 out.materialDataDefined.append( bytes.repeated( verticesCount ) );
169}
170
171void QgsPolygon3DSymbolHandler::processFeature( const QgsFeature &f, const Qgs3DRenderContext &context )
172{
173 if ( f.geometry().isNull() )
174 return;
175
176 PolygonData &out = mSelectedIds.contains( f.id() ) ? outSelected : outNormal;
177
178 QgsGeometry geom = f.geometry();
180
181 // segmentize curved geometries if necessary
183 {
184 geom = QgsGeometry( g->segmentize() );
185 g = geom.constGet()->simplifiedTypeRef();
186 }
187
188 const QgsPropertyCollection &ddp = mSymbol->dataDefinedProperties();
189 const bool hasDDHeight = ddp.isActive( QgsAbstract3DSymbol::Property::Height );
190 const bool hasDDExtrusion = ddp.isActive( QgsAbstract3DSymbol::Property::ExtrusionHeight );
191
192 float offset = mSymbol->offset();
193 float extrusionHeight = mSymbol->extrusionHeight();
194 if ( hasDDHeight )
195 offset = static_cast<float>( ddp.valueAsDouble( QgsAbstract3DSymbol::Property::Height, context.expressionContext(), offset ) );
196 if ( hasDDExtrusion )
197 extrusionHeight = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::ExtrusionHeight, context.expressionContext(), extrusionHeight );
198
199 if ( const QgsPolygon *poly = qgsgeometry_cast< const QgsPolygon *>( g ) )
200 {
201 processPolygon( poly, f.id(), offset, extrusionHeight, context, out );
202 }
203 else if ( const QgsMultiPolygon *mpoly = qgsgeometry_cast< const QgsMultiPolygon *>( g ) )
204 {
205 for ( int i = 0; i < mpoly->numGeometries(); ++i )
206 {
207 const QgsPolygon *poly = mpoly->polygonN( i );
208 processPolygon( poly, f.id(), offset, extrusionHeight, context, out );
209 }
210 }
211 else if ( const QgsGeometryCollection *gc = qgsgeometry_cast< const QgsGeometryCollection *>( g ) )
212 {
213 for ( int i = 0; i < gc->numGeometries(); ++i )
214 {
215 const QgsAbstractGeometry *g2 = gc->geometryN( i );
217 {
218 const QgsPolygon *poly = static_cast< const QgsPolygon *>( g2 );
219 processPolygon( poly, f.id(), offset, extrusionHeight, context, out );
220 }
221 }
222 }
223 else if ( const QgsPolyhedralSurface *polySurface = qgsgeometry_cast< const QgsPolyhedralSurface *>( g ) )
224 {
225 for ( int i = 0; i < polySurface->numPatches(); ++i )
226 {
227 const QgsPolygon *poly = polySurface->patchN( i );
228 processPolygon( poly, f.id(), offset, extrusionHeight, context, out );
229 }
230 }
231 else
232 qWarning() << "not a polygon";
233
234 mFeatureCount++;
235}
236
237void QgsPolygon3DSymbolHandler::finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context )
238{
239 // create entity for selected and not selected
240 makeEntity( parent, context, outNormal, false );
241 makeEntity( parent, context, outSelected, true );
242
243 mZMin = std::min( outNormal.tessellator->zMinimum(), outSelected.tessellator->zMinimum() );
244 mZMax = std::max( outNormal.tessellator->zMaximum(), outSelected.tessellator->zMaximum() );
245
246 // add entity for edges
247 if ( mSymbol->edgesEnabled() && !outEdges.indexes.isEmpty() )
248 {
249 QgsLineMaterial *mat = new QgsLineMaterial;
250 mat->setLineColor( mSymbol->edgeColor() );
251 mat->setLineWidth( mSymbol->edgeWidth() );
252
253 Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
254 entity->setObjectName( parent->objectName() + "_EDGES" );
255
256 // geometry renderer
257 Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer;
258 renderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::LineStripAdjacency );
259 renderer->setGeometry( outEdges.createGeometry( entity ) );
260 renderer->setVertexCount( outEdges.indexes.count() );
261 renderer->setPrimitiveRestartEnabled( true );
262 renderer->setRestartIndexValue( 0 );
263
264 // add transform (our geometry has coordinates relative to mChunkOrigin)
265 Qt3DCore::QTransform *tr = new Qt3DCore::QTransform;
266 QVector3D nodeTranslation = ( mChunkOrigin - context.origin() ).toVector3D();
267 tr->setTranslation( nodeTranslation );
268
269 // make entity
270 entity->addComponent( renderer );
271 entity->addComponent( mat );
272 entity->addComponent( tr );
273 entity->setParent( parent );
274 }
275}
276
277
278void QgsPolygon3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, PolygonData &polyData, bool selected )
279{
280 if ( polyData.tessellator->dataVerticesCount() == 0 )
281 return; // nothing to show - no need to create the entity
282
283 QgsMaterial *mat = material( mSymbol.get(), selected, context );
284
285 // extract vertex buffer data from tessellator
286 const QByteArray data( reinterpret_cast<const char *>( polyData.tessellator->data().constData() ), static_cast<int>( polyData.tessellator->data().count() * sizeof( float ) ) );
287 const int nVerts = data.count() / polyData.tessellator->stride();
288
289 const QgsPhongTexturedMaterialSettings *texturedMaterialSettings = dynamic_cast< const QgsPhongTexturedMaterialSettings * >( mSymbol->materialSettings() );
290
291 QgsTessellatedPolygonGeometry *geometry = new QgsTessellatedPolygonGeometry( true, mSymbol->invertNormals(), mSymbol->addBackFaces(),
292 texturedMaterialSettings && texturedMaterialSettings->requiresTextureCoordinates() );
293
294 geometry->setData( data, nVerts, polyData.triangleIndexFids, polyData.triangleIndexStartingIndices );
295
296 if ( mSymbol->materialSettings()->dataDefinedProperties().hasActiveProperties() )
297 mSymbol->materialSettings()->applyDataDefinedToGeometry( geometry, nVerts, polyData.materialDataDefined );
298
299 Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer;
300 renderer->setGeometry( geometry );
301
302 // add transform (our geometry has coordinates relative to mChunkOrigin)
303 Qt3DCore::QTransform *tr = new Qt3DCore::QTransform;
304 QVector3D nodeTranslation = ( mChunkOrigin - context.origin() ).toVector3D();
305 tr->setTranslation( nodeTranslation );
306
307 // make entity
308 Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
309 entity->setObjectName( parent->objectName() + "_CHUNK_MESH" );
310 entity->addComponent( renderer );
311 entity->addComponent( mat );
312 entity->addComponent( tr );
313 entity->setParent( parent );
314
315 if ( !selected )
316 renderer->setProperty( Qgs3DTypes::PROP_NAME_3D_RENDERER_FLAG, Qgs3DTypes::Main3DRenderer ); // temporary measure to distinguish between "selected" and "main"
317
318// cppcheck wrongly believes entity will leak
319// cppcheck-suppress memleak
320}
321
322// front/back side culling
323static void applyCullingMode( Qgs3DTypes::CullingMode cullingMode, QgsMaterial *material )
324{
325 const auto techniques = material->effect()->techniques();
326 for ( auto tit = techniques.constBegin(); tit != techniques.constEnd(); ++tit )
327 {
328 auto renderPasses = ( *tit )->renderPasses();
329 for ( auto rpit = renderPasses.begin(); rpit != renderPasses.end(); ++rpit )
330 {
331 Qt3DRender::QCullFace *cullFace = new Qt3DRender::QCullFace;
332 cullFace->setMode( Qgs3DUtils::qt3DcullingMode( cullingMode ) );
333 ( *rpit )->addRenderState( cullFace );
334 }
335 }
336}
337
338QgsMaterial *QgsPolygon3DSymbolHandler::material( const QgsPolygon3DSymbol *symbol, bool isSelected, const Qgs3DRenderContext &context ) const
339{
340 QgsMaterialContext materialContext;
341 materialContext.setIsSelected( isSelected );
342 materialContext.setSelectionColor( context.selectionColor() );
343
344 const bool dataDefined = mSymbol->materialSettings()->dataDefinedProperties().hasActiveProperties();
345 QgsMaterial *material = symbol->materialSettings()->toMaterial( dataDefined ?
347 materialContext );
348 applyCullingMode( symbol->cullingMode(), material );
349 return material;
350}
351
352
353// --------------
354
355
356namespace Qgs3DSymbolImpl
357{
358
359
360 QgsFeature3DHandler *handlerForPolygon3DSymbol( QgsVectorLayer *layer, const QgsAbstract3DSymbol *symbol )
361 {
362 const QgsPolygon3DSymbol *polygonSymbol = dynamic_cast< const QgsPolygon3DSymbol * >( symbol );
363 if ( !polygonSymbol )
364 return nullptr;
365
366 return new QgsPolygon3DSymbolHandler( polygonSymbol, layer->selectedFeatureIds() );
367 }
368}
369
@ Polygon
Polygon.
QColor selectionColor() const
Returns color used for selected features.
QgsExpressionContext & expressionContext()
Gets the expression context.
QgsVector3D origin() const
Returns coordinates in map CRS at which 3D scene has origin (0,0,0)
@ Main3DRenderer
Renderer for normal entities.
Definition qgs3dtypes.h:49
static const char * PROP_NAME_3D_RENDERER_FLAG
Qt property name to hold the 3D geometry renderer flag.
Definition qgs3dtypes.h:44
CullingMode
Triangle culling mode.
Definition qgs3dtypes.h:36
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....
float 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