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