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