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