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