QGIS API Documentation 3.39.0-Master (47f7b3a4989)
Loading...
Searching...
No Matches
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
JoinStyle
Join styles for buffers.
Definition qgis.h:1791
@ Round
Use rounded joins.
EndCapStyle
End cap styles for buffers.
Definition qgis.h:1778
@ 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.
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:58
QgsFeatureId id
Definition qgsfeature.h:66
QgsGeometry geometry
Definition qgsfeature.h:69
int numGeometries() const
Returns the number of geometries within the collection.
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.
Does vector analysis using the geos library and handles import, export, exception handling*.
Definition qgsgeos.h:137
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.
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....
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.
static Qgis::WkbType flatType(Qgis::WkbType type)
Returns the flat type for a WKB type.
@ 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:5465
Qt3DCore::QGeometry Qt3DQGeometry
QSet< QgsFeatureId > QgsFeatureIds
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
Qt3DCore::QAttribute Qt3DQAttribute
Qt3DCore::QBuffer Qt3DQBuffer
Qt3DCore::QGeometry Qt3DQGeometry