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