QGIS API Documentation 4.1.0-Master (3fcefe620d1)
Loading...
Searching...
No Matches
qgspoint3dsymbol_p.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgspoint3dsymbol_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 "qgspoint3dsymbol_p.h"
17
18#include <memory>
19
20#include "qgs3d.h"
21#include "qgs3drendercontext.h"
22#include "qgs3dutils.h"
23#include "qgsapplication.h"
26#include "qgsgeotransform.h"
27#include "qgsgltf3dutils.h"
29#include "qgslogger.h"
32#include "qgsobj3dutils.h"
33#include "qgsphongmaterial.h"
37#include "qgspoint3dsymbol.h"
38#include "qgssourcecache.h"
39#include "qgstexturematerial.h"
40#include "qgsvectorlayer.h"
41
42#include <QFileInfo>
43#include <QString>
44#include <QUrl>
45#include <QVector3D>
46#include <Qt3DCore/QAttribute>
47#include <Qt3DCore/QBuffer>
48#include <Qt3DCore/QGeometry>
49#include <Qt3DExtras/QConeGeometry>
50#include <Qt3DExtras/QCuboidGeometry>
51#include <Qt3DExtras/QCylinderGeometry>
52#include <Qt3DExtras/QExtrudedTextGeometry>
53#include <Qt3DExtras/QPhongMaterial>
54#include <Qt3DExtras/QPlaneGeometry>
55#include <Qt3DExtras/QSphereGeometry>
56#include <Qt3DExtras/QTorusGeometry>
57#include <Qt3DRender/QEffect>
58#include <Qt3DRender/QGeometryRenderer>
59#include <Qt3DRender/QGraphicsApiFilter>
60#include <Qt3DRender/QPaintedTextureImage>
61#include <Qt3DRender/QParameter>
62#include <Qt3DRender/QTechnique>
63
64using namespace Qt::StringLiterals;
65
67
68
69//* INSTANCED RENDERING *//
70
71
72class QgsInstancedPoint3DSymbolHandler : public QgsFeature3DHandler
73{
74 public:
75 QgsInstancedPoint3DSymbolHandler( const QgsPoint3DSymbol *symbol, const QgsFeatureIds &selectedIds )
76 : mSymbol( static_cast<QgsPoint3DSymbol *>( symbol->clone() ) )
77 , mSelectedIds( selectedIds )
78 {}
79
80 bool prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames, const QgsBox3D &chunkExtent ) override;
81 void processFeature( const QgsFeature &feature, const Qgs3DRenderContext &context ) override;
82 void finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context ) override;
83
84 private:
85 static QgsMaterial *material( const QgsPoint3DSymbol *symbol, const QgsMaterialContext &materialContext, bool hasDataDefinedScale, bool hasDataDefinedRotation );
86 static Qt3DRender::QGeometryRenderer *renderer( const QgsPoint3DSymbol *symbol, const QVector<QVector3D> &positions, const QVector<QVector3D> &scales, const QVector<QVector4D> rotations );
87 static Qt3DCore::QGeometry *symbolGeometry( const QgsPoint3DSymbol *symbol );
88
90 struct PointData
91 {
92 QVector<QVector3D> positions; // contains triplets of float x,y,z for each point
93 QVector<QVector3D> scales;
94 QVector<QVector4D> rotations;
95 };
96
97 void makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, PointData &out, bool selected );
98
99 // input specific for this class
100 std::unique_ptr<QgsPoint3DSymbol> mSymbol;
101 QVector3D mSymbolScale;
102 QQuaternion mSymbolRotation;
103 QVector3D mPointTranslation;
104 // inputs - generic
105 QgsFeatureIds mSelectedIds;
106 // outputs
107 PointData outNormal;
108 PointData outSelected;
109};
110
111
112bool QgsInstancedPoint3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames, const QgsBox3D &chunkExtent )
113{
114 mChunkOrigin = chunkExtent.center();
115 mChunkExtent = chunkExtent;
116
117 QSet<QString> attrs = mSymbol->dataDefinedProperties().referencedFields( context.expressionContext() );
118 attributeNames.unite( attrs );
119 attrs = mSymbol->materialSettings()->dataDefinedProperties().referencedFields( context.expressionContext() );
120 attributeNames.unite( attrs );
121
122 Qgs3DUtils::decomposeTransformMatrix( mSymbol->transform(), mPointTranslation, mSymbolRotation, mSymbolScale );
123
124 return true;
125}
126
127void QgsInstancedPoint3DSymbolHandler::processFeature( const QgsFeature &feature, const Qgs3DRenderContext &context )
128{
129 PointData &out = mSelectedIds.contains( feature.id() ) ? outSelected : outNormal;
130
131 if ( feature.geometry().isNull() )
132 return;
133
134 const QgsPropertyCollection &ddp = mSymbol->dataDefinedProperties();
135
136 QgsVector3D translation = mPointTranslation;
137 const bool hasDDTranslation = ddp.isActive( QgsAbstract3DSymbol::Property::TranslationX )
140 if ( hasDDTranslation )
141 {
142 const double translationX = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::TranslationX, context.expressionContext(), translation.x() );
143 const double translationY = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::TranslationY, context.expressionContext(), translation.y() );
144 const double translationZ = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::TranslationZ, context.expressionContext(), translation.z() );
145 translation = QgsVector3D( translationX, translationY, translationZ );
146 }
147
148 const std::size_t oldSize = out.positions.size();
149 Qgs3DUtils::extractPointPositions( feature, context, mChunkOrigin, mSymbol->altitudeClamping(), out.positions, translation );
150
151 const std::size_t added = out.positions.size() - oldSize;
152
154
155 if ( hasDDScale )
156 {
157 out.scales.resize( out.positions.size() );
158 QVector3D *outScale = out.scales.data() + oldSize;
159
160 const double scaleX = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::ScaleX, context.expressionContext(), mSymbolScale.x() );
161 const double scaleY = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::ScaleY, context.expressionContext(), mSymbolScale.y() );
162 const double scaleZ = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::ScaleZ, context.expressionContext(), mSymbolScale.z() );
163
164 for ( std::size_t i = 0; i < added; ++i )
165 {
166 ( *outScale++ ) = QVector3D( static_cast< float >( scaleX ), static_cast< float >( scaleY ), static_cast< float >( scaleZ ) );
167 }
168 }
169
170 const bool hasDDRotation = ddp.isActive( QgsAbstract3DSymbol::Property::RotationX )
173 if ( hasDDRotation )
174 {
175 out.rotations.resize( out.positions.size() );
176 QVector4D *outRotation = out.rotations.data() + oldSize;
177
178 // extract default rotation components from symbol rotation
179 const QVector3D baseEuler = mSymbolRotation.toEulerAngles();
180
181 const double rotationX = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::RotationX, context.expressionContext(), baseEuler.x() );
182 const double rotationY = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::RotationY, context.expressionContext(), baseEuler.y() );
183 const double rotationZ = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::RotationZ, context.expressionContext(), baseEuler.z() );
184
185 //... and then re-calculate the rotation vector for this feature
186 const QQuaternion finalQuat = QQuaternion::fromEulerAngles( static_cast< float >( rotationX ), static_cast< float >( rotationY ), static_cast< float >( rotationZ ) );
187 const QVector4D finalVec4 = finalQuat.toVector4D();
188
189 for ( std::size_t i = 0; i < added; ++i )
190 {
191 ( *outRotation++ ) = finalVec4;
192 }
193 }
194
195 mFeatureCount++;
196}
197
198void QgsInstancedPoint3DSymbolHandler::finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context )
199{
200 makeEntity( parent, context, outNormal, false );
201 makeEntity( parent, context, outSelected, true );
202
203 auto updateZRangeFromPointData = [this]( const PointData &pointData ) {
204 const QVector3D *scales = pointData.scales.empty() ? nullptr : pointData.scales.constData();
205 for ( const QVector3D &pos : std::as_const( pointData.positions ) )
206 {
207 double minZ = 0;
208 double maxZ = 0;
209
210 // also account for the actual height of the objects themselves
211 // NOTE -- these calculations are naive, and assume no rotation or scaling of the symbol!
212 switch ( mSymbol->shape() )
213 {
215 {
216 const float length = mSymbol->shapeProperty( u"length"_s ).toFloat();
217 minZ -= length * 0.5f;
218 maxZ += length * 0.5f;
219 break;
220 }
221
223 {
224 const float radius = mSymbol->shapeProperty( u"radius"_s ).toFloat();
225 minZ -= radius;
226 maxZ += radius;
227 break;
228 }
229
231 {
232 const float length = mSymbol->shapeProperty( u"length"_s ).toFloat();
233 minZ -= length * 0.5f;
234 maxZ += length * 0.5f;
235 break;
236 }
237
239 {
240 const float size = mSymbol->shapeProperty( u"size"_s ).toFloat();
241 minZ -= size * 0.5f;
242 maxZ += size * 0.5f;
243 break;
244 }
245
247 {
248 const float radius = mSymbol->shapeProperty( u"radius"_s ).toFloat();
249 minZ -= radius;
250 maxZ += radius;
251 break;
252 }
253
255 {
256 // worst case scenario -- even though planes are usually rotated so that they are flat,
257 // let's account for possible overridden rotation
258 const float size = mSymbol->shapeProperty( u"size"_s ).toFloat();
259 minZ -= size * 0.5f;
260 maxZ += size * 0.5f;
261 break;
262 }
263
267 break;
268 }
269
270 if ( scales )
271 {
272 const double zScale = ( *scales++ )[2];
273 minZ *= zScale;
274 maxZ *= zScale;
275 }
276
277 // as we are relative to chunk center elevation we have to add mChunkOrigin.z()
278 minZ += pos.z() + mChunkOrigin.z();
279 maxZ += pos.z() + mChunkOrigin.z();
280
281 if ( minZ < mZMin )
282 mZMin = static_cast< float >( minZ );
283 if ( maxZ > mZMax )
284 mZMax = static_cast< float >( maxZ );
285 }
286 };
287
288 updateZRangeFromPointData( outNormal );
289 updateZRangeFromPointData( outSelected );
290}
291
292void QgsInstancedPoint3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, PointData &out, bool selected )
293{
294 if ( out.positions.isEmpty() )
295 {
296 return; // nothing to show - no need to create the entity
297 }
298
299 // build the default material
301 materialContext.setIsSelected( selected );
302 materialContext.setIsHighlighted( mHighlightingEnabled );
303 QgsMaterial *mat = material( mSymbol.get(), materialContext, !out.scales.empty(), !out.rotations.empty() );
304 if ( !mat )
305 return;
306
307 mat->addParameter( new Qt3DRender::QParameter( "symbolScale", mSymbolScale, mat ) );
308 mat->addParameter( new Qt3DRender::QParameter( "symbolRotation", mSymbolRotation.toVector4D(), mat ) );
309
310 // add transform (our geometry has coordinates relative to mChunkOrigin)
311 QgsGeoTransform *tr = new QgsGeoTransform;
312 tr->setGeoTranslation( mChunkOrigin );
313
314 // build the entity
315 Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
316 entity->addComponent( renderer( mSymbol.get(), out.positions, out.scales, out.rotations ) );
317 entity->addComponent( mat );
318 entity->addComponent( tr );
319 entity->setParent( parent );
320
321 // cppcheck wrongly believes entity will leak
322 // cppcheck-suppress memleak
323}
324
325
326QgsMaterial *QgsInstancedPoint3DSymbolHandler::material( const QgsPoint3DSymbol *symbol, const QgsMaterialContext &materialContext, bool hasDataDefinedScale, bool hasDataDefinedRotation )
327{
329 if ( hasDataDefinedScale )
331 if ( hasDataDefinedRotation )
333
334 const QString upAxis = symbol->shapeProperty( u"upAxis"_s ).toString();
335 const QString forwardAxis = symbol->shapeProperty( u"forwardAxis"_s ).toString();
336
337 const QMatrix4x4 meshTransform = Qgs3DUtils::axisTransformMatrix( !upAxis.isEmpty() ? upAxis : u"y"_s, !forwardAxis.isEmpty() ? forwardAxis : u"-z"_s );
338
339 if ( materialContext.isHighlighted() )
340 {
341 QgsHighlightMaterial *mat = new QgsHighlightMaterial();
342 mat->setInstancingEnabled( true, flags );
343 mat->setInstancingMeshTransform( meshTransform );
344 return mat;
345 }
346
347 const QgsAbstractMaterialSettings *settings = symbol->materialSettings();
348 if ( const QgsAbstractMaterial3DHandler *handler = Qgs3D::handlerForMaterialSettings( settings ) )
349 {
350 return handler->toInstancedMaterial( settings, materialContext, flags, meshTransform );
351 }
352
353 return nullptr;
354}
355
356Qt3DRender::QGeometryRenderer *QgsInstancedPoint3DSymbolHandler::renderer(
357 const QgsPoint3DSymbol *symbol, const QVector<QVector3D> &positions, const QVector<QVector3D> &scales, const QVector<QVector4D> rotations
358)
359{
360 const std::size_t count = positions.count();
361 const std::size_t byteCount = positions.count() * sizeof( QVector3D );
362 QByteArray ba;
363 ba.resize( byteCount );
364 memcpy( ba.data(), positions.constData(), byteCount );
365
366 Qt3DCore::QBuffer *instanceBuffer = new Qt3DCore::QBuffer();
367 instanceBuffer->setData( ba );
368
369 Qt3DCore::QAttribute *instanceTranslationAttribute = new Qt3DCore::QAttribute;
370 instanceTranslationAttribute->setName( u"instanceTranslation"_s );
371 instanceTranslationAttribute->setAttributeType( Qt3DCore::QAttribute::VertexAttribute );
372 instanceTranslationAttribute->setVertexBaseType( Qt3DCore::QAttribute::Float );
373 instanceTranslationAttribute->setVertexSize( 3 );
374 instanceTranslationAttribute->setByteOffset( 0 );
375 instanceTranslationAttribute->setDivisor( 1 );
376 instanceTranslationAttribute->setBuffer( instanceBuffer );
377 instanceTranslationAttribute->setCount( count );
378 instanceTranslationAttribute->setByteStride( 3 * sizeof( float ) );
379
380 Qt3DCore::QGeometry *geometry = symbolGeometry( symbol );
381 geometry->addAttribute( instanceTranslationAttribute );
382 geometry->setBoundingVolumePositionAttribute( instanceTranslationAttribute );
383
384 if ( !scales.empty() )
385 {
386 auto scaleBuffer = new Qt3DCore::QBuffer();
387 auto instanceScaleAttribute = new Qt3DCore::QAttribute;
388 instanceScaleAttribute->setName( u"instanceScale"_s );
389 instanceScaleAttribute->setAttributeType( Qt3DCore::QAttribute::VertexAttribute );
390 instanceScaleAttribute->setVertexBaseType( Qt3DCore::QAttribute::Float );
391 instanceScaleAttribute->setVertexSize( 3 );
392 instanceScaleAttribute->setByteOffset( 0 );
393 instanceScaleAttribute->setDivisor( 1 );
394 instanceScaleAttribute->setByteStride( 3 * sizeof( float ) );
395 QByteArray scaleBa;
396 scaleBa.resize( byteCount );
397 memcpy( scaleBa.data(), scales.constData(), byteCount );
398
399 scaleBuffer->setData( scaleBa );
400 instanceScaleAttribute->setCount( count );
401
402 instanceScaleAttribute->setBuffer( scaleBuffer );
403 geometry->addAttribute( instanceScaleAttribute );
404 }
405
406 if ( !rotations.empty() )
407 {
408 auto rotationBuffer = new Qt3DCore::QBuffer();
409 auto instanceRotationAttribute = new Qt3DCore::QAttribute;
410 instanceRotationAttribute->setName( u"instanceRotation"_s );
411 instanceRotationAttribute->setAttributeType( Qt3DCore::QAttribute::VertexAttribute );
412 instanceRotationAttribute->setVertexBaseType( Qt3DCore::QAttribute::Float );
413 instanceRotationAttribute->setVertexSize( 4 );
414 instanceRotationAttribute->setByteOffset( 0 );
415 instanceRotationAttribute->setDivisor( 1 );
416 instanceRotationAttribute->setByteStride( 4 * sizeof( float ) );
417
418 QByteArray rotationBa;
419 const std::size_t rotationByteCount = positions.count() * sizeof( QVector4D );
420 rotationBa.resize( rotationByteCount );
421 memcpy( rotationBa.data(), rotations.constData(), rotationByteCount );
422 rotationBuffer->setData( rotationBa );
423 instanceRotationAttribute->setCount( count );
424
425 instanceRotationAttribute->setBuffer( rotationBuffer );
426 geometry->addAttribute( instanceRotationAttribute );
427 }
428
429 Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer;
430 renderer->setGeometry( geometry );
431 renderer->setInstanceCount( count );
432
433 return renderer;
434}
435
436Qt3DCore::QGeometry *QgsInstancedPoint3DSymbolHandler::symbolGeometry( const QgsPoint3DSymbol *symbol )
437{
438 switch ( symbol->shape() )
439 {
441 {
442 const float radius = symbol->shapeProperty( u"radius"_s ).toFloat();
443 const float length = symbol->shapeProperty( u"length"_s ).toFloat();
444 Qt3DExtras::QCylinderGeometry *g = new Qt3DExtras::QCylinderGeometry;
445 //g->setRings(2); // how many vertices vertically
446 //g->setSlices(8); // how many vertices on circumference
447 g->setRadius( radius );
448 g->setLength( length );
449 return g;
450 }
451
453 {
454 const float radius = symbol->shapeProperty( u"radius"_s ).toFloat();
455 const int rings = symbol->shapeProperty( u"rings"_s ).toInt();
456 const int slices = symbol->shapeProperty( u"slices"_s ).toInt();
457
458 const bool tangents = symbol->materialSettings() && symbol->materialSettings()->requiresTangents();
459 Qt3DExtras::QSphereGeometry *g = new Qt3DExtras::QSphereGeometry;
460 g->setRadius( radius );
461 g->setRings( rings );
462 g->setSlices( slices );
463 g->setGenerateTangents( tangents );
464 return g;
465 }
466
468 {
469 const float length = symbol->shapeProperty( u"length"_s ).toFloat();
470 const float bottomRadius = symbol->shapeProperty( u"bottomRadius"_s ).toFloat();
471 const float topRadius = symbol->shapeProperty( u"topRadius"_s ).toFloat();
472
473 Qt3DExtras::QConeGeometry *g = new Qt3DExtras::QConeGeometry;
474 g->setLength( length );
475 g->setBottomRadius( bottomRadius );
476 g->setTopRadius( topRadius );
477 //g->setHasBottomEndcap(hasBottomEndcap);
478 //g->setHasTopEndcap(hasTopEndcap);
479 return g;
480 }
481
483 {
484 const float size = symbol->shapeProperty( u"size"_s ).toFloat();
485 Qt3DExtras::QCuboidGeometry *g = new Qt3DExtras::QCuboidGeometry;
486 g->setXExtent( size );
487 g->setYExtent( size );
488 g->setZExtent( size );
489 return g;
490 }
491
493 {
494 const float radius = symbol->shapeProperty( u"radius"_s ).toFloat();
495 const float minorRadius = symbol->shapeProperty( u"minorRadius"_s ).toFloat();
496 Qt3DExtras::QTorusGeometry *g = new Qt3DExtras::QTorusGeometry;
497 g->setRadius( radius );
498 g->setMinorRadius( minorRadius );
499 return g;
500 }
501
503 {
504 const float size = symbol->shapeProperty( u"size"_s ).toFloat();
505 Qt3DExtras::QPlaneGeometry *g = new Qt3DExtras::QPlaneGeometry;
506 g->setWidth( size );
507 g->setHeight( size );
508 return g;
509 }
510
512 {
513 const float depth = symbol->shapeProperty( u"depth"_s ).toFloat();
514 const QString text = symbol->shapeProperty( u"text"_s ).toString();
515 Qt3DExtras::QExtrudedTextGeometry *g = new Qt3DExtras::QExtrudedTextGeometry;
516 g->setDepth( depth );
517 g->setText( text );
518 return g;
519 }
520
523 break;
524 }
525 Q_ASSERT( false );
526 return nullptr;
527}
528
529//* 3D MODEL RENDERING *//
530
531
532class QgsModelPoint3DSymbolHandler : public QgsFeature3DHandler
533{
534 public:
535 QgsModelPoint3DSymbolHandler( const QgsPoint3DSymbol *symbol, const QgsFeatureIds &selectedIds )
536 : mSymbol( static_cast<QgsPoint3DSymbol *>( symbol->clone() ) )
537 , mSelectedIds( selectedIds )
538 {}
539
540 bool prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames, const QgsBox3D &chunkExtent ) override;
541 void processFeature( const QgsFeature &feature, const Qgs3DRenderContext &context ) override;
542 void finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context ) override;
543
544 private:
545 void addInstancedEntities(
546 const QVector<QVector3D> &positions,
547 const QVector<QVector3D> &scales,
548 const QVector<QQuaternion> &rotations,
549 const QgsVector3D &chunkOrigin,
550 const QgsPoint3DSymbol *symbol,
551 Qt3DCore::QEntity *parent,
552 const QgsMaterialContext &materialContext,
553 bool useEmbeddedTexture
554 );
555
557 struct PointData
558 {
559 QVector<QVector3D> positions; // contains triplets of float x,y,z for each point
560 QVector<QVector3D> scales;
561 QVector<QQuaternion> rotations;
562 };
563
564 void makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, PointData &out, bool selected );
565
566 // input specific for this class
567 std::unique_ptr<QgsPoint3DSymbol> mSymbol;
568
569 QVector3D mSymbolScale;
570 QQuaternion mSymbolRotation;
571 QVector3D mPointTranslation;
572
573 // inputs - generic
574 QgsFeatureIds mSelectedIds;
575 // outputs
576 PointData outNormal;
577 PointData outSelected;
578};
579
580bool QgsModelPoint3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames, const QgsBox3D &chunkExtent )
581{
582 Q_UNUSED( context )
583 Q_UNUSED( attributeNames )
584
585 mChunkOrigin = chunkExtent.center();
586 mChunkExtent = chunkExtent;
587
588 QSet<QString> attrs = mSymbol->dataDefinedProperties().referencedFields( context.expressionContext() );
589 attributeNames.unite( attrs );
590
591 Qgs3DUtils::decomposeTransformMatrix( mSymbol->transform(), mPointTranslation, mSymbolRotation, mSymbolScale );
592 return true;
593}
594
595void QgsModelPoint3DSymbolHandler::processFeature( const QgsFeature &feature, const Qgs3DRenderContext &context )
596{
597 PointData &out = mSelectedIds.contains( feature.id() ) ? outSelected : outNormal;
598
599 if ( feature.geometry().isNull() )
600 return;
601
602 const QgsPropertyCollection &ddp = mSymbol->dataDefinedProperties();
603
604 QgsVector3D translation = mPointTranslation;
605 const bool hasDDTranslation = ddp.isActive( QgsAbstract3DSymbol::Property::TranslationX )
608 if ( hasDDTranslation )
609 {
610 const double translationX = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::TranslationX, context.expressionContext(), translation.x() );
611 const double translationY = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::TranslationY, context.expressionContext(), translation.y() );
612 const double translationZ = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::TranslationZ, context.expressionContext(), translation.z() );
613 translation = QgsVector3D( translationX, translationY, translationZ );
614 }
615
616 const std::size_t oldSize = out.positions.size();
617 Qgs3DUtils::extractPointPositions( feature, context, mChunkOrigin, mSymbol->altitudeClamping(), out.positions, translation );
618 const std::size_t added = out.positions.size() - oldSize;
619
621 if ( hasDDScale )
622 {
623 out.scales.resize( out.positions.size() );
624 QVector3D *outScale = out.scales.data() + oldSize;
625 const double scaleX = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::ScaleX, context.expressionContext(), mSymbolScale.x() );
626 const double scaleY = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::ScaleY, context.expressionContext(), mSymbolScale.y() );
627 const double scaleZ = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::ScaleZ, context.expressionContext(), mSymbolScale.z() );
628 const QVector3D scale( static_cast< float >( scaleX ), static_cast< float >( scaleY ), static_cast< float >( scaleZ ) );
629 for ( std::size_t i = 0; i < added; ++i )
630 ( *outScale++ ) = scale;
631 }
632
633 const bool hasDDRotation = ddp.isActive( QgsAbstract3DSymbol::Property::RotationX )
636 if ( hasDDRotation )
637 {
638 out.rotations.resize( out.positions.size() );
639 QQuaternion *outRotation = out.rotations.data() + oldSize;
640 const QVector3D baseEuler = mSymbolRotation.toEulerAngles();
641 const double rotationX = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::RotationX, context.expressionContext(), baseEuler.x() );
642 const double rotationY = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::RotationY, context.expressionContext(), baseEuler.y() );
643 const double rotationZ = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::RotationZ, context.expressionContext(), baseEuler.z() );
644 const QQuaternion rotation = QQuaternion::fromEulerAngles( static_cast< float >( rotationX ), static_cast< float >( rotationY ), static_cast< float >( rotationZ ) );
645 for ( std::size_t i = 0; i < added; ++i )
646 ( *outRotation++ ) = rotation;
647 }
648
649 mFeatureCount++;
650}
651
652void QgsModelPoint3DSymbolHandler::finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context )
653{
654 makeEntity( parent, context, outNormal, false );
655 makeEntity( parent, context, outSelected, true );
656
657 updateZRangeFromPositions( outNormal.positions );
658 updateZRangeFromPositions( outSelected.positions );
659
660 // the elevation offset is applied separately in QTransform added to sub-entities
661 const float symbolHeight = mSymbol->transform().data()[14];
662 // as we are relative to chunk center elevation we have to add mChunkOrigin.z()
663 mZMin += static_cast<float>( symbolHeight + mChunkOrigin.z() );
664 mZMax += static_cast<float>( symbolHeight + mChunkOrigin.z() );
665}
666
667void QgsModelPoint3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, PointData &out, bool selected )
668{
669 if ( out.positions.isEmpty() )
670 {
671 return; // nothing to show - no need to create the entity
672 }
673
674 const QgsAbstractMaterialSettings *settings = mSymbol->materialSettings();
675 const bool useEmbeddedTexture = !mSymbol->shapeProperty( u"overwriteMaterial"_s ).toBool() && ( !settings || settings->type() == "null"_L1 );
676
678 materialContext.setIsSelected( selected );
679 materialContext.setIsHighlighted( mHighlightingEnabled );
680
681 addInstancedEntities( out.positions, out.scales, out.rotations, mChunkOrigin, mSymbol.get(), parent, materialContext, useEmbeddedTexture );
682}
683
684void QgsModelPoint3DSymbolHandler::addInstancedEntities(
685 const QVector<QVector3D> &positions,
686 const QVector<QVector3D> &scales,
687 const QVector<QQuaternion> &rotations,
688 const QgsVector3D &chunkOrigin,
689 const QgsPoint3DSymbol *symbol,
690 Qt3DCore::QEntity *parent,
691 const QgsMaterialContext &materialContext,
692 bool useEmbeddedTexture
693)
694{
695 const QString source = QgsApplication::sourceCache()->localFilePath( symbol->shapeProperty( u"model"_s ).toString() );
696
697 std::vector<QgsMeshNodeData> meshes;
698
699 const QString suffix = QFileInfo( source ).suffix().toLower();
700 if ( suffix == "gltf"_L1 || suffix == "glb"_L1 )
701 {
702 QStringList gltfErrors;
703 for ( QgsMeshNodeData &m : QgsGltf3DUtils::buildGltfGeometries( source, materialContext, &gltfErrors, parent ) )
704 meshes.push_back( std::move( m ) );
705 if ( !gltfErrors.isEmpty() )
706 QgsDebugError( u"GLTF instancing errors for '%1': %2"_s.arg( source, gltfErrors.join( ", "_L1 ) ) );
707 }
708 else if ( suffix == "obj"_L1 )
709 {
710 for ( QgsMeshNodeData &m : QgsObj3DUtils::buildObjGeometries( source, materialContext ) )
711 meshes.push_back( std::move( m ) );
712 }
713 else
714 {
715 QgsDebugError( u"Unsupported model file suffix '%1' for source: %2"_s.arg( suffix, source ) );
716 return;
717 }
718
719 if ( meshes.empty() )
720 {
721 QgsDebugMsgLevel( u"No meshes loaded for model symbol source: %1"_s.arg( source ), 2 );
722 return;
723 }
724
725 const int count = positions.size();
726
727 QByteArray translationData( reinterpret_cast<const char *>( positions.constData() ), static_cast<qsizetype>( count * sizeof( QVector3D ) ) );
728
729 const QString upAxis = symbol->shapeProperty( u"upAxis"_s ).toString();
730 const QString forwardAxis = symbol->shapeProperty( u"forwardAxis"_s ).toString();
731
732 Qgis::InstancedMaterialFlags instancedFlags;
733 if ( !scales.empty() )
735 if ( !rotations.empty() )
737
738 const QMatrix4x4 meshTransform = Qgs3DUtils::axisTransformMatrix( upAxis, forwardAxis );
739
740 Qt3DCore::QBuffer *translationBufferData = new Qt3DCore::QBuffer( parent );
741 translationBufferData->setData( translationData );
742
743 Qt3DCore::QBuffer *scaleBufferData = nullptr;
744 if ( !scales.empty() )
745 {
746 QByteArray scaleData( reinterpret_cast<const char *>( scales.constData() ), static_cast<qsizetype>( count * sizeof( QVector3D ) ) );
747 scaleBufferData = new Qt3DCore::QBuffer( parent );
748 scaleBufferData->setData( scaleData );
749 }
750
751 Qt3DCore::QBuffer *rotationBufferData = nullptr;
752 if ( !rotations.empty() )
753 {
754 QVector<QVector4D> rotationVectors;
755 rotationVectors.reserve( count );
756 for ( const QQuaternion &q : rotations )
757 rotationVectors.append( q.toVector4D() );
758 QByteArray rotationData( reinterpret_cast<const char *>( rotationVectors.constData() ), static_cast<qsizetype>( count * sizeof( QVector4D ) ) );
759 rotationBufferData = new Qt3DCore::QBuffer( parent );
760 rotationBufferData->setData( rotationData );
761 }
762
763 for ( QgsMeshNodeData &mesh : meshes )
764 {
765 Qt3DCore::QGeometry *geom = mesh.geometry.release();
766
767 Qt3DCore::QAttribute *translationAttribute = new Qt3DCore::QAttribute;
768 translationAttribute->setName( u"instanceTranslation"_s );
769 translationAttribute->setAttributeType( Qt3DCore::QAttribute::VertexAttribute );
770 translationAttribute->setVertexBaseType( Qt3DCore::QAttribute::Float );
771 translationAttribute->setVertexSize( 3 );
772 translationAttribute->setByteOffset( 0 );
773 translationAttribute->setByteStride( 3 * sizeof( float ) );
774 translationAttribute->setDivisor( 1 );
775 translationAttribute->setCount( static_cast<uint>( count ) );
776 translationAttribute->setBuffer( translationBufferData );
777 geom->addAttribute( translationAttribute );
778 geom->setBoundingVolumePositionAttribute( translationAttribute );
779
780 if ( scaleBufferData )
781 {
782 Qt3DCore::QAttribute *scaleAttribute = new Qt3DCore::QAttribute;
783 scaleAttribute->setName( u"instanceScale"_s );
784 scaleAttribute->setAttributeType( Qt3DCore::QAttribute::VertexAttribute );
785 scaleAttribute->setVertexBaseType( Qt3DCore::QAttribute::Float );
786 scaleAttribute->setVertexSize( 3 );
787 scaleAttribute->setByteOffset( 0 );
788 scaleAttribute->setByteStride( 3 * sizeof( float ) );
789 scaleAttribute->setDivisor( 1 );
790 scaleAttribute->setCount( static_cast<uint>( count ) );
791 scaleAttribute->setBuffer( scaleBufferData );
792 geom->addAttribute( scaleAttribute );
793 }
794
795 if ( rotationBufferData )
796 {
797 Qt3DCore::QAttribute *rotationAttribute = new Qt3DCore::QAttribute;
798 rotationAttribute->setName( u"instanceRotation"_s );
799 rotationAttribute->setAttributeType( Qt3DCore::QAttribute::VertexAttribute );
800 rotationAttribute->setVertexBaseType( Qt3DCore::QAttribute::Float );
801 rotationAttribute->setVertexSize( 4 );
802 rotationAttribute->setByteOffset( 0 );
803 rotationAttribute->setByteStride( 4 * sizeof( float ) );
804 rotationAttribute->setDivisor( 1 );
805 rotationAttribute->setCount( static_cast<uint>( count ) );
806 rotationAttribute->setBuffer( rotationBufferData );
807 geom->addAttribute( rotationAttribute );
808 }
809
810 const QMatrix4x4 meshTransformCombined = meshTransform * mesh.meshTransform;
811
812 QgsMaterial *mat = nullptr;
813 if ( materialContext.isHighlighted() )
814 {
815 QgsHighlightMaterial *highlightMaterial = new QgsHighlightMaterial();
816 highlightMaterial->setInstancingEnabled( true, instancedFlags );
817 highlightMaterial->setInstancingMeshTransform( meshTransformCombined );
818 mat = highlightMaterial;
819 }
820 else if ( useEmbeddedTexture )
821 {
822 if ( QgsPhongTexturedMaterial *phongTexMat = qobject_cast<QgsPhongTexturedMaterial *>( mesh.material.get() ) )
823 {
824 phongTexMat->setInstancingEnabled( true, instancedFlags );
825 phongTexMat->setInstancingMeshTransform( meshTransformCombined );
826 }
827 else if ( QgsPhongMaterial *phongMat = qobject_cast<QgsPhongMaterial *>( mesh.material.get() ) )
828 {
829 phongMat->setInstancingEnabled( true, instancedFlags );
830 phongMat->setInstancingMeshTransform( meshTransformCombined );
831 }
832 else if ( QgsMetalRoughMaterial *pbrMat = qobject_cast<QgsMetalRoughMaterial *>( mesh.material.get() ) )
833 {
834 pbrMat->setInstancingEnabled( true, instancedFlags );
835 pbrMat->setInstancingMeshTransform( meshTransformCombined );
836 }
837 else if ( QgsTextureMaterial *gltfTexMat = qobject_cast<QgsTextureMaterial *>( mesh.material.get() ) )
838 {
839 gltfTexMat->setInstancingEnabled( true, instancedFlags );
840 gltfTexMat->setInstancingMeshTransform( meshTransformCombined );
841 }
842 mat = mesh.material.release();
843 }
844 else
845 {
846 const QgsAbstractMaterialSettings *settings = symbol->materialSettings();
847 if ( const QgsAbstractMaterial3DHandler *handler = Qgs3D::handlerForMaterialSettings( settings ) )
848 mat = handler->toInstancedMaterial( settings, materialContext, instancedFlags, meshTransformCombined );
849 }
850
851 if ( !mat )
852 {
853 QgsMetalRoughMaterial *metal = new QgsMetalRoughMaterial();
854 metal->setInstancingEnabled( true, instancedFlags );
855 metal->setInstancingMeshTransform( meshTransformCombined );
856 mat = metal;
857 }
858
859 mat->addParameter( new Qt3DRender::QParameter( "symbolScale", mSymbolScale, mat ) );
860 mat->addParameter( new Qt3DRender::QParameter( "symbolRotation", mSymbolRotation.toVector4D(), mat ) );
861
862 Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer;
863 renderer->setGeometry( geom );
864 renderer->setInstanceCount( static_cast<int>( count ) );
865
866 QgsGeoTransform *geoTransform = new QgsGeoTransform;
867 geoTransform->setGeoTranslation( chunkOrigin );
868
869 Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
870 entity->addComponent( renderer );
871 entity->addComponent( mat );
872 entity->addComponent( geoTransform );
873 entity->setParent( parent );
874
875 // cppcheck wrongly believes entity will leak
876 // cppcheck-suppress memleak
877 }
878}
879
880// --------------
881
882//* BILLBOARD RENDERING *//
883
884class QgsPoint3DBillboardSymbolHandler : public QgsFeature3DHandler
885{
886 public:
887 QgsPoint3DBillboardSymbolHandler( const QgsPoint3DSymbol *symbol, const QgsFeatureIds &selectedIds )
888 : mSymbol( static_cast<QgsPoint3DSymbol *>( symbol->clone() ) )
889 , mSelectedIds( selectedIds )
890 {}
891
892 bool prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames, const QgsBox3D &chunkExtent ) override;
893 void processFeature( const QgsFeature &feature, const Qgs3DRenderContext &context ) override;
894 void finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context ) override;
895
896 private:
898 struct PointData
899 {
900 QVector<QVector3D> positions; // contains triplets of float x,y,z for each point
901 };
902
903 void makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, PointData &out, bool selected );
904
905 // input specific for this class
906 std::unique_ptr<QgsPoint3DSymbol> mSymbol;
907 // inputs - generic
908 QgsFeatureIds mSelectedIds;
909 // outputs
910 PointData outNormal;
911 PointData outSelected;
912};
913
914bool QgsPoint3DBillboardSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames, const QgsBox3D &chunkExtent )
915{
916 Q_UNUSED( context )
917 Q_UNUSED( attributeNames )
918
919 mChunkOrigin = chunkExtent.center();
920 mChunkExtent = chunkExtent;
921
922 return true;
923}
924
925void QgsPoint3DBillboardSymbolHandler::processFeature( const QgsFeature &feature, const Qgs3DRenderContext &context )
926{
927 PointData &out = mSelectedIds.contains( feature.id() ) ? outSelected : outNormal;
928
929 if ( feature.geometry().isNull() )
930 return;
931
932 Qgs3DUtils::extractPointPositions( feature, context, mChunkOrigin, mSymbol->altitudeClamping(), out.positions );
933 mFeatureCount++;
934}
935
936void QgsPoint3DBillboardSymbolHandler::finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context )
937{
938 makeEntity( parent, context, outNormal, false );
939 makeEntity( parent, context, outSelected, true );
940
941 updateZRangeFromPositions( outNormal.positions );
942 updateZRangeFromPositions( outSelected.positions );
943
944 // the elevation offset is applied externally through a QTransform of QEntity so let's account for it
945 const float billboardHeight = mSymbol->billboardHeight();
946 // as we are relative to chunk center elevation we have to add mChunkOrigin.z()
947 mZMin += static_cast<float>( billboardHeight + mChunkOrigin.z() );
948 mZMax += static_cast<float>( billboardHeight + mChunkOrigin.z() );
949}
950
951void QgsPoint3DBillboardSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, PointData &out, bool selected )
952{
953 if ( out.positions.isEmpty() )
954 {
955 return; // nothing to show - no need to create the entity
956 }
957
958 // Billboard Geometry
959 QgsBillboardGeometry *billboardGeometry = new QgsBillboardGeometry();
960 billboardGeometry->setPositions( out.positions );
961
962 // Billboard Geometry Renderer
963 Qt3DRender::QGeometryRenderer *billboardGeometryRenderer = new Qt3DRender::QGeometryRenderer;
964 billboardGeometryRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::Points );
965 billboardGeometryRenderer->setGeometry( billboardGeometry );
966 billboardGeometryRenderer->setVertexCount( billboardGeometry->count() );
967
968 // Billboard Material
970 QgsMarkerSymbol *symbol = mSymbol->billboardSymbol();
971
972 if ( symbol )
973 {
974 billboardMaterial->setTexture2DFromSymbol( symbol, context, selected );
975 }
976 else
977 {
978 billboardMaterial->useDefaultSymbol( context, selected );
979 }
980
981 // Billboard Transform
982 QgsGeoTransform *billboardTransform = new QgsGeoTransform;
983 billboardTransform->setGeoTranslation( mChunkOrigin + QgsVector3D( 0, 0, mSymbol->billboardHeight() ) );
984
985 // Build the entity
986 Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
987
988 entity->addComponent( billboardMaterial );
989 entity->addComponent( billboardTransform );
990 entity->addComponent( billboardGeometryRenderer );
991 entity->setParent( parent );
992
993 // cppcheck wrongly believes entity will leak
994 // cppcheck-suppress memleak
995}
996
997
998namespace Qgs3DSymbolImpl
999{
1000
1001 QgsFeature3DHandler *handlerForPoint3DSymbol( const QgsVectorLayer *layer, const QgsAbstract3DSymbol *symbol )
1002 {
1003 const QgsPoint3DSymbol *pointSymbol = dynamic_cast<const QgsPoint3DSymbol *>( symbol );
1004 if ( !pointSymbol )
1005 return nullptr;
1006
1007 if ( pointSymbol->shape() == Qgis::Point3DShape::Model )
1008 return new QgsModelPoint3DSymbolHandler( pointSymbol, layer->selectedFeatureIds() );
1009 // Add proper handler for billboard
1010 else if ( pointSymbol->shape() == Qgis::Point3DShape::Billboard )
1011 return new QgsPoint3DBillboardSymbolHandler( pointSymbol, layer->selectedFeatureIds() );
1012 else
1013 return new QgsInstancedPoint3DSymbolHandler( pointSymbol, layer->selectedFeatureIds() );
1014 }
1015} // namespace Qgs3DSymbolImpl
1016
@ Plane
Flat plane.
Definition qgis.h:4359
@ Cylinder
Cylinder.
Definition qgis.h:4354
@ Torus
Torus.
Definition qgis.h:4358
@ ExtrudedText
Extruded text.
Definition qgis.h:4360
@ Model
Model.
Definition qgis.h:4361
@ Sphere
Sphere.
Definition qgis.h:4355
@ Billboard
Billboard.
Definition qgis.h:4362
QFlags< InstancedMaterialFlag > InstancedMaterialFlags
Definition qgis.h:4398
@ DataDefinedRotation
Per-instance data-defined rotation.
Definition qgis.h:4395
@ DataDefinedScale
Per-instance data-defined scale.
Definition qgis.h:4394
Rendering context for preparation of 3D entities.
QgsExpressionContext & expressionContext()
Gets the expression context.
static void decomposeTransformMatrix(const QMatrix4x4 &matrix, QVector3D &translation, QQuaternion &rotation, QVector3D &scale)
Tries to decompose a 4x4 transform matrix into translation, rotation and scale components.
static void extractPointPositions(const QgsFeature &f, const Qgs3DRenderContext &context, const QgsVector3D &chunkOrigin, Qgis::AltitudeClamping altClamp, QVector< QVector3D > &positions, const QgsVector3D &translation=QgsVector3D(0, 0, 0))
Calculates (x,y,z) positions of (multi)point from the given feature.
static QMatrix4x4 axisTransformMatrix(const QString &upAxis, const QString &forwardAxis)
Computes a 3x3 orientation matrix from the given up and forward axis strings.
static const QgsAbstractMaterial3DHandler * handlerForMaterialSettings(const QgsAbstractMaterialSettings *settings)
Returns the handler to use for a material settings.
Definition qgs3d.cpp:135
Abstract base class for material 3D handlers.
Abstract base class for material settings.
virtual bool requiresTangents() const
Returns true if the material requires tangents generated during triangulation.
virtual QString type() const =0
Returns the unique type name for the material.
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.
static QgsSourceCache * sourceCache()
Returns the application's source cache, used for caching embedded and remote source strings as local ...
Geometry of the billboard rendering for points in 3D map view.
void setPositions(const QVector< QVector3D > &vertices)
Sets the vertex positions for the billboards.
A 3-dimensional box composed of x, y, z coordinates.
Definition qgsbox3d.h:45
QgsVector3D center() const
Returns the center of the box as a vector.
Definition qgsbox3d.cpp:124
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:60
QgsFeatureId id
Definition qgsfeature.h:63
QgsGeometry geometry
Definition qgsfeature.h:66
A marker symbol type, for rendering Point and MultiPoint geometries.
Context settings for a material.
void setIsSelected(bool isSelected)
Sets whether the material should represent a selected state.
bool isHighlighted() const
Returns true if the material should represent a highlighted 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.
Definition qgsmaterial.h:40
Material of the billboard rendering for points in 3D map view.
void useDefaultSymbol(const Qgs3DRenderContext &context, bool selected=false)
Set default symbol for the texture with context and selected parameter for rendering.
void setTexture2DFromSymbol(const QgsMarkerSymbol *markerSymbol, const Qgs3DRenderContext &context, bool selected=false)
Set markerSymbol for the texture with context and selected parameter for rendering.
3D symbol that draws point geometries as 3D objects using one of the predefined shapes.
QgsMarkerSymbol * billboardSymbol() const
Returns a symbol for billboard.
Qgis::Point3DShape shape() const
Returns 3D shape for points.
QgsAbstractMaterialSettings * materialSettings() const override
Returns material settings used for shading of the symbol.
QVariant shapeProperty(const QString &property) const
Returns the value for a specific shape property.
A grouped map of multiple QgsProperty objects, each referenced by an integer key value.
bool isActive(int key) const final
Returns true if the collection contains an active property with the specified key.
QString localFilePath(const QString &path, bool blocking=false)
Returns a local file path reflecting the content of a specified source path.
A 3D vector (similar to QVector3D) with the difference that it uses double precision instead of singl...
Definition qgsvector3d.h:33
double y() const
Returns Y coordinate.
Definition qgsvector3d.h:60
double z() const
Returns Z coordinate.
Definition qgsvector3d.h:62
double x() const
Returns X coordinate.
Definition qgsvector3d.h:58
Q_INVOKABLE const QgsFeatureIds & selectedFeatureIds() const
Returns a list of the selected features IDs in this layer.
QSet< QgsFeatureId > QgsFeatureIds
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:80
#define QgsDebugError(str)
Definition qgslogger.h:71
Pairs a Qt3D geometry with its material and transform.
Definition qgs3dutils.h:71