QGIS API Documentation 3.39.0-Master (d85f3c2a281)
Loading...
Searching...
No Matches
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"
82#include "qgs3dutils.h"
83#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 const uint dataSize = static_cast<uint>( data.size() );
95 QVector<T> result;
96
97 if ( bytesStride == 0 )
98 {
99 QgsDebugError( "bytesStride==0, the attribute probably was not set properly" );
100 return result;
101 }
102
103 const char *pData = data.constData();
104 for ( unsigned int i = bytesOffset; i < dataSize; i += bytesStride )
105 {
106 for ( unsigned int j = 0; j < vertexSize * sizeof( T ); j += sizeof( T ) )
107 {
108 T v;
109 memcpy( &v, pData + i + j, sizeof( T ) );
110 result.push_back( v );
111 }
112 }
113 return result;
114}
115
116template<typename T>
117QVector<uint> _getIndexDataImplementation( const QByteArray &data )
118{
119 QVector<uint> result;
120 const char *pData = data.constData();
121 for ( int i = 0; i < data.size(); i += sizeof( T ) )
122 {
123 T v;
124 memcpy( &v, pData + i, sizeof( T ) );
125 result.push_back( ( uint ) v );
126 }
127 return result;
128}
129
130QVector<uint> getIndexData( Qt3DQAttribute *indexAttribute, const QByteArray &data )
131{
132 switch ( indexAttribute->vertexBaseType() )
133 {
134 case Qt3DQAttribute::VertexBaseType::Int:
135 return _getIndexDataImplementation<int>( data );
136 case Qt3DQAttribute::VertexBaseType::UnsignedInt:
137 return _getIndexDataImplementation<uint>( data );
138 case Qt3DQAttribute::VertexBaseType::Short:
139 return _getIndexDataImplementation<short>( data );
140 case Qt3DQAttribute::VertexBaseType::UnsignedShort:
141 return _getIndexDataImplementation<ushort>( data );
142 case Qt3DQAttribute::VertexBaseType::Byte:
143 return _getIndexDataImplementation<char>( data );
144 case Qt3DQAttribute::VertexBaseType::UnsignedByte:
145 return _getIndexDataImplementation<uchar>( data );
146 default:
147 QgsDebugError( "Probably trying to get index data using an attribute that has vertex data" );
148 break;
149 }
150 return QVector<uint>();
151}
152
153QByteArray getData( Qt3DQBuffer *buffer )
154{
155 QByteArray bytes = buffer->data();
156 if ( bytes.isNull() )
157 {
158 QgsDebugError( "QBuffer is null" );
159 }
160 return bytes;
161}
162
163Qt3DQAttribute *findAttribute( Qt3DQGeometry *geometry, const QString &name, Qt3DQAttribute::AttributeType type )
164{
165 QVector<Qt3DQAttribute *> attributes = geometry->attributes();
166 for ( Qt3DQAttribute *attribute : attributes )
167 {
168 if ( attribute->attributeType() != type ) continue;
169 if ( name.isEmpty() || attribute->name() == name ) return attribute;
170 }
171 return nullptr;
172}
173
174template<typename Component>
175Component *findTypedComponent( Qt3DCore::QEntity *entity )
176{
177 if ( !entity )
178 return nullptr;
179 QVector<Qt3DCore::QComponent *> components = entity->components();
180 for ( Qt3DCore::QComponent *component : components )
181 {
182 Component *typedComponent = qobject_cast<Component *>( component );
183 if ( typedComponent )
184 return typedComponent;
185 }
186 return nullptr;
187}
188
189bool Qgs3DSceneExporter::parseVectorLayerEntity( Qt3DCore::QEntity *entity, QgsVectorLayer *layer )
190{
191 QgsAbstract3DRenderer *abstractRenderer = layer->renderer3D();
192 const QString rendererType = abstractRenderer->type();
193
194 if ( rendererType == "rulebased" )
195 {
196 int prevSize = mObjects.size();
197 // Potential bug: meshes loaded using Qt3DRender::QSceneLoader will probably have wrong scale and translation
198 const QList<Qt3DRender::QGeometryRenderer *> renderers = entity->findChildren<Qt3DRender::QGeometryRenderer *>();
199 for ( Qt3DRender::QGeometryRenderer *renderer : renderers )
200 {
201 Qt3DCore::QEntity *parentEntity = qobject_cast<Qt3DCore::QEntity *>( renderer->parent() );
202 if ( !parentEntity )
203 continue;
204 Qgs3DExportObject *object = processGeometryRenderer( renderer, layer->name() + QStringLiteral( "_" ) );
205 if ( !object )
206 continue;
207 if ( mExportTextures )
208 processEntityMaterial( parentEntity, object );
209 mObjects.push_back( object );
210 }
211 return mObjects.size() > prevSize;
212 }
213
214 else if ( rendererType == "vector" )
215 {
216 QgsVectorLayer3DRenderer *vectorLayerRenderer = dynamic_cast< QgsVectorLayer3DRenderer *>( abstractRenderer );
217 if ( vectorLayerRenderer )
218 {
219 const QgsAbstract3DSymbol *symbol = vectorLayerRenderer->symbol();
220 return symbol->exportGeometries( this, entity, layer->name() + QStringLiteral( "_" ) );
221 }
222 else
223 return false;
224 }
225
226 else
227 {
228 // TODO: handle pointcloud/mesh/etc. layers
229 QgsDebugMsgLevel( QStringLiteral( "Type '%1' of layer '%2' is not exportable." ).arg( layer->name(), rendererType ), 2 );
230 return false;
231 }
232
233 return false;
234}
235
236void Qgs3DSceneExporter::processEntityMaterial( Qt3DCore::QEntity *entity, Qgs3DExportObject *object )
237{
238 Qt3DExtras::QPhongMaterial *phongMaterial = findTypedComponent<Qt3DExtras::QPhongMaterial>( entity );
239 if ( phongMaterial )
240 {
242 object->setupMaterial( &material );
243 }
244
245 Qt3DExtras::QDiffuseSpecularMaterial *diffuseMapMaterial = findTypedComponent<Qt3DExtras::QDiffuseSpecularMaterial>( entity );
246 if ( diffuseMapMaterial )
247 {
248 const Qt3DRender::QTexture2D *diffuseTexture = diffuseMapMaterial->diffuse().value< Qt3DRender::QTexture2D * >();
249 if ( diffuseTexture )
250 {
251 const QVector<Qt3DRender::QAbstractTextureImage *> textureImages = diffuseTexture->textureImages();
252 for ( const Qt3DRender::QAbstractTextureImage *tex : textureImages )
253 {
254 const QgsImageTexture *imageTexture = dynamic_cast<const QgsImageTexture *>( tex );
255 if ( imageTexture )
256 {
257 const QImage image = imageTexture->getImage();
258 object->setTextureImage( image );
259 break;
260 }
261 }
262 }
263 }
264}
265
266void Qgs3DSceneExporter::parseTerrain( QgsTerrainEntity *terrain, const QString &layerName )
267{
268 Qgs3DMapSettings *settings = terrain->mapSettings();
269 if ( !settings->terrainRenderingEnabled() )
270 return;
271
272 QgsChunkNode *node = terrain->rootNode();
273
274 QgsTerrainGenerator *generator = settings->terrainGenerator();
275 if ( !generator )
276 return;
277 QgsTerrainTileEntity *terrainTile = nullptr;
278 QgsTerrainTextureGenerator *textureGenerator = terrain->textureGenerator();
279 textureGenerator->waitForFinished();
280 const QSize oldResolution = textureGenerator->textureSize();
281 textureGenerator->setTextureSize( QSize( mTerrainTextureResolution, mTerrainTextureResolution ) );
282 switch ( generator->type() )
283 {
285 terrainTile = getDemTerrainEntity( terrain, node );
286 parseDemTile( terrainTile, layerName + QStringLiteral( "_" ) );
287 break;
289 terrainTile = getFlatTerrainEntity( terrain, node );
290 parseFlatTile( terrainTile, layerName + QStringLiteral( "_" ) );
291 break;
293 terrainTile = getMeshTerrainEntity( terrain, node );
294 parseMeshTile( terrainTile, layerName + QStringLiteral( "_" ) );
295 break;
296 // TODO: implement other terrain types
299 break;
300 }
301 textureGenerator->setTextureSize( oldResolution );
302}
303
304QgsTerrainTileEntity *Qgs3DSceneExporter::getFlatTerrainEntity( QgsTerrainEntity *terrain, QgsChunkNode *node )
305{
306 QgsFlatTerrainGenerator *generator = dynamic_cast<QgsFlatTerrainGenerator *>( terrain->mapSettings()->terrainGenerator() );
307 FlatTerrainChunkLoader *flatTerrainLoader = qobject_cast<FlatTerrainChunkLoader *>( generator->createChunkLoader( node ) );
308 if ( mExportTextures )
309 terrain->textureGenerator()->waitForFinished();
310 // the entity we created will be deallocated once the scene exporter is deallocated
311 Qt3DCore::QEntity *entity = flatTerrainLoader->createEntity( this );
312 QgsTerrainTileEntity *tileEntity = qobject_cast<QgsTerrainTileEntity *>( entity );
313 return tileEntity;
314}
315
316QgsTerrainTileEntity *Qgs3DSceneExporter::getDemTerrainEntity( QgsTerrainEntity *terrain, QgsChunkNode *node )
317{
318 // Just create a new tile (we don't need to export exact level of details as in the scene)
319 // create the entity synchronously and then it will be deleted once our scene exporter instance is deallocated
320 QgsDemTerrainGenerator *generator = dynamic_cast<QgsDemTerrainGenerator *>( terrain->mapSettings()->terrainGenerator()->clone() );
321 generator->setResolution( mTerrainResolution );
322 QgsDemTerrainTileLoader *loader = qobject_cast<QgsDemTerrainTileLoader *>( generator->createChunkLoader( node ) );
323 generator->heightMapGenerator()->waitForFinished();
324 if ( mExportTextures )
325 terrain->textureGenerator()->waitForFinished();
326 QgsTerrainTileEntity *tileEntity = qobject_cast<QgsTerrainTileEntity *>( loader->createEntity( this ) );
327 delete generator;
328 return tileEntity;
329}
330
331QgsTerrainTileEntity *Qgs3DSceneExporter::getMeshTerrainEntity( QgsTerrainEntity *terrain, QgsChunkNode *node )
332{
333 QgsMeshTerrainGenerator *generator = dynamic_cast<QgsMeshTerrainGenerator *>( terrain->mapSettings()->terrainGenerator() );
334 QgsMeshTerrainTileLoader *loader = qobject_cast<QgsMeshTerrainTileLoader *>( generator->createChunkLoader( node ) );
335 // TODO: export textures
336 QgsTerrainTileEntity *tileEntity = qobject_cast<QgsTerrainTileEntity *>( loader->createEntity( this ) );
337 return tileEntity;
338}
339
340void Qgs3DSceneExporter::parseFlatTile( QgsTerrainTileEntity *tileEntity, const QString &layerName )
341{
342 Qt3DRender::QGeometryRenderer *mesh = findTypedComponent<Qt3DRender::QGeometryRenderer>( tileEntity );
343 Qt3DCore::QTransform *transform = findTypedComponent<Qt3DCore::QTransform>( tileEntity );
344
345 Qt3DQGeometry *geometry = mesh->geometry();
346 Qt3DExtras::QPlaneGeometry *tileGeometry = qobject_cast<Qt3DExtras::QPlaneGeometry *>( geometry );
347 if ( !tileGeometry )
348 {
349 QgsDebugError( "Qt3DExtras::QPlaneGeometry* is expected but something else was given" );
350 return;
351 }
352
353 const float scale = transform->scale();
354 const QVector3D translation = transform->translation();
355
356 // Generate vertice data
357 Qt3DQAttribute *positionAttribute = tileGeometry->positionAttribute();
358 if ( !positionAttribute )
359 {
360 QgsDebugError( QString( "Cannot export '%1' - geometry has no position attribute!" ).arg( layerName ) );
361 return;
362 }
363 const QByteArray verticesBytes = getData( positionAttribute->buffer() );
364 if ( verticesBytes.isNull() )
365 {
366 QgsDebugError( QString( "Geometry for '%1' has position attribute with empty data!" ).arg( layerName ) );
367 return;
368 }
369 const QVector<float> positionBuffer = getAttributeData<float>( positionAttribute, verticesBytes );
370
371 // Generate index data
372 Qt3DQAttribute *indexAttribute = tileGeometry->indexAttribute();
373 if ( !indexAttribute )
374 {
375 QgsDebugError( QString( "Cannot export '%1' - geometry has no index attribute!" ).arg( layerName ) );
376 return;
377 }
378 const QByteArray indexBytes = getData( indexAttribute->buffer() );
379 if ( indexBytes.isNull() )
380 {
381 QgsDebugError( QString( "Geometry for '%1' has index attribute with empty data!" ).arg( layerName ) );
382 return;
383 }
384 const QVector<uint> indexesBuffer = getIndexData( indexAttribute, indexBytes );
385
386 QString objectNamePrefix = layerName;
387 if ( objectNamePrefix != QString() ) objectNamePrefix += QString();
388
389 Qgs3DExportObject *object = new Qgs3DExportObject( getObjectName( objectNamePrefix + QStringLiteral( "Flat_tile" ) ) );
390 mObjects.push_back( object );
391
392 object->setSmoothEdges( mSmoothEdges );
393 object->setupPositionCoordinates( positionBuffer, scale, translation );
394 object->setupFaces( indexesBuffer );
395
396 if ( mExportNormals )
397 {
398 // Everts
399 QVector<float> normalsBuffer;
400 for ( int i = 0; i < positionBuffer.size(); i += 3 ) normalsBuffer << 0.0f << 1.0f << 0.0f;
401 object->setupNormalCoordinates( normalsBuffer );
402 }
403
404 Qt3DQAttribute *texCoordsAttribute = tileGeometry->texCoordAttribute();
405 if ( mExportTextures && texCoordsAttribute )
406 {
407 // Reuse vertex buffer data for texture coordinates
408 const QVector<float> texCoords = getAttributeData<float>( texCoordsAttribute, verticesBytes );
409 object->setupTextureCoordinates( texCoords );
410
411 QgsTerrainTextureImage *textureImage = tileEntity->textureImage();
412 const QImage img = textureImage->getImage();
413 object->setTextureImage( img );
414 }
415}
416
417void Qgs3DSceneExporter::parseDemTile( QgsTerrainTileEntity *tileEntity, const QString &layerName )
418{
419 Qt3DRender::QGeometryRenderer *mesh = findTypedComponent<Qt3DRender::QGeometryRenderer>( tileEntity );
420 Qt3DCore::QTransform *transform = findTypedComponent<Qt3DCore::QTransform>( tileEntity );
421
422 Qt3DQGeometry *geometry = mesh->geometry();
423 DemTerrainTileGeometry *tileGeometry = qobject_cast<DemTerrainTileGeometry *>( geometry );
424 if ( !tileGeometry )
425 {
426 QgsDebugError( "DemTerrainTileGeometry* is expected but something else was given" );
427 return;
428 }
429
430 const float scale = transform->scale();
431 const QVector3D translation = transform->translation();
432
433 Qt3DQAttribute *positionAttribute = tileGeometry->positionAttribute();
434 const QByteArray positionBytes = positionAttribute->buffer()->data();
435 const QVector<float> positionBuffer = getAttributeData<float>( positionAttribute, positionBytes );
436
437 Qt3DQAttribute *indexAttribute = tileGeometry->indexAttribute();
438 const QByteArray indexBytes = indexAttribute->buffer()->data();
439 const QVector<unsigned int> indexBuffer = getIndexData( indexAttribute, indexBytes );
440
441 Qgs3DExportObject *object = new Qgs3DExportObject( getObjectName( layerName + QStringLiteral( "DEM_tile" ) ) );
442 mObjects.push_back( object );
443
444 object->setSmoothEdges( mSmoothEdges );
445 object->setupPositionCoordinates( positionBuffer, scale, translation );
446 object->setupFaces( indexBuffer );
447
448 Qt3DQAttribute *normalsAttributes = tileGeometry->normalAttribute();
449 if ( mExportNormals && normalsAttributes )
450 {
451 const QByteArray normalsBytes = normalsAttributes->buffer()->data();
452 const QVector<float> normalsBuffer = getAttributeData<float>( normalsAttributes, normalsBytes );
453 object->setupNormalCoordinates( normalsBuffer );
454 }
455
456 Qt3DQAttribute *texCoordsAttribute = tileGeometry->texCoordsAttribute();
457 if ( mExportTextures && texCoordsAttribute )
458 {
459 const QByteArray texCoordsBytes = texCoordsAttribute->buffer()->data();
460 const QVector<float> texCoordsBuffer = getAttributeData<float>( texCoordsAttribute, texCoordsBytes );
461 object->setupTextureCoordinates( texCoordsBuffer );
462
463 QgsTerrainTextureImage *textureImage = tileEntity->textureImage();
464 const QImage img = textureImage->getImage();
465 object->setTextureImage( img );
466 }
467}
468
469void Qgs3DSceneExporter::parseMeshTile( QgsTerrainTileEntity *tileEntity, const QString &layerName )
470{
471 QString objectNamePrefix = layerName;
472 if ( objectNamePrefix != QString() ) objectNamePrefix += QStringLiteral( "_" );
473
474 const QList<Qt3DRender::QGeometryRenderer *> renderers = tileEntity->findChildren<Qt3DRender::QGeometryRenderer *>();
475 for ( Qt3DRender::QGeometryRenderer *renderer : renderers )
476 {
477 Qgs3DExportObject *obj = processGeometryRenderer( renderer, objectNamePrefix );
478 if ( !obj )
479 continue;
480 mObjects << obj;
481 }
482}
483
484QVector<Qgs3DExportObject *> Qgs3DSceneExporter::processInstancedPointGeometry( Qt3DCore::QEntity *entity, const QString &objectNamePrefix )
485{
486 QVector<Qgs3DExportObject *> objects;
487 const QList<Qt3DQGeometry *> geometriesList = entity->findChildren<Qt3DQGeometry *>();
488 for ( Qt3DQGeometry *geometry : geometriesList )
489 {
490 Qt3DQAttribute *positionAttribute = findAttribute( geometry, Qt3DQAttribute::defaultPositionAttributeName(), Qt3DQAttribute::VertexAttribute );
491 Qt3DQAttribute *indexAttribute = findAttribute( geometry, QString(), Qt3DQAttribute::IndexAttribute );
492 if ( !positionAttribute || !indexAttribute )
493 {
494 QgsDebugError( QString( "Cannot export '%1' - geometry has no position or index attribute!" ).arg( objectNamePrefix ) );
495 continue;
496 }
497
498 const QByteArray vertexBytes = positionAttribute->buffer()->data();
499 const QByteArray indexBytes = indexAttribute->buffer()->data();
500 if ( vertexBytes.isNull() || indexBytes.isNull() )
501 {
502 QgsDebugError( QString( "Geometry for '%1' has position or index attribute with empty data!" ).arg( objectNamePrefix ) );
503 continue;
504 }
505
506 const QVector<float> positionData = getAttributeData<float>( positionAttribute, vertexBytes );
507 const QVector<uint> indexData = getIndexData( indexAttribute, indexBytes );
508
509 Qt3DQAttribute *instanceDataAttribute = findAttribute( geometry, QStringLiteral( "pos" ), Qt3DQAttribute::VertexAttribute );
510 if ( !instanceDataAttribute )
511 {
512 QgsDebugError( QString( "Cannot export '%1' - geometry has no instanceData attribute!" ).arg( objectNamePrefix ) );
513 continue;
514 }
515 const QByteArray instancePositionBytes = getData( instanceDataAttribute->buffer() );
516 if ( instancePositionBytes.isNull() )
517 {
518 QgsDebugError( QString( "Geometry for '%1' has instanceData attribute with empty data!" ).arg( objectNamePrefix ) );
519 continue;
520 }
521 QVector<float> instancePosition = getAttributeData<float>( instanceDataAttribute, instancePositionBytes );
522
523 for ( int i = 0; i < instancePosition.size(); i += 3 )
524 {
525 Qgs3DExportObject *object = new Qgs3DExportObject( getObjectName( objectNamePrefix + QStringLiteral( "instance_point" ) ) );
526 objects.push_back( object );
527 object->setupPositionCoordinates( positionData, 1.0f, QVector3D( instancePosition[i], instancePosition[i + 1], instancePosition[i + 2] ) );
528 object->setupFaces( indexData );
529
530 object->setSmoothEdges( mSmoothEdges );
531
532 Qt3DQAttribute *normalsAttribute = findAttribute( geometry, Qt3DQAttribute::defaultNormalAttributeName(), Qt3DQAttribute::VertexAttribute );
533 if ( mExportNormals && normalsAttribute )
534 {
535 // Reuse vertex bytes
536 const QVector<float> normalsData = getAttributeData<float>( normalsAttribute, vertexBytes );
537 object->setupNormalCoordinates( normalsData );
538 }
539 }
540 }
541
542 return objects;
543}
544
545QVector<Qgs3DExportObject *> Qgs3DSceneExporter::processSceneLoaderGeometries( Qt3DRender::QSceneLoader *sceneLoader, const QString &objectNamePrefix )
546{
547 QVector<Qgs3DExportObject *> objects;
548 Qt3DCore::QEntity *sceneLoaderParent = qobject_cast<Qt3DCore::QEntity *>( sceneLoader->parent() );
549 Qt3DCore::QTransform *entityTransform = findTypedComponent<Qt3DCore::QTransform>( sceneLoaderParent );
550 float sceneScale = 1.0f;
551 QVector3D sceneTranslation( 0.0f, 0.0f, 0.0f );
552 if ( entityTransform )
553 {
554 sceneScale = entityTransform->scale();
555 sceneTranslation = entityTransform->translation();
556 }
557 QStringList entityNames = sceneLoader->entityNames();
558 for ( const QString &entityName : entityNames )
559 {
560 Qt3DRender::QGeometryRenderer *mesh = qobject_cast<Qt3DRender::QGeometryRenderer *>( sceneLoader->component( entityName, Qt3DRender::QSceneLoader::GeometryRendererComponent ) );
561 Qgs3DExportObject *object = processGeometryRenderer( mesh, objectNamePrefix, sceneScale, sceneTranslation );
562 if ( !object )
563 continue;
564 objects.push_back( object );
565 }
566 return objects;
567}
568
569Qgs3DExportObject *Qgs3DSceneExporter::processGeometryRenderer( Qt3DRender::QGeometryRenderer *geomRenderer, const QString &objectNamePrefix, float sceneScale, QVector3D sceneTranslation )
570{
571 // We only export triangles for now
572 if ( geomRenderer->primitiveType() != Qt3DRender::QGeometryRenderer::Triangles )
573 return nullptr;
574
575 Qt3DQGeometry *geometry = geomRenderer->geometry();
576 if ( ! geometry )
577 return nullptr;
578
579 // === Compute triangleIndexStartingIndiceToKeep according to duplicated features
580 //
581 // In the case of polygons, we have multiple feature geometries within the same geometry object (QgsTessellatedPolygonGeometry).
582 // The QgsTessellatedPolygonGeometry class holds the list of all feature ids included.
583 // To avoid exporting the same geometry (it can be included in multiple QgsTessellatedPolygonGeometry) more than once,
584 // we keep a list of already exported fid and compare with the fid of the current QgsTessellatedPolygonGeometry.
585 // As we cannot retrieve the specific geometry part for a featureId from the QgsTessellatedPolygonGeometry, we only reject
586 // the geometry if all the featureid are already present.
587 QVector<std::pair<uint, uint>> triangleIndexStartingIndiceToKeep;
588 QgsTessellatedPolygonGeometry *tessGeom = dynamic_cast<QgsTessellatedPolygonGeometry *>( geometry );
589 if ( tessGeom )
590 {
591 QVector<QgsFeatureId> featureIds = tessGeom->featureIds();
592
593 QSet<QgsFeatureId> tempFeatToAdd;
594 QVector<uint> triangleIndex = tessGeom->triangleIndexStartingIndices();
595
596 for ( int idx = 0; idx < featureIds.size(); idx++ )
597 {
598 const QgsFeatureId feat = featureIds[idx];
599 if ( ! mExportedFeatureIds.contains( feat ) )
600 {
601 // add the feature (as it was unknown) to temp set and not to the mExportedFeatureIds (as featureIds can have the same id multiple times)
602 tempFeatToAdd += feat;
603
604 // keep the feature triangle indexes
605 const uint startIdx = triangleIndex[idx] * 3;
606 const uint endIdx = idx < triangleIndex.size() - 1 ? triangleIndex[idx + 1] * 3 : std::numeric_limits<uint>::max();
607
608 if ( startIdx < endIdx ) // keep only valid intervals
609 triangleIndexStartingIndiceToKeep.append( std::pair<uint, uint>( startIdx, endIdx ) );
610 }
611 }
612 mExportedFeatureIds += tempFeatToAdd;
613
614 if ( triangleIndexStartingIndiceToKeep.isEmpty() ) // all featureid are already exported
615 {
616 return nullptr;
617 }
618 }
619
620 // === Compute inherited scale and translation from child entity to parent
621 float scale = 1.0f;
622 QVector3D translation( 0.0f, 0.0f, 0.0f );
623 QObject *parent = geomRenderer->parent();
624 while ( parent )
625 {
626 Qt3DCore::QEntity *entity = qobject_cast<Qt3DCore::QEntity *>( parent );
627 Qt3DCore::QTransform *transform = findTypedComponent<Qt3DCore::QTransform>( entity );
628 if ( transform )
629 {
630 scale *= transform->scale();
631 translation += transform->translation();
632 }
633 parent = parent->parent();
634 }
635
636 Qt3DQAttribute *positionAttribute = nullptr;
637 Qt3DQAttribute *indexAttribute = nullptr;
638 QByteArray indexBytes, vertexBytes;
639 QVector<uint> indexDataTmp;
640 QVector<uint> indexData;
641 QVector<float> positionData;
642
643 // === Extract position data
644 positionAttribute = findAttribute( geometry, Qt3DQAttribute::defaultPositionAttributeName(), Qt3DQAttribute::VertexAttribute );
645 if ( !positionAttribute )
646 {
647 QgsDebugError( QString( "Cannot export '%1' - geometry has no position attribute!" ).arg( objectNamePrefix ) );
648 return nullptr;
649 }
650
651 vertexBytes = getData( positionAttribute->buffer() );
652 if ( vertexBytes.isNull() )
653 {
654 QgsDebugError( QString( "Will not export '%1' as geometry has empty position data!" ).arg( objectNamePrefix ) );
655 return nullptr;
656 }
657
658 positionData = getAttributeData<float>( positionAttribute, vertexBytes );
659
660 // === Search for face index data
661 QVector<Qt3DQAttribute *> attributes = geometry->attributes();
662 for ( Qt3DQAttribute *attribute : attributes )
663 {
664 if ( attribute->attributeType() == Qt3DQAttribute::IndexAttribute )
665 {
666 indexAttribute = attribute;
667 indexBytes = getData( indexAttribute->buffer() );
668 if ( indexBytes.isNull() )
669 {
670 QgsDebugError( QString( "Geometry for '%1' has index attribute with empty data!" ).arg( objectNamePrefix ) );
671 }
672 else
673 {
674 indexDataTmp = getIndexData( indexAttribute, indexBytes );
675 break;
676 }
677 }
678 }
679
680 // for tessellated polygons that don't have index attributes, build them from positionData
681 if ( !indexAttribute )
682 {
683 for ( uint i = 0; i < static_cast<uint>( positionData.size() / 3 ); ++i )
684 {
685 indexDataTmp.push_back( i );
686 }
687 }
688
689 // === Filter face index data if needed
690 if ( triangleIndexStartingIndiceToKeep.isEmpty() )
691 {
692 // when geometry is NOT a QgsTessellatedPolygonGeometry, no filter, take them all
693 indexData.append( indexDataTmp );
694 }
695 else
696 {
697 // when geometry is a QgsTessellatedPolygonGeometry, filter according to triangleIndexStartingIndiceToKeep
698 int intervalIdx = 0;
699 const int triangleIndexStartingIndiceToKeepSize = triangleIndexStartingIndiceToKeep.size();
700 const uint indexDataTmpSize = static_cast<uint>( indexDataTmp.size() );
701 for ( uint i = 0; i < indexDataTmpSize; ++i )
702 {
703 uint idx = indexDataTmp[static_cast<int>( i )];
704 // search for valid triangle index interval
705 while ( intervalIdx < triangleIndexStartingIndiceToKeepSize
706 && idx > triangleIndexStartingIndiceToKeep[intervalIdx].first
707 && idx >= triangleIndexStartingIndiceToKeep[intervalIdx].second )
708 {
709 intervalIdx++;
710 }
711
712 // keep only the one within the triangle index interval
713 if ( intervalIdx < triangleIndexStartingIndiceToKeepSize
714 && idx >= triangleIndexStartingIndiceToKeep[intervalIdx].first
715 && idx < triangleIndexStartingIndiceToKeep[intervalIdx].second )
716 {
717 indexData.push_back( idx );
718 }
719 }
720 }
721
722 // === Create Qgs3DExportObject
723 Qgs3DExportObject *object = new Qgs3DExportObject( getObjectName( objectNamePrefix + QStringLiteral( "mesh_geometry" ) ) );
724 object->setupPositionCoordinates( positionData, scale * sceneScale, translation + sceneTranslation );
725 object->setupFaces( indexData );
726
727 Qt3DQAttribute *normalsAttribute = findAttribute( geometry, Qt3DQAttribute::defaultNormalAttributeName(), Qt3DQAttribute::VertexAttribute );
728 if ( mExportNormals && normalsAttribute )
729 {
730 // Reuse vertex bytes
731 const QVector<float> normalsData = getAttributeData<float>( normalsAttribute, vertexBytes );
732 object->setupNormalCoordinates( normalsData );
733 }
734
735 Qt3DQAttribute *texCoordsAttribute = findAttribute( geometry, Qt3DQAttribute::defaultTextureCoordinateAttributeName(), Qt3DQAttribute::VertexAttribute );
736 if ( mExportTextures && texCoordsAttribute )
737 {
738 // Reuse vertex bytes
739 const QVector<float> texCoordsData = getAttributeData<float>( texCoordsAttribute, vertexBytes );
740 object->setupTextureCoordinates( texCoordsData );
741 }
742
743 return object;
744}
745
746QVector<Qgs3DExportObject *> Qgs3DSceneExporter::processLines( Qt3DCore::QEntity *entity, const QString &objectNamePrefix )
747{
748 QVector<Qgs3DExportObject *> objs;
749 const QList<Qt3DRender::QGeometryRenderer *> renderers = entity->findChildren<Qt3DRender::QGeometryRenderer *>();
750 for ( Qt3DRender::QGeometryRenderer *renderer : renderers )
751 {
752 if ( renderer->primitiveType() != Qt3DRender::QGeometryRenderer::LineStripAdjacency ) continue;
753 Qt3DQGeometry *geom = renderer->geometry();
754 Qt3DQAttribute *positionAttribute = findAttribute( geom, Qt3DQAttribute::defaultPositionAttributeName(), Qt3DQAttribute::VertexAttribute );
755 Qt3DQAttribute *indexAttribute = findAttribute( geom, QString(), Qt3DQAttribute::IndexAttribute );
756 if ( !positionAttribute || !indexAttribute )
757 {
758 QgsDebugError( QString( "Cannot export '%1' - geometry has no position or index attribute!" ).arg( objectNamePrefix ) );
759 continue;
760 }
761
762 const QByteArray vertexBytes = getData( positionAttribute->buffer() );
763 const QByteArray indexBytes = getData( indexAttribute->buffer() );
764 if ( vertexBytes.isNull() || indexBytes.isNull() )
765 {
766 QgsDebugError( QString( "Geometry for '%1' has position or index attribute with empty data!" ).arg( objectNamePrefix ) );
767 continue;
768 }
769 const QVector<float> positionData = getAttributeData<float>( positionAttribute, vertexBytes );
770 const QVector<uint> indexData = getIndexData( indexAttribute, indexBytes );
771
772 Qgs3DExportObject *exportObject = new Qgs3DExportObject( getObjectName( objectNamePrefix + QStringLiteral( "line" ) ) );
773 exportObject->setType( Qgs3DExportObject::LineStrip );
774 exportObject->setupPositionCoordinates( positionData );
775 exportObject->setupLine( indexData );
776
777 objs.push_back( exportObject );
778 }
779 return objs;
780}
781
782Qgs3DExportObject *Qgs3DSceneExporter::processPoints( Qt3DCore::QEntity *entity, const QString &objectNamePrefix )
783{
784 QVector<float> points;
785 const QList<Qt3DRender::QGeometryRenderer *> renderers = entity->findChildren<Qt3DRender::QGeometryRenderer *>();
786 for ( Qt3DRender::QGeometryRenderer *renderer : renderers )
787 {
788 Qt3DQGeometry *geometry = qobject_cast<QgsBillboardGeometry *>( renderer->geometry() );
789 if ( !geometry )
790 continue;
791 Qt3DQAttribute *positionAttribute = findAttribute( geometry, Qt3DQAttribute::defaultPositionAttributeName(), Qt3DQAttribute::VertexAttribute );
792 if ( !positionAttribute )
793 {
794 QgsDebugError( QString( "Cannot export '%1' - geometry has no position attribute!" ).arg( objectNamePrefix ) );
795 continue;
796 }
797 const QByteArray positionBytes = getData( positionAttribute->buffer() );
798 if ( positionBytes.isNull() )
799 {
800 QgsDebugError( QString( "Geometry for '%1' has position attribute with empty data!" ).arg( objectNamePrefix ) );
801 continue;
802 }
803 const QVector<float> positions = getAttributeData<float>( positionAttribute, positionBytes );
804 points << positions;
805 }
806 Qgs3DExportObject *obj = new Qgs3DExportObject( getObjectName( objectNamePrefix + QStringLiteral( "points" ) ) );
808 obj->setupPositionCoordinates( points );
809 return obj;
810}
811
812void Qgs3DSceneExporter::save( const QString &sceneName, const QString &sceneFolderPath, int precision )
813{
814 const QString objFilePath = QDir( sceneFolderPath ).filePath( sceneName + QStringLiteral( ".obj" ) );
815 const QString mtlFilePath = QDir( sceneFolderPath ).filePath( sceneName + QStringLiteral( ".mtl" ) );
816
817 QFile file( objFilePath );
818 if ( !file.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) )
819 {
820 QgsDebugError( QStringLiteral( "Scene can not be exported to '%1'. File access error." ).arg( objFilePath ) );
821 return;
822 }
823 QFile mtlFile( mtlFilePath );
824 if ( !mtlFile.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) )
825 {
826 QgsDebugError( QStringLiteral( "Scene can not be exported to '%1'. File access error." ).arg( mtlFilePath ) );
827 return;
828 }
829
830 float maxfloat = std::numeric_limits<float>::max(), minFloat = std::numeric_limits<float>::lowest();
831 float minX = maxfloat, minY = maxfloat, minZ = maxfloat, maxX = minFloat, maxY = minFloat, maxZ = minFloat;
832 for ( Qgs3DExportObject *obj : qAsConst( mObjects ) )
833 {
834 obj->objectBounds( minX, minY, minZ, maxX, maxY, maxZ );
835 }
836
837 float diffX = 1.0f, diffY = 1.0f, diffZ = 1.0f;
838 diffX = maxX - minX;
839 diffY = maxY - minY;
840 diffZ = maxZ - minZ;
841
842 const float centerX = ( minX + maxX ) / 2.0f;
843 const float centerY = ( minY + maxY ) / 2.0f;
844 const float centerZ = ( minZ + maxZ ) / 2.0f;
845
846 const float scale = std::max( diffX, std::max( diffY, diffZ ) );
847
848 QTextStream out( &file );
849 // set material library name
850 const QString mtlLibName = sceneName + ".mtl";
851 out << "mtllib " << mtlLibName << "\n";
852
853 QTextStream mtlOut( &mtlFile );
854 for ( Qgs3DExportObject *obj : qAsConst( mObjects ) )
855 {
856 if ( !obj )
857 continue;
858 // Set object name
859 const QString material = obj->saveMaterial( mtlOut, sceneFolderPath );
860 out << "o " << obj->name() << "\n";
861 if ( material != QString() )
862 out << "usemtl " << material << "\n";
863 obj->saveTo( out, scale / mScale, QVector3D( centerX, centerY, centerZ ), precision );
864 }
865
866 QgsDebugMsgLevel( QStringLiteral( "Scene exported to '%1'" ).arg( objFilePath ), 2 );
867}
868
869QString Qgs3DSceneExporter::getObjectName( const QString &name )
870{
871 QString ret = name;
872 if ( usedObjectNamesCounter.contains( name ) )
873 {
874 ret = QStringLiteral( "%1%2" ).arg( name ).arg( usedObjectNamesCounter[name] );
875 usedObjectNamesCounter[name]++;
876 }
877 else
878 usedObjectNamesCounter[name] = 2;
879 return ret;
880}
Manages the data of each object of the scene (positions, normals, texture coordinates ....
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 saveTo(QTextStream &out, float scale, const QVector3D &center, int precision=6)
Saves the current object to the output stream while scaling the object and centering it to be visible...
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 save(const QString &sceneName, const QString &sceneFolderPath, int precision=6)
Saves the scene to a .obj file.
void parseTerrain(QgsTerrainEntity *terrain, const QString &layer)
Creates terrain export objects from the terrain entity.
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.
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:80
QgsAbstract3DRenderer * renderer3D() const
Returns 3D renderer associated with the layer.
@ QuantizedMesh
Terrain is built from quantized mesh tiles.
@ 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.
QVector< QgsFeatureId > featureIds() const
Returns included feature ids.
QVector< uint > triangleIndexStartingIndices() const
Returns triangle index for features. For a feature featureIds()[i], matching triangles start at trian...
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
Qt3DCore::QBuffer Qt3DQBuffer
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
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:39
#define QgsDebugError(str)
Definition qgslogger.h:38
int precision