QGIS API Documentation 4.1.0-Master (376402f9aeb)
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 "qgs3d.h"
19#include "qgs3drendercontext.h"
20#include "qgs3dutils.h"
21#include "qgsapplication.h"
24#include "qgsgeotransform.h"
28#include "qgspoint3dsymbol.h"
29#include "qgssourcecache.h"
30#include "qgsvectorlayer.h"
31
32#include <QString>
33#include <QUrl>
34#include <QVector3D>
35#include <Qt3DCore/QAttribute>
36#include <Qt3DCore/QBuffer>
37#include <Qt3DCore/QGeometry>
38#include <Qt3DExtras/QConeGeometry>
39#include <Qt3DExtras/QCuboidGeometry>
40#include <Qt3DExtras/QCylinderGeometry>
41#include <Qt3DExtras/QExtrudedTextGeometry>
42#include <Qt3DExtras/QPhongMaterial>
43#include <Qt3DExtras/QPlaneGeometry>
44#include <Qt3DExtras/QSphereGeometry>
45#include <Qt3DExtras/QTorusGeometry>
46#include <Qt3DRender/QEffect>
47#include <Qt3DRender/QGraphicsApiFilter>
48#include <Qt3DRender/QMesh>
49#include <Qt3DRender/QPaintedTextureImage>
50#include <Qt3DRender/QParameter>
51#include <Qt3DRender/QSceneLoader>
52#include <Qt3DRender/QTechnique>
53
54using namespace Qt::StringLiterals;
55
57
58
59//* INSTANCED RENDERING *//
60
61
62class QgsInstancedPoint3DSymbolHandler : public QgsFeature3DHandler
63{
64 public:
65 QgsInstancedPoint3DSymbolHandler( const QgsPoint3DSymbol *symbol, const QgsFeatureIds &selectedIds )
66 : mSymbol( static_cast<QgsPoint3DSymbol *>( symbol->clone() ) )
67 , mSelectedIds( selectedIds )
68 {}
69
70 bool prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames, const QgsBox3D &chunkExtent ) override;
71 void processFeature( const QgsFeature &feature, const Qgs3DRenderContext &context ) override;
72 void finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context ) override;
73
74 private:
75 static QgsMaterial *material( const QgsPoint3DSymbol *symbol, const QgsMaterialContext &materialContext, bool hasDataDefinedScale, bool hasDataDefinedRotation );
76 static Qt3DRender::QGeometryRenderer *renderer( const QgsPoint3DSymbol *symbol, const QVector<QVector3D> &positions, const QVector<QVector3D> &scales, const QVector<QVector4D> rotations );
77 static Qt3DCore::QGeometry *symbolGeometry( const QgsPoint3DSymbol *symbol );
78
80 struct PointData
81 {
82 QVector<QVector3D> positions; // contains triplets of float x,y,z for each point
83 QVector<QVector3D> scales;
84 QVector<QVector4D> rotations;
85 };
86
87 void makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, PointData &out, bool selected );
88
89 // input specific for this class
90 std::unique_ptr<QgsPoint3DSymbol> mSymbol;
91 QVector3D mSymbolScale;
92 QQuaternion mSymbolRotation;
93 QVector3D mPointTranslation;
94 // inputs - generic
95 QgsFeatureIds mSelectedIds;
96 // outputs
97 PointData outNormal;
98 PointData outSelected;
99};
100
101
102bool QgsInstancedPoint3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames, const QgsBox3D &chunkExtent )
103{
104 mChunkOrigin = chunkExtent.center();
105 mChunkExtent = chunkExtent;
106
107 QSet<QString> attrs = mSymbol->dataDefinedProperties().referencedFields( context.expressionContext() );
108 attributeNames.unite( attrs );
109 attrs = mSymbol->materialSettings()->dataDefinedProperties().referencedFields( context.expressionContext() );
110 attributeNames.unite( attrs );
111
112 Qgs3DUtils::decomposeTransformMatrix( mSymbol->transform(), mPointTranslation, mSymbolRotation, mSymbolScale );
113
114 return true;
115}
116
117void QgsInstancedPoint3DSymbolHandler::processFeature( const QgsFeature &feature, const Qgs3DRenderContext &context )
118{
119 PointData &out = mSelectedIds.contains( feature.id() ) ? outSelected : outNormal;
120
121 if ( feature.geometry().isNull() )
122 return;
123
124 const QgsPropertyCollection &ddp = mSymbol->dataDefinedProperties();
125
126 QgsVector3D translation = mPointTranslation;
127 const bool hasDDTranslation = ddp.isActive( QgsAbstract3DSymbol::Property::TranslationX )
130 if ( hasDDTranslation )
131 {
132 const double translationX = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::TranslationX, context.expressionContext(), translation.x() );
133 const double translationY = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::TranslationY, context.expressionContext(), translation.y() );
134 const double translationZ = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::TranslationZ, context.expressionContext(), translation.z() );
135 translation = QgsVector3D( translationX, translationY, translationZ );
136 }
137
138 const std::size_t oldSize = out.positions.size();
139 Qgs3DUtils::extractPointPositions( feature, context, mChunkOrigin, mSymbol->altitudeClamping(), out.positions, translation );
140
141 const std::size_t added = out.positions.size() - oldSize;
142
144
145 if ( hasDDScale )
146 {
147 out.scales.resize( out.positions.size() );
148 QVector3D *outScale = out.scales.data() + oldSize;
149
150 const double scaleX = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::ScaleX, context.expressionContext(), mSymbolScale.x() );
151 const double scaleY = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::ScaleY, context.expressionContext(), mSymbolScale.y() );
152 const double scaleZ = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::ScaleZ, context.expressionContext(), mSymbolScale.z() );
153
154 for ( std::size_t i = 0; i < added; ++i )
155 {
156 ( *outScale++ ) = QVector3D( static_cast< float >( scaleX ), static_cast< float >( scaleY ), static_cast< float >( scaleZ ) );
157 }
158 }
159
160 const bool hasDDRotation = ddp.isActive( QgsAbstract3DSymbol::Property::RotationX )
163 if ( hasDDRotation )
164 {
165 out.rotations.resize( out.positions.size() );
166 QVector4D *outRotation = out.rotations.data() + oldSize;
167
168 // extract default rotation components from symbol rotation
169 const QVector3D baseEuler = mSymbolRotation.toEulerAngles();
170
171 const double rotationX = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::RotationX, context.expressionContext(), baseEuler.x() );
172 const double rotationY = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::RotationY, context.expressionContext(), baseEuler.y() );
173 const double rotationZ = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::RotationZ, context.expressionContext(), baseEuler.z() );
174
175 //... and then re-calculate the rotation vector for this feature
176 const QQuaternion finalQuat = QQuaternion::fromEulerAngles( static_cast< float >( rotationX ), static_cast< float >( rotationY ), static_cast< float >( rotationZ ) );
177 const QVector4D finalVec4 = finalQuat.toVector4D();
178
179 for ( std::size_t i = 0; i < added; ++i )
180 {
181 ( *outRotation++ ) = finalVec4;
182 }
183 }
184
185 mFeatureCount++;
186}
187
188void QgsInstancedPoint3DSymbolHandler::finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context )
189{
190 makeEntity( parent, context, outNormal, false );
191 makeEntity( parent, context, outSelected, true );
192
193 auto updateZRangeFromPointData = [this]( const PointData &pointData ) {
194 const QVector3D *scales = pointData.scales.empty() ? nullptr : pointData.scales.constData();
195 for ( const QVector3D &pos : std::as_const( pointData.positions ) )
196 {
197 double minZ = 0;
198 double maxZ = 0;
199
200 // also account for the actual height of the objects themselves
201 // NOTE -- these calculations are naive, and assume no rotation or scaling of the symbol!
202 switch ( mSymbol->shape() )
203 {
205 {
206 const float length = mSymbol->shapeProperty( u"length"_s ).toFloat();
207 minZ -= length * 0.5f;
208 maxZ += length * 0.5f;
209 break;
210 }
211
213 {
214 const float radius = mSymbol->shapeProperty( u"radius"_s ).toFloat();
215 minZ -= radius;
216 maxZ += radius;
217 break;
218 }
219
221 {
222 const float length = mSymbol->shapeProperty( u"length"_s ).toFloat();
223 minZ -= length * 0.5f;
224 maxZ += length * 0.5f;
225 break;
226 }
227
229 {
230 const float size = mSymbol->shapeProperty( u"size"_s ).toFloat();
231 minZ -= size * 0.5f;
232 maxZ += size * 0.5f;
233 break;
234 }
235
237 {
238 const float radius = mSymbol->shapeProperty( u"radius"_s ).toFloat();
239 minZ -= radius;
240 maxZ += radius;
241 break;
242 }
243
245 {
246 // worst case scenario -- even though planes are usually rotated so that they are flat,
247 // let's account for possible overridden rotation
248 const float size = mSymbol->shapeProperty( u"size"_s ).toFloat();
249 minZ -= size * 0.5f;
250 maxZ += size * 0.5f;
251 break;
252 }
253
257 break;
258 }
259
260 if ( scales )
261 {
262 const double zScale = ( *scales++ )[2];
263 minZ *= zScale;
264 maxZ *= zScale;
265 }
266
267 minZ += pos.z();
268 maxZ += pos.z();
269
270 if ( minZ < mZMin )
271 mZMin = static_cast< float >( minZ );
272 if ( maxZ > mZMax )
273 mZMax = static_cast< float >( maxZ );
274 }
275 };
276
277 updateZRangeFromPointData( outNormal );
278 updateZRangeFromPointData( outSelected );
279}
280
281void QgsInstancedPoint3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, PointData &out, bool selected )
282{
283 if ( out.positions.isEmpty() )
284 {
285 return; // nothing to show - no need to create the entity
286 }
287
288 // build the default material
289 QgsMaterialContext materialContext;
290 materialContext.setIsSelected( selected );
291 materialContext.setSelectionColor( context.selectionColor() );
292 materialContext.setIsHighlighted( mHighlightingEnabled );
293 QgsMaterial *mat = material( mSymbol.get(), materialContext, !out.scales.empty(), !out.rotations.empty() );
294
295 mat->addParameter( new Qt3DRender::QParameter( "symbolScale", mSymbolScale, mat ) );
296 mat->addParameter( new Qt3DRender::QParameter( "symbolRotation", mSymbolRotation.toVector4D(), mat ) );
297
298 // add transform (our geometry has coordinates relative to mChunkOrigin)
299 QgsGeoTransform *tr = new QgsGeoTransform;
300 tr->setGeoTranslation( mChunkOrigin );
301
302 // build the entity
303 Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
304 entity->addComponent( renderer( mSymbol.get(), out.positions, out.scales, out.rotations ) );
305 entity->addComponent( mat );
306 entity->addComponent( tr );
307 entity->setParent( parent );
308
309 // cppcheck wrongly believes entity will leak
310 // cppcheck-suppress memleak
311}
312
313
314QgsMaterial *QgsInstancedPoint3DSymbolHandler::material( const QgsPoint3DSymbol *symbol, const QgsMaterialContext &materialContext, bool hasDataDefinedScale, bool hasDataDefinedRotation )
315{
316 std::unique_ptr<QgsMaterial> material;
317
318 if ( materialContext.isHighlighted() )
319 {
320 material = std::make_unique<QgsHighlightMaterial>( Qgis::MaterialRenderingTechnique::InstancedPoints );
321 }
322 else
323 {
324 Qt3DRender::QFilterKey *filterKey = new Qt3DRender::QFilterKey;
325 filterKey->setName( u"renderingStyle"_s );
326 filterKey->setValue( "forward" );
327
328 Qt3DRender::QShaderProgram *shaderProgram = new Qt3DRender::QShaderProgram;
329
330 const QByteArray vertexShaderCode = Qt3DRender::QShaderProgram::loadSource( QUrl( u"qrc:/shaders/instanced.vert"_s ) );
331 QStringList defines;
332 if ( hasDataDefinedScale )
333 defines << u"USE_INSTANCE_SCALE"_s;
334 if ( hasDataDefinedRotation )
335 defines << u"USE_INSTANCE_ROTATION"_s;
336
337 const QByteArray finalVertexShaderCode = Qgs3DUtils::addDefinesToShaderCode( vertexShaderCode, defines );
338 shaderProgram->setVertexShaderCode( finalVertexShaderCode );
339 shaderProgram->setFragmentShaderCode( Qt3DRender::QShaderProgram::loadSource( QUrl( u"qrc:/shaders/phong.frag"_s ) ) );
340
341 Qt3DRender::QRenderPass *renderPass = new Qt3DRender::QRenderPass;
342 renderPass->setShaderProgram( shaderProgram );
343
344 Qt3DRender::QTechnique *technique = new Qt3DRender::QTechnique;
345 technique->addFilterKey( filterKey );
346 technique->addRenderPass( renderPass );
347 technique->graphicsApiFilter()->setApi( Qt3DRender::QGraphicsApiFilter::OpenGL );
348 technique->graphicsApiFilter()->setProfile( Qt3DRender::QGraphicsApiFilter::CoreProfile );
349 technique->graphicsApiFilter()->setMajorVersion( 3 );
350 technique->graphicsApiFilter()->setMinorVersion( 2 );
351
352 Qt3DRender::QEffect *effect = new Qt3DRender::QEffect;
353 effect->addTechnique( technique );
354
355 Qgs3D::addMaterialParametersToEffect( effect, symbol->materialSettings(), materialContext );
356
357 material = std::make_unique<QgsMaterial>();
358 material->setEffect( effect );
359 }
360
361 return material.release();
362}
363
364Qt3DRender::QGeometryRenderer *QgsInstancedPoint3DSymbolHandler::renderer(
365 const QgsPoint3DSymbol *symbol, const QVector<QVector3D> &positions, const QVector<QVector3D> &scales, const QVector<QVector4D> rotations
366)
367{
368 const std::size_t count = positions.count();
369 const std::size_t byteCount = positions.count() * sizeof( QVector3D );
370 QByteArray ba;
371 ba.resize( byteCount );
372 memcpy( ba.data(), positions.constData(), byteCount );
373
374 Qt3DCore::QBuffer *instanceBuffer = new Qt3DCore::QBuffer();
375 instanceBuffer->setData( ba );
376
377 Qt3DCore::QAttribute *instanceTranslationAttribute = new Qt3DCore::QAttribute;
378 instanceTranslationAttribute->setName( u"instanceTranslation"_s );
379 instanceTranslationAttribute->setAttributeType( Qt3DCore::QAttribute::VertexAttribute );
380 instanceTranslationAttribute->setVertexBaseType( Qt3DCore::QAttribute::Float );
381 instanceTranslationAttribute->setVertexSize( 3 );
382 instanceTranslationAttribute->setByteOffset( 0 );
383 instanceTranslationAttribute->setDivisor( 1 );
384 instanceTranslationAttribute->setBuffer( instanceBuffer );
385 instanceTranslationAttribute->setCount( count );
386 instanceTranslationAttribute->setByteStride( 3 * sizeof( float ) );
387
388 Qt3DCore::QGeometry *geometry = symbolGeometry( symbol );
389 geometry->addAttribute( instanceTranslationAttribute );
390 geometry->setBoundingVolumePositionAttribute( instanceTranslationAttribute );
391
392 if ( !scales.empty() )
393 {
394 auto scaleBuffer = new Qt3DCore::QBuffer();
395 auto instanceScaleAttribute = new Qt3DCore::QAttribute;
396 instanceScaleAttribute->setName( u"instanceScale"_s );
397 instanceScaleAttribute->setAttributeType( Qt3DCore::QAttribute::VertexAttribute );
398 instanceScaleAttribute->setVertexBaseType( Qt3DCore::QAttribute::Float );
399 instanceScaleAttribute->setVertexSize( 3 );
400 instanceScaleAttribute->setByteOffset( 0 );
401 instanceScaleAttribute->setDivisor( 1 );
402 instanceScaleAttribute->setByteStride( 3 * sizeof( float ) );
403 QByteArray scaleBa;
404 scaleBa.resize( byteCount );
405 memcpy( scaleBa.data(), scales.constData(), byteCount );
406
407 scaleBuffer->setData( scaleBa );
408 instanceScaleAttribute->setCount( count );
409
410 instanceScaleAttribute->setBuffer( scaleBuffer );
411 geometry->addAttribute( instanceScaleAttribute );
412 }
413
414 if ( !rotations.empty() )
415 {
416 auto rotationBuffer = new Qt3DCore::QBuffer();
417 auto instanceRotationAttribute = new Qt3DCore::QAttribute;
418 instanceRotationAttribute->setName( u"instanceRotation"_s );
419 instanceRotationAttribute->setAttributeType( Qt3DCore::QAttribute::VertexAttribute );
420 instanceRotationAttribute->setVertexBaseType( Qt3DCore::QAttribute::Float );
421 instanceRotationAttribute->setVertexSize( 4 );
422 instanceRotationAttribute->setByteOffset( 0 );
423 instanceRotationAttribute->setDivisor( 1 );
424 instanceRotationAttribute->setByteStride( 4 * sizeof( float ) );
425
426 QByteArray rotationBa;
427 const std::size_t rotationByteCount = positions.count() * sizeof( QVector4D );
428 rotationBa.resize( rotationByteCount );
429 memcpy( rotationBa.data(), rotations.constData(), rotationByteCount );
430 rotationBuffer->setData( rotationBa );
431 instanceRotationAttribute->setCount( count );
432
433 instanceRotationAttribute->setBuffer( rotationBuffer );
434 geometry->addAttribute( instanceRotationAttribute );
435 }
436
437 Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer;
438 renderer->setGeometry( geometry );
439 renderer->setInstanceCount( count );
440
441 return renderer;
442}
443
444Qt3DCore::QGeometry *QgsInstancedPoint3DSymbolHandler::symbolGeometry( const QgsPoint3DSymbol *symbol )
445{
446 switch ( symbol->shape() )
447 {
449 {
450 const float radius = symbol->shapeProperty( u"radius"_s ).toFloat();
451 const float length = symbol->shapeProperty( u"length"_s ).toFloat();
452 Qt3DExtras::QCylinderGeometry *g = new Qt3DExtras::QCylinderGeometry;
453 //g->setRings(2); // how many vertices vertically
454 //g->setSlices(8); // how many vertices on circumference
455 g->setRadius( radius );
456 g->setLength( length );
457 return g;
458 }
459
461 {
462 const float radius = symbol->shapeProperty( u"radius"_s ).toFloat();
463 Qt3DExtras::QSphereGeometry *g = new Qt3DExtras::QSphereGeometry;
464 g->setRadius( radius );
465 return g;
466 }
467
469 {
470 const float length = symbol->shapeProperty( u"length"_s ).toFloat();
471 const float bottomRadius = symbol->shapeProperty( u"bottomRadius"_s ).toFloat();
472 const float topRadius = symbol->shapeProperty( u"topRadius"_s ).toFloat();
473
474 Qt3DExtras::QConeGeometry *g = new Qt3DExtras::QConeGeometry;
475 g->setLength( length );
476 g->setBottomRadius( bottomRadius );
477 g->setTopRadius( topRadius );
478 //g->setHasBottomEndcap(hasBottomEndcap);
479 //g->setHasTopEndcap(hasTopEndcap);
480 return g;
481 }
482
484 {
485 const float size = symbol->shapeProperty( u"size"_s ).toFloat();
486 Qt3DExtras::QCuboidGeometry *g = new Qt3DExtras::QCuboidGeometry;
487 g->setXExtent( size );
488 g->setYExtent( size );
489 g->setZExtent( size );
490 return g;
491 }
492
494 {
495 const float radius = symbol->shapeProperty( u"radius"_s ).toFloat();
496 const float minorRadius = symbol->shapeProperty( u"minorRadius"_s ).toFloat();
497 Qt3DExtras::QTorusGeometry *g = new Qt3DExtras::QTorusGeometry;
498 g->setRadius( radius );
499 g->setMinorRadius( minorRadius );
500 return g;
501 }
502
504 {
505 const float size = symbol->shapeProperty( u"size"_s ).toFloat();
506 Qt3DExtras::QPlaneGeometry *g = new Qt3DExtras::QPlaneGeometry;
507 g->setWidth( size );
508 g->setHeight( size );
509 return g;
510 }
511
513 {
514 const float depth = symbol->shapeProperty( u"depth"_s ).toFloat();
515 const QString text = symbol->shapeProperty( u"text"_s ).toString();
516 Qt3DExtras::QExtrudedTextGeometry *g = new Qt3DExtras::QExtrudedTextGeometry;
517 g->setDepth( depth );
518 g->setText( text );
519 return g;
520 }
521
524 break;
525 }
526 Q_ASSERT( false );
527 return nullptr;
528}
529
530//* 3D MODEL RENDERING *//
531
532
533class QgsModelPoint3DSymbolHandler : public QgsFeature3DHandler
534{
535 public:
536 QgsModelPoint3DSymbolHandler( const QgsPoint3DSymbol *symbol, const QgsFeatureIds &selectedIds )
537 : mSymbol( static_cast<QgsPoint3DSymbol *>( symbol->clone() ) )
538 , mSelectedIds( selectedIds )
539 {}
540
541 bool prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames, const QgsBox3D &chunkExtent ) override;
542 void processFeature( const QgsFeature &feature, const Qgs3DRenderContext &context ) override;
543 void finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context ) override;
544
545 private:
546 static void addSceneEntities(
547 const Qgs3DRenderContext &context,
548 const QVector<QVector3D> &positions,
549 const QVector<QVector3D> &scales,
550 const QVector<QQuaternion> &rotations,
551 const QgsVector3D &chunkOrigin,
552 const QgsPoint3DSymbol *symbol,
553 Qt3DCore::QEntity *parent
554 );
555 static void addMeshEntities(
556 const Qgs3DRenderContext &context,
557 const QVector<QVector3D> &positions,
558 const QVector<QVector3D> &scales,
559 const QVector<QQuaternion> &rotations,
560 const QgsVector3D &chunkOrigin,
561 const QgsPoint3DSymbol *symbol,
562 Qt3DCore::QEntity *parent,
563 bool areSelected,
564 bool areHighlighted
565 );
566 static QgsGeoTransform *transform( QVector3D position, const QMatrix4x4 &transform, const QgsVector3D &chunkOrigin );
567
569 struct PointData
570 {
571 QVector<QVector3D> positions; // contains triplets of float x,y,z for each point
572 QVector<QVector3D> scales;
573 QVector<QQuaternion> rotations;
574 };
575
576 void makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, PointData &out, bool selected );
577
578 // input specific for this class
579 std::unique_ptr<QgsPoint3DSymbol> mSymbol;
580
581 QVector3D mSymbolScale;
582 QQuaternion mSymbolRotation;
583 QVector3D mPointTranslation;
584
585 // inputs - generic
586 QgsFeatureIds mSelectedIds;
587 // outputs
588 PointData outNormal;
589 PointData outSelected;
590};
591
592bool QgsModelPoint3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames, const QgsBox3D &chunkExtent )
593{
594 Q_UNUSED( context )
595 Q_UNUSED( attributeNames )
596
597 mChunkOrigin = chunkExtent.center();
598 mChunkExtent = chunkExtent;
599
600 QSet<QString> attrs = mSymbol->dataDefinedProperties().referencedFields( context.expressionContext() );
601 attributeNames.unite( attrs );
602
603 Qgs3DUtils::decomposeTransformMatrix( mSymbol->transform(), mPointTranslation, mSymbolRotation, mSymbolScale );
604 return true;
605}
606
607void QgsModelPoint3DSymbolHandler::processFeature( const QgsFeature &feature, const Qgs3DRenderContext &context )
608{
609 PointData &out = mSelectedIds.contains( feature.id() ) ? outSelected : outNormal;
610
611 if ( feature.geometry().isNull() )
612 return;
613
614 const QgsPropertyCollection &ddp = mSymbol->dataDefinedProperties();
615
616 QgsVector3D translation = mPointTranslation;
617 const bool hasDDTranslation = ddp.isActive( QgsAbstract3DSymbol::Property::TranslationX )
620 if ( hasDDTranslation )
621 {
622 const double translationX = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::TranslationX, context.expressionContext(), translation.x() );
623 const double translationY = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::TranslationY, context.expressionContext(), translation.y() );
624 const double translationZ = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::TranslationZ, context.expressionContext(), translation.z() );
625 translation = QgsVector3D( translationX, translationY, translationZ );
626 }
627
628 const std::size_t oldSize = out.positions.size();
629 Qgs3DUtils::extractPointPositions( feature, context, mChunkOrigin, mSymbol->altitudeClamping(), out.positions, translation );
630 const std::size_t added = out.positions.size() - oldSize;
631
632 QVector3D scale = mSymbolScale;
634 if ( hasDDScale )
635 {
636 const double scaleX = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::ScaleX, context.expressionContext(), mSymbolScale.x() );
637 const double scaleY = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::ScaleY, context.expressionContext(), mSymbolScale.y() );
638 const double scaleZ = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::ScaleZ, context.expressionContext(), mSymbolScale.z() );
639 scale = QVector3D( static_cast< float >( scaleX ), static_cast< float >( scaleY ), static_cast< float >( scaleZ ) );
640 }
641
642 out.scales.resize( out.positions.size() );
643 QVector3D *outScale = out.scales.data() + oldSize;
644 for ( std::size_t i = 0; i < added; ++i )
645 {
646 ( *outScale++ ) = scale;
647 }
648
649 QQuaternion rotation = mSymbolRotation;
650 const bool hasDDRotation = ddp.isActive( QgsAbstract3DSymbol::Property::RotationX )
653 if ( hasDDRotation )
654 {
655 // extract default rotation components from symbol rotation
656 const QVector3D baseEuler = mSymbolRotation.toEulerAngles();
657
658 const double rotationX = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::RotationX, context.expressionContext(), baseEuler.x() );
659 const double rotationY = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::RotationY, context.expressionContext(), baseEuler.y() );
660 const double rotationZ = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::RotationZ, context.expressionContext(), baseEuler.z() );
661
662 //... and then re-calculate the rotation vector for this feature
663 rotation = QQuaternion::fromEulerAngles( static_cast< float >( rotationX ), static_cast< float >( rotationY ), static_cast< float >( rotationZ ) );
664 }
665
666 out.rotations.resize( out.positions.size() );
667 QQuaternion *outRotation = out.rotations.data() + oldSize;
668 for ( std::size_t i = 0; i < added; ++i )
669 {
670 ( *outRotation++ ) = rotation;
671 }
672
673 mFeatureCount++;
674}
675
676void QgsModelPoint3DSymbolHandler::finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context )
677{
678 makeEntity( parent, context, outNormal, false );
679 makeEntity( parent, context, outSelected, true );
680
681 updateZRangeFromPositions( outNormal.positions );
682 updateZRangeFromPositions( outSelected.positions );
683
684 // the elevation offset is applied separately in QTransform added to sub-entities
685 const float symbolHeight = mSymbol->transform().data()[14];
686 mZMin += symbolHeight;
687 mZMax += symbolHeight;
688}
689
690void QgsModelPoint3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, PointData &out, bool selected )
691{
692 if ( out.positions.isEmpty() )
693 {
694 return; // nothing to show - no need to create the entity
695 }
696
697 if ( selected )
698 {
699 addMeshEntities( context, out.positions, out.scales, out.rotations, mChunkOrigin, mSymbol.get(), parent, true, mHighlightingEnabled );
700 }
701 else
702 {
703 // "overwriteMaterial" is a legacy setting indicating that non-embedded material should be used
704 if ( mSymbol->shapeProperty( u"overwriteMaterial"_s ).toBool() || ( mSymbol->materialSettings() && mSymbol->materialSettings()->type() != "null"_L1 ) || mHighlightingEnabled )
705 {
706 addMeshEntities( context, out.positions, out.scales, out.rotations, mChunkOrigin, mSymbol.get(), parent, false, mHighlightingEnabled );
707 }
708 else
709 {
710 addSceneEntities( context, out.positions, out.scales, out.rotations, mChunkOrigin, mSymbol.get(), parent );
711 }
712 }
713}
714
715QVector3D stringToAxis( const QString &axis )
716{
717 if ( axis == "x"_L1 )
718 return QVector3D( 1.0f, 0.0f, 0.0f );
719 if ( axis == "-x"_L1 )
720 return QVector3D( -1.0f, 0.0f, 0.0f );
721 if ( axis == "y"_L1 )
722 return QVector3D( 0.0f, 1.0f, 0.0f );
723 if ( axis == "-y"_L1 )
724 return QVector3D( 0.0f, -1.0f, 0.0f );
725 if ( axis == "z"_L1 )
726 return QVector3D( 0.0f, 0.0f, 1.0f );
727 if ( axis == "-z"_L1 )
728 return QVector3D( 0.0f, 0.0f, -1.0f );
729
730 return QVector3D();
731}
732
733QMatrix4x4 createZUpTransform( const QString &upAxis, const QString &forwardAxis )
734{
735 QVector3D up = stringToAxis( upAxis );
736 QVector3D forward = stringToAxis( forwardAxis );
737
738 if ( up.isNull() || forward.isNull() || std::abs( QVector3D::dotProduct( up, forward ) ) > 1e-6f )
739 {
740 // no transform (identity matrix) on error
741 return QMatrix4x4();
742 }
743
744 QVector3D right = QVector3D::crossProduct( forward, up ).normalized();
745 return QMatrix4x4( right.x(), right.y(), right.z(), 0.0f, forward.x(), forward.y(), forward.z(), 0.0f, up.x(), up.y(), up.z(), 0.0f, 0.0f, 0.0f, 0.0f, 1.0f );
746}
747
748void QgsModelPoint3DSymbolHandler::addSceneEntities(
749 const Qgs3DRenderContext &context,
750 const QVector<QVector3D> &positions,
751 const QVector<QVector3D> &scales,
752 const QVector<QQuaternion> &rotations,
753 const QgsVector3D &chunkOrigin,
754 const QgsPoint3DSymbol *symbol,
755 Qt3DCore::QEntity *parent
756)
757{
758 Q_UNUSED( context );
759 const QString source = QgsApplication::sourceCache()->localFilePath( symbol->shapeProperty( u"model"_s ).toString() );
760 // if the source is remote, the Qgs3DMapScene will take care of refreshing this 3D symbol when the source is fetched
761
762 const QString upAxis = symbol->shapeProperty( u"upAxis"_s ).toString();
763 const QString forwardAxis = symbol->shapeProperty( u"forwardAxis"_s ).toString();
764 const QMatrix4x4 zUpMatrix = createZUpTransform( upAxis, forwardAxis );
765
766 if ( !source.isEmpty() )
767 {
768 int index = 0;
769 for ( const QVector3D &position : positions )
770 {
771 // build the entity
772 Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
773
774 const QUrl url = QUrl::fromLocalFile( source );
775 Qt3DRender::QSceneLoader *modelLoader = new Qt3DRender::QSceneLoader;
776 modelLoader->setSource( url );
777
778 QMatrix4x4 entityTransform;
779 entityTransform.scale( scales.at( index ) );
780 entityTransform.rotate( rotations.at( index ) );
781 entityTransform *= zUpMatrix;
782
783 entity->addComponent( modelLoader );
784 entity->addComponent( transform( position, entityTransform, chunkOrigin ) );
785 entity->setParent( parent );
786
787 // cppcheck wrongly believes entity will leak
788 // cppcheck-suppress memleak
789 index++;
790 }
791 }
792 else
793 {
794 QgsDebugMsgLevel( u"File '%1' is not accessible!"_s.arg( symbol->shapeProperty( u"model"_s ).toString() ), 1 );
795 }
796}
797
798void QgsModelPoint3DSymbolHandler::addMeshEntities(
799 const Qgs3DRenderContext &context,
800 const QVector<QVector3D> &positions,
801 const QVector<QVector3D> &scales,
802 const QVector<QQuaternion> &rotations,
803 const QgsVector3D &chunkOrigin,
804 const QgsPoint3DSymbol *symbol,
805 Qt3DCore::QEntity *parent,
806 bool areSelected,
807 bool areHighlighted
808)
809{
810 if ( positions.empty() )
811 return;
812
813 const QString upAxis = symbol->shapeProperty( u"upAxis"_s ).toString();
814 const QString forwardAxis = symbol->shapeProperty( u"forwardAxis"_s ).toString();
815 const QMatrix4x4 zUpMatrix = createZUpTransform( upAxis, forwardAxis );
816
817 const QString source = QgsApplication::sourceCache()->localFilePath( symbol->shapeProperty( u"model"_s ).toString() );
818 if ( !source.isEmpty() )
819 {
820 // build the default material
821 QgsMaterialContext materialContext;
822 materialContext.setIsSelected( areSelected );
823 materialContext.setSelectionColor( context.selectionColor() );
824 materialContext.setIsHighlighted( areHighlighted );
825
827 if ( !mat )
828 return;
829
830 const QUrl url = QUrl::fromLocalFile( source );
831
832 // get nodes
833 int index = 0;
834 for ( const QVector3D &position : positions )
835 {
836 // build the entity
837 Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
838
839 Qt3DRender::QMesh *mesh = new Qt3DRender::QMesh;
840 mesh->setSource( url );
841
842 entity->addComponent( mesh );
843 entity->addComponent( mat );
844
845 QMatrix4x4 entityTransform;
846 entityTransform.scale( scales.at( index ) );
847 entityTransform.rotate( rotations.at( index ) );
848 entityTransform *= zUpMatrix;
849
850 entity->addComponent( transform( position, entityTransform, chunkOrigin ) );
851 entity->setParent( parent );
852
853 // cppcheck wrongly believes entity will leak
854 // cppcheck-suppress memleak
855 index++;
856 }
857 }
858 else
859 {
860 QgsDebugMsgLevel( u"File '%1' is not accessible!"_s.arg( symbol->shapeProperty( u"model"_s ).toString() ), 1 );
861 }
862}
863
864QgsGeoTransform *QgsModelPoint3DSymbolHandler::transform( QVector3D position, const QMatrix4x4 &transform, const QgsVector3D &chunkOrigin )
865{
866 // position is relative to chunkOrigin
867 QgsGeoTransform *tr = new QgsGeoTransform;
868 tr->setMatrix( transform );
869 tr->setGeoTranslation( chunkOrigin + position + tr->translation() );
870 return tr;
871}
872
873// --------------
874
875//* BILLBOARD RENDERING *//
876
877class QgsPoint3DBillboardSymbolHandler : public QgsFeature3DHandler
878{
879 public:
880 QgsPoint3DBillboardSymbolHandler( const QgsPoint3DSymbol *symbol, const QgsFeatureIds &selectedIds )
881 : mSymbol( static_cast<QgsPoint3DSymbol *>( symbol->clone() ) )
882 , mSelectedIds( selectedIds )
883 {}
884
885 bool prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames, const QgsBox3D &chunkExtent ) override;
886 void processFeature( const QgsFeature &feature, const Qgs3DRenderContext &context ) override;
887 void finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context ) override;
888
889 private:
891 struct PointData
892 {
893 QVector<QVector3D> positions; // contains triplets of float x,y,z for each point
894 };
895
896 void makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, PointData &out, bool selected );
897
898 // input specific for this class
899 std::unique_ptr<QgsPoint3DSymbol> mSymbol;
900 // inputs - generic
901 QgsFeatureIds mSelectedIds;
902 // outputs
903 PointData outNormal;
904 PointData outSelected;
905};
906
907bool QgsPoint3DBillboardSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames, const QgsBox3D &chunkExtent )
908{
909 Q_UNUSED( context )
910 Q_UNUSED( attributeNames )
911
912 mChunkOrigin = chunkExtent.center();
913 mChunkExtent = chunkExtent;
914
915 return true;
916}
917
918void QgsPoint3DBillboardSymbolHandler::processFeature( const QgsFeature &feature, const Qgs3DRenderContext &context )
919{
920 PointData &out = mSelectedIds.contains( feature.id() ) ? outSelected : outNormal;
921
922 if ( feature.geometry().isNull() )
923 return;
924
925 Qgs3DUtils::extractPointPositions( feature, context, mChunkOrigin, mSymbol->altitudeClamping(), out.positions );
926 mFeatureCount++;
927}
928
929void QgsPoint3DBillboardSymbolHandler::finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context )
930{
931 makeEntity( parent, context, outNormal, false );
932 makeEntity( parent, context, outSelected, true );
933
934 updateZRangeFromPositions( outNormal.positions );
935 updateZRangeFromPositions( outSelected.positions );
936
937 // the elevation offset is applied externally through a QTransform of QEntity so let's account for it
938 const float billboardHeight = mSymbol->billboardHeight();
939 mZMin += billboardHeight;
940 mZMax += billboardHeight;
941}
942
943void QgsPoint3DBillboardSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, PointData &out, bool selected )
944{
945 if ( out.positions.isEmpty() )
946 {
947 return; // nothing to show - no need to create the entity
948 }
949
950 // Billboard Geometry
951 QgsBillboardGeometry *billboardGeometry = new QgsBillboardGeometry();
952 billboardGeometry->setPositions( out.positions );
953
954 // Billboard Geometry Renderer
955 Qt3DRender::QGeometryRenderer *billboardGeometryRenderer = new Qt3DRender::QGeometryRenderer;
956 billboardGeometryRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::Points );
957 billboardGeometryRenderer->setGeometry( billboardGeometry );
958 billboardGeometryRenderer->setVertexCount( billboardGeometry->count() );
959
960 // Billboard Material
962 QgsMarkerSymbol *symbol = mSymbol->billboardSymbol();
963
964 if ( symbol )
965 {
966 billboardMaterial->setTexture2DFromSymbol( symbol, context, selected );
967 }
968 else
969 {
970 billboardMaterial->useDefaultSymbol( context, selected );
971 }
972
973 // Billboard Transform
974 QgsGeoTransform *billboardTransform = new QgsGeoTransform;
975 billboardTransform->setGeoTranslation( mChunkOrigin + QgsVector3D( 0, 0, mSymbol->billboardHeight() ) );
976
977 // Build the entity
978 Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
979
980 entity->addComponent( billboardMaterial );
981 entity->addComponent( billboardTransform );
982 entity->addComponent( billboardGeometryRenderer );
983 entity->setParent( parent );
984
985 // cppcheck wrongly believes entity will leak
986 // cppcheck-suppress memleak
987}
988
989
990namespace Qgs3DSymbolImpl
991{
992
993 QgsFeature3DHandler *handlerForPoint3DSymbol( QgsVectorLayer *layer, const QgsAbstract3DSymbol *symbol )
994 {
995 const QgsPoint3DSymbol *pointSymbol = dynamic_cast<const QgsPoint3DSymbol *>( symbol );
996 if ( !pointSymbol )
997 return nullptr;
998
999 if ( pointSymbol->shape() == Qgis::Point3DShape::Model )
1000 return new QgsModelPoint3DSymbolHandler( pointSymbol, layer->selectedFeatureIds() );
1001 // Add proper handler for billboard
1002 else if ( pointSymbol->shape() == Qgis::Point3DShape::Billboard )
1003 return new QgsPoint3DBillboardSymbolHandler( pointSymbol, layer->selectedFeatureIds() );
1004 else
1005 return new QgsInstancedPoint3DSymbolHandler( pointSymbol, layer->selectedFeatureIds() );
1006 }
1007} // namespace Qgs3DSymbolImpl
1008
@ Plane
Flat plane.
Definition qgis.h:4326
@ Cylinder
Cylinder.
Definition qgis.h:4321
@ Torus
Torus.
Definition qgis.h:4325
@ ExtrudedText
Extruded text.
Definition qgis.h:4327
@ Model
Model.
Definition qgis.h:4328
@ Sphere
Sphere.
Definition qgis.h:4322
@ Billboard
Billboard.
Definition qgis.h:4329
@ Triangles
Triangle based rendering (default).
Definition qgis.h:4343
@ InstancedPoints
Instanced based rendering, requiring triangles and point data.
Definition qgis.h:4345
Rendering context for preparation of 3D entities.
QColor selectionColor() const
Returns color used for selected features.
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 QByteArray addDefinesToShaderCode(const QByteArray &shaderCode, const QStringList &defines)
Inserts some define macros into a shader source code.
static QgsMaterial * toMaterial(const QgsAbstractMaterialSettings *settings, Qgis::MaterialRenderingTechnique technique, const QgsMaterialContext &context)
Creates a new QgsMaterial object representing the material settings.
Definition qgs3d.cpp:141
static void addMaterialParametersToEffect(Qt3DRender::QEffect *effect, const QgsAbstractMaterialSettings *settings, const QgsMaterialContext &materialContext)
Adds parameters from the material settings to a destination effect.
Definition qgs3d.cpp:159
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:68
QgsGeometry geometry
Definition qgsfeature.h:71
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.
void setSelectionColor(const QColor &color)
Sets the color for representing materials in 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.
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.
QgsAbstractMaterialSettings * materialSettings() const
Returns material settings used for shading of the symbol.
QgsMarkerSymbol * billboardSymbol() const
Returns a symbol for billboard.
Qgis::Point3DShape shape() const
Returns 3D shape for points.
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:63