QGIS API Documentation  3.8.0-Zanzibar (11aff65)
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 <Qt3DRender/QAttribute>
19 #include <Qt3DRender/QBuffer>
20 #include <Qt3DRender/QEffect>
21 #include <Qt3DRender/QGraphicsApiFilter>
22 #include <Qt3DRender/QParameter>
23 #include <Qt3DRender/QTechnique>
24 
25 #include <Qt3DExtras/QCylinderGeometry>
26 #include <Qt3DExtras/QConeGeometry>
27 #include <Qt3DExtras/QCuboidGeometry>
28 #include <Qt3DExtras/QPlaneGeometry>
29 #include <Qt3DExtras/QSphereGeometry>
30 #include <Qt3DExtras/QTorusGeometry>
31 #include <Qt3DExtras/QPhongMaterial>
32 #include <Qt3DRender/QSceneLoader>
33 
34 #include <Qt3DRender/QMesh>
35 
36 #if QT_VERSION >= 0x050900
37 #include <Qt3DExtras/QExtrudedTextGeometry>
38 #endif
39 
40 #include <QUrl>
41 #include <QVector3D>
42 
43 #include "qgspoint3dsymbol.h"
44 #include "qgs3dmapsettings.h"
45 
46 #include "qgsvectorlayer.h"
47 #include "qgspoint.h"
48 #include "qgs3dutils.h"
49 
51 
52 
53 //* INSTANCED RENDERING *//
54 
55 
56 class QgsInstancedPoint3DSymbolHandler : public QgsFeature3DHandler
57 {
58  public:
59  QgsInstancedPoint3DSymbolHandler( const QgsPoint3DSymbol &symbol, const QgsFeatureIds &selectedIds )
60  : mSymbol( symbol ), mSelectedIds( selectedIds ) {}
61 
62  bool prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames ) override;
63  void processFeature( QgsFeature &feature, const Qgs3DRenderContext &context ) override;
64  void finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context ) override;
65 
66  private:
67 
68  static Qt3DRender::QMaterial *material( const QgsPoint3DSymbol &symbol );
69  static Qt3DRender::QGeometryRenderer *renderer( const QgsPoint3DSymbol &symbol, const QVector<QVector3D> &positions );
70  static Qt3DRender::QGeometry *symbolGeometry( QgsPoint3DSymbol::Shape shape, const QVariantMap &shapeProperties );
71 
73  struct PointData
74  {
75  QVector<QVector3D> positions; // contains triplets of float x,y,z for each point
76  };
77 
78  void makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, PointData &out, bool selected );
79 
80  // input specific for this class
81  const QgsPoint3DSymbol &mSymbol;
82  // inputs - generic
83  QgsFeatureIds mSelectedIds;
84 
85  // outputs
86  PointData outNormal;
87  PointData outSelected;
88 };
89 
90 
91 bool QgsInstancedPoint3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames )
92 {
93  Q_UNUSED( context )
94  Q_UNUSED( attributeNames )
95  return true;
96 }
97 
98 void QgsInstancedPoint3DSymbolHandler::processFeature( QgsFeature &feature, const Qgs3DRenderContext &context )
99 {
100  PointData &out = mSelectedIds.contains( feature.id() ) ? outSelected : outNormal;
101 
102  if ( feature.geometry().isNull() )
103  return;
104 
105  Qgs3DUtils::extractPointPositions( feature, context.map(), mSymbol.altitudeClamping(), out.positions );
106 }
107 
108 void QgsInstancedPoint3DSymbolHandler::finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context )
109 {
110  makeEntity( parent, context, outNormal, false );
111  makeEntity( parent, context, outSelected, true );
112 }
113 
114 void QgsInstancedPoint3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, PointData &out, bool selected )
115 {
116  // build the default material
117  Qt3DRender::QMaterial *mat = material( mSymbol );
118 
119  if ( selected )
120  {
121  // update the material with selection colors
122  for ( Qt3DRender::QParameter *param : mat->effect()->parameters() )
123  {
124  if ( param->name() == QLatin1String( "kd" ) ) // diffuse
125  param->setValue( context.map().selectionColor() );
126  else if ( param->name() == QLatin1String( "ka" ) ) // ambient
127  param->setValue( context.map().selectionColor().darker() );
128  }
129  }
130 
131  // build the entity
132  Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
133  entity->addComponent( renderer( mSymbol, out.positions ) );
134  entity->addComponent( mat );
135  entity->setParent( parent );
136 }
137 
138 
139 
140 Qt3DRender::QMaterial *QgsInstancedPoint3DSymbolHandler::material( const QgsPoint3DSymbol &symbol )
141 {
142  Qt3DRender::QFilterKey *filterKey = new Qt3DRender::QFilterKey;
143  filterKey->setName( QStringLiteral( "renderingStyle" ) );
144  filterKey->setValue( "forward" );
145 
146  // the fragment shader implements a simplified version of phong shading that uses hardcoded light
147  // (instead of whatever light we have defined in the scene)
148  // TODO: use phong shading that respects lights from the scene
149  Qt3DRender::QShaderProgram *shaderProgram = new Qt3DRender::QShaderProgram;
150  shaderProgram->setVertexShaderCode( Qt3DRender::QShaderProgram::loadSource( QUrl( QStringLiteral( "qrc:/shaders/instanced.vert" ) ) ) );
151  shaderProgram->setFragmentShaderCode( Qt3DRender::QShaderProgram::loadSource( QUrl( QStringLiteral( "qrc:/shaders/instanced.frag" ) ) ) );
152 
153  Qt3DRender::QRenderPass *renderPass = new Qt3DRender::QRenderPass;
154  renderPass->setShaderProgram( shaderProgram );
155 
156  Qt3DRender::QTechnique *technique = new Qt3DRender::QTechnique;
157  technique->addFilterKey( filterKey );
158  technique->addRenderPass( renderPass );
159  technique->graphicsApiFilter()->setApi( Qt3DRender::QGraphicsApiFilter::OpenGL );
160  technique->graphicsApiFilter()->setProfile( Qt3DRender::QGraphicsApiFilter::CoreProfile );
161  technique->graphicsApiFilter()->setMajorVersion( 3 );
162  technique->graphicsApiFilter()->setMinorVersion( 2 );
163 
164  Qt3DRender::QParameter *ambientParameter = new Qt3DRender::QParameter( QStringLiteral( "ka" ), QColor::fromRgbF( 0.05f, 0.05f, 0.05f, 1.0f ) );
165  Qt3DRender::QParameter *diffuseParameter = new Qt3DRender::QParameter( QStringLiteral( "kd" ), QColor::fromRgbF( 0.7f, 0.7f, 0.7f, 1.0f ) );
166  Qt3DRender::QParameter *specularParameter = new Qt3DRender::QParameter( QStringLiteral( "ks" ), QColor::fromRgbF( 0.01f, 0.01f, 0.01f, 1.0f ) );
167  Qt3DRender::QParameter *shininessParameter = new Qt3DRender::QParameter( QStringLiteral( "shininess" ), 150.0f );
168 
169  diffuseParameter->setValue( symbol.material().diffuse() );
170  ambientParameter->setValue( symbol.material().ambient() );
171  specularParameter->setValue( symbol.material().specular() );
172  shininessParameter->setValue( symbol.material().shininess() );
173 
174  QMatrix4x4 transformMatrix = symbol.transform();
175  QMatrix3x3 normalMatrix = transformMatrix.normalMatrix(); // transponed inverse of 3x3 sub-matrix
176 
177  // QMatrix3x3 is not supported for passing to shaders, so we pass QMatrix4x4
178  float *n = normalMatrix.data();
179  QMatrix4x4 normalMatrix4(
180  n[0], n[3], n[6], 0,
181  n[1], n[4], n[7], 0,
182  n[2], n[5], n[8], 0,
183  0, 0, 0, 0 );
184 
185  Qt3DRender::QParameter *paramInst = new Qt3DRender::QParameter;
186  paramInst->setName( QStringLiteral( "inst" ) );
187  paramInst->setValue( transformMatrix );
188 
189  Qt3DRender::QParameter *paramInstNormal = new Qt3DRender::QParameter;
190  paramInstNormal->setName( QStringLiteral( "instNormal" ) );
191  paramInstNormal->setValue( normalMatrix4 );
192 
193  Qt3DRender::QEffect *effect = new Qt3DRender::QEffect;
194  effect->addTechnique( technique );
195  effect->addParameter( paramInst );
196  effect->addParameter( paramInstNormal );
197 
198  effect->addParameter( ambientParameter );
199  effect->addParameter( diffuseParameter );
200  effect->addParameter( specularParameter );
201  effect->addParameter( shininessParameter );
202 
203  Qt3DRender::QMaterial *material = new Qt3DRender::QMaterial;
204  material->setEffect( effect );
205 
206  return material;
207 }
208 
209 Qt3DRender::QGeometryRenderer *QgsInstancedPoint3DSymbolHandler::renderer( const QgsPoint3DSymbol &symbol, const QVector<QVector3D> &positions )
210 {
211  int count = positions.count();
212  int byteCount = positions.count() * sizeof( QVector3D );
213  QByteArray ba;
214  ba.resize( byteCount );
215  memcpy( ba.data(), positions.constData(), byteCount );
216 
217  Qt3DRender::QBuffer *instanceBuffer = new Qt3DRender::QBuffer( Qt3DRender::QBuffer::VertexBuffer );
218  instanceBuffer->setData( ba );
219 
220  Qt3DRender::QAttribute *instanceDataAttribute = new Qt3DRender::QAttribute;
221  instanceDataAttribute->setName( QStringLiteral( "pos" ) );
222  instanceDataAttribute->setAttributeType( Qt3DRender::QAttribute::VertexAttribute );
223  instanceDataAttribute->setVertexBaseType( Qt3DRender::QAttribute::Float );
224  instanceDataAttribute->setVertexSize( 3 );
225  instanceDataAttribute->setDivisor( 1 );
226  instanceDataAttribute->setBuffer( instanceBuffer );
227  instanceDataAttribute->setCount( count );
228  instanceDataAttribute->setByteStride( 3 * sizeof( float ) );
229 
230  Qt3DRender::QGeometry *geometry = symbolGeometry( symbol.shape(), symbol.shapeProperties() );
231  geometry->addAttribute( instanceDataAttribute );
232  geometry->setBoundingVolumePositionAttribute( instanceDataAttribute );
233 
234  Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer;
235  renderer->setGeometry( geometry );
236  renderer->setInstanceCount( count );
237 
238  return renderer;
239 }
240 
241 Qt3DRender::QGeometry *QgsInstancedPoint3DSymbolHandler::symbolGeometry( QgsPoint3DSymbol::Shape shape, const QVariantMap &shapeProperties )
242 {
243  switch ( shape )
244  {
246  {
247  float radius = shapeProperties[QStringLiteral( "radius" )].toFloat();
248  float length = shapeProperties[QStringLiteral( "length" )].toFloat();
249  Qt3DExtras::QCylinderGeometry *g = new Qt3DExtras::QCylinderGeometry;
250  //g->setRings(2); // how many vertices vertically
251  //g->setSlices(8); // how many vertices on circumference
252  g->setRadius( radius ? radius : 10 );
253  g->setLength( length ? length : 10 );
254  return g;
255  }
256 
258  {
259  float radius = shapeProperties[QStringLiteral( "radius" )].toFloat();
260  Qt3DExtras::QSphereGeometry *g = new Qt3DExtras::QSphereGeometry;
261  g->setRadius( radius ? radius : 10 );
262  return g;
263  }
264 
266  {
267  float length = shapeProperties[QStringLiteral( "length" )].toFloat();
268  float bottomRadius = shapeProperties[QStringLiteral( "bottomRadius" )].toFloat();
269  float topRadius = shapeProperties[QStringLiteral( "topRadius" )].toFloat();
270  Qt3DExtras::QConeGeometry *g = new Qt3DExtras::QConeGeometry;
271  g->setLength( length ? length : 10 );
272  g->setBottomRadius( bottomRadius );
273  g->setTopRadius( topRadius );
274  //g->setHasBottomEndcap(hasBottomEndcap);
275  //g->setHasTopEndcap(hasTopEndcap);
276  return g;
277  }
278 
280  {
281  float size = shapeProperties[QStringLiteral( "size" )].toFloat();
282  Qt3DExtras::QCuboidGeometry *g = new Qt3DExtras::QCuboidGeometry;
283  g->setXExtent( size ? size : 10 );
284  g->setYExtent( size ? size : 10 );
285  g->setZExtent( size ? size : 10 );
286  return g;
287  }
288 
290  {
291  float radius = shapeProperties[QStringLiteral( "radius" )].toFloat();
292  float minorRadius = shapeProperties[QStringLiteral( "minorRadius" )].toFloat();
293  Qt3DExtras::QTorusGeometry *g = new Qt3DExtras::QTorusGeometry;
294  g->setRadius( radius ? radius : 10 );
295  g->setMinorRadius( minorRadius ? minorRadius : 5 );
296  return g;
297  }
298 
300  {
301  float size = shapeProperties[QStringLiteral( "size" )].toFloat();
302  Qt3DExtras::QPlaneGeometry *g = new Qt3DExtras::QPlaneGeometry;
303  g->setWidth( size ? size : 10 );
304  g->setHeight( size ? size : 10 );
305  return g;
306  }
307 
308 #if QT_VERSION >= 0x050900
310  {
311  float depth = shapeProperties[QStringLiteral( "depth" )].toFloat();
312  QString text = shapeProperties[QStringLiteral( "text" )].toString();
313  Qt3DExtras::QExtrudedTextGeometry *g = new Qt3DExtras::QExtrudedTextGeometry;
314  g->setDepth( depth ? depth : 1 );
315  g->setText( text );
316  return g;
317  }
318 #endif
319 
320  default:
321  Q_ASSERT( false );
322  return nullptr;
323  }
324 }
325 
326 //* 3D MODEL RENDERING *//
327 
328 
329 class QgsModelPoint3DSymbolHandler : public QgsFeature3DHandler
330 {
331  public:
332  QgsModelPoint3DSymbolHandler( const QgsPoint3DSymbol &symbol, const QgsFeatureIds &selectedIds )
333  : mSymbol( symbol ), mSelectedIds( selectedIds ) {}
334 
335  bool prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames ) override;
336  void processFeature( QgsFeature &feature, const Qgs3DRenderContext &context ) override;
337  void finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context ) override;
338 
339  private:
340 
341  static void addSceneEntities( const Qgs3DMapSettings &map, const QVector<QVector3D> &positions, const QgsPoint3DSymbol &symbol, Qt3DCore::QEntity *parent );
342  static void addMeshEntities( const Qgs3DMapSettings &map, const QVector<QVector3D> &positions, const QgsPoint3DSymbol &symbol, Qt3DCore::QEntity *parent, bool are_selected );
343  static Qt3DCore::QTransform *transform( QVector3D position, const QgsPoint3DSymbol &symbol );
344 
346  struct PointData
347  {
348  QVector<QVector3D> positions; // contains triplets of float x,y,z for each point
349  };
350 
351  void makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, PointData &out, bool selected );
352 
353  // input specific for this class
354  const QgsPoint3DSymbol &mSymbol;
355  // inputs - generic
356  QgsFeatureIds mSelectedIds;
357 
358  // outputs
359  PointData outNormal;
360  PointData outSelected;
361 };
362 
363 bool QgsModelPoint3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames )
364 {
365  Q_UNUSED( context )
366  Q_UNUSED( attributeNames )
367  return true;
368 }
369 
370 void QgsModelPoint3DSymbolHandler::processFeature( QgsFeature &feature, const Qgs3DRenderContext &context )
371 {
372  PointData &out = mSelectedIds.contains( feature.id() ) ? outSelected : outNormal;
373 
374  if ( feature.geometry().isNull() )
375  return;
376 
377  Qgs3DUtils::extractPointPositions( feature, context.map(), mSymbol.altitudeClamping(), out.positions );
378 }
379 
380 void QgsModelPoint3DSymbolHandler::finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context )
381 {
382  makeEntity( parent, context, outNormal, false );
383  makeEntity( parent, context, outSelected, true );
384 }
385 
386 void QgsModelPoint3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, PointData &out, bool selected )
387 {
388  if ( selected )
389  {
390  addMeshEntities( context.map(), out.positions, mSymbol, parent, true );
391  }
392  else
393  {
394  if ( mSymbol.shapeProperties()[QStringLiteral( "overwriteMaterial" )].toBool() )
395  {
396  addMeshEntities( context.map(), out.positions, mSymbol, parent, false );
397  }
398  else
399  {
400  addSceneEntities( context.map(), out.positions, mSymbol, parent );
401  }
402  }
403 }
404 
405 
406 
407 void QgsModelPoint3DSymbolHandler::addSceneEntities( const Qgs3DMapSettings &map, const QVector<QVector3D> &positions, const QgsPoint3DSymbol &symbol, Qt3DCore::QEntity *parent )
408 {
409  Q_UNUSED( map )
410  for ( const QVector3D &position : positions )
411  {
412  // build the entity
413  Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
414 
415  QUrl url = QUrl::fromLocalFile( symbol.shapeProperties()[QStringLiteral( "model" )].toString() );
416  Qt3DRender::QSceneLoader *modelLoader = new Qt3DRender::QSceneLoader;
417  modelLoader->setSource( url );
418 
419  entity->addComponent( modelLoader );
420  entity->addComponent( transform( position, symbol ) );
421  entity->setParent( parent );
422  }
423 }
424 
425 void QgsModelPoint3DSymbolHandler::addMeshEntities( const Qgs3DMapSettings &map, const QVector<QVector3D> &positions, const QgsPoint3DSymbol &symbol, Qt3DCore::QEntity *parent, bool are_selected )
426 {
427  // build the default material
428  Qt3DExtras::QPhongMaterial *mat = Qgs3DUtils::phongMaterial( symbol.material() );
429 
430  if ( are_selected )
431  {
432  mat->setDiffuse( map.selectionColor() );
433  mat->setAmbient( map.selectionColor().darker() );
434  }
435 
436  // get nodes
437  for ( const QVector3D &position : positions )
438  {
439  // build the entity
440  Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
441 
442  QUrl url = QUrl::fromLocalFile( symbol.shapeProperties()[QStringLiteral( "model" )].toString() );
443  Qt3DRender::QMesh *mesh = new Qt3DRender::QMesh;
444  mesh->setSource( url );
445 
446  entity->addComponent( mesh );
447  entity->addComponent( mat );
448  entity->addComponent( transform( position, symbol ) );
449  entity->setParent( parent );
450  }
451 }
452 
453 Qt3DCore::QTransform *QgsModelPoint3DSymbolHandler::transform( QVector3D position, const QgsPoint3DSymbol &symbol )
454 {
455  Qt3DCore::QTransform *tr = new Qt3DCore::QTransform;
456  tr->setMatrix( symbol.transform() );
457  tr->setTranslation( position + tr->translation() );
458  return tr;
459 }
460 
461 // --------------
462 
463 
464 namespace Qgs3DSymbolImpl
465 {
466 
467  QgsFeature3DHandler *handlerForPoint3DSymbol( QgsVectorLayer *layer, const QgsPoint3DSymbol &symbol )
468  {
469  if ( symbol.shape() == QgsPoint3DSymbol::Model )
470  return new QgsModelPoint3DSymbolHandler( symbol, layer->selectedFeatureIds() );
471  else
472  return new QgsInstancedPoint3DSymbolHandler( symbol, layer->selectedFeatureIds() );
473  }
474 
475  Qt3DCore::QEntity *entityForPoint3DSymbol( const Qgs3DMapSettings &map, QgsVectorLayer *layer, const QgsPoint3DSymbol &symbol )
476  {
477  QgsFeature3DHandler *handler = handlerForPoint3DSymbol( layer, symbol );
478  Qt3DCore::QEntity *e = entityFromHandler( handler, map, layer );
479  delete handler;
480  return e;
481  }
482 }
483 
QgsFeatureId id
Definition: qgsfeature.h:64
float shininess() const
Returns shininess of the surface.
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:34
QColor selectionColor() const
Returns color used for selected features.
QColor specular() const
Returns specular color component.
static Qt3DExtras::QPhongMaterial * phongMaterial(const QgsPhongMaterialSettings &settings)
Returns phong material object based on the material settings.
Definition: qgs3dutils.cpp:490
Shape shape() const
Returns 3D shape for points.
QVariantMap shapeProperties() const
Returns a key-value dictionary of point shape properties.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:55
Supported in Qt 5.9+.
3 Definition of the world
QMatrix4x4 transform() const
Returns transform for individual objects represented by the symbol.
static void extractPointPositions(QgsFeature &f, const Qgs3DMapSettings &map, Qgs3DTypes::AltitudeClamping altClamp, QVector< QVector3D > &positions)
Calculates (x,y,z) positions of (multi)point from the given feature.
Definition: qgs3dutils.cpp:346
const QgsFeatureIds & selectedFeatureIds() const
Returns a list of the selected features IDs in this layer.
3 3D symbol that draws point geometries as 3D objects using one of the predefined shapes...
QgsPhongMaterialSettings material() const
Returns material used for shading of the symbol.
Shape
3D shape types supported by the symbol
QColor ambient() const
Returns ambient color component.
QColor diffuse() const
Returns diffuse color component.
QgsGeometry geometry
Definition: qgsfeature.h:67
Represents a vector layer which manages a vector based data sets.