QGIS API Documentation 4.1.0-Master (ca2ac17535b)
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 // as we are relative to chunk center elevation we have to add mChunkOrigin.z()
268 minZ += pos.z() + mChunkOrigin.z();
269 maxZ += pos.z() + mChunkOrigin.z();
270
271 if ( minZ < mZMin )
272 mZMin = static_cast< float >( minZ );
273 if ( maxZ > mZMax )
274 mZMax = static_cast< float >( maxZ );
275 }
276 };
277
278 updateZRangeFromPointData( outNormal );
279 updateZRangeFromPointData( outSelected );
280}
281
282void QgsInstancedPoint3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, PointData &out, bool selected )
283{
284 if ( out.positions.isEmpty() )
285 {
286 return; // nothing to show - no need to create the entity
287 }
288
289 // build the default material
291 materialContext.setIsSelected( selected );
292 materialContext.setIsHighlighted( mHighlightingEnabled );
293 QgsMaterial *mat = material( mSymbol.get(), materialContext, !out.scales.empty(), !out.rotations.empty() );
294 if ( !mat )
295 return;
296
297 mat->addParameter( new Qt3DRender::QParameter( "symbolScale", mSymbolScale, mat ) );
298 mat->addParameter( new Qt3DRender::QParameter( "symbolRotation", mSymbolRotation.toVector4D(), mat ) );
299
300 // add transform (our geometry has coordinates relative to mChunkOrigin)
301 QgsGeoTransform *tr = new QgsGeoTransform;
302 tr->setGeoTranslation( mChunkOrigin );
303
304 // build the entity
305 Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
306 entity->addComponent( renderer( mSymbol.get(), out.positions, out.scales, out.rotations ) );
307 entity->addComponent( mat );
308 entity->addComponent( tr );
309 entity->setParent( parent );
310
311 // cppcheck wrongly believes entity will leak
312 // cppcheck-suppress memleak
313}
314
315
316QgsMaterial *QgsInstancedPoint3DSymbolHandler::material( const QgsPoint3DSymbol *symbol, const QgsMaterialContext &materialContext, bool hasDataDefinedScale, bool hasDataDefinedRotation )
317{
318 if ( materialContext.isHighlighted() )
319 return new QgsHighlightMaterial( Qgis::MaterialRenderingTechnique::InstancedPoints );
320
321 const QgsAbstractMaterialSettings *settings = symbol->materialSettings();
322 if ( const QgsAbstractMaterial3DHandler *handler = Qgs3D::handlerForMaterialSettings( settings ) )
323 {
325 if ( hasDataDefinedScale )
327 if ( hasDataDefinedRotation )
329 return handler->toInstancedMaterial( settings, materialContext, flags );
330 }
331
332 return nullptr;
333}
334
335Qt3DRender::QGeometryRenderer *QgsInstancedPoint3DSymbolHandler::renderer(
336 const QgsPoint3DSymbol *symbol, const QVector<QVector3D> &positions, const QVector<QVector3D> &scales, const QVector<QVector4D> rotations
337)
338{
339 const std::size_t count = positions.count();
340 const std::size_t byteCount = positions.count() * sizeof( QVector3D );
341 QByteArray ba;
342 ba.resize( byteCount );
343 memcpy( ba.data(), positions.constData(), byteCount );
344
345 Qt3DCore::QBuffer *instanceBuffer = new Qt3DCore::QBuffer();
346 instanceBuffer->setData( ba );
347
348 Qt3DCore::QAttribute *instanceTranslationAttribute = new Qt3DCore::QAttribute;
349 instanceTranslationAttribute->setName( u"instanceTranslation"_s );
350 instanceTranslationAttribute->setAttributeType( Qt3DCore::QAttribute::VertexAttribute );
351 instanceTranslationAttribute->setVertexBaseType( Qt3DCore::QAttribute::Float );
352 instanceTranslationAttribute->setVertexSize( 3 );
353 instanceTranslationAttribute->setByteOffset( 0 );
354 instanceTranslationAttribute->setDivisor( 1 );
355 instanceTranslationAttribute->setBuffer( instanceBuffer );
356 instanceTranslationAttribute->setCount( count );
357 instanceTranslationAttribute->setByteStride( 3 * sizeof( float ) );
358
359 Qt3DCore::QGeometry *geometry = symbolGeometry( symbol );
360 geometry->addAttribute( instanceTranslationAttribute );
361 geometry->setBoundingVolumePositionAttribute( instanceTranslationAttribute );
362
363 if ( !scales.empty() )
364 {
365 auto scaleBuffer = new Qt3DCore::QBuffer();
366 auto instanceScaleAttribute = new Qt3DCore::QAttribute;
367 instanceScaleAttribute->setName( u"instanceScale"_s );
368 instanceScaleAttribute->setAttributeType( Qt3DCore::QAttribute::VertexAttribute );
369 instanceScaleAttribute->setVertexBaseType( Qt3DCore::QAttribute::Float );
370 instanceScaleAttribute->setVertexSize( 3 );
371 instanceScaleAttribute->setByteOffset( 0 );
372 instanceScaleAttribute->setDivisor( 1 );
373 instanceScaleAttribute->setByteStride( 3 * sizeof( float ) );
374 QByteArray scaleBa;
375 scaleBa.resize( byteCount );
376 memcpy( scaleBa.data(), scales.constData(), byteCount );
377
378 scaleBuffer->setData( scaleBa );
379 instanceScaleAttribute->setCount( count );
380
381 instanceScaleAttribute->setBuffer( scaleBuffer );
382 geometry->addAttribute( instanceScaleAttribute );
383 }
384
385 if ( !rotations.empty() )
386 {
387 auto rotationBuffer = new Qt3DCore::QBuffer();
388 auto instanceRotationAttribute = new Qt3DCore::QAttribute;
389 instanceRotationAttribute->setName( u"instanceRotation"_s );
390 instanceRotationAttribute->setAttributeType( Qt3DCore::QAttribute::VertexAttribute );
391 instanceRotationAttribute->setVertexBaseType( Qt3DCore::QAttribute::Float );
392 instanceRotationAttribute->setVertexSize( 4 );
393 instanceRotationAttribute->setByteOffset( 0 );
394 instanceRotationAttribute->setDivisor( 1 );
395 instanceRotationAttribute->setByteStride( 4 * sizeof( float ) );
396
397 QByteArray rotationBa;
398 const std::size_t rotationByteCount = positions.count() * sizeof( QVector4D );
399 rotationBa.resize( rotationByteCount );
400 memcpy( rotationBa.data(), rotations.constData(), rotationByteCount );
401 rotationBuffer->setData( rotationBa );
402 instanceRotationAttribute->setCount( count );
403
404 instanceRotationAttribute->setBuffer( rotationBuffer );
405 geometry->addAttribute( instanceRotationAttribute );
406 }
407
408 Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer;
409 renderer->setGeometry( geometry );
410 renderer->setInstanceCount( count );
411
412 return renderer;
413}
414
415Qt3DCore::QGeometry *QgsInstancedPoint3DSymbolHandler::symbolGeometry( const QgsPoint3DSymbol *symbol )
416{
417 switch ( symbol->shape() )
418 {
420 {
421 const float radius = symbol->shapeProperty( u"radius"_s ).toFloat();
422 const float length = symbol->shapeProperty( u"length"_s ).toFloat();
423 Qt3DExtras::QCylinderGeometry *g = new Qt3DExtras::QCylinderGeometry;
424 //g->setRings(2); // how many vertices vertically
425 //g->setSlices(8); // how many vertices on circumference
426 g->setRadius( radius );
427 g->setLength( length );
428 return g;
429 }
430
432 {
433 const float radius = symbol->shapeProperty( u"radius"_s ).toFloat();
434 const int rings = symbol->shapeProperty( u"rings"_s ).toInt();
435 const int slices = symbol->shapeProperty( u"slices"_s ).toInt();
436
437 const bool tangents = symbol->materialSettings() && symbol->materialSettings()->requiresTangents();
438 Qt3DExtras::QSphereGeometry *g = new Qt3DExtras::QSphereGeometry;
439 g->setRadius( radius );
440 g->setRings( rings );
441 g->setSlices( slices );
442 g->setGenerateTangents( tangents );
443 return g;
444 }
445
447 {
448 const float length = symbol->shapeProperty( u"length"_s ).toFloat();
449 const float bottomRadius = symbol->shapeProperty( u"bottomRadius"_s ).toFloat();
450 const float topRadius = symbol->shapeProperty( u"topRadius"_s ).toFloat();
451
452 Qt3DExtras::QConeGeometry *g = new Qt3DExtras::QConeGeometry;
453 g->setLength( length );
454 g->setBottomRadius( bottomRadius );
455 g->setTopRadius( topRadius );
456 //g->setHasBottomEndcap(hasBottomEndcap);
457 //g->setHasTopEndcap(hasTopEndcap);
458 return g;
459 }
460
462 {
463 const float size = symbol->shapeProperty( u"size"_s ).toFloat();
464 Qt3DExtras::QCuboidGeometry *g = new Qt3DExtras::QCuboidGeometry;
465 g->setXExtent( size );
466 g->setYExtent( size );
467 g->setZExtent( size );
468 return g;
469 }
470
472 {
473 const float radius = symbol->shapeProperty( u"radius"_s ).toFloat();
474 const float minorRadius = symbol->shapeProperty( u"minorRadius"_s ).toFloat();
475 Qt3DExtras::QTorusGeometry *g = new Qt3DExtras::QTorusGeometry;
476 g->setRadius( radius );
477 g->setMinorRadius( minorRadius );
478 return g;
479 }
480
482 {
483 const float size = symbol->shapeProperty( u"size"_s ).toFloat();
484 Qt3DExtras::QPlaneGeometry *g = new Qt3DExtras::QPlaneGeometry;
485 g->setWidth( size );
486 g->setHeight( size );
487 return g;
488 }
489
491 {
492 const float depth = symbol->shapeProperty( u"depth"_s ).toFloat();
493 const QString text = symbol->shapeProperty( u"text"_s ).toString();
494 Qt3DExtras::QExtrudedTextGeometry *g = new Qt3DExtras::QExtrudedTextGeometry;
495 g->setDepth( depth );
496 g->setText( text );
497 return g;
498 }
499
502 break;
503 }
504 Q_ASSERT( false );
505 return nullptr;
506}
507
508//* 3D MODEL RENDERING *//
509
510
511class QgsModelPoint3DSymbolHandler : public QgsFeature3DHandler
512{
513 public:
514 QgsModelPoint3DSymbolHandler( const QgsPoint3DSymbol *symbol, const QgsFeatureIds &selectedIds )
515 : mSymbol( static_cast<QgsPoint3DSymbol *>( symbol->clone() ) )
516 , mSelectedIds( selectedIds )
517 {}
518
519 bool prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames, const QgsBox3D &chunkExtent ) override;
520 void processFeature( const QgsFeature &feature, const Qgs3DRenderContext &context ) override;
521 void finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context ) override;
522
523 private:
524 static void addSceneEntities(
525 const Qgs3DRenderContext &context,
526 const QVector<QVector3D> &positions,
527 const QVector<QVector3D> &scales,
528 const QVector<QQuaternion> &rotations,
529 const QgsVector3D &chunkOrigin,
530 const QgsPoint3DSymbol *symbol,
531 Qt3DCore::QEntity *parent
532 );
533 static void addMeshEntities(
534 const Qgs3DRenderContext &context,
535 const QVector<QVector3D> &positions,
536 const QVector<QVector3D> &scales,
537 const QVector<QQuaternion> &rotations,
538 const QgsVector3D &chunkOrigin,
539 const QgsPoint3DSymbol *symbol,
540 Qt3DCore::QEntity *parent,
541 bool areSelected,
542 bool areHighlighted
543 );
544 static QgsGeoTransform *transform( QVector3D position, const QMatrix4x4 &transform, const QgsVector3D &chunkOrigin );
545
547 struct PointData
548 {
549 QVector<QVector3D> positions; // contains triplets of float x,y,z for each point
550 QVector<QVector3D> scales;
551 QVector<QQuaternion> rotations;
552 };
553
554 void makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, PointData &out, bool selected );
555
556 // input specific for this class
557 std::unique_ptr<QgsPoint3DSymbol> mSymbol;
558
559 QVector3D mSymbolScale;
560 QQuaternion mSymbolRotation;
561 QVector3D mPointTranslation;
562
563 // inputs - generic
564 QgsFeatureIds mSelectedIds;
565 // outputs
566 PointData outNormal;
567 PointData outSelected;
568};
569
570bool QgsModelPoint3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames, const QgsBox3D &chunkExtent )
571{
572 Q_UNUSED( context )
573 Q_UNUSED( attributeNames )
574
575 mChunkOrigin = chunkExtent.center();
576 mChunkExtent = chunkExtent;
577
578 QSet<QString> attrs = mSymbol->dataDefinedProperties().referencedFields( context.expressionContext() );
579 attributeNames.unite( attrs );
580
581 Qgs3DUtils::decomposeTransformMatrix( mSymbol->transform(), mPointTranslation, mSymbolRotation, mSymbolScale );
582 return true;
583}
584
585void QgsModelPoint3DSymbolHandler::processFeature( const QgsFeature &feature, const Qgs3DRenderContext &context )
586{
587 PointData &out = mSelectedIds.contains( feature.id() ) ? outSelected : outNormal;
588
589 if ( feature.geometry().isNull() )
590 return;
591
592 const QgsPropertyCollection &ddp = mSymbol->dataDefinedProperties();
593
594 QgsVector3D translation = mPointTranslation;
595 const bool hasDDTranslation = ddp.isActive( QgsAbstract3DSymbol::Property::TranslationX )
598 if ( hasDDTranslation )
599 {
600 const double translationX = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::TranslationX, context.expressionContext(), translation.x() );
601 const double translationY = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::TranslationY, context.expressionContext(), translation.y() );
602 const double translationZ = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::TranslationZ, context.expressionContext(), translation.z() );
603 translation = QgsVector3D( translationX, translationY, translationZ );
604 }
605
606 const std::size_t oldSize = out.positions.size();
607 Qgs3DUtils::extractPointPositions( feature, context, mChunkOrigin, mSymbol->altitudeClamping(), out.positions, translation );
608 const std::size_t added = out.positions.size() - oldSize;
609
610 QVector3D scale = mSymbolScale;
612 if ( hasDDScale )
613 {
614 const double scaleX = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::ScaleX, context.expressionContext(), mSymbolScale.x() );
615 const double scaleY = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::ScaleY, context.expressionContext(), mSymbolScale.y() );
616 const double scaleZ = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::ScaleZ, context.expressionContext(), mSymbolScale.z() );
617 scale = QVector3D( static_cast< float >( scaleX ), static_cast< float >( scaleY ), static_cast< float >( scaleZ ) );
618 }
619
620 out.scales.resize( out.positions.size() );
621 QVector3D *outScale = out.scales.data() + oldSize;
622 for ( std::size_t i = 0; i < added; ++i )
623 {
624 ( *outScale++ ) = scale;
625 }
626
627 QQuaternion rotation = mSymbolRotation;
628 const bool hasDDRotation = ddp.isActive( QgsAbstract3DSymbol::Property::RotationX )
631 if ( hasDDRotation )
632 {
633 // extract default rotation components from symbol rotation
634 const QVector3D baseEuler = mSymbolRotation.toEulerAngles();
635
636 const double rotationX = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::RotationX, context.expressionContext(), baseEuler.x() );
637 const double rotationY = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::RotationY, context.expressionContext(), baseEuler.y() );
638 const double rotationZ = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::RotationZ, context.expressionContext(), baseEuler.z() );
639
640 //... and then re-calculate the rotation vector for this feature
641 rotation = QQuaternion::fromEulerAngles( static_cast< float >( rotationX ), static_cast< float >( rotationY ), static_cast< float >( rotationZ ) );
642 }
643
644 out.rotations.resize( out.positions.size() );
645 QQuaternion *outRotation = out.rotations.data() + oldSize;
646 for ( std::size_t i = 0; i < added; ++i )
647 {
648 ( *outRotation++ ) = rotation;
649 }
650
651 mFeatureCount++;
652}
653
654void QgsModelPoint3DSymbolHandler::finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context )
655{
656 makeEntity( parent, context, outNormal, false );
657 makeEntity( parent, context, outSelected, true );
658
659 updateZRangeFromPositions( outNormal.positions );
660 updateZRangeFromPositions( outSelected.positions );
661
662 // the elevation offset is applied separately in QTransform added to sub-entities
663 const float symbolHeight = mSymbol->transform().data()[14];
664 // as we are relative to chunk center elevation we have to add mChunkOrigin.z()
665 mZMin += static_cast<float>( symbolHeight + mChunkOrigin.z() );
666 mZMax += static_cast<float>( symbolHeight + mChunkOrigin.z() );
667}
668
669void QgsModelPoint3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, PointData &out, bool selected )
670{
671 if ( out.positions.isEmpty() )
672 {
673 return; // nothing to show - no need to create the entity
674 }
675
676 if ( selected )
677 {
678 addMeshEntities( context, out.positions, out.scales, out.rotations, mChunkOrigin, mSymbol.get(), parent, true, mHighlightingEnabled );
679 }
680 else
681 {
682 // "overwriteMaterial" is a legacy setting indicating that non-embedded material should be used
683 if ( mSymbol->shapeProperty( u"overwriteMaterial"_s ).toBool() || ( mSymbol->materialSettings() && mSymbol->materialSettings()->type() != "null"_L1 ) || mHighlightingEnabled )
684 {
685 addMeshEntities( context, out.positions, out.scales, out.rotations, mChunkOrigin, mSymbol.get(), parent, false, mHighlightingEnabled );
686 }
687 else
688 {
689 addSceneEntities( context, out.positions, out.scales, out.rotations, mChunkOrigin, mSymbol.get(), parent );
690 }
691 }
692}
693
694QVector3D stringToAxis( const QString &axis )
695{
696 if ( axis == "x"_L1 )
697 return QVector3D( 1.0f, 0.0f, 0.0f );
698 if ( axis == "-x"_L1 )
699 return QVector3D( -1.0f, 0.0f, 0.0f );
700 if ( axis == "y"_L1 )
701 return QVector3D( 0.0f, 1.0f, 0.0f );
702 if ( axis == "-y"_L1 )
703 return QVector3D( 0.0f, -1.0f, 0.0f );
704 if ( axis == "z"_L1 )
705 return QVector3D( 0.0f, 0.0f, 1.0f );
706 if ( axis == "-z"_L1 )
707 return QVector3D( 0.0f, 0.0f, -1.0f );
708
709 return QVector3D();
710}
711
712QMatrix4x4 createZUpTransform( const QString &upAxis, const QString &forwardAxis )
713{
714 QVector3D up = stringToAxis( upAxis );
715 QVector3D forward = stringToAxis( forwardAxis );
716
717 if ( up.isNull() || forward.isNull() || std::abs( QVector3D::dotProduct( up, forward ) ) > 1e-6f )
718 {
719 // no transform (identity matrix) on error
720 return QMatrix4x4();
721 }
722
723 QVector3D right = QVector3D::crossProduct( forward, up ).normalized();
724 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 );
725}
726
727void QgsModelPoint3DSymbolHandler::addSceneEntities(
728 const Qgs3DRenderContext &context,
729 const QVector<QVector3D> &positions,
730 const QVector<QVector3D> &scales,
731 const QVector<QQuaternion> &rotations,
732 const QgsVector3D &chunkOrigin,
733 const QgsPoint3DSymbol *symbol,
734 Qt3DCore::QEntity *parent
735)
736{
737 Q_UNUSED( context );
738 const QString source = QgsApplication::sourceCache()->localFilePath( symbol->shapeProperty( u"model"_s ).toString() );
739 // if the source is remote, the Qgs3DMapScene will take care of refreshing this 3D symbol when the source is fetched
740
741 const QString upAxis = symbol->shapeProperty( u"upAxis"_s ).toString();
742 const QString forwardAxis = symbol->shapeProperty( u"forwardAxis"_s ).toString();
743 const QMatrix4x4 zUpMatrix = createZUpTransform( upAxis, forwardAxis );
744
745 if ( !source.isEmpty() )
746 {
747 int index = 0;
748 for ( const QVector3D &position : positions )
749 {
750 // build the entity
751 Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
752
753 const QUrl url = QUrl::fromLocalFile( source );
754 Qt3DRender::QSceneLoader *modelLoader = new Qt3DRender::QSceneLoader;
755 modelLoader->setSource( url );
756
757 QMatrix4x4 entityTransform;
758 entityTransform.scale( scales.at( index ) );
759 entityTransform.rotate( rotations.at( index ) );
760 entityTransform *= zUpMatrix;
761
762 entity->addComponent( modelLoader );
763 entity->addComponent( transform( position, entityTransform, chunkOrigin ) );
764 entity->setParent( parent );
765
766 // cppcheck wrongly believes entity will leak
767 // cppcheck-suppress memleak
768 index++;
769 }
770 }
771 else
772 {
773 QgsDebugMsgLevel( u"File '%1' is not accessible!"_s.arg( symbol->shapeProperty( u"model"_s ).toString() ), 1 );
774 }
775}
776
777void QgsModelPoint3DSymbolHandler::addMeshEntities(
778 const Qgs3DRenderContext &context,
779 const QVector<QVector3D> &positions,
780 const QVector<QVector3D> &scales,
781 const QVector<QQuaternion> &rotations,
782 const QgsVector3D &chunkOrigin,
783 const QgsPoint3DSymbol *symbol,
784 Qt3DCore::QEntity *parent,
785 bool areSelected,
786 bool areHighlighted
787)
788{
789 if ( positions.empty() )
790 return;
791
792 const QString upAxis = symbol->shapeProperty( u"upAxis"_s ).toString();
793 const QString forwardAxis = symbol->shapeProperty( u"forwardAxis"_s ).toString();
794 const QMatrix4x4 zUpMatrix = createZUpTransform( upAxis, forwardAxis );
795
796 const QString source = QgsApplication::sourceCache()->localFilePath( symbol->shapeProperty( u"model"_s ).toString() );
797 if ( !source.isEmpty() )
798 {
799 // build the default material
801 materialContext.setIsSelected( areSelected );
802 materialContext.setIsHighlighted( areHighlighted );
803
805 if ( !mat )
806 return;
807
808 const QUrl url = QUrl::fromLocalFile( source );
809
810 // get nodes
811 int index = 0;
812 for ( const QVector3D &position : positions )
813 {
814 // build the entity
815 Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
816
817 Qt3DRender::QMesh *mesh = new Qt3DRender::QMesh;
818 mesh->setSource( url );
819
820 entity->addComponent( mesh );
821 entity->addComponent( mat );
822
823 QMatrix4x4 entityTransform;
824 entityTransform.scale( scales.at( index ) );
825 entityTransform.rotate( rotations.at( index ) );
826 entityTransform *= zUpMatrix;
827
828 entity->addComponent( transform( position, entityTransform, chunkOrigin ) );
829 entity->setParent( parent );
830
831 // cppcheck wrongly believes entity will leak
832 // cppcheck-suppress memleak
833 index++;
834 }
835 }
836 else
837 {
838 QgsDebugMsgLevel( u"File '%1' is not accessible!"_s.arg( symbol->shapeProperty( u"model"_s ).toString() ), 1 );
839 }
840}
841
842QgsGeoTransform *QgsModelPoint3DSymbolHandler::transform( QVector3D position, const QMatrix4x4 &transform, const QgsVector3D &chunkOrigin )
843{
844 // position is relative to chunkOrigin
845 QgsGeoTransform *tr = new QgsGeoTransform;
846 tr->setMatrix( transform );
847 tr->setGeoTranslation( chunkOrigin + position + tr->translation() );
848 return tr;
849}
850
851// --------------
852
853//* BILLBOARD RENDERING *//
854
855class QgsPoint3DBillboardSymbolHandler : public QgsFeature3DHandler
856{
857 public:
858 QgsPoint3DBillboardSymbolHandler( const QgsPoint3DSymbol *symbol, const QgsFeatureIds &selectedIds )
859 : mSymbol( static_cast<QgsPoint3DSymbol *>( symbol->clone() ) )
860 , mSelectedIds( selectedIds )
861 {}
862
863 bool prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames, const QgsBox3D &chunkExtent ) override;
864 void processFeature( const QgsFeature &feature, const Qgs3DRenderContext &context ) override;
865 void finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context ) override;
866
867 private:
869 struct PointData
870 {
871 QVector<QVector3D> positions; // contains triplets of float x,y,z for each point
872 };
873
874 void makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, PointData &out, bool selected );
875
876 // input specific for this class
877 std::unique_ptr<QgsPoint3DSymbol> mSymbol;
878 // inputs - generic
879 QgsFeatureIds mSelectedIds;
880 // outputs
881 PointData outNormal;
882 PointData outSelected;
883};
884
885bool QgsPoint3DBillboardSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames, const QgsBox3D &chunkExtent )
886{
887 Q_UNUSED( context )
888 Q_UNUSED( attributeNames )
889
890 mChunkOrigin = chunkExtent.center();
891 mChunkExtent = chunkExtent;
892
893 return true;
894}
895
896void QgsPoint3DBillboardSymbolHandler::processFeature( const QgsFeature &feature, const Qgs3DRenderContext &context )
897{
898 PointData &out = mSelectedIds.contains( feature.id() ) ? outSelected : outNormal;
899
900 if ( feature.geometry().isNull() )
901 return;
902
903 Qgs3DUtils::extractPointPositions( feature, context, mChunkOrigin, mSymbol->altitudeClamping(), out.positions );
904 mFeatureCount++;
905}
906
907void QgsPoint3DBillboardSymbolHandler::finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context )
908{
909 makeEntity( parent, context, outNormal, false );
910 makeEntity( parent, context, outSelected, true );
911
912 updateZRangeFromPositions( outNormal.positions );
913 updateZRangeFromPositions( outSelected.positions );
914
915 // the elevation offset is applied externally through a QTransform of QEntity so let's account for it
916 const float billboardHeight = mSymbol->billboardHeight();
917 // as we are relative to chunk center elevation we have to add mChunkOrigin.z()
918 mZMin += static_cast<float>( billboardHeight + mChunkOrigin.z() );
919 mZMax += static_cast<float>( billboardHeight + mChunkOrigin.z() );
920}
921
922void QgsPoint3DBillboardSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, PointData &out, bool selected )
923{
924 if ( out.positions.isEmpty() )
925 {
926 return; // nothing to show - no need to create the entity
927 }
928
929 // Billboard Geometry
930 QgsBillboardGeometry *billboardGeometry = new QgsBillboardGeometry();
931 billboardGeometry->setPositions( out.positions );
932
933 // Billboard Geometry Renderer
934 Qt3DRender::QGeometryRenderer *billboardGeometryRenderer = new Qt3DRender::QGeometryRenderer;
935 billboardGeometryRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::Points );
936 billboardGeometryRenderer->setGeometry( billboardGeometry );
937 billboardGeometryRenderer->setVertexCount( billboardGeometry->count() );
938
939 // Billboard Material
941 QgsMarkerSymbol *symbol = mSymbol->billboardSymbol();
942
943 if ( symbol )
944 {
945 billboardMaterial->setTexture2DFromSymbol( symbol, context, selected );
946 }
947 else
948 {
949 billboardMaterial->useDefaultSymbol( context, selected );
950 }
951
952 // Billboard Transform
953 QgsGeoTransform *billboardTransform = new QgsGeoTransform;
954 billboardTransform->setGeoTranslation( mChunkOrigin + QgsVector3D( 0, 0, mSymbol->billboardHeight() ) );
955
956 // Build the entity
957 Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
958
959 entity->addComponent( billboardMaterial );
960 entity->addComponent( billboardTransform );
961 entity->addComponent( billboardGeometryRenderer );
962 entity->setParent( parent );
963
964 // cppcheck wrongly believes entity will leak
965 // cppcheck-suppress memleak
966}
967
968
969namespace Qgs3DSymbolImpl
970{
971
972 QgsFeature3DHandler *handlerForPoint3DSymbol( const QgsVectorLayer *layer, const QgsAbstract3DSymbol *symbol )
973 {
974 const QgsPoint3DSymbol *pointSymbol = dynamic_cast<const QgsPoint3DSymbol *>( symbol );
975 if ( !pointSymbol )
976 return nullptr;
977
978 if ( pointSymbol->shape() == Qgis::Point3DShape::Model )
979 return new QgsModelPoint3DSymbolHandler( pointSymbol, layer->selectedFeatureIds() );
980 // Add proper handler for billboard
981 else if ( pointSymbol->shape() == Qgis::Point3DShape::Billboard )
982 return new QgsPoint3DBillboardSymbolHandler( pointSymbol, layer->selectedFeatureIds() );
983 else
984 return new QgsInstancedPoint3DSymbolHandler( pointSymbol, layer->selectedFeatureIds() );
985 }
986} // namespace Qgs3DSymbolImpl
987
@ 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
QFlags< InstancedMaterialFlag > InstancedMaterialFlags
Definition qgis.h:4365
@ DataDefinedRotation
Per-instance data-defined rotation.
Definition qgis.h:4362
@ DataDefinedScale
Per-instance data-defined scale.
Definition qgis.h:4361
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 QgsMaterial * toMaterial(const QgsAbstractMaterialSettings *settings, Qgis::MaterialRenderingTechnique technique, const QgsMaterialContext &context)
Creates a new QgsMaterial object representing the material settings.
Definition qgs3d.cpp:152
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.
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:63