QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgsline3dsymbol_p.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsline3dsymbol_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
16#include "qgsline3dsymbol_p.h"
17
18#include "qgsline3dsymbol.h"
19#include "qgslinematerial_p.h"
20#include "qgslinevertexdata_p.h"
22#include "qgstessellator.h"
23#include "qgs3dmapsettings.h"
24//#include "qgsterraingenerator.h"
25#include "qgs3dutils.h"
26
27#include "qgsvectorlayer.h"
28#include "qgsmultilinestring.h"
29#include "qgsmultipolygon.h"
30#include "qgsgeos.h"
32#include "qgspolygon.h"
34#include "qgsmessagelog.h"
35
36#include <Qt3DExtras/QPhongMaterial>
37#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
38#include <Qt3DRender/QAttribute>
39#include <Qt3DRender/QBuffer>
40
41typedef Qt3DRender::QAttribute Qt3DQAttribute;
42typedef Qt3DRender::QBuffer Qt3DQBuffer;
43typedef Qt3DRender::QGeometry Qt3DQGeometry;
44#else
45#include <Qt3DCore/QAttribute>
46#include <Qt3DCore/QBuffer>
47
48typedef Qt3DCore::QAttribute Qt3DQAttribute;
49typedef Qt3DCore::QBuffer Qt3DQBuffer;
50typedef Qt3DCore::QGeometry Qt3DQGeometry;
51#endif
52#include <Qt3DRender/QGeometryRenderer>
53
55
56// -----------
57
58
59class QgsBufferedLine3DSymbolHandler : public QgsFeature3DHandler
60{
61 public:
62 QgsBufferedLine3DSymbolHandler( const QgsLine3DSymbol *symbol, const QgsFeatureIds &selectedIds )
63 : mSymbol( static_cast< QgsLine3DSymbol *>( symbol->clone() ) )
64 , mSelectedIds( selectedIds ) {}
65
66 bool prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames ) override;
67 void processFeature( const QgsFeature &feature, const Qgs3DRenderContext &context ) override;
68 void finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context ) override;
69
70 private:
71
73 struct LineData
74 {
75 std::unique_ptr<QgsTessellator> tessellator;
76 QVector<QgsFeatureId> triangleIndexFids;
77 QVector<uint> triangleIndexStartingIndices;
78 };
79
80 void processPolygon( QgsPolygon *polyBuffered, QgsFeatureId fid, float height, float extrusionHeight, const Qgs3DRenderContext &context, LineData &out );
81
82 void makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, LineData &out, bool selected );
83
84 // input specific for this class
85 std::unique_ptr< QgsLine3DSymbol > mSymbol;
86 // inputs - generic
87 QgsFeatureIds mSelectedIds;
88
89 // outputs
90 LineData outNormal;
91 LineData outSelected;
92};
93
94
95
96bool QgsBufferedLine3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames )
97{
98 Q_UNUSED( attributeNames )
99
100 const QgsPhongTexturedMaterialSettings *texturedMaterialSettings = dynamic_cast< const QgsPhongTexturedMaterialSettings * >( mSymbol->materialSettings() );
101
102 outNormal.tessellator.reset( new QgsTessellator( context.map().origin().x(), context.map().origin().y(), true,
103 false, false, false, texturedMaterialSettings ? texturedMaterialSettings->requiresTextureCoordinates() : false,
104 3,
105 texturedMaterialSettings ? texturedMaterialSettings->textureRotation() : 0 ) );
106 outSelected.tessellator.reset( new QgsTessellator( context.map().origin().x(), context.map().origin().y(), true,
107 false, false, false, texturedMaterialSettings ? texturedMaterialSettings->requiresTextureCoordinates() : false,
108 3,
109 texturedMaterialSettings ? texturedMaterialSettings->textureRotation() : 0 ) );
110
111 return true;
112}
113
114void QgsBufferedLine3DSymbolHandler::processFeature( const QgsFeature &f, const Qgs3DRenderContext &context )
115{
116 if ( f.geometry().isNull() )
117 return;
118
119 LineData &out = mSelectedIds.contains( f.id() ) ? outSelected : outNormal;
120
121 QgsGeometry geom = f.geometry();
123
124 // segmentize curved geometries if necessary
126 {
127 geom = QgsGeometry( g->segmentize() );
128 g = geom.constGet()->simplifiedTypeRef();
129 }
130
131 // TODO: configurable
132 const int nSegments = 4;
134 const Qgis::JoinStyle joinStyle = Qgis::JoinStyle::Round;
135 const double mitreLimit = 0;
136
137 const QgsGeos engine( g );
138
139 double width = mSymbol->width();
140 if ( qgsDoubleNear( width, 0 ) )
141 {
142 // a zero-width buffered line should be treated like a "wall" or "fence" -- we fake this by bumping the width to a very tiny amount,
143 // so that we get a very narrow polygon shape to work with...
144 width = 0.001;
145 }
146
147 QgsAbstractGeometry *buffered = engine.buffer( width / 2., nSegments, endCapStyle, joinStyle, mitreLimit ); // factory
148 if ( !buffered )
149 return;
150
152 {
153 QgsPolygon *polyBuffered = static_cast<QgsPolygon *>( buffered );
154 processPolygon( polyBuffered, f.id(), mSymbol->offset(), mSymbol->extrusionHeight(), context, out );
155 }
156 else if ( QgsWkbTypes::flatType( buffered->wkbType() ) == Qgis::WkbType::MultiPolygon )
157 {
158 QgsMultiPolygon *mpolyBuffered = static_cast<QgsMultiPolygon *>( buffered );
159 for ( int i = 0; i < mpolyBuffered->numGeometries(); ++i )
160 {
161 QgsPolygon *polyBuffered = static_cast<QgsPolygon *>( mpolyBuffered->polygonN( i ) )->clone(); // need to clone individual geometry parts
162 processPolygon( polyBuffered, f.id(), mSymbol->offset(), mSymbol->extrusionHeight(), context, out );
163 }
164 delete buffered;
165 }
166 mFeatureCount++;
167}
168
169void QgsBufferedLine3DSymbolHandler::processPolygon( QgsPolygon *polyBuffered, QgsFeatureId fid, float height, float extrusionHeight, const Qgs3DRenderContext &context, LineData &out )
170{
171 Qgs3DUtils::clampAltitudes( polyBuffered, mSymbol->altitudeClamping(), mSymbol->altitudeBinding(), height, context.map() );
172
173 Q_ASSERT( out.tessellator->dataVerticesCount() % 3 == 0 );
174 const uint startingTriangleIndex = static_cast<uint>( out.tessellator->dataVerticesCount() / 3 );
175 out.triangleIndexStartingIndices.append( startingTriangleIndex );
176 out.triangleIndexFids.append( fid );
177 out.tessellator->addPolygon( *polyBuffered, extrusionHeight );
178 if ( !out.tessellator->error().isEmpty() )
179 {
180 QgsMessageLog::logMessage( out.tessellator->error(), QObject::tr( "3D" ) );
181 }
182
183 delete polyBuffered;
184}
185
186void QgsBufferedLine3DSymbolHandler::finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context )
187{
188 // create entity for selected and not selected
189 makeEntity( parent, context, outNormal, false );
190 makeEntity( parent, context, outSelected, true );
191
192 mZMin = std::min( outNormal.tessellator->zMinimum(), outSelected.tessellator->zMinimum() );
193 mZMax = std::max( outNormal.tessellator->zMaximum(), outSelected.tessellator->zMaximum() );
194}
195
196
197void QgsBufferedLine3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, LineData &out, bool selected )
198{
199 if ( out.tessellator->dataVerticesCount() == 0 )
200 return; // nothing to show - no need to create the entity
201
202 QgsMaterialContext materialContext;
203 materialContext.setIsSelected( selected );
204 materialContext.setSelectionColor( context.map().selectionColor() );
205 Qt3DRender::QMaterial *mat = mSymbol->materialSettings()->toMaterial( QgsMaterialSettingsRenderingTechnique::Triangles, materialContext );
206
207 // extract vertex buffer data from tessellator
208 const QByteArray data( ( const char * )out.tessellator->data().constData(), out.tessellator->data().count() * sizeof( float ) );
209 const int nVerts = data.count() / out.tessellator->stride();
210
211 const QgsPhongTexturedMaterialSettings *texturedMaterialSettings = dynamic_cast< const QgsPhongTexturedMaterialSettings * >( mSymbol->materialSettings() );
212
213 QgsTessellatedPolygonGeometry *geometry = new QgsTessellatedPolygonGeometry( true, false, false,
214 texturedMaterialSettings ? texturedMaterialSettings->requiresTextureCoordinates() : false );
215 geometry->setData( data, nVerts, out.triangleIndexFids, out.triangleIndexStartingIndices );
216
217 Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer;
218 renderer->setGeometry( geometry );
219
220 // make entity
221 Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
222 entity->addComponent( renderer );
223 entity->addComponent( mat );
224 entity->setParent( parent );
225
226 if ( !selected )
227 renderer->setProperty( Qgs3DTypes::PROP_NAME_3D_RENDERER_FLAG, Qgs3DTypes::Main3DRenderer ); // temporary measure to distinguish between "selected" and "main"
228
229 // cppcheck wrongly believes entity will leak
230 // cppcheck-suppress memleak
231}
232
233
234// --------------
235
236
237class QgsThickLine3DSymbolHandler : public QgsFeature3DHandler
238{
239 public:
240 QgsThickLine3DSymbolHandler( const QgsLine3DSymbol *symbol, const QgsFeatureIds &selectedIds )
241 : mSymbol( static_cast< QgsLine3DSymbol * >( symbol->clone() ) )
242 , mSelectedIds( selectedIds )
243 {
244 }
245
246 bool prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames ) override;
247 void processFeature( const QgsFeature &feature, const Qgs3DRenderContext &context ) override;
248 void finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context ) override;
249
250 private:
251
252
253 void makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, QgsLineVertexData &out, bool selected );
254 Qt3DExtras::QPhongMaterial *material( const QgsLine3DSymbol &symbol ) const;
255 void processMaterialDatadefined( uint verticesCount, const QgsExpressionContext &context, QgsLineVertexData &out );
256
257 // input specific for this class
258 std::unique_ptr< QgsLine3DSymbol > mSymbol;
259 // inputs - generic
260 QgsFeatureIds mSelectedIds;
261
262 // outputs
263 QgsLineVertexData outNormal;
264 QgsLineVertexData outSelected;
265};
266
267
268
269bool QgsThickLine3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames )
270{
271 Q_UNUSED( attributeNames )
272
273 outNormal.withAdjacency = true;
274 outSelected.withAdjacency = true;
275 outNormal.init( mSymbol->altitudeClamping(), mSymbol->altitudeBinding(), mSymbol->offset(), &context.map() );
276 outSelected.init( mSymbol->altitudeClamping(), mSymbol->altitudeBinding(), mSymbol->offset(), &context.map() );
277
278 QSet<QString> attrs = mSymbol->dataDefinedProperties().referencedFields( context.expressionContext() );
279 attributeNames.unite( attrs );
280 attrs = mSymbol->materialSettings()->dataDefinedProperties().referencedFields( context.expressionContext() );
281 attributeNames.unite( attrs );
282
283 if ( mSymbol->materialSettings()->dataDefinedProperties().isActive( QgsAbstractMaterialSettings::Property::Ambient ) )
284 {
285 processMaterialDatadefined( outNormal.vertices.size(), context.expressionContext(), outNormal );
286 processMaterialDatadefined( outSelected.vertices.size(), context.expressionContext(), outSelected );
287 }
288
289 return true;
290}
291
292void QgsThickLine3DSymbolHandler::processFeature( const QgsFeature &f, const Qgs3DRenderContext &context )
293{
294 Q_UNUSED( context )
295 if ( f.geometry().isNull() )
296 return;
297
298 QgsLineVertexData &out = mSelectedIds.contains( f.id() ) ? outSelected : outNormal;
299
300 const int oldVerticesCount = out.vertices.size();
301
302 QgsGeometry geom = f.geometry();
304
305 // segmentize curved geometries if necessary
307 {
308 geom = QgsGeometry( g->segmentize() );
309 g = geom.constGet()->simplifiedTypeRef();
310 }
311
312 if ( const QgsLineString *ls = qgsgeometry_cast<const QgsLineString *>( g ) )
313 {
314 out.addLineString( *ls );
315 }
316 else if ( const QgsMultiLineString *mls = qgsgeometry_cast<const QgsMultiLineString *>( g ) )
317 {
318 for ( int nGeom = 0; nGeom < mls->numGeometries(); ++nGeom )
319 {
320 const QgsLineString *ls = mls->lineStringN( nGeom );
321 out.addLineString( *ls );
322 }
323 }
324
325 if ( mSymbol->materialSettings()->dataDefinedProperties().isActive( QgsAbstractMaterialSettings::Property::Ambient ) )
326 processMaterialDatadefined( out.vertices.size() - oldVerticesCount, context.expressionContext(), out );
327
328 mFeatureCount++;
329}
330
331void QgsThickLine3DSymbolHandler::finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context )
332{
333 // create entity for selected and not selected
334 makeEntity( parent, context, outNormal, false );
335 makeEntity( parent, context, outSelected, true );
336
337 updateZRangeFromPositions( outNormal.vertices );
338 updateZRangeFromPositions( outSelected.vertices );
339}
340
341
342void QgsThickLine3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, QgsLineVertexData &out, bool selected )
343{
344 if ( out.indexes.isEmpty() )
345 return;
346
347 // material (only ambient color is used for the color)
348 QgsMaterialContext materialContext;
349 materialContext.setIsSelected( selected );
350 materialContext.setSelectionColor( context.map().selectionColor() );
351 Qt3DRender::QMaterial *mat = mSymbol->materialSettings()->toMaterial( QgsMaterialSettingsRenderingTechnique::Lines, materialContext );
352 if ( !mat )
353 {
354 const QgsSimpleLineMaterialSettings defaultMaterial;
355 mat = defaultMaterial.toMaterial( QgsMaterialSettingsRenderingTechnique::Lines, materialContext );
356 }
357
358 if ( QgsLineMaterial *lineMaterial = dynamic_cast< QgsLineMaterial * >( mat ) )
359 lineMaterial->setLineWidth( mSymbol->width() );
360
361 Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
362
363 // geometry renderer
364 Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer;
365 renderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::LineStripAdjacency );
366 Qt3DQGeometry *geometry = out.createGeometry( entity );
367
368 if ( mSymbol->materialSettings()->dataDefinedProperties().isActive( QgsAbstractMaterialSettings::Property::Ambient ) )
369 mSymbol->materialSettings()->applyDataDefinedToGeometry( geometry, out.vertices.size(), out.materialDataDefined );
370
371 renderer->setGeometry( geometry );
372
373 renderer->setVertexCount( out.indexes.count() );
374 renderer->setPrimitiveRestartEnabled( true );
375 renderer->setRestartIndexValue( 0 );
376
377 // make entity
378 entity->addComponent( renderer );
379 entity->addComponent( mat );
380 entity->setParent( parent );
381}
382
383void QgsThickLine3DSymbolHandler::processMaterialDatadefined( uint verticesCount, const QgsExpressionContext &context, QgsLineVertexData &out )
384{
385 const QByteArray bytes = mSymbol->materialSettings()->dataDefinedVertexColorsAsByte( context );
386 out.materialDataDefined.append( bytes.repeated( verticesCount ) );
387}
388
389
390// --------------
391
392
393namespace Qgs3DSymbolImpl
394{
395
396 QgsFeature3DHandler *handlerForLine3DSymbol( QgsVectorLayer *layer, const QgsAbstract3DSymbol *symbol )
397 {
398 const QgsLine3DSymbol *lineSymbol = dynamic_cast< const QgsLine3DSymbol * >( symbol );
399 if ( !lineSymbol )
400 return nullptr;
401
402 if ( lineSymbol->renderAsSimpleLines() )
403 return new QgsThickLine3DSymbolHandler( lineSymbol, layer->selectedFeatureIds() );
404 else
405 return new QgsBufferedLine3DSymbolHandler( lineSymbol, layer->selectedFeatureIds() );
406 }
407
408 Qt3DCore::QEntity *entityForLine3DSymbol( const Qgs3DMapSettings &map, QgsVectorLayer *layer, const QgsLine3DSymbol &symbol )
409 {
410 QgsFeature3DHandler *handler = handlerForLine3DSymbol( layer, &symbol );
411 Qt3DCore::QEntity *e = entityFromHandler( handler, map, layer );
412 delete handler;
413 return e;
414 }
415}
416
JoinStyle
Join styles for buffers.
Definition: qgis.h:1694
@ Round
Use rounded joins.
EndCapStyle
End cap styles for buffers.
Definition: qgis.h:1681
@ Round
Round cap.
@ Polygon
Polygon.
@ MultiPolygon
MultiPolygon.
@ 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
static void clampAltitudes(QgsLineString *lineString, Qgis::AltitudeClamping altClamp, Qgis::AltitudeBinding altBind, const QgsPoint &centroid, float offset, const Qgs3DMapSettings &map)
Clamps altitude of vertices of a linestring according to the settings.
Definition: qgs3dutils.cpp:390
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.
@ Ambient
Ambient color (phong material)
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
int numGeometries() const
Returns the number of geometries within the collection.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:162
Q_GADGET bool isNull
Definition: qgsgeometry.h:164
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
Does vector analysis using the geos library and handles import, export, exception handling*.
Definition: qgsgeos.h:98
bool renderAsSimpleLines() const
Returns whether the renderer will render data with simple lines (otherwise it uses buffer)
QgsAbstract3DSymbol * clone() const override SIP_FACTORY
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.
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 line string geometry collection.
Multi polygon geometry collection.
QgsPolygon * polygonN(int index)
Returns the polygon with the specified index.
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.
Polygon geometry type.
Definition: qgspolygon.h:33
Qt3DRender::QMaterial * toMaterial(QgsMaterialSettingsRenderingTechnique technique, const QgsMaterialContext &context) const override
Creates a new QMaterial object representing the material settings.
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(Qgis::WkbType type)
Returns true if the WKB type is a curved type or can contain curved geometries.
Definition: qgswkbtypes.h:806
static Qgis::WkbType flatType(Qgis::WkbType type)
Returns the flat type for a WKB type.
Definition: qgswkbtypes.h:628
@ Triangles
Triangle based rendering (default)
@ Lines
Line based rendering, requires line data.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:5207
Qt3DCore::QGeometry Qt3DQGeometry
Definition: qgs3daxis.cpp:29
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
Qt3DCore::QAttribute Qt3DQAttribute
Qt3DCore::QBuffer Qt3DQBuffer
Qt3DCore::QGeometry Qt3DQGeometry