QGIS API Documentation 3.27.0-Master (75dc696944)
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
53#include "qgsterrainentity_p.h"
54#include "qgschunknode_p.h"
55#include "qgsterraingenerator.h"
56#include "qgs3dmapsettings.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"
72#include "qgspolygon3dsymbol.h"
73#include "qgsline3dsymbol.h"
74#include "qgspoint3dsymbol.h"
76#include "qgs3dutils.h"
78#include "qgsimagetexture.h"
79
80#include <numeric>
81
82template<typename T>
83QVector<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
109template<typename T>
110QVector<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
123QVector<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
146QByteArray 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
156Qt3DRender::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
166template<typename Component>
167Component *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
179bool 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 *parentEntity = qobject_cast<Qt3DCore::QEntity *>( renderer->parent() );
198 if ( !parentEntity )
199 continue;
200 Qgs3DExportObject *object = processGeometryRenderer( renderer, layer->name() + QStringLiteral( "_" ) );
201 if ( object == nullptr ) continue;
202 if ( mExportTextures )
203 processEntityMaterial( parentEntity, object );
204 mObjects.push_back( object );
205 }
206 return true;
207 }
208 else
209 {
210 QgsVectorLayer3DRenderer *vectorLayerRenderer = dynamic_cast< QgsVectorLayer3DRenderer *>( abstractVectorRenderer );
211 if ( vectorLayerRenderer )
212 {
213 const QgsAbstract3DSymbol *symbol = vectorLayerRenderer->symbol();
214 const bool exported = symbol->exportGeometries( this, entity, layer->name() + QStringLiteral( "_" ) );
215 return exported;
216 }
217 else
218 return false;
219 }
220 }
221 return false;
222}
223
224void Qgs3DSceneExporter::processEntityMaterial( Qt3DCore::QEntity *entity, Qgs3DExportObject *object )
225{
226 Qt3DExtras::QPhongMaterial *phongMaterial = findTypedComponent<Qt3DExtras::QPhongMaterial>( entity );
227 if ( phongMaterial != nullptr )
228 {
230 object->setupMaterial( &material );
231 }
232 Qt3DExtras::QDiffuseSpecularMaterial *diffuseMapMaterial = findTypedComponent<Qt3DExtras::QDiffuseSpecularMaterial>( entity );
233
234 if ( diffuseMapMaterial != nullptr )
235 {
236 const QVector<Qt3DRender::QAbstractTextureImage *> textureImages = diffuseMapMaterial->diffuse().value< Qt3DRender::QTexture2D * >()->textureImages();
237 QgsImageTexture *imageTexture = nullptr;
238 for ( Qt3DRender::QAbstractTextureImage *tex : textureImages )
239 {
240 imageTexture = dynamic_cast<QgsImageTexture *>( tex );
241 if ( imageTexture != nullptr ) break;
242 }
243 if ( imageTexture != nullptr )
244 {
245 const QImage image = imageTexture->getImage();
246 object->setTextureImage( image );
247 }
248 }
249}
250
251void Qgs3DSceneExporter::parseTerrain( QgsTerrainEntity *terrain, const QString &layerName )
252{
253 const Qgs3DMapSettings &settings = terrain->map3D();
254 if ( !settings.terrainRenderingEnabled() )
255 return;
256
257 QgsChunkNode *node = terrain->rootNode();
258
259 QgsTerrainGenerator *generator = settings.terrainGenerator();
260 if ( !generator )
261 return;
262 QgsTerrainTileEntity *terrainTile = nullptr;
263 QgsTerrainTextureGenerator *textureGenerator = terrain->textureGenerator();
264 textureGenerator->waitForFinished();
265 const QSize oldResolution = textureGenerator->textureSize();
266 textureGenerator->setTextureSize( QSize( mTerrainTextureResolution, mTerrainTextureResolution ) );
267 switch ( generator->type() )
268 {
270 terrainTile = getDemTerrainEntity( terrain, node );
271 parseDemTile( terrainTile, layerName + QStringLiteral( "_" ) );
272 break;
274 terrainTile = getFlatTerrainEntity( terrain, node );
275 parseFlatTile( terrainTile, layerName + QStringLiteral( "_" ) );
276 break;
277 // TODO: implement other terrain types
279 terrainTile = getMeshTerrainEntity( terrain, node );
280 parseMeshTile( terrainTile, layerName + QStringLiteral( "_" ) );
281 break;
283 break;
284 }
285 textureGenerator->setTextureSize( oldResolution );
286}
287
288QgsTerrainTileEntity *Qgs3DSceneExporter::getFlatTerrainEntity( QgsTerrainEntity *terrain, QgsChunkNode *node )
289{
290 QgsFlatTerrainGenerator *generator = dynamic_cast<QgsFlatTerrainGenerator *>( terrain->map3D().terrainGenerator() );
291 FlatTerrainChunkLoader *flatTerrainLoader = qobject_cast<FlatTerrainChunkLoader *>( generator->createChunkLoader( node ) );
292 if ( mExportTextures )
293 terrain->textureGenerator()->waitForFinished();
294 // the entity we created will be deallocated once the scene exporter is deallocated
295 Qt3DCore::QEntity *entity = flatTerrainLoader->createEntity( this );
296 QgsTerrainTileEntity *tileEntity = qobject_cast<QgsTerrainTileEntity *>( entity );
297 return tileEntity;
298}
299
300QgsTerrainTileEntity *Qgs3DSceneExporter::getDemTerrainEntity( QgsTerrainEntity *terrain, QgsChunkNode *node )
301{
302 // Just create a new tile (we don't need to export exact level of details as in the scene)
303 // create the entity synchronously and then it will be deleted once our scene exporter instance is deallocated
304 QgsDemTerrainGenerator *generator = dynamic_cast<QgsDemTerrainGenerator *>( terrain->map3D().terrainGenerator()->clone() );
305 generator->setResolution( mTerrainResolution );
306 QgsDemTerrainTileLoader *loader = qobject_cast<QgsDemTerrainTileLoader *>( generator->createChunkLoader( node ) );
307 generator->heightMapGenerator()->waitForFinished();
308 if ( mExportTextures )
309 terrain->textureGenerator()->waitForFinished();
310 QgsTerrainTileEntity *tileEntity = qobject_cast<QgsTerrainTileEntity *>( loader->createEntity( this ) );
311 delete generator;
312 return tileEntity;
313}
314
315QgsTerrainTileEntity *Qgs3DSceneExporter::getMeshTerrainEntity( QgsTerrainEntity *terrain, QgsChunkNode *node )
316{
317 QgsMeshTerrainGenerator *generator = dynamic_cast<QgsMeshTerrainGenerator *>( terrain->map3D().terrainGenerator() );
318 QgsMeshTerrainTileLoader *loader = qobject_cast<QgsMeshTerrainTileLoader *>( generator->createChunkLoader( node ) );
319 // TODO: export textures
320 QgsTerrainTileEntity *tileEntity = qobject_cast<QgsTerrainTileEntity *>( loader->createEntity( this ) );
321 return tileEntity;
322}
323
324void Qgs3DSceneExporter::parseFlatTile( QgsTerrainTileEntity *tileEntity, const QString &layerName )
325{
326 Qt3DRender::QGeometryRenderer *mesh = findTypedComponent<Qt3DRender::QGeometryRenderer>( tileEntity );
327 Qt3DCore::QTransform *transform = findTypedComponent<Qt3DCore::QTransform>( tileEntity );
328
329 Qt3DRender::QGeometry *geometry = mesh->geometry();
330 Qt3DExtras::QPlaneGeometry *tileGeometry = qobject_cast<Qt3DExtras::QPlaneGeometry *>( geometry );
331 if ( tileGeometry == nullptr )
332 {
333 QgsDebugMsg( "Qt3DExtras::QPlaneGeometry* is expected but something else was given" );
334 return;
335 }
336
337 const float scale = transform->scale();
338 const QVector3D translation = transform->translation();
339
340 // Generate vertice data
341 Qt3DRender::QAttribute *positionAttribute = tileGeometry->positionAttribute();
342 const QByteArray verticesBytes = getData( positionAttribute->buffer() );
343 const QVector<float> positionBuffer = getAttributeData<float>( positionAttribute, verticesBytes );
344
345 // Generate index data
346 Qt3DRender::QAttribute *indexAttribute = tileGeometry->indexAttribute();
347 const QByteArray indexBytes = getData( indexAttribute->buffer() );
348 const QVector<uint> indexesBuffer = getIndexData( indexAttribute, indexBytes );
349
350 QString objectNamePrefix = layerName;
351 if ( objectNamePrefix != QString() ) objectNamePrefix += QString();
352
353 Qgs3DExportObject *object = new Qgs3DExportObject( getObjectName( objectNamePrefix + QStringLiteral( "Flat_tile" ) ) );
354 mObjects.push_back( object );
355
356 object->setSmoothEdges( mSmoothEdges );
357 object->setupPositionCoordinates( positionBuffer, scale, translation );
358 object->setupFaces( indexesBuffer );
359
360 if ( mExportNormals )
361 {
362 // Everts
363 QVector<float> normalsBuffer;
364 for ( int i = 0; i < positionBuffer.size(); i += 3 ) normalsBuffer << 0.0f << 1.0f << 0.0f;
365 object->setupNormalCoordinates( normalsBuffer );
366 }
367
368 Qt3DRender::QAttribute *texCoordsAttribute = tileGeometry->texCoordAttribute();
369 if ( mExportTextures && texCoordsAttribute != nullptr )
370 {
371 // Reuse vertex buffer data for texture coordinates
372 const QVector<float> texCoords = getAttributeData<float>( texCoordsAttribute, verticesBytes );
373 object->setupTextureCoordinates( texCoords );
374
375 QgsTerrainTextureImage *textureImage = tileEntity->textureImage();
376 const QImage img = textureImage->getImage();
377 object->setTextureImage( img );
378 }
379}
380
381void Qgs3DSceneExporter::parseDemTile( QgsTerrainTileEntity *tileEntity, const QString &layerName )
382{
383 Qt3DRender::QGeometryRenderer *mesh = findTypedComponent<Qt3DRender::QGeometryRenderer>( tileEntity );
384 Qt3DCore::QTransform *transform = findTypedComponent<Qt3DCore::QTransform>( tileEntity );
385
386 Qt3DRender::QGeometry *geometry = mesh->geometry();
387 DemTerrainTileGeometry *tileGeometry = qobject_cast<DemTerrainTileGeometry *>( geometry );
388 if ( tileGeometry == nullptr )
389 {
390 QgsDebugMsg( "DemTerrainTileGeometry* is expected but something else was given" );
391 return;
392 }
393
394 const float scale = transform->scale();
395 const QVector3D translation = transform->translation();
396
397 Qt3DRender::QAttribute *positionAttribute = tileGeometry->positionAttribute();
398 const QByteArray positionBytes = positionAttribute->buffer()->data();
399 const QVector<float> positionBuffer = getAttributeData<float>( positionAttribute, positionBytes );
400
401 Qt3DRender::QAttribute *indexAttribute = tileGeometry->indexAttribute();
402 const QByteArray indexBytes = indexAttribute->buffer()->data();
403 const QVector<unsigned int> indexBuffer = getIndexData( indexAttribute, indexBytes );
404
405 Qgs3DExportObject *object = new Qgs3DExportObject( getObjectName( layerName + QStringLiteral( "DEM_tile" ) ) );
406 mObjects.push_back( object );
407
408 object->setSmoothEdges( mSmoothEdges );
409 object->setupPositionCoordinates( positionBuffer, scale, translation );
410 object->setupFaces( indexBuffer );
411
412 Qt3DRender::QAttribute *normalsAttributes = tileGeometry->normalAttribute();
413 if ( mExportNormals && normalsAttributes != nullptr )
414 {
415 const QByteArray normalsBytes = normalsAttributes->buffer()->data();
416 const QVector<float> normalsBuffer = getAttributeData<float>( normalsAttributes, normalsBytes );
417 object->setupNormalCoordinates( normalsBuffer );
418 }
419
420 Qt3DRender::QAttribute *texCoordsAttribute = tileGeometry->texCoordsAttribute();
421 if ( mExportTextures && texCoordsAttribute != nullptr )
422 {
423 const QByteArray texCoordsBytes = texCoordsAttribute->buffer()->data();
424 const QVector<float> texCoordsBuffer = getAttributeData<float>( texCoordsAttribute, texCoordsBytes );
425 object->setupTextureCoordinates( texCoordsBuffer );
426
427 QgsTerrainTextureImage *textureImage = tileEntity->textureImage();
428 const QImage img = textureImage->getImage();
429 object->setTextureImage( img );
430 }
431}
432
433void Qgs3DSceneExporter::parseMeshTile( QgsTerrainTileEntity *tileEntity, const QString &layerName )
434{
435 QString objectNamePrefix = layerName;
436 if ( objectNamePrefix != QString() ) objectNamePrefix += QStringLiteral( "_" );
437
438 const QList<Qt3DRender::QGeometryRenderer *> renderers = tileEntity->findChildren<Qt3DRender::QGeometryRenderer *>();
439 for ( Qt3DRender::QGeometryRenderer *renderer : renderers )
440 {
441 Qgs3DExportObject *obj = processGeometryRenderer( renderer, objectNamePrefix );
442 if ( obj == nullptr ) continue;
443 mObjects << obj;
444 }
445}
446
447QVector<Qgs3DExportObject *> Qgs3DSceneExporter::processInstancedPointGeometry( Qt3DCore::QEntity *entity, const QString &objectNamePrefix )
448{
449 QVector<Qgs3DExportObject *> objects;
450 const QList<Qt3DRender::QGeometry *> geometriesList = entity->findChildren<Qt3DRender::QGeometry *>();
451 for ( Qt3DRender::QGeometry *geometry : geometriesList )
452 {
453 Qt3DRender::QAttribute *positionAttribute = findAttribute( geometry, Qt3DRender::QAttribute::defaultPositionAttributeName(), Qt3DRender::QAttribute::VertexAttribute );
454 Qt3DRender::QAttribute *indexAttribute = nullptr;
455 for ( Qt3DRender::QAttribute *attribute : geometry->attributes() )
456 {
457 if ( attribute->attributeType() == Qt3DRender::QAttribute::IndexAttribute )
458 indexAttribute = attribute;
459 }
460 if ( positionAttribute == nullptr || indexAttribute == nullptr )
461 continue;
462 const QByteArray vertexBytes = getData( positionAttribute->buffer() );
463 const QByteArray indexBytes = getData( indexAttribute->buffer() );
464 const QVector<float> positionData = getAttributeData<float>( positionAttribute, vertexBytes );
465 const QVector<uint> indexData = getIndexData( indexAttribute, indexBytes );
466
467 Qt3DRender::QAttribute *instanceDataAttribute = findAttribute( geometry, QStringLiteral( "pos" ), Qt3DRender::QAttribute::VertexAttribute );
468 const QByteArray instancePositionBytes = getData( instanceDataAttribute->buffer() );
469 QVector<float> instancePosition = getAttributeData<float>( instanceDataAttribute, instancePositionBytes );
470 for ( int i = 0; i < instancePosition.size(); i += 3 )
471 {
472 Qgs3DExportObject *object = new Qgs3DExportObject( getObjectName( objectNamePrefix + QStringLiteral( "shape_geometry" ) ) );
473 objects.push_back( object );
474 object->setupPositionCoordinates( positionData, 1.0f, QVector3D( instancePosition[i], instancePosition[i + 1], instancePosition[i + 2] ) );
475 object->setupFaces( indexData );
476
477 object->setSmoothEdges( mSmoothEdges );
478
479 Qt3DRender::QAttribute *normalsAttribute = findAttribute( geometry, Qt3DRender::QAttribute::defaultNormalAttributeName(), Qt3DRender::QAttribute::VertexAttribute );
480 if ( mExportNormals && normalsAttribute != nullptr )
481 {
482 // Reuse vertex bytes
483 const QVector<float> normalsData = getAttributeData<float>( normalsAttribute, vertexBytes );
484 object->setupNormalCoordinates( normalsData );
485 }
486 }
487 }
488 return objects;
489}
490
491QVector<Qgs3DExportObject *> Qgs3DSceneExporter::processSceneLoaderGeometries( Qt3DRender::QSceneLoader *sceneLoader, const QString &objectNamePrefix )
492{
493 QVector<Qgs3DExportObject *> objects;
494 Qt3DCore::QEntity *sceneLoaderParent = qobject_cast<Qt3DCore::QEntity *>( sceneLoader->parent() );
495 Qt3DCore::QTransform *entityTransform = findTypedComponent<Qt3DCore::QTransform>( sceneLoaderParent );
496 float sceneScale = 1.0f;
497 QVector3D sceneTranslation( 0.0f, 0.0f, 0.0f );
498 if ( entityTransform != nullptr )
499 {
500 sceneScale = entityTransform->scale();
501 sceneTranslation = entityTransform->translation();
502 }
503 for ( const QString &entityName : sceneLoader->entityNames() )
504 {
505 Qt3DRender::QGeometryRenderer *mesh = qobject_cast<Qt3DRender::QGeometryRenderer *>( sceneLoader->component( entityName, Qt3DRender::QSceneLoader::GeometryRendererComponent ) );
506 Qgs3DExportObject *object = processGeometryRenderer( mesh, objectNamePrefix, sceneScale, sceneTranslation );
507 if ( object == nullptr ) continue;
508 objects.push_back( object );
509 }
510 return objects;
511}
512
513Qgs3DExportObject *Qgs3DSceneExporter::processGeometryRenderer( Qt3DRender::QGeometryRenderer *mesh, const QString &objectNamePrefix, float sceneScale, QVector3D sceneTranslation )
514{
515 // We only export triangles for now
516 if ( mesh->primitiveType() != Qt3DRender::QGeometryRenderer::Triangles ) return nullptr;
517
518 float scale = 1.0f;
519 QVector3D translation( 0.0f, 0.0f, 0.0f );
520 QObject *parent = mesh->parent();
521 while ( parent != nullptr )
522 {
523 Qt3DCore::QEntity *entity = qobject_cast<Qt3DCore::QEntity *>( parent );
524 Qt3DCore::QTransform *transform = findTypedComponent<Qt3DCore::QTransform>( entity );
525 if ( transform != nullptr )
526 {
527 scale *= transform->scale();
528 translation += transform->translation();
529 }
530 parent = parent->parent();
531 }
532
533 Qt3DRender::QGeometry *geometry = mesh->geometry();
534
535 Qt3DRender::QAttribute *positionAttribute = findAttribute( geometry, Qt3DRender::QAttribute::defaultPositionAttributeName(), Qt3DRender::QAttribute::VertexAttribute );
536 Qt3DRender::QAttribute *indexAttribute = nullptr;
537 QByteArray indexBytes, vertexBytes;
538 QVector<uint> indexData;
539 QVector<float> positionData;
540 for ( Qt3DRender::QAttribute *attribute : geometry->attributes() )
541 {
542 if ( attribute->attributeType() == Qt3DRender::QAttribute::IndexAttribute )
543 indexAttribute = attribute;
544 }
545
546 if ( indexAttribute != nullptr )
547 {
548 indexBytes = getData( indexAttribute->buffer() );
549 indexData = getIndexData( indexAttribute, indexBytes );
550 }
551
552 if ( positionAttribute != nullptr )
553 {
554 vertexBytes = getData( positionAttribute->buffer() );
555 positionData = getAttributeData<float>( positionAttribute, vertexBytes );
556 }
557
558// For tessellated polygons that don't have index attributes
559 if ( positionAttribute != nullptr && indexAttribute == nullptr )
560 {
561 for ( int i = 0; i < positionData.size() / 3; ++i )
562 indexData.push_back( i );
563 }
564
565 if ( positionAttribute == nullptr )
566 {
567 QgsDebugMsg( "Geometry renderer with null data was being processed" );
568 return nullptr;
569 }
570
571 Qgs3DExportObject *object = new Qgs3DExportObject( getObjectName( objectNamePrefix + QStringLiteral( "mesh_geometry" ) ) );
572 object->setupPositionCoordinates( positionData, scale * sceneScale, translation + sceneTranslation );
573 object->setupFaces( indexData );
574
575 Qt3DRender::QAttribute *normalsAttribute = findAttribute( geometry, Qt3DRender::QAttribute::defaultNormalAttributeName(), Qt3DRender::QAttribute::VertexAttribute );
576 if ( mExportNormals && normalsAttribute != nullptr )
577 {
578 // Reuse vertex bytes
579 const QVector<float> normalsData = getAttributeData<float>( normalsAttribute, vertexBytes );
580 object->setupNormalCoordinates( normalsData );
581 }
582
583 Qt3DRender::QAttribute *texCoordsAttribute = findAttribute( geometry, Qt3DRender::QAttribute::defaultTextureCoordinateAttributeName(), Qt3DRender::QAttribute::VertexAttribute );
584 if ( mExportTextures && texCoordsAttribute != nullptr )
585 {
586 // Reuse vertex bytes
587 const QVector<float> texCoordsData = getAttributeData<float>( texCoordsAttribute, vertexBytes );
588 object->setupTextureCoordinates( texCoordsData );
589 }
590
591 return object;
592}
593
594QVector<Qgs3DExportObject *> Qgs3DSceneExporter::processLines( Qt3DCore::QEntity *entity, const QString &objectNamePrefix )
595{
596 QVector<Qgs3DExportObject *> objs;
597 const QList<Qt3DRender::QGeometryRenderer *> renderers = entity->findChildren<Qt3DRender::QGeometryRenderer *>();
598 for ( Qt3DRender::QGeometryRenderer *renderer : renderers )
599 {
600 if ( renderer->primitiveType() != Qt3DRender::QGeometryRenderer::LineStripAdjacency ) continue;
601 Qt3DRender::QGeometry *geom = renderer->geometry();
602 Qt3DRender::QAttribute *positionAttribute = findAttribute( geom, Qt3DRender::QAttribute::defaultPositionAttributeName(), Qt3DRender::QAttribute::VertexAttribute );
603 Qt3DRender::QAttribute *indexAttribute = nullptr;
604 for ( Qt3DRender::QAttribute *attribute : geom->attributes() )
605 {
606 if ( attribute->attributeType() == Qt3DRender::QAttribute::IndexAttribute )
607 {
608 indexAttribute = attribute;
609 break;
610 }
611 }
612 if ( positionAttribute == nullptr || indexAttribute == nullptr )
613 {
614 QgsDebugMsg( "Position or index attribute was not found" );
615 continue;
616 }
617
618 const QByteArray vertexBytes = getData( positionAttribute->buffer() );
619 const QByteArray indexBytes = getData( indexAttribute->buffer() );
620 const QVector<float> positionData = getAttributeData<float>( positionAttribute, vertexBytes );
621 const QVector<uint> indexData = getIndexData( indexAttribute, indexBytes );
622
623 Qgs3DExportObject *exportObject = new Qgs3DExportObject( getObjectName( objectNamePrefix + QStringLiteral( "line" ) ) );
624 exportObject->setType( Qgs3DExportObject::LineStrip );
625 exportObject->setupPositionCoordinates( positionData );
626 exportObject->setupLine( indexData );
627
628 objs.push_back( exportObject );
629 }
630 return objs;
631}
632
633Qgs3DExportObject *Qgs3DSceneExporter::processPoints( Qt3DCore::QEntity *entity, const QString &objectNamePrefix )
634{
635 QVector<float> points;
636 const QList<Qt3DRender::QGeometryRenderer *> renderers = entity->findChildren<Qt3DRender::QGeometryRenderer *>();
637 for ( Qt3DRender::QGeometryRenderer *renderer : renderers )
638 {
639 Qt3DRender::QGeometry *geometry = qobject_cast<QgsBillboardGeometry *>( renderer->geometry() );
640 if ( geometry == nullptr )
641 continue;
642 Qt3DRender::QAttribute *positionAttribute = findAttribute( geometry, Qt3DRender::QAttribute::defaultPositionAttributeName(), Qt3DRender::QAttribute::VertexAttribute );
643 const QByteArray positionBytes = getData( positionAttribute->buffer() );
644 if ( positionBytes.size() == 0 )
645 continue;
646 const QVector<float> positions = getAttributeData<float>( positionAttribute, positionBytes );
647 points << positions;
648 }
649 Qgs3DExportObject *obj = new Qgs3DExportObject( getObjectName( objectNamePrefix + QStringLiteral( "points" ) ) );
651 obj->setupPositionCoordinates( points );
652 return obj;
653}
654
655void Qgs3DSceneExporter::save( const QString &sceneName, const QString &sceneFolderPath )
656{
657 const QString objFilePath = QDir( sceneFolderPath ).filePath( sceneName + QStringLiteral( ".obj" ) );
658 const QString mtlFilePath = QDir( sceneFolderPath ).filePath( sceneName + QStringLiteral( ".mtl" ) );
659
660 QFile file( objFilePath );
661 if ( !file.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) )
662 return;
663 QFile mtlFile( mtlFilePath );
664 if ( !mtlFile.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) )
665 return;
666
667 float maxfloat = std::numeric_limits<float>::max(), minFloat = std::numeric_limits<float>::lowest();
668 float minX = maxfloat, minY = maxfloat, minZ = maxfloat, maxX = minFloat, maxY = minFloat, maxZ = minFloat;
669 for ( Qgs3DExportObject *obj : mObjects ) obj->objectBounds( minX, minY, minZ, maxX, maxY, maxZ );
670
671 float diffX = 1.0f, diffY = 1.0f, diffZ = 1.0f;
672 diffX = maxX - minX;
673 diffY = maxY - minY;
674 diffZ = maxZ - minZ;
675
676 const float centerX = ( minX + maxX ) / 2.0f;
677 const float centerY = ( minY + maxY ) / 2.0f;
678 const float centerZ = ( minZ + maxZ ) / 2.0f;
679
680 const float scale = std::max( diffX, std::max( diffY, diffZ ) );
681
682 QTextStream out( &file );
683 // set material library name
684 const QString mtlLibName = sceneName + ".mtl";
685 out << "mtllib " << mtlLibName << "\n";
686
687 QTextStream mtlOut( &mtlFile );
688 for ( Qgs3DExportObject *obj : mObjects )
689 {
690 if ( obj == nullptr ) continue;
691 // Set object name
692 const QString material = obj->saveMaterial( mtlOut, sceneFolderPath );
693 out << "o " << obj->name() << "\n";
694 if ( material != QString() )
695 out << "usemtl " << material << "\n";
696 obj->saveTo( out, scale / mScale, QVector3D( centerX, centerY, centerZ ) );
697 }
698}
699
700QString Qgs3DSceneExporter::getObjectName( const QString &name )
701{
702 QString ret = name;
703 if ( usedObjectNamesCounter.contains( name ) )
704 {
705 ret = QStringLiteral( "%1%2" ).arg( name ).arg( usedObjectNamesCounter[name] );
706 usedObjectNamesCounter[name]++;
707 }
708 else
709 usedObjectNamesCounter[name] = 2;
710 return ret;
711}
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 the terrain generator.
bool terrainRenderingEnabled() const
Returns whether the 2D terrain surface will be rendered.
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:671
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)
QVector< T > getAttributeData(Qt3DRender::QAttribute *attribute, const QByteArray &data)
QVector< uint > _getIndexDataImplementation(const QByteArray &data)
Component * findTypedComponent(Qt3DCore::QEntity *entity)
QByteArray getData(Qt3DRender::QBuffer *buffer)
Qt3DRender::QAttribute * findAttribute(Qt3DRender::QGeometry *geometry, const QString &name, Qt3DRender::QAttribute::AttributeType type)
#define QgsDebugMsg(str)
Definition: qgslogger.h:38