42#include <Qt3DCore/QBuffer>
43#include <Qt3DRender/QAbstractTextureImage>
44#include <Qt3DRender/QCullFace>
45#include <Qt3DRender/QEffect>
46#include <Qt3DRender/QGeometryRenderer>
47#include <Qt3DRender/QTechnique>
48#include <Qt3DRender/QTexture>
50using namespace Qt::StringLiterals;
55class QgsPolygon3DSymbolHandler :
public QgsFeature3DHandler
58 QgsPolygon3DSymbolHandler(
const QgsPolygon3DSymbol *symbol,
const QgsFeatureIds &selectedIds )
59 : mSymbol( static_cast<QgsPolygon3DSymbol *>( symbol->clone() ) )
60 , mSelectedIds( selectedIds )
63 bool prepare(
const Qgs3DRenderContext &context, QSet<QString> &attributeNames,
const QgsBox3D &chunkExtent )
override;
64 void processFeature(
const QgsFeature &f,
const Qgs3DRenderContext &context )
override;
65 void finalize( Qt3DCore::QEntity *parent,
const Qgs3DRenderContext &context )
override;
71 std::unique_ptr<QgsTessellator> tessellator;
72 QVector<QgsFeatureId> triangleIndexFids;
73 QVector<uint> triangleIndexStartingIndices;
74 QByteArray materialDataDefined;
75 QVector<float> ddTextureTransformData;
79 const QgsPolygon *poly,
82 float extrusionHeight,
83 float dataDefinedTextureScale,
84 float dataDefinedTextureRotation,
85 float dataDefinedTextureOffsetX,
86 float dataDefinedTextureOffsetY,
87 const Qgs3DRenderContext &context,
90 void processMaterialDatadefined( uint verticesCount,
const QgsExpressionContext &context, PolygonData &out );
91 void makeEntity( Qt3DCore::QEntity *parent,
const Qgs3DRenderContext &context, PolygonData &out,
bool selected );
92 QgsMaterial *material(
const QgsPolygon3DSymbol *symbol,
bool isSelected,
const Qgs3DRenderContext &context )
const;
95 std::unique_ptr<QgsPolygon3DSymbol> mSymbol;
99 PolygonData outNormal;
100 PolygonData outSelected;
102 QgsLineVertexData outEdges;
105 bool mWasClippedToExtent =
false;
109bool QgsPolygon3DSymbolHandler::prepare(
const Qgs3DRenderContext &context, QSet<QString> &attributeNames,
const QgsBox3D &chunkExtent )
111 mChunkOrigin = chunkExtent.
center();
112 mChunkOrigin.
setZ( 0. );
113 mChunkExtent = chunkExtent;
115 outEdges.init( mSymbol->altitudeClamping(), mSymbol->altitudeBinding(), 0, context, mChunkOrigin );
121 auto tessellator = std::make_unique<QgsTessellator>();
122 tessellator->setOrigin( mChunkOrigin );
123 tessellator->setAddNormals(
true );
124 tessellator->setInvertNormals( mSymbol->invertNormals() );
125 tessellator->setBackFacesEnabled( mSymbol->addBackFaces() );
126 tessellator->setExtrusionFaces( mSymbol->extrusionFaces() );
127 tessellator->setAddTextureUVs( requiresTextureCoordinates );
128 tessellator->setAddTangents( requiresTangents );
131 outNormal.tessellator = std::move( tessellator );
133 tessellator = std::make_unique<QgsTessellator>();
134 tessellator->setOrigin( mChunkOrigin );
135 tessellator->setAddNormals(
true );
136 tessellator->setInvertNormals( mSymbol->invertNormals() );
137 tessellator->setBackFacesEnabled( mSymbol->addBackFaces() );
138 tessellator->setExtrusionFaces( mSymbol->extrusionFaces() );
139 tessellator->setAddTextureUVs( requiresTextureCoordinates );
140 tessellator->setAddTangents( requiresTangents );
143 outSelected.tessellator = std::move( tessellator );
145 QSet<QString> attrs = mSymbol->dataDefinedProperties().referencedFields( context.
expressionContext() );
146 attributeNames.unite( attrs );
148 attributeNames.unite( attrs );
152static void addClosedRingEdges( QgsLineVertexData &edges,
const QgsLineString &ring,
float extraHeightOffset )
154 std::unique_ptr<QgsLineString> openRing( ring.
clone() );
155 openRing->deleteVertex(
QgsVertexId( 0, 0, openRing->numPoints() - 1 ) );
156 edges.addLineString( *openRing, extraHeightOffset,
true );
159void QgsPolygon3DSymbolHandler::processPolygon(
163 float extrusionHeight,
164 float dataDefinedTextureScale,
165 float dataDefinedTextureRotation,
166 float dataDefinedTextureOffsetX,
167 float dataDefinedTextureOffsetY,
172 std::unique_ptr<QgsPolygon> polyClone( poly->
clone() );
174 if ( mSymbol->edgesEnabled() )
183 if ( mWasClippedToExtent )
188 const QVector< QgsPointXY > extentPoints {
189 { mChunkExtent.xMinimum(), mChunkExtent.yMinimum() },
190 { mChunkExtent.xMaximum(), mChunkExtent.yMinimum() },
191 { mChunkExtent.xMaximum(), mChunkExtent.yMaximum() },
192 { mChunkExtent.xMinimum(), mChunkExtent.yMaximum() },
193 { mChunkExtent.xMinimum(), mChunkExtent.yMinimum() }
195 auto extentLinestring = std::make_unique<QgsLineString>( extentPoints );
199 cleanedExteriorRingGeometry = exteriorRingGeometry.
difference(
QgsGeometry( extentLinestring.release() ) );
207 if ( mWasClippedToExtent )
208 outEdges.addLineString( *line, offset );
210 addClosedRingEdges( outEdges, *line, offset );
215 for (
int i = 0; i < mline->numGeometries(); ++i )
219 outEdges.addLineString( *line, offset );
226 for (
int i = 0; i < polyClone->numInteriorRings(); ++i )
227 addClosedRingEdges( outEdges, *
static_cast<const QgsLineString *
>( polyClone->interiorRing( i ) ), offset );
229 if ( extrusionHeight != 0.f )
236 if ( mWasClippedToExtent )
237 outEdges.addLineString( *line, extrusionHeight + offset );
239 addClosedRingEdges( outEdges, *line, extrusionHeight + offset );
240 outEdges.addVerticalLines( *line, extrusionHeight, offset );
245 for (
int i = 0; i < mline->numGeometries(); ++i )
249 outEdges.addLineString( *line, extrusionHeight + offset );
250 outEdges.addVerticalLines( *line, extrusionHeight, offset );
257 for (
int i = 0; i < polyClone->numInteriorRings(); ++i )
260 addClosedRingEdges( outEdges, *interior, extrusionHeight + offset );
261 outEdges.addVerticalLines( *interior, extrusionHeight, offset );
268 Q_ASSERT( out.tessellator->dataVerticesCount() % 3 == 0 );
269 const uint startingTriangleIndex =
static_cast<uint
>( out.tessellator->dataVerticesCount() / 3 );
270 out.triangleIndexStartingIndices.append( startingTriangleIndex );
271 out.triangleIndexFids.append( fid );
273 const size_t oldVertexCount = out.tessellator->uniqueVertexCount();
274 out.tessellator->addPolygon( *polyClone, extrusionHeight );
275 if ( !out.tessellator->error().isEmpty() )
280 const QgsPropertyCollection &dataDefinedProperties = mSymbol->materialSettings()->dataDefinedProperties();
283 const uint newUniqueVertices = out.tessellator->uniqueVertexCount() - oldVertexCount;
284 processMaterialDatadefined( newUniqueVertices, context.
expressionContext(), out );
290 for ( uint i = 0; i < newUniqueVertices; ++i )
292 out.ddTextureTransformData << dataDefinedTextureOffsetX << dataDefinedTextureOffsetY << dataDefinedTextureScale << dataDefinedTextureRotation;
298void QgsPolygon3DSymbolHandler::processMaterialDatadefined( uint verticesCount,
const QgsExpressionContext &context, QgsPolygon3DSymbolHandler::PolygonData &out )
303 bytes = handler->dataDefinedVertexColorsAsByte( mSymbol->materialSettings(), context );
305 out.materialDataDefined.append( bytes.repeated( verticesCount ) );
313 PolygonData &out = mSelectedIds.contains( f.
id() ) ? outSelected : outNormal;
316 mWasClippedToExtent = clipGeometryIfTooLarge( geom );
334 float offset = mSymbol->offset();
335 float extrusionHeight = mSymbol->extrusionHeight();
338 if ( hasDDExtrusion )
342 const bool hasTextureScale = materialSettings
345 const bool hasTextureRotation = materialSettings
348 const bool hasTextureOffset = materialSettings
351 float dataDefinedTextureRotation = 0;
352 float dataDefinedTextureScale = 1;
353 float dataDefinedTextureOffsetX = 0;
354 float dataDefinedTextureOffsetY = 0;
357 dataDefinedTextureRotation = phongTexturedMaterial->textureRotation();
358 dataDefinedTextureScale = phongTexturedMaterial->textureScale();
359 dataDefinedTextureOffsetX = phongTexturedMaterial->textureOffset().x();
360 dataDefinedTextureOffsetY = phongTexturedMaterial->textureOffset().y();
364 dataDefinedTextureRotation = metalRoughTexturedMaterial->textureRotation();
365 dataDefinedTextureScale = metalRoughTexturedMaterial->textureScale();
366 dataDefinedTextureOffsetX = metalRoughTexturedMaterial->textureOffset().x();
367 dataDefinedTextureOffsetY = metalRoughTexturedMaterial->textureOffset().y();
370 if ( hasTextureScale || hasTextureRotation || hasTextureOffset )
372 dataDefinedTextureRotation =
static_cast< float >(
375 dataDefinedTextureScale =
static_cast< float >(
384 dataDefinedTextureOffsetX =
static_cast< float >( ddOffset.x() );
385 dataDefinedTextureOffsetY =
static_cast< float >( ddOffset.y() );
391 processPolygon( poly, f.
id(), offset, extrusionHeight, dataDefinedTextureScale, dataDefinedTextureRotation, dataDefinedTextureOffsetX, dataDefinedTextureOffsetY, context, out );
395 for (
int i = 0; i < mpoly->numGeometries(); ++i )
397 const QgsPolygon *poly = mpoly->polygonN( i );
398 processPolygon( poly, f.
id(), offset, extrusionHeight, dataDefinedTextureScale, dataDefinedTextureRotation, dataDefinedTextureOffsetX, dataDefinedTextureOffsetY, context, out );
403 for (
int i = 0; i < gc->numGeometries(); ++i )
409 processPolygon( poly, f.
id(), offset, extrusionHeight, dataDefinedTextureScale, dataDefinedTextureRotation, dataDefinedTextureOffsetX, dataDefinedTextureOffsetY, context, out );
415 for (
int i = 0; i < polySurface->numPatches(); ++i )
417 const QgsPolygon *poly = polySurface->patchN( i );
418 processPolygon( poly, f.
id(), offset, extrusionHeight, dataDefinedTextureScale, dataDefinedTextureRotation, dataDefinedTextureOffsetX, dataDefinedTextureOffsetY, context, out );
422 qWarning() <<
"not a polygon";
427void QgsPolygon3DSymbolHandler::finalize( Qt3DCore::QEntity *parent,
const Qgs3DRenderContext &context )
430 makeEntity( parent, context, outNormal,
false );
431 makeEntity( parent, context, outSelected,
true );
433 mZMin = std::min( outNormal.tessellator->zMinimum(), outSelected.tessellator->zMinimum() );
434 mZMax = std::max( outNormal.tessellator->zMaximum(), outSelected.tessellator->zMaximum() );
437 if ( mSymbol->edgesEnabled() && !outEdges.pointsA.isEmpty() && !mHighlightingEnabled )
439 QgsLineMaterial *mat =
new QgsLineMaterial;
440 mat->setLineColor( mSymbol->edgeColor() );
441 mat->setLineWidth( mSymbol->edgeWidth() );
443 Qt3DCore::QEntity *entity = outEdges.createSegmentEntity( mat );
444 entity->setObjectName( parent->objectName() +
"_EDGES" );
447 QgsGeoTransform *tr =
new QgsGeoTransform;
448 tr->setGeoTranslation( mChunkOrigin );
450 entity->addComponent( tr );
451 entity->setParent( parent );
453 if ( Qt3DCore::QEntity *joinEntity = outEdges.createJoinEntity( mat ) )
455 joinEntity->setObjectName( parent->objectName() +
"_EDGES_JOINS" );
456 QgsGeoTransform *joinTr =
new QgsGeoTransform;
457 joinTr->setGeoTranslation( mChunkOrigin );
458 joinEntity->addComponent( joinTr );
459 joinEntity->setParent( parent );
465void QgsPolygon3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent,
const Qgs3DRenderContext &context, PolygonData &polyData,
bool selected )
467 if ( polyData.tessellator->dataVerticesCount() == 0 )
470 QgsMaterial *mat = material( mSymbol.get(), selected, context );
473 const QByteArray vertexBuffer = polyData.tessellator->vertexBuffer();
474 const QByteArray indexBuffer = polyData.tessellator->indexBuffer();
475 const int vertexCount = polyData.tessellator->uniqueVertexCount();
476 const size_t indexCount = polyData.tessellator->dataVerticesCount();
480 mSymbol->invertNormals(),
481 mSymbol->addBackFaces(),
482 mSymbol->materialSettings() && mSymbol->materialSettings()->requiresTextureCoordinates(),
483 mSymbol->materialSettings() && mSymbol->materialSettings()->requiresTangents()
486 geometry->
setVertexBufferData( vertexBuffer, vertexCount, polyData.triangleIndexFids, polyData.triangleIndexStartingIndices );
489 if ( mSymbol->materialSettings()->dataDefinedProperties().hasActiveProperties() )
493 handler->applyDataDefinedToGeometry( mSymbol->materialSettings(), geometry, vertexCount, polyData.materialDataDefined );
496 if ( !polyData.ddTextureTransformData.isEmpty() )
498 auto textureTransformBuffer =
new Qt3DCore::QBuffer( geometry );
500 QByteArray byteArray(
501 reinterpret_cast<const char *
>( polyData.ddTextureTransformData.data() ),
502 static_cast<int>( polyData.ddTextureTransformData.size() *
sizeof(
float ) )
504 textureTransformBuffer->setData( byteArray );
505 auto textureTransformAttribute =
new Qt3DCore::QAttribute( geometry );
506 textureTransformAttribute->setName( u
"ddTextureTransform"_s );
507 textureTransformAttribute->setVertexBaseType( Qt3DCore::QAttribute::Float );
508 textureTransformAttribute->setVertexSize( 4 );
509 textureTransformAttribute->setAttributeType( Qt3DCore::QAttribute::VertexAttribute );
510 textureTransformAttribute->setBuffer( textureTransformBuffer );
511 textureTransformAttribute->setByteStride( 4 *
sizeof(
float ) );
512 textureTransformAttribute->setCount( vertexCount );
514 geometry->addAttribute( textureTransformAttribute );
517 Qt3DRender::QGeometryRenderer *renderer =
new Qt3DRender::QGeometryRenderer;
518 renderer->setGeometry( geometry );
521 QgsGeoTransform *tr =
new QgsGeoTransform;
522 tr->setGeoTranslation( mChunkOrigin );
525 Qt3DCore::QEntity *entity =
new Qt3DCore::QEntity;
526 entity->setObjectName( parent->objectName() +
"_CHUNK_MESH" );
527 entity->addComponent( renderer );
528 entity->addComponent( mat );
529 entity->addComponent( tr );
530 entity->setParent( parent );
542 const auto techniques = material->effect()->techniques();
543 for (
auto tit = techniques.constBegin(); tit != techniques.constEnd(); ++tit )
545 auto renderPasses = ( *tit )->renderPasses();
546 for (
auto rpit = renderPasses.begin(); rpit != renderPasses.end(); ++rpit )
548 Qt3DRender::QCullFace *cullFace =
new Qt3DRender::QCullFace;
550 ( *rpit )->addRenderState( cullFace );
561 const bool dataDefined = mSymbol->materialSettings()->dataDefinedProperties().hasActiveProperties();
563 applyCullingMode( symbol->
cullingMode(), material );
571namespace Qgs3DSymbolImpl
575 QgsFeature3DHandler *handlerForPolygon3DSymbol(
const QgsVectorLayer *layer,
const QgsAbstract3DSymbol *symbol )
577 const QgsPolygon3DSymbol *polygonSymbol =
dynamic_cast<const QgsPolygon3DSymbol *
>( symbol );
578 if ( !polygonSymbol )
@ Triangles
Triangle based rendering (default).
@ TrianglesDataDefined
Triangle based rendering with possibility of datadefined color.
Rendering context for preparation of 3D entities.
QgsExpressionContext & expressionContext()
Gets the expression context.
@ Main3DRenderer
Renderer for normal entities.
static const char * PROP_NAME_3D_RENDERER_FLAG
Qt property name to hold the 3D geometry renderer flag.
CullingMode
Triangle culling mode.
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 ¢roid, float offset, const Qgs3DRenderContext &context)
Clamps altitude of vertices of a linestring according to the settings.
static QgsMaterial * toMaterial(const QgsAbstractMaterialSettings *settings, Qgis::MaterialRenderingTechnique technique, const QgsMaterialContext &context)
Creates a new QgsMaterial object representing the material settings.
static const QgsAbstractMaterial3DHandler * handlerForMaterialSettings(const QgsAbstractMaterialSettings *settings)
Returns the handler to use for a material 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 QgsAbstractGeometry * clone() const =0
Clones the geometry by performing a deep copy.
Abstract base class for material 3D handlers.
Abstract base class for material settings.
virtual QSet< QgsAbstractMaterialSettings::Property > supportedProperties() const
Returns the set of data-defined properties supported by this material.
virtual bool requiresTangents() const
Returns true if the material requires tangents generated during triangulation.
@ TextureOffset
Texture offset.
@ TextureRotation
Texture rotation.
@ TextureScale
Texture scale.
QgsPropertyCollection dataDefinedProperties() const
Returns the symbol material property collection, used for data defined overrides.
virtual bool requiresTextureCoordinates() const
Returns true if the material requires texture coordinates to be generated during triangulation.
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.
A 3-dimensional box composed of x, y, z coordinates.
QgsVector3D center() const
Returns the center of the box as a vector.
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...
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.
bool isEmpty() const
Returns true if the geometry is empty (eg a linestring with no vertices, or a collection with no geom...
bool convertGeometryCollectionToSubclass(Qgis::GeometryType geomType)
Converts geometry collection to a the desired geometry type subclass (multi-point,...
QgsGeometry difference(const QgsGeometry &geometry, const QgsGeometryParameters ¶meters=QgsGeometryParameters(), QgsFeedback *feedback=nullptr) const
Returns a geometry representing the points making up this geometry that do not make up other.
Line string geometry type, with support for z-dimension and m-values.
QgsLineString * clone() const override
Clones the geometry by performing a deep copy.
Context settings for a material.
void setIsSelected(bool isSelected)
Sets whether the material should represent a selected state.
void setIsHighlighted(bool isHighlighted)
Sets whether the material should represent a highlighted state.
static QgsMaterialContext fromRenderContext(const Qgs3DRenderContext &context)
Constructs a material context from the settings in a 3D render context.
Base class for all materials used within QGIS 3D views.
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(), Qgis::StringFormat format=Qgis::StringFormat::PlainText)
Adds a message to the log instance (and creates it if necessary).
A PBR metal rough shading material used for rendering with support for image texture maps.
Multi line string geometry collection.
Multi polygon geometry collection.
A Phong shading model with diffuse texture map.
3D symbol that draws polygon geometries as planar polygons, optionally extruded (with added walls).
Qgs3DTypes::CullingMode cullingMode() const
Returns front/back culling mode.
QgsAbstractMaterialSettings * materialSettings() const override
Returns material settings used for shading of the symbol.
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.
QVariant value(int key, const QgsExpressionContext &context, const QVariant &defaultValue=QVariant()) const final
Returns the calculated value of the property with the specified key from within the collection.
bool isActive(int key) const final
Returns true if the collection contains an active property with the specified key.
bool hasActiveProperties() const final
Returns true if the collection has any active properties, or false if all properties within the colle...
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.
static QPointF toPoint(const QVariant &value, bool *ok=nullptr)
Converts a value to a point.
Qt3DRender::QGeometry subclass that represents polygons tessellated into 3D geometry.
void setVertexBufferData(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.
void setIndexBufferData(const QByteArray &indexBufferData, size_t indexCount)
Sets index buffer data.
void setZ(double z)
Sets Z coordinate.
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.
T qgsgeometry_cast(QgsAbstractGeometry *geom)
QSet< QgsFeatureId > QgsFeatureIds
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
Utility class for identifying a unique vertex within a geometry.