QGIS API Documentation 4.1.0-Master (31622b25bb0)
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 Qt3DExtras::QSphereGeometry *g = new Qt3DExtras::QSphereGeometry;
435 g->setRadius( radius );
436 return g;
437 }
438
440 {
441 const float length = symbol->shapeProperty( u"length"_s ).toFloat();
442 const float bottomRadius = symbol->shapeProperty( u"bottomRadius"_s ).toFloat();
443 const float topRadius = symbol->shapeProperty( u"topRadius"_s ).toFloat();
444
445 Qt3DExtras::QConeGeometry *g = new Qt3DExtras::QConeGeometry;
446 g->setLength( length );
447 g->setBottomRadius( bottomRadius );
448 g->setTopRadius( topRadius );
449 //g->setHasBottomEndcap(hasBottomEndcap);
450 //g->setHasTopEndcap(hasTopEndcap);
451 return g;
452 }
453
455 {
456 const float size = symbol->shapeProperty( u"size"_s ).toFloat();
457 Qt3DExtras::QCuboidGeometry *g = new Qt3DExtras::QCuboidGeometry;
458 g->setXExtent( size );
459 g->setYExtent( size );
460 g->setZExtent( size );
461 return g;
462 }
463
465 {
466 const float radius = symbol->shapeProperty( u"radius"_s ).toFloat();
467 const float minorRadius = symbol->shapeProperty( u"minorRadius"_s ).toFloat();
468 Qt3DExtras::QTorusGeometry *g = new Qt3DExtras::QTorusGeometry;
469 g->setRadius( radius );
470 g->setMinorRadius( minorRadius );
471 return g;
472 }
473
475 {
476 const float size = symbol->shapeProperty( u"size"_s ).toFloat();
477 Qt3DExtras::QPlaneGeometry *g = new Qt3DExtras::QPlaneGeometry;
478 g->setWidth( size );
479 g->setHeight( size );
480 return g;
481 }
482
484 {
485 const float depth = symbol->shapeProperty( u"depth"_s ).toFloat();
486 const QString text = symbol->shapeProperty( u"text"_s ).toString();
487 Qt3DExtras::QExtrudedTextGeometry *g = new Qt3DExtras::QExtrudedTextGeometry;
488 g->setDepth( depth );
489 g->setText( text );
490 return g;
491 }
492
495 break;
496 }
497 Q_ASSERT( false );
498 return nullptr;
499}
500
501//* 3D MODEL RENDERING *//
502
503
504class QgsModelPoint3DSymbolHandler : public QgsFeature3DHandler
505{
506 public:
507 QgsModelPoint3DSymbolHandler( const QgsPoint3DSymbol *symbol, const QgsFeatureIds &selectedIds )
508 : mSymbol( static_cast<QgsPoint3DSymbol *>( symbol->clone() ) )
509 , mSelectedIds( selectedIds )
510 {}
511
512 bool prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames, const QgsBox3D &chunkExtent ) override;
513 void processFeature( const QgsFeature &feature, const Qgs3DRenderContext &context ) override;
514 void finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context ) override;
515
516 private:
517 static void addSceneEntities(
518 const Qgs3DRenderContext &context,
519 const QVector<QVector3D> &positions,
520 const QVector<QVector3D> &scales,
521 const QVector<QQuaternion> &rotations,
522 const QgsVector3D &chunkOrigin,
523 const QgsPoint3DSymbol *symbol,
524 Qt3DCore::QEntity *parent
525 );
526 static void addMeshEntities(
527 const Qgs3DRenderContext &context,
528 const QVector<QVector3D> &positions,
529 const QVector<QVector3D> &scales,
530 const QVector<QQuaternion> &rotations,
531 const QgsVector3D &chunkOrigin,
532 const QgsPoint3DSymbol *symbol,
533 Qt3DCore::QEntity *parent,
534 bool areSelected,
535 bool areHighlighted
536 );
537 static QgsGeoTransform *transform( QVector3D position, const QMatrix4x4 &transform, const QgsVector3D &chunkOrigin );
538
540 struct PointData
541 {
542 QVector<QVector3D> positions; // contains triplets of float x,y,z for each point
543 QVector<QVector3D> scales;
544 QVector<QQuaternion> rotations;
545 };
546
547 void makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, PointData &out, bool selected );
548
549 // input specific for this class
550 std::unique_ptr<QgsPoint3DSymbol> mSymbol;
551
552 QVector3D mSymbolScale;
553 QQuaternion mSymbolRotation;
554 QVector3D mPointTranslation;
555
556 // inputs - generic
557 QgsFeatureIds mSelectedIds;
558 // outputs
559 PointData outNormal;
560 PointData outSelected;
561};
562
563bool QgsModelPoint3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames, const QgsBox3D &chunkExtent )
564{
565 Q_UNUSED( context )
566 Q_UNUSED( attributeNames )
567
568 mChunkOrigin = chunkExtent.center();
569 mChunkExtent = chunkExtent;
570
571 QSet<QString> attrs = mSymbol->dataDefinedProperties().referencedFields( context.expressionContext() );
572 attributeNames.unite( attrs );
573
574 Qgs3DUtils::decomposeTransformMatrix( mSymbol->transform(), mPointTranslation, mSymbolRotation, mSymbolScale );
575 return true;
576}
577
578void QgsModelPoint3DSymbolHandler::processFeature( const QgsFeature &feature, const Qgs3DRenderContext &context )
579{
580 PointData &out = mSelectedIds.contains( feature.id() ) ? outSelected : outNormal;
581
582 if ( feature.geometry().isNull() )
583 return;
584
585 const QgsPropertyCollection &ddp = mSymbol->dataDefinedProperties();
586
587 QgsVector3D translation = mPointTranslation;
588 const bool hasDDTranslation = ddp.isActive( QgsAbstract3DSymbol::Property::TranslationX )
591 if ( hasDDTranslation )
592 {
593 const double translationX = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::TranslationX, context.expressionContext(), translation.x() );
594 const double translationY = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::TranslationY, context.expressionContext(), translation.y() );
595 const double translationZ = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::TranslationZ, context.expressionContext(), translation.z() );
596 translation = QgsVector3D( translationX, translationY, translationZ );
597 }
598
599 const std::size_t oldSize = out.positions.size();
600 Qgs3DUtils::extractPointPositions( feature, context, mChunkOrigin, mSymbol->altitudeClamping(), out.positions, translation );
601 const std::size_t added = out.positions.size() - oldSize;
602
603 QVector3D scale = mSymbolScale;
605 if ( hasDDScale )
606 {
607 const double scaleX = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::ScaleX, context.expressionContext(), mSymbolScale.x() );
608 const double scaleY = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::ScaleY, context.expressionContext(), mSymbolScale.y() );
609 const double scaleZ = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::ScaleZ, context.expressionContext(), mSymbolScale.z() );
610 scale = QVector3D( static_cast< float >( scaleX ), static_cast< float >( scaleY ), static_cast< float >( scaleZ ) );
611 }
612
613 out.scales.resize( out.positions.size() );
614 QVector3D *outScale = out.scales.data() + oldSize;
615 for ( std::size_t i = 0; i < added; ++i )
616 {
617 ( *outScale++ ) = scale;
618 }
619
620 QQuaternion rotation = mSymbolRotation;
621 const bool hasDDRotation = ddp.isActive( QgsAbstract3DSymbol::Property::RotationX )
624 if ( hasDDRotation )
625 {
626 // extract default rotation components from symbol rotation
627 const QVector3D baseEuler = mSymbolRotation.toEulerAngles();
628
629 const double rotationX = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::RotationX, context.expressionContext(), baseEuler.x() );
630 const double rotationY = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::RotationY, context.expressionContext(), baseEuler.y() );
631 const double rotationZ = ddp.valueAsDouble( QgsAbstract3DSymbol::Property::RotationZ, context.expressionContext(), baseEuler.z() );
632
633 //... and then re-calculate the rotation vector for this feature
634 rotation = QQuaternion::fromEulerAngles( static_cast< float >( rotationX ), static_cast< float >( rotationY ), static_cast< float >( rotationZ ) );
635 }
636
637 out.rotations.resize( out.positions.size() );
638 QQuaternion *outRotation = out.rotations.data() + oldSize;
639 for ( std::size_t i = 0; i < added; ++i )
640 {
641 ( *outRotation++ ) = rotation;
642 }
643
644 mFeatureCount++;
645}
646
647void QgsModelPoint3DSymbolHandler::finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context )
648{
649 makeEntity( parent, context, outNormal, false );
650 makeEntity( parent, context, outSelected, true );
651
652 updateZRangeFromPositions( outNormal.positions );
653 updateZRangeFromPositions( outSelected.positions );
654
655 // the elevation offset is applied separately in QTransform added to sub-entities
656 const float symbolHeight = mSymbol->transform().data()[14];
657 // as we are relative to chunk center elevation we have to add mChunkOrigin.z()
658 mZMin += static_cast<float>( symbolHeight + mChunkOrigin.z() );
659 mZMax += static_cast<float>( symbolHeight + mChunkOrigin.z() );
660}
661
662void QgsModelPoint3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, PointData &out, bool selected )
663{
664 if ( out.positions.isEmpty() )
665 {
666 return; // nothing to show - no need to create the entity
667 }
668
669 if ( selected )
670 {
671 addMeshEntities( context, out.positions, out.scales, out.rotations, mChunkOrigin, mSymbol.get(), parent, true, mHighlightingEnabled );
672 }
673 else
674 {
675 // "overwriteMaterial" is a legacy setting indicating that non-embedded material should be used
676 if ( mSymbol->shapeProperty( u"overwriteMaterial"_s ).toBool() || ( mSymbol->materialSettings() && mSymbol->materialSettings()->type() != "null"_L1 ) || mHighlightingEnabled )
677 {
678 addMeshEntities( context, out.positions, out.scales, out.rotations, mChunkOrigin, mSymbol.get(), parent, false, mHighlightingEnabled );
679 }
680 else
681 {
682 addSceneEntities( context, out.positions, out.scales, out.rotations, mChunkOrigin, mSymbol.get(), parent );
683 }
684 }
685}
686
687QVector3D stringToAxis( const QString &axis )
688{
689 if ( axis == "x"_L1 )
690 return QVector3D( 1.0f, 0.0f, 0.0f );
691 if ( axis == "-x"_L1 )
692 return QVector3D( -1.0f, 0.0f, 0.0f );
693 if ( axis == "y"_L1 )
694 return QVector3D( 0.0f, 1.0f, 0.0f );
695 if ( axis == "-y"_L1 )
696 return QVector3D( 0.0f, -1.0f, 0.0f );
697 if ( axis == "z"_L1 )
698 return QVector3D( 0.0f, 0.0f, 1.0f );
699 if ( axis == "-z"_L1 )
700 return QVector3D( 0.0f, 0.0f, -1.0f );
701
702 return QVector3D();
703}
704
705QMatrix4x4 createZUpTransform( const QString &upAxis, const QString &forwardAxis )
706{
707 QVector3D up = stringToAxis( upAxis );
708 QVector3D forward = stringToAxis( forwardAxis );
709
710 if ( up.isNull() || forward.isNull() || std::abs( QVector3D::dotProduct( up, forward ) ) > 1e-6f )
711 {
712 // no transform (identity matrix) on error
713 return QMatrix4x4();
714 }
715
716 QVector3D right = QVector3D::crossProduct( forward, up ).normalized();
717 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 );
718}
719
720void QgsModelPoint3DSymbolHandler::addSceneEntities(
721 const Qgs3DRenderContext &context,
722 const QVector<QVector3D> &positions,
723 const QVector<QVector3D> &scales,
724 const QVector<QQuaternion> &rotations,
725 const QgsVector3D &chunkOrigin,
726 const QgsPoint3DSymbol *symbol,
727 Qt3DCore::QEntity *parent
728)
729{
730 Q_UNUSED( context );
731 const QString source = QgsApplication::sourceCache()->localFilePath( symbol->shapeProperty( u"model"_s ).toString() );
732 // if the source is remote, the Qgs3DMapScene will take care of refreshing this 3D symbol when the source is fetched
733
734 const QString upAxis = symbol->shapeProperty( u"upAxis"_s ).toString();
735 const QString forwardAxis = symbol->shapeProperty( u"forwardAxis"_s ).toString();
736 const QMatrix4x4 zUpMatrix = createZUpTransform( upAxis, forwardAxis );
737
738 if ( !source.isEmpty() )
739 {
740 int index = 0;
741 for ( const QVector3D &position : positions )
742 {
743 // build the entity
744 Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
745
746 const QUrl url = QUrl::fromLocalFile( source );
747 Qt3DRender::QSceneLoader *modelLoader = new Qt3DRender::QSceneLoader;
748 modelLoader->setSource( url );
749
750 QMatrix4x4 entityTransform;
751 entityTransform.scale( scales.at( index ) );
752 entityTransform.rotate( rotations.at( index ) );
753 entityTransform *= zUpMatrix;
754
755 entity->addComponent( modelLoader );
756 entity->addComponent( transform( position, entityTransform, chunkOrigin ) );
757 entity->setParent( parent );
758
759 // cppcheck wrongly believes entity will leak
760 // cppcheck-suppress memleak
761 index++;
762 }
763 }
764 else
765 {
766 QgsDebugMsgLevel( u"File '%1' is not accessible!"_s.arg( symbol->shapeProperty( u"model"_s ).toString() ), 1 );
767 }
768}
769
770void QgsModelPoint3DSymbolHandler::addMeshEntities(
771 const Qgs3DRenderContext &context,
772 const QVector<QVector3D> &positions,
773 const QVector<QVector3D> &scales,
774 const QVector<QQuaternion> &rotations,
775 const QgsVector3D &chunkOrigin,
776 const QgsPoint3DSymbol *symbol,
777 Qt3DCore::QEntity *parent,
778 bool areSelected,
779 bool areHighlighted
780)
781{
782 if ( positions.empty() )
783 return;
784
785 const QString upAxis = symbol->shapeProperty( u"upAxis"_s ).toString();
786 const QString forwardAxis = symbol->shapeProperty( u"forwardAxis"_s ).toString();
787 const QMatrix4x4 zUpMatrix = createZUpTransform( upAxis, forwardAxis );
788
789 const QString source = QgsApplication::sourceCache()->localFilePath( symbol->shapeProperty( u"model"_s ).toString() );
790 if ( !source.isEmpty() )
791 {
792 // build the default material
794 materialContext.setIsSelected( areSelected );
795 materialContext.setIsHighlighted( areHighlighted );
796
798 if ( !mat )
799 return;
800
801 const QUrl url = QUrl::fromLocalFile( source );
802
803 // get nodes
804 int index = 0;
805 for ( const QVector3D &position : positions )
806 {
807 // build the entity
808 Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
809
810 Qt3DRender::QMesh *mesh = new Qt3DRender::QMesh;
811 mesh->setSource( url );
812
813 entity->addComponent( mesh );
814 entity->addComponent( mat );
815
816 QMatrix4x4 entityTransform;
817 entityTransform.scale( scales.at( index ) );
818 entityTransform.rotate( rotations.at( index ) );
819 entityTransform *= zUpMatrix;
820
821 entity->addComponent( transform( position, entityTransform, chunkOrigin ) );
822 entity->setParent( parent );
823
824 // cppcheck wrongly believes entity will leak
825 // cppcheck-suppress memleak
826 index++;
827 }
828 }
829 else
830 {
831 QgsDebugMsgLevel( u"File '%1' is not accessible!"_s.arg( symbol->shapeProperty( u"model"_s ).toString() ), 1 );
832 }
833}
834
835QgsGeoTransform *QgsModelPoint3DSymbolHandler::transform( QVector3D position, const QMatrix4x4 &transform, const QgsVector3D &chunkOrigin )
836{
837 // position is relative to chunkOrigin
838 QgsGeoTransform *tr = new QgsGeoTransform;
839 tr->setMatrix( transform );
840 tr->setGeoTranslation( chunkOrigin + position + tr->translation() );
841 return tr;
842}
843
844// --------------
845
846//* BILLBOARD RENDERING *//
847
848class QgsPoint3DBillboardSymbolHandler : public QgsFeature3DHandler
849{
850 public:
851 QgsPoint3DBillboardSymbolHandler( const QgsPoint3DSymbol *symbol, const QgsFeatureIds &selectedIds )
852 : mSymbol( static_cast<QgsPoint3DSymbol *>( symbol->clone() ) )
853 , mSelectedIds( selectedIds )
854 {}
855
856 bool prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames, const QgsBox3D &chunkExtent ) override;
857 void processFeature( const QgsFeature &feature, const Qgs3DRenderContext &context ) override;
858 void finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context ) override;
859
860 private:
862 struct PointData
863 {
864 QVector<QVector3D> positions; // contains triplets of float x,y,z for each point
865 };
866
867 void makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, PointData &out, bool selected );
868
869 // input specific for this class
870 std::unique_ptr<QgsPoint3DSymbol> mSymbol;
871 // inputs - generic
872 QgsFeatureIds mSelectedIds;
873 // outputs
874 PointData outNormal;
875 PointData outSelected;
876};
877
878bool QgsPoint3DBillboardSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames, const QgsBox3D &chunkExtent )
879{
880 Q_UNUSED( context )
881 Q_UNUSED( attributeNames )
882
883 mChunkOrigin = chunkExtent.center();
884 mChunkExtent = chunkExtent;
885
886 return true;
887}
888
889void QgsPoint3DBillboardSymbolHandler::processFeature( const QgsFeature &feature, const Qgs3DRenderContext &context )
890{
891 PointData &out = mSelectedIds.contains( feature.id() ) ? outSelected : outNormal;
892
893 if ( feature.geometry().isNull() )
894 return;
895
896 Qgs3DUtils::extractPointPositions( feature, context, mChunkOrigin, mSymbol->altitudeClamping(), out.positions );
897 mFeatureCount++;
898}
899
900void QgsPoint3DBillboardSymbolHandler::finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context )
901{
902 makeEntity( parent, context, outNormal, false );
903 makeEntity( parent, context, outSelected, true );
904
905 updateZRangeFromPositions( outNormal.positions );
906 updateZRangeFromPositions( outSelected.positions );
907
908 // the elevation offset is applied externally through a QTransform of QEntity so let's account for it
909 const float billboardHeight = mSymbol->billboardHeight();
910 // as we are relative to chunk center elevation we have to add mChunkOrigin.z()
911 mZMin += static_cast<float>( billboardHeight + mChunkOrigin.z() );
912 mZMax += static_cast<float>( billboardHeight + mChunkOrigin.z() );
913}
914
915void QgsPoint3DBillboardSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, PointData &out, bool selected )
916{
917 if ( out.positions.isEmpty() )
918 {
919 return; // nothing to show - no need to create the entity
920 }
921
922 // Billboard Geometry
923 QgsBillboardGeometry *billboardGeometry = new QgsBillboardGeometry();
924 billboardGeometry->setPositions( out.positions );
925
926 // Billboard Geometry Renderer
927 Qt3DRender::QGeometryRenderer *billboardGeometryRenderer = new Qt3DRender::QGeometryRenderer;
928 billboardGeometryRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::Points );
929 billboardGeometryRenderer->setGeometry( billboardGeometry );
930 billboardGeometryRenderer->setVertexCount( billboardGeometry->count() );
931
932 // Billboard Material
934 QgsMarkerSymbol *symbol = mSymbol->billboardSymbol();
935
936 if ( symbol )
937 {
938 billboardMaterial->setTexture2DFromSymbol( symbol, context, selected );
939 }
940 else
941 {
942 billboardMaterial->useDefaultSymbol( context, selected );
943 }
944
945 // Billboard Transform
946 QgsGeoTransform *billboardTransform = new QgsGeoTransform;
947 billboardTransform->setGeoTranslation( mChunkOrigin + QgsVector3D( 0, 0, mSymbol->billboardHeight() ) );
948
949 // Build the entity
950 Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
951
952 entity->addComponent( billboardMaterial );
953 entity->addComponent( billboardTransform );
954 entity->addComponent( billboardGeometryRenderer );
955 entity->setParent( parent );
956
957 // cppcheck wrongly believes entity will leak
958 // cppcheck-suppress memleak
959}
960
961
962namespace Qgs3DSymbolImpl
963{
964
965 QgsFeature3DHandler *handlerForPoint3DSymbol( const QgsVectorLayer *layer, const QgsAbstract3DSymbol *symbol )
966 {
967 const QgsPoint3DSymbol *pointSymbol = dynamic_cast<const QgsPoint3DSymbol *>( symbol );
968 if ( !pointSymbol )
969 return nullptr;
970
971 if ( pointSymbol->shape() == Qgis::Point3DShape::Model )
972 return new QgsModelPoint3DSymbolHandler( pointSymbol, layer->selectedFeatureIds() );
973 // Add proper handler for billboard
974 else if ( pointSymbol->shape() == Qgis::Point3DShape::Billboard )
975 return new QgsPoint3DBillboardSymbolHandler( pointSymbol, layer->selectedFeatureIds() );
976 else
977 return new QgsInstancedPoint3DSymbolHandler( pointSymbol, layer->selectedFeatureIds() );
978 }
979} // namespace Qgs3DSymbolImpl
980
@ 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.
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