QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
qgs3dsceneexporter.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgs3dsceneexporter.cpp
3  --------------------------------------
4  Date : June 2020
5  Copyright : (C) 2020 by Belgacem Nedjima
6  Email : gb underscore nedjima at esi dot dz
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 "qgs3dsceneexporter.h"
17 
18 #include <QVector>
19 #include <Qt3DCore/QEntity>
20 #include <Qt3DCore/QComponent>
21 #include <Qt3DCore/QNode>
22 #include <Qt3DRender/QGeometry>
23 #include <Qt3DRender/QAttribute>
24 #include <Qt3DRender/QBuffer>
25 #include <Qt3DRender/QGeometryRenderer>
26 #include <Qt3DExtras/QPlaneGeometry>
27 #include <Qt3DCore/QTransform>
28 #include <Qt3DRender/QMaterial>
29 #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
30 #include <Qt3DExtras/QDiffuseMapMaterial>
31 #else
32 #include <Qt3DExtras/QDiffuseSpecularMaterial>
33 #endif
34 #include <Qt3DExtras/QTextureMaterial>
35 #include <Qt3DRender/QTextureImage>
36 #include <Qt3DRender/QTexture>
37 #include <Qt3DRender/QBufferDataGenerator>
38 #include <Qt3DRender/QBufferDataGeneratorPtr>
39 #include <Qt3DRender/QMesh>
40 #include <Qt3DRender/QSceneLoader>
41 #include <Qt3DRender/QAbstractTexture>
42 #include <Qt3DExtras/QCylinderGeometry>
43 #include <Qt3DExtras/QConeGeometry>
44 #include <Qt3DExtras/QSphereGeometry>
45 #include <Qt3DExtras/QCuboidGeometry>
46 #include <Qt3DExtras/QTorusGeometry>
47 #include <Qt3DExtras/QExtrudedTextMesh>
48 #include <Qt3DExtras/QPhongMaterial>
49 #include <Qt3DRender/QAbstractTextureImage>
50 
51 #include <QByteArray>
52 #include <QFile>
53 #include <QTextStream>
54 
56 #include "qgsterraintileentity_p.h"
57 #include "qgsterrainentity_p.h"
58 #include "qgschunknode_p.h"
59 #include "qgsterraingenerator.h"
60 #include "qgs3dmapsettings.h"
62 #include "qgsdemterraingenerator.h"
65 #include "qgs3dexportobject.h"
68 #include "qgsmesh3dgeometry_p.h"
69 #include "qgsmeshlayer.h"
70 #include "qgsmesh3dentity_p.h"
71 #include "qgsmeshterraingenerator.h"
72 #include "qgsvectorlayer.h"
73 #include "qgsabstract3drenderer.h"
76 #include "qgspolygon3dsymbol.h"
77 #include "qgsline3dsymbol.h"
78 #include "qgspoint3dsymbol.h"
79 #include "qgsrulebased3drenderer.h"
80 #include "qgs3dutils.h"
81 #include "qgsbillboardgeometry.h"
82 #include "qgsimagetexture.h"
83 
84 #include <numeric>
85 
86 template<typename T>
87 QVector<T> getAttributeData( Qt3DRender::QAttribute *attribute, const QByteArray &data )
88 {
89  uint bytesOffset = attribute->byteOffset();
90  uint bytesStride = attribute->byteStride();
91  uint vertexSize = attribute->vertexSize();
92  QVector<T> result;
93 
94  if ( bytesStride == 0 )
95  {
96  QgsDebugMsg( "bytesStride==0, the attribute probably was not set properly" );
97  return result;
98  }
99 
100  const char *pData = data.constData();
101  for ( int i = bytesOffset; i < data.size(); i += bytesStride )
102  {
103  for ( unsigned int j = 0; j < vertexSize * sizeof( T ); j += sizeof( T ) )
104  {
105  T v;
106  memcpy( &v, pData + i + j, sizeof( T ) );
107  result.push_back( v );
108  }
109  }
110  return result;
111 }
112 
113 template<typename T>
114 QVector<uint> _getIndexDataImplementation( const QByteArray &data )
115 {
116  QVector<uint> result;
117  const char *pData = data.constData();
118  for ( int i = 0; i < data.size(); i += sizeof( T ) )
119  {
120  T v;
121  memcpy( &v, pData + i, sizeof( T ) );
122  result.push_back( ( uint ) v );
123  }
124  return result;
125 }
126 
127 QVector<uint> getIndexData( Qt3DRender::QAttribute *indexAttribute, const QByteArray &data )
128 {
129  switch ( indexAttribute->vertexBaseType() )
130  {
131  case Qt3DRender::QAttribute::VertexBaseType::Int:
132  return _getIndexDataImplementation<int>( data );
133  case Qt3DRender::QAttribute::VertexBaseType::UnsignedInt:
134  return _getIndexDataImplementation<uint>( data );
135  case Qt3DRender::QAttribute::VertexBaseType::Short:
136  return _getIndexDataImplementation<short>( data );
137  case Qt3DRender::QAttribute::VertexBaseType::UnsignedShort:
138  return _getIndexDataImplementation<ushort>( data );
139  case Qt3DRender::QAttribute::VertexBaseType::Byte:
140  return _getIndexDataImplementation<char>( data );
141  case Qt3DRender::QAttribute::VertexBaseType::UnsignedByte:
142  return _getIndexDataImplementation<uchar>( data );
143  default:
144  QgsDebugMsg( "Probably trying to get index data using an attribute that has vertex data" );
145  break;
146  }
147  return QVector<uint>();
148 }
149 
150 QByteArray getData( Qt3DRender::QBuffer *buffer )
151 {
152  QByteArray bytes = buffer->data();
153  Qt3DRender::QBufferDataGeneratorPtr dataGenerator = buffer->dataGenerator();
154  if ( bytes.isNull() )
155  {
156  if ( !dataGenerator.isNull() )
157  bytes = dataGenerator->operator()();
158  else
159  QgsDebugMsg( "QBuffer is null" );
160  }
161  return bytes;
162 }
163 
164 Qt3DRender::QAttribute *findAttribute( Qt3DRender::QGeometry *geometry, const QString &name, Qt3DRender::QAttribute::AttributeType type )
165 {
166  for ( Qt3DRender::QAttribute *attribute : geometry->attributes() )
167  {
168  if ( attribute->attributeType() != type ) continue;
169  if ( attribute->name() == name ) return attribute;
170  }
171  return nullptr;
172 }
173 
174 template<typename Component>
175 Component *findTypedComponent( Qt3DCore::QEntity *entity )
176 {
177  if ( entity == nullptr ) return nullptr;
178  for ( Qt3DCore::QComponent *component : entity->components() )
179  {
180  Component *typedComponent = qobject_cast<Component *>( component );
181  if ( typedComponent != nullptr )
182  return typedComponent;
183  }
184  return nullptr;
185 }
186 
187 bool Qgs3DSceneExporter::parseVectorLayerEntity( Qt3DCore::QEntity *entity, QgsVectorLayer *layer )
188 {
189  QgsAbstract3DRenderer *abstractRenderer = layer->renderer3D();
190  QString rendererType = abstractRenderer->type();
191 
192  if ( rendererType == "mesh" )
193  {
194  // TODO: handle mesh layers
195  }
196  else
197  {
198  QgsAbstractVectorLayer3DRenderer *abstractVectorRenderer = dynamic_cast< QgsAbstractVectorLayer3DRenderer *>( abstractRenderer );
199  if ( rendererType == "rulebased" )
200  {
201  // Potential bug: meshes loaded using Qt3DRender::QSceneLoader will probably have wrong scale and translation
202  QList<Qt3DRender::QGeometryRenderer *> renderers = entity->findChildren<Qt3DRender::QGeometryRenderer *>();
203  for ( Qt3DRender::QGeometryRenderer *renderer : renderers )
204  {
205  Qt3DCore::QEntity *entity = qobject_cast<Qt3DCore::QEntity *>( renderer->parent() );
206  if ( entity == nullptr ) continue;
207  Qgs3DExportObject *object = processGeometryRenderer( renderer, layer->name() + QStringLiteral( "_" ) );
208  if ( object == nullptr ) continue;
209  if ( mExportTextures )
210  processEntityMaterial( entity, object );
211  mObjects.push_back( object );
212  }
213  return true;
214  }
215  else
216  {
217  QgsVectorLayer3DRenderer *vectorLayerRenderer = dynamic_cast< QgsVectorLayer3DRenderer *>( abstractVectorRenderer );
218  const QgsAbstract3DSymbol *symbol = vectorLayerRenderer->symbol();
219  bool exported = symbol->exportGeometries( this, entity, layer->name() + QStringLiteral( "_" ) );
220  return exported;
221  }
222  }
223  return false;
224 }
225 
226 void Qgs3DSceneExporter::processEntityMaterial( Qt3DCore::QEntity *entity, Qgs3DExportObject *object )
227 {
228  Qt3DExtras::QPhongMaterial *phongMaterial = findTypedComponent<Qt3DExtras::QPhongMaterial>( entity );
229  if ( phongMaterial != nullptr )
230  {
232  object->setupMaterial( &material );
233  }
234 #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
235  Qt3DExtras::QDiffuseMapMaterial *diffuseMapMaterial = findTypedComponent<Qt3DExtras::QDiffuseMapMaterial>( entity );
236 #else
237  Qt3DExtras::QDiffuseSpecularMaterial *diffuseMapMaterial = findTypedComponent<Qt3DExtras::QDiffuseSpecularMaterial>( entity );
238 #endif
239 
240  if ( diffuseMapMaterial != nullptr )
241  {
242 #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
243  QVector<Qt3DRender::QAbstractTextureImage *> textureImages = diffuseMapMaterial->diffuse()->textureImages();
244 #else
245  QVector<Qt3DRender::QAbstractTextureImage *> textureImages = diffuseMapMaterial->diffuse().value< Qt3DRender::QTexture2D * >()->textureImages();
246 #endif
247  QgsImageTexture *imageTexture = nullptr;
248  for ( Qt3DRender::QAbstractTextureImage *tex : textureImages )
249  {
250  imageTexture = dynamic_cast<QgsImageTexture *>( tex );
251  if ( imageTexture != nullptr ) break;
252  }
253  if ( imageTexture != nullptr )
254  {
255  QImage image = imageTexture->getImage();
256  object->setTextureImage( image );
257  }
258  }
259 }
260 
261 void Qgs3DSceneExporter::parseTerrain( QgsTerrainEntity *terrain, const QString &layerName )
262 {
263  const Qgs3DMapSettings &settings = terrain->map3D();
264  QgsChunkNode *node = terrain->rootNode();
265 
266  QgsTerrainGenerator *generator = settings.terrainGenerator();
267  QgsTerrainTileEntity *terrainTile = nullptr;
268  QgsTerrainTextureGenerator *textureGenerator = terrain->textureGenerator();
269  textureGenerator->waitForFinished();
270  QSize oldResolution = textureGenerator->textureSize();
271  textureGenerator->setTextureSize( QSize( mTerrainTextureResolution, mTerrainTextureResolution ) );
272  switch ( generator->type() )
273  {
275  terrainTile = getDemTerrainEntity( terrain, node );
276  parseDemTile( terrainTile, layerName + QStringLiteral( "_" ) );
277  break;
279  terrainTile = getFlatTerrainEntity( terrain, node );
280  parseFlatTile( terrainTile, layerName + QStringLiteral( "_" ) );
281  break;
282  // TODO: implement other terrain types
284  terrainTile = getMeshTerrainEntity( terrain, node );
285  parseMeshTile( terrainTile, layerName + QStringLiteral( "_" ) );
286  break;
288  break;
289  }
290  textureGenerator->setTextureSize( oldResolution );
291 }
292 
293 QgsTerrainTileEntity *Qgs3DSceneExporter::getFlatTerrainEntity( QgsTerrainEntity *terrain, QgsChunkNode *node )
294 {
295  QgsFlatTerrainGenerator *generator = dynamic_cast<QgsFlatTerrainGenerator *>( terrain->map3D().terrainGenerator() );
296  FlatTerrainChunkLoader *flatTerrainLoader = qobject_cast<FlatTerrainChunkLoader *>( generator->createChunkLoader( node ) );
297  if ( mExportTextures )
298  terrain->textureGenerator()->waitForFinished();
299  // the entity we created will be deallocated once the scene exporter is deallocated
300  Qt3DCore::QEntity *entity = flatTerrainLoader->createEntity( this );
301  QgsTerrainTileEntity *tileEntity = qobject_cast<QgsTerrainTileEntity *>( entity );
302  return tileEntity;
303 }
304 
305 QgsTerrainTileEntity *Qgs3DSceneExporter::getDemTerrainEntity( QgsTerrainEntity *terrain, QgsChunkNode *node )
306 {
307  // Just create a new tile (we don't need to export exact level of details as in the scene)
308  // create the entity synchronously and then it will be deleted once our scene exporter instance is deallocated
309  QgsDemTerrainGenerator *generator = dynamic_cast<QgsDemTerrainGenerator *>( terrain->map3D().terrainGenerator()->clone() );
310  generator->setResolution( mTerrainResolution );
311  QgsDemTerrainTileLoader *loader = qobject_cast<QgsDemTerrainTileLoader *>( generator->createChunkLoader( node ) );
312  generator->heightMapGenerator()->waitForFinished();
313  if ( mExportTextures )
314  terrain->textureGenerator()->waitForFinished();
315  QgsTerrainTileEntity *tileEntity = qobject_cast<QgsTerrainTileEntity *>( loader->createEntity( this ) );
316  delete generator;
317  return tileEntity;
318 }
319 
320 QgsTerrainTileEntity *Qgs3DSceneExporter::getMeshTerrainEntity( QgsTerrainEntity *terrain, QgsChunkNode *node )
321 {
322  QgsMeshTerrainGenerator *generator = dynamic_cast<QgsMeshTerrainGenerator *>( terrain->map3D().terrainGenerator() );
323  QgsMeshTerrainTileLoader *loader = qobject_cast<QgsMeshTerrainTileLoader *>( generator->createChunkLoader( node ) );
324  // TODO: export textures
325  QgsTerrainTileEntity *tileEntity = qobject_cast<QgsTerrainTileEntity *>( loader->createEntity( this ) );
326  return tileEntity;
327 }
328 
329 void Qgs3DSceneExporter::parseFlatTile( QgsTerrainTileEntity *tileEntity, const QString &layerName )
330 {
331  Qt3DRender::QGeometryRenderer *mesh = findTypedComponent<Qt3DRender::QGeometryRenderer>( tileEntity );
332  Qt3DCore::QTransform *transform = findTypedComponent<Qt3DCore::QTransform>( tileEntity );
333 
334  Qt3DRender::QGeometry *geometry = mesh->geometry();
335  Qt3DExtras::QPlaneGeometry *tileGeometry = qobject_cast<Qt3DExtras::QPlaneGeometry *>( geometry );
336  if ( tileGeometry == nullptr )
337  {
338  QgsDebugMsg( "Qt3DExtras::QPlaneGeometry* is expected but something else was given" );
339  return;
340  }
341 
342  float scale = transform->scale();
343  QVector3D translation = transform->translation();
344 
345  // Generate vertice data
346  Qt3DRender::QAttribute *positionAttribute = tileGeometry->positionAttribute();
347  QByteArray verticesBytes = getData( positionAttribute->buffer() );
348  QVector<float> positionBuffer = getAttributeData<float>( positionAttribute, verticesBytes );
349 
350  // Generate index data
351  Qt3DRender::QAttribute *indexAttribute = tileGeometry->indexAttribute();
352  QByteArray indexBytes = getData( indexAttribute->buffer() );
353  QVector<uint> indexesBuffer = getIndexData( indexAttribute, indexBytes );
354 
355  QString objectNamePrefix = layerName;
356  if ( objectNamePrefix != QString() ) objectNamePrefix += QString();
357 
358  Qgs3DExportObject *object = new Qgs3DExportObject( getObjectName( objectNamePrefix + QStringLiteral( "Flat_tile" ) ) );
359  mObjects.push_back( object );
360 
361  object->setSmoothEdges( mSmoothEdges );
362  object->setupPositionCoordinates( positionBuffer, scale, translation );
363  object->setupFaces( indexesBuffer );
364 
365  if ( mExportNormals )
366  {
367  // Everts
368  QVector<float> normalsBuffer;
369  for ( int i = 0; i < positionBuffer.size(); i += 3 ) normalsBuffer << 0.0f << 1.0f << 0.0f;
370  object->setupNormalCoordinates( normalsBuffer );
371  }
372 
373  Qt3DRender::QAttribute *texCoordsAttribute = tileGeometry->texCoordAttribute();
374  if ( mExportTextures && texCoordsAttribute != nullptr )
375  {
376  // Reuse vertex buffer data for texture coordinates
377  QVector<float> texCoords = getAttributeData<float>( texCoordsAttribute, verticesBytes );
378  object->setupTextureCoordinates( texCoords );
379 
380  QgsTerrainTextureImage *textureImage = tileEntity->textureImage();
381  QImage img = textureImage->getImage();
382  object->setTextureImage( img );
383  }
384 }
385 
386 void Qgs3DSceneExporter::parseDemTile( QgsTerrainTileEntity *tileEntity, const QString &layerName )
387 {
388  Qt3DRender::QGeometryRenderer *mesh = findTypedComponent<Qt3DRender::QGeometryRenderer>( tileEntity );
389  Qt3DCore::QTransform *transform = findTypedComponent<Qt3DCore::QTransform>( tileEntity );
390 
391  Qt3DRender::QGeometry *geometry = mesh->geometry();
392  DemTerrainTileGeometry *tileGeometry = qobject_cast<DemTerrainTileGeometry *>( geometry );
393  if ( tileGeometry == nullptr )
394  {
395  QgsDebugMsg( "DemTerrainTileGeometry* is expected but something else was given" );
396  return;
397  }
398 
399  float scale = transform->scale();
400  QVector3D translation = transform->translation();
401 
402  Qt3DRender::QAttribute *positionAttribute = tileGeometry->positionAttribute();
403  QByteArray positionBytes = positionAttribute->buffer()->data();
404  QVector<float> positionBuffer = getAttributeData<float>( positionAttribute, positionBytes );
405 
406  Qt3DRender::QAttribute *indexAttribute = tileGeometry->indexAttribute();
407  QByteArray indexBytes = indexAttribute->buffer()->data();
408  QVector<unsigned int> indexBuffer = getIndexData( indexAttribute, indexBytes );
409 
410  QString objectNamePrefix = layerName;
411  if ( objectNamePrefix != QString() ) objectNamePrefix += QStringLiteral( "_" );
412 
413  Qgs3DExportObject *object = new Qgs3DExportObject( getObjectName( layerName + QStringLiteral( "DEM_tile" ) ) );
414  mObjects.push_back( object );
415 
416  object->setSmoothEdges( mSmoothEdges );
417  object->setupPositionCoordinates( positionBuffer, scale, translation );
418  object->setupFaces( indexBuffer );
419 
420  Qt3DRender::QAttribute *normalsAttributes = tileGeometry->normalAttribute();
421  if ( mExportNormals && normalsAttributes != nullptr )
422  {
423  QByteArray normalsBytes = normalsAttributes->buffer()->data();
424  QVector<float> normalsBuffer = getAttributeData<float>( normalsAttributes, normalsBytes );
425  object->setupNormalCoordinates( normalsBuffer );
426  }
427 
428  Qt3DRender::QAttribute *texCoordsAttribute = tileGeometry->texCoordsAttribute();
429  if ( mExportTextures && texCoordsAttribute != nullptr )
430  {
431  QByteArray texCoordsBytes = texCoordsAttribute->buffer()->data();
432  QVector<float> texCoordsBuffer = getAttributeData<float>( texCoordsAttribute, texCoordsBytes );
433  object->setupTextureCoordinates( texCoordsBuffer );
434 
435  QgsTerrainTextureImage *textureImage = tileEntity->textureImage();
436  QImage img = textureImage->getImage();
437  object->setTextureImage( img );
438  }
439 }
440 
441 void Qgs3DSceneExporter::parseMeshTile( QgsTerrainTileEntity *tileEntity, const QString &layerName )
442 {
443  QString objectNamePrefix = layerName;
444  if ( objectNamePrefix != QString() ) objectNamePrefix += QStringLiteral( "_" );
445 
446  QList<Qt3DRender::QGeometryRenderer *> renderers = tileEntity->findChildren<Qt3DRender::QGeometryRenderer *>();
447  for ( Qt3DRender::QGeometryRenderer *renderer : renderers )
448  {
449  Qgs3DExportObject *obj = processGeometryRenderer( renderer, objectNamePrefix );
450  if ( obj == nullptr ) continue;
451  mObjects << obj;
452  }
453 }
454 
455 QVector<Qgs3DExportObject *> Qgs3DSceneExporter::processInstancedPointGeometry( Qt3DCore::QEntity *entity, const QString &objectNamePrefix )
456 {
457  QVector<Qgs3DExportObject *> objects;
458  QList<Qt3DRender::QGeometry *> geometriesList = entity->findChildren<Qt3DRender::QGeometry *>();
459  for ( Qt3DRender::QGeometry *geometry : geometriesList )
460  {
461  Qt3DRender::QAttribute *positionAttribute = findAttribute( geometry, Qt3DRender::QAttribute::defaultPositionAttributeName(), Qt3DRender::QAttribute::VertexAttribute );
462  Qt3DRender::QAttribute *indexAttribute = nullptr;
463  for ( Qt3DRender::QAttribute *attribute : geometry->attributes() )
464  {
465  if ( attribute->attributeType() == Qt3DRender::QAttribute::IndexAttribute )
466  indexAttribute = attribute;
467  }
468  if ( positionAttribute == nullptr || indexAttribute == nullptr )
469  continue;
470  QByteArray vertexBytes = getData( positionAttribute->buffer() );
471  QByteArray indexBytes = getData( indexAttribute->buffer() );
472  QVector<float> positionData = getAttributeData<float>( positionAttribute, vertexBytes );
473  QVector<uint> indexData = getIndexData( indexAttribute, indexBytes );
474 
475  Qt3DRender::QAttribute *instanceDataAttribute = findAttribute( geometry, QStringLiteral( "pos" ), Qt3DRender::QAttribute::VertexAttribute );
476  QByteArray instancePositionBytes = getData( instanceDataAttribute->buffer() );
477  QVector<float> instancePosition = getAttributeData<float>( instanceDataAttribute, instancePositionBytes );
478  for ( int i = 0; i < instancePosition.size(); i += 3 )
479  {
480  Qgs3DExportObject *object = new Qgs3DExportObject( getObjectName( objectNamePrefix + QStringLiteral( "shape_geometry" ) ) );
481  objects.push_back( object );
482  object->setupPositionCoordinates( positionData, 1.0f, QVector3D( instancePosition[i], instancePosition[i + 1], instancePosition[i + 2] ) );
483  object->setupFaces( indexData );
484 
485  object->setSmoothEdges( mSmoothEdges );
486 
487  Qt3DRender::QAttribute *normalsAttribute = findAttribute( geometry, Qt3DRender::QAttribute::defaultNormalAttributeName(), Qt3DRender::QAttribute::VertexAttribute );
488  if ( mExportNormals && normalsAttribute != nullptr )
489  {
490  // Reuse vertex bytes
491  QVector<float> normalsData = getAttributeData<float>( normalsAttribute, vertexBytes );
492  object->setupNormalCoordinates( normalsData );
493  }
494  }
495  }
496  return objects;
497 }
498 
499 QVector<Qgs3DExportObject *> Qgs3DSceneExporter::processSceneLoaderGeometries( Qt3DRender::QSceneLoader *sceneLoader, const QString &objectNamePrefix )
500 {
501  QVector<Qgs3DExportObject *> objects;
502  Qt3DCore::QEntity *sceneLoaderParent = qobject_cast<Qt3DCore::QEntity *>( sceneLoader->parent() );
503  Qt3DCore::QTransform *entityTransform = findTypedComponent<Qt3DCore::QTransform>( sceneLoaderParent );
504  float sceneScale = 1.0f;
505  QVector3D sceneTranslation( 0.0f, 0.0f, 0.0f );
506  if ( entityTransform != nullptr )
507  {
508  sceneScale = entityTransform->scale();
509  sceneTranslation = entityTransform->translation();
510  }
511  for ( QString entityName : sceneLoader->entityNames() )
512  {
513  Qt3DRender::QGeometryRenderer *mesh = qobject_cast<Qt3DRender::QGeometryRenderer *>( sceneLoader->component( entityName, Qt3DRender::QSceneLoader::GeometryRendererComponent ) );
514  Qgs3DExportObject *object = processGeometryRenderer( mesh, objectNamePrefix, sceneScale, sceneTranslation );
515  if ( object == nullptr ) continue;
516  objects.push_back( object );
517  }
518  return objects;
519 }
520 
521 Qgs3DExportObject *Qgs3DSceneExporter::processGeometryRenderer( Qt3DRender::QGeometryRenderer *mesh, const QString &objectNamePrefix, float sceneScale, QVector3D sceneTranslation )
522 {
523  // We only export triangles for now
524  if ( mesh->primitiveType() != Qt3DRender::QGeometryRenderer::Triangles ) return nullptr;
525 
526  float scale = 1.0f;
527  QVector3D translation( 0.0f, 0.0f, 0.0f );
528  QObject *parent = mesh->parent();
529  while ( parent != nullptr )
530  {
531  Qt3DCore::QEntity *entity = qobject_cast<Qt3DCore::QEntity *>( parent );
532  Qt3DCore::QTransform *transform = findTypedComponent<Qt3DCore::QTransform>( entity );
533  if ( transform != nullptr )
534  {
535  scale *= transform->scale();
536  translation += transform->translation();
537  }
538  parent = parent->parent();
539  }
540 
541  Qt3DRender::QGeometry *geometry = mesh->geometry();
542 
543  Qt3DRender::QAttribute *positionAttribute = findAttribute( geometry, Qt3DRender::QAttribute::defaultPositionAttributeName(), Qt3DRender::QAttribute::VertexAttribute );
544  Qt3DRender::QAttribute *indexAttribute = nullptr;
545  QByteArray indexBytes, vertexBytes;
546  QVector<uint> indexData;
547  QVector<float> positionData;
548  for ( Qt3DRender::QAttribute *attribute : geometry->attributes() )
549  {
550  if ( attribute->attributeType() == Qt3DRender::QAttribute::IndexAttribute )
551  indexAttribute = attribute;
552  }
553 
554  if ( indexAttribute != nullptr )
555  {
556  indexBytes = getData( indexAttribute->buffer() );
557  indexData = getIndexData( indexAttribute, indexBytes );
558  }
559 
560  if ( positionAttribute != nullptr )
561  {
562  vertexBytes = getData( positionAttribute->buffer() );
563  positionData = getAttributeData<float>( positionAttribute, vertexBytes );
564  }
565 
566 // For tessellated polygons that don't have index attributes
567  if ( positionAttribute != nullptr && indexAttribute == nullptr )
568  {
569  for ( int i = 0; i < positionData.size() / 3; ++i )
570  indexData.push_back( i );
571  }
572 
573  if ( positionAttribute == nullptr )
574  {
575  QgsDebugMsg( "Geometry renderer with null data was being processed" );
576  return nullptr;
577  }
578 
579  Qgs3DExportObject *object = new Qgs3DExportObject( getObjectName( objectNamePrefix + QStringLiteral( "mesh_geometry" ) ) );
580  object->setupPositionCoordinates( positionData, scale * sceneScale, translation + sceneTranslation );
581  object->setupFaces( indexData );
582 
583  Qt3DRender::QAttribute *normalsAttribute = findAttribute( geometry, Qt3DRender::QAttribute::defaultNormalAttributeName(), Qt3DRender::QAttribute::VertexAttribute );
584  if ( mExportNormals && normalsAttribute != nullptr )
585  {
586  // Reuse vertex bytes
587  QVector<float> normalsData = getAttributeData<float>( normalsAttribute, vertexBytes );
588  object->setupNormalCoordinates( normalsData );
589  }
590 
591  Qt3DRender::QAttribute *texCoordsAttribute = findAttribute( geometry, Qt3DRender::QAttribute::defaultTextureCoordinateAttributeName(), Qt3DRender::QAttribute::VertexAttribute );
592  if ( mExportTextures && texCoordsAttribute != nullptr )
593  {
594  // Reuse vertex bytes
595  QVector<float> texCoordsData = getAttributeData<float>( texCoordsAttribute, vertexBytes );
596  object->setupTextureCoordinates( texCoordsData );
597  }
598 
599  return object;
600 }
601 
602 QVector<Qgs3DExportObject *> Qgs3DSceneExporter::processLines( Qt3DCore::QEntity *entity, const QString &objectNamePrefix )
603 {
604  QVector<Qgs3DExportObject *> objs;
605  QList<Qt3DRender::QGeometryRenderer *> renderers = entity->findChildren<Qt3DRender::QGeometryRenderer *>();
606  for ( Qt3DRender::QGeometryRenderer *renderer : renderers )
607  {
608  if ( renderer->primitiveType() != Qt3DRender::QGeometryRenderer::LineStripAdjacency ) continue;
609  Qt3DRender::QGeometry *geom = renderer->geometry();
610  Qt3DRender::QAttribute *positionAttribute = findAttribute( geom, Qt3DRender::QAttribute::defaultPositionAttributeName(), Qt3DRender::QAttribute::VertexAttribute );
611  Qt3DRender::QAttribute *indexAttribute = nullptr;
612  for ( Qt3DRender::QAttribute *attribute : geom->attributes() )
613  {
614  if ( attribute->attributeType() == Qt3DRender::QAttribute::IndexAttribute )
615  {
616  indexAttribute = attribute;
617  break;
618  }
619  }
620  if ( positionAttribute == nullptr || indexAttribute == nullptr )
621  {
622  QgsDebugMsg( "Position or index attribute was not found" );
623  continue;
624  }
625 
626  QByteArray vertexBytes = getData( positionAttribute->buffer() );
627  QByteArray indexBytes = getData( indexAttribute->buffer() );
628  QVector<float> positionData = getAttributeData<float>( positionAttribute, vertexBytes );
629  QVector<uint> indexData = getIndexData( indexAttribute, indexBytes );
630 
631  Qgs3DExportObject *exportObject = new Qgs3DExportObject( getObjectName( objectNamePrefix + QStringLiteral( "line" ) ) );
632  exportObject->setType( Qgs3DExportObject::LineStrip );
633  exportObject->setupPositionCoordinates( positionData );
634  exportObject->setupLine( indexData );
635 
636  objs.push_back( exportObject );
637  }
638  return objs;
639 }
640 
641 Qgs3DExportObject *Qgs3DSceneExporter::processPoints( Qt3DCore::QEntity *entity, const QString &objectNamePrefix )
642 {
643  QVector<float> points;
644  QList<Qt3DRender::QGeometryRenderer *> renderers = entity->findChildren<Qt3DRender::QGeometryRenderer *>();
645  for ( Qt3DRender::QGeometryRenderer *renderer : renderers )
646  {
647  Qt3DRender::QGeometry *geometry = qobject_cast<QgsBillboardGeometry *>( renderer->geometry() );
648  if ( geometry == nullptr )
649  continue;
650  Qt3DRender::QAttribute *positionAttribute = findAttribute( geometry, Qt3DRender::QAttribute::defaultPositionAttributeName(), Qt3DRender::QAttribute::VertexAttribute );
651  QByteArray positionBytes = getData( positionAttribute->buffer() );
652  if ( positionBytes.size() == 0 )
653  continue;
654  QVector<float> positions = getAttributeData<float>( positionAttribute, positionBytes );
655  points << positions;
656  }
657  Qgs3DExportObject *obj = new Qgs3DExportObject( getObjectName( objectNamePrefix + QStringLiteral( "points" ) ) );
659  obj->setupPositionCoordinates( points );
660  return obj;
661 }
662 
663 void Qgs3DSceneExporter::save( const QString &sceneName, const QString &sceneFolderPath )
664 {
665  QString objFilePath = QDir( sceneFolderPath ).filePath( sceneName + QStringLiteral( ".obj" ) );
666  QString mtlFilePath = QDir( sceneFolderPath ).filePath( sceneName + QStringLiteral( ".mtl" ) );
667 
668  QFile file( objFilePath );
669  if ( !file.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) )
670  return;
671  QFile mtlFile( mtlFilePath );
672  if ( !mtlFile.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) )
673  return;
674 
675  float maxfloat = std::numeric_limits<float>::max(), minFloat = std::numeric_limits<float>::lowest();
676  float minX = maxfloat, minY = maxfloat, minZ = maxfloat, maxX = minFloat, maxY = minFloat, maxZ = minFloat;
677  for ( Qgs3DExportObject *obj : mObjects ) obj->objectBounds( minX, minY, minZ, maxX, maxY, maxZ );
678 
679  float diffX = 1.0f, diffY = 1.0f, diffZ = 1.0f;
680  diffX = maxX - minX;
681  diffY = maxY - minY;
682  diffZ = maxZ - minZ;
683 
684  float centerX = ( minX + maxX ) / 2.0f;
685  float centerY = ( minY + maxY ) / 2.0f;
686  float centerZ = ( minZ + maxZ ) / 2.0f;
687 
688  float scale = std::max( diffX, std::max( diffY, diffZ ) );
689 
690  QTextStream out( &file );
691  // set material library name
692  QString mtlLibName = sceneName + ".mtl";
693  out << "mtllib " << mtlLibName << "\n";
694 
695  QTextStream mtlOut( &mtlFile );
696  for ( Qgs3DExportObject *obj : mObjects )
697  {
698  if ( obj == nullptr ) continue;
699  // Set object name
700  QString material = obj->saveMaterial( mtlOut, sceneFolderPath );
701  out << "o " << obj->name() << "\n";
702  if ( material != QString() )
703  out << "usemtl " << material << "\n";
704  obj->saveTo( out, scale / mScale, QVector3D( centerX, centerY, centerZ ) );
705  }
706 }
707 
708 QString Qgs3DSceneExporter::getObjectName( const QString &name )
709 {
710  QString ret = name;
711  if ( usedObjectNamesCounter.contains( name ) )
712  {
713  ret = QStringLiteral( "%1%2" ).arg( name ).arg( usedObjectNamesCounter[name] );
714  usedObjectNamesCounter[name]++;
715  }
716  else
717  usedObjectNamesCounter[name] = 2;
718  return ret;
719 }
Manages the data of each object of the scene (positions, normals, texture coordinates ....
void saveTo(QTextStream &out, float scale, const QVector3D &center)
Saves the current object to the output stream while scaling the object and centering it to be visible...
void setType(ObjectType type)
Sets the object type.
void objectBounds(float &minX, float &minY, float &minZ, float &maxX, float &maxY, float &maxZ)
Updates the box bounds explained with the current object bounds This expands the bounding box if the ...
void setupPositionCoordinates(const QVector< float > &positionsBuffer, float scale=1.0f, const QVector3D &translation=QVector3D(0, 0, 0))
Sets positions coordinates and does the translation and scaling.
QString name() const
Returns the object name.
QString saveMaterial(QTextStream &mtlOut, const QString &folder)
saves the texture of the object and material information
void setupLine(const QVector< uint > &facesIndexes)
sets line vertex indexes
QgsTerrainGenerator * terrainGenerator() const
Returns terrain generator. It takes care of producing terrain tiles from the input data.
void parseTerrain(QgsTerrainEntity *terrain, const QString &layer)
Creates terrain export objects from the terrain entity.
void save(const QString &sceneName, const QString &sceneFolderPath)
Saves the scene to a .obj file.
float scale() const
Returns the scale of the exported 3D model.
bool parseVectorLayerEntity(Qt3DCore::QEntity *entity, QgsVectorLayer *layer)
Creates necessary export objects from entity if it represents valid vector layer entity Returns false...
static QgsPhongMaterialSettings phongMaterialFromQt3DComponent(Qt3DExtras::QPhongMaterial *material)
Returns phong material settings object based on the Qt3D material.
Definition: qgs3dutils.cpp:556
Base class for all renderers that may to participate in 3D view.
virtual QString type() const =0
Returns unique identifier of the renderer class (used to identify subclass)
virtual bool exportGeometries(Qgs3DSceneExporter *exporter, Qt3DCore::QEntity *entity, const QString &objectNamePrefix) const
Exports the geometries contained within the hierarchy of entity.
QgsDemHeightMapGenerator * heightMapGenerator()
Returns height map generator object - takes care of extraction of elevations from the layer)
QgsChunkLoader * createChunkLoader(QgsChunkNode *node) const override
void setResolution(int resolution)
Sets resolution of the generator (how many elevation samples on one side of a terrain tile)
QgsChunkLoader * createChunkLoader(QgsChunkNode *node) const override SIP_FACTORY
Holds an image that can be used as a texture in the 3D view.
QImage getImage() const
Returns the image.
QString name
Definition: qgsmaplayer.h:88
QgsAbstract3DRenderer * renderer3D() const
Returns 3D renderer associated with the layer.
@ Dem
Terrain is built from raster layer with digital elevation model.
@ Online
Terrain is built from downloaded tiles with digital elevation model.
@ Mesh
Terrain is built from mesh layer with z value on vertices.
@ Flat
The whole terrain is flat area.
virtual Type type() const =0
What texture generator implementation is this.
3D renderer that renders all features of a vector layer with the same 3D symbol.
const QgsAbstract3DSymbol * symbol() const
Returns 3D symbol associated with the renderer.
Represents a vector layer which manages a vector based data sets.
QVector< uint > getIndexData(Qt3DRender::QAttribute *indexAttribute, const QByteArray &data)
Qt3DRender::QAttribute * findAttribute(Qt3DRender::QGeometry *geometry, const QString &name, Qt3DRender::QAttribute::AttributeType type)
Component * findTypedComponent(Qt3DCore::QEntity *entity)
QVector< T > getAttributeData(Qt3DRender::QAttribute *attribute, const QByteArray &data)
QByteArray getData(Qt3DRender::QBuffer *buffer)
QVector< uint > _getIndexDataImplementation(const QByteArray &data)
#define QgsDebugMsg(str)
Definition: qgslogger.h:38