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