QGIS API Documentation 3.99.0-Master (d270888f95f)
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 <numeric>
19
20#include "qgs3dexportobject.h"
21#include "qgs3dmapsettings.h"
22#include "qgs3dutils.h"
25#include "qgschunknode.h"
30#include "qgsgeotransform.h"
31#include "qgsimagetexture.h"
32#include "qgsmeshterraingenerator.h"
33#include "qgsmeshterraintileloader_p.h"
34#include "qgsterrainentity.h"
35#include "qgsterraingenerator.h"
40#include "qgsvectorlayer.h"
42
43#include <QByteArray>
44#include <QFile>
45#include <QString>
46#include <QTextStream>
47#include <QVector>
48#include <Qt3DCore/QAttribute>
49#include <Qt3DCore/QBuffer>
50#include <Qt3DCore/QComponent>
51#include <Qt3DCore/QEntity>
52#include <Qt3DCore/QGeometry>
53#include <Qt3DCore/QNode>
54#include <Qt3DCore/QTransform>
55#include <Qt3DExtras/QConeGeometry>
56#include <Qt3DExtras/QCuboidGeometry>
57#include <Qt3DExtras/QCylinderGeometry>
58#include <Qt3DExtras/QDiffuseSpecularMaterial>
59#include <Qt3DExtras/QExtrudedTextMesh>
60#include <Qt3DExtras/QPhongMaterial>
61#include <Qt3DExtras/QPlaneGeometry>
62#include <Qt3DExtras/QSphereGeometry>
63#include <Qt3DExtras/QTextureMaterial>
64#include <Qt3DExtras/QTorusGeometry>
65#include <Qt3DRender/QAbstractTexture>
66#include <Qt3DRender/QAbstractTextureImage>
67#include <Qt3DRender/QGeometryRenderer>
68#include <Qt3DRender/QMesh>
69#include <Qt3DRender/QSceneLoader>
70#include <Qt3DRender/QTexture>
71#include <Qt3DRender/QTextureImage>
72
73#include "moc_qgs3dsceneexporter.cpp"
74
75using namespace Qt::StringLiterals;
76
77template<typename T>
78QVector<T> getAttributeData( Qt3DCore::QAttribute *attribute, const QByteArray &data )
79{
80 const uint bytesOffset = attribute->byteOffset();
81 const uint bytesStride = attribute->byteStride();
82 const uint vertexSize = attribute->vertexSize();
83 const uint dataSize = static_cast<uint>( data.size() );
84 QVector<T> result;
85
86 if ( bytesStride == 0 )
87 {
88 QgsDebugError( "bytesStride==0, the attribute probably was not set properly" );
89 return result;
90 }
91
92 const char *pData = data.constData();
93 for ( unsigned int i = bytesOffset; i < dataSize; i += bytesStride )
94 {
95 for ( unsigned int j = 0; j < vertexSize * sizeof( T ); j += sizeof( T ) )
96 {
97 T v;
98 memcpy( &v, pData + i + j, sizeof( T ) );
99 result.push_back( v );
100 }
101 }
102 return result;
103}
104
105template<typename T>
106QVector<uint> _getIndexDataImplementation( const QByteArray &data )
107{
108 QVector<uint> result;
109 const char *pData = data.constData();
110 for ( int i = 0; i < data.size(); i += sizeof( T ) )
111 {
112 T v;
113 memcpy( &v, pData + i, sizeof( T ) );
114 result.push_back( ( uint ) v );
115 }
116 return result;
117}
118
119QVector<uint> getIndexData( Qt3DCore::QAttribute *indexAttribute, const QByteArray &data )
120{
121 switch ( indexAttribute->vertexBaseType() )
122 {
123 case Qt3DCore::QAttribute::VertexBaseType::Int:
125 case Qt3DCore::QAttribute::VertexBaseType::UnsignedInt:
127 case Qt3DCore::QAttribute::VertexBaseType::Short:
129 case Qt3DCore::QAttribute::VertexBaseType::UnsignedShort:
131 case Qt3DCore::QAttribute::VertexBaseType::Byte:
133 case Qt3DCore::QAttribute::VertexBaseType::UnsignedByte:
135 default:
136 QgsDebugError( "Probably trying to get index data using an attribute that has vertex data" );
137 break;
138 }
139 return QVector<uint>();
140}
141
142QByteArray getData( Qt3DCore::QBuffer *buffer )
143{
144 QByteArray bytes = buffer->data();
145 if ( bytes.isNull() )
146 {
147 QgsDebugError( "QBuffer is null" );
148 }
149 return bytes;
150}
151
152Qt3DCore::QAttribute *findAttribute( Qt3DCore::QGeometry *geometry, const QString &name, Qt3DCore::QAttribute::AttributeType type )
153{
154 QVector<Qt3DCore::QAttribute *> attributes = geometry->attributes();
155 for ( Qt3DCore::QAttribute *attribute : attributes )
156 {
157 if ( attribute->attributeType() != type )
158 continue;
159 if ( name.isEmpty() || attribute->name() == name )
160 return attribute;
161 }
162 return nullptr;
163}
164
165template<typename Component>
166Component *findTypedComponent( Qt3DCore::QEntity *entity )
167{
168 if ( !entity )
169 return nullptr;
170 QVector<Qt3DCore::QComponent *> components = entity->components();
171 for ( Qt3DCore::QComponent *component : components )
172 {
173 Component *typedComponent = qobject_cast<Component *>( component );
174 if ( typedComponent )
175 return typedComponent;
176 }
177 return nullptr;
178}
179
180bool Qgs3DSceneExporter::parseVectorLayerEntity( Qt3DCore::QEntity *entity, QgsVectorLayer *layer )
181{
182 QgsAbstract3DRenderer *abstractRenderer = layer->renderer3D();
183 const QString rendererType = abstractRenderer->type();
184
185 if ( rendererType == "rulebased" )
186 {
187 int prevSize = mObjects.size();
188 // Potential bug: meshes loaded using Qt3DRender::QSceneLoader will probably have wrong scale and translation
189 const QList<Qt3DRender::QGeometryRenderer *> renderers = entity->findChildren<Qt3DRender::QGeometryRenderer *>();
190 for ( Qt3DRender::QGeometryRenderer *renderer : renderers )
191 {
192 Qt3DCore::QEntity *parentEntity = qobject_cast<Qt3DCore::QEntity *>( renderer->parent() );
193 if ( !parentEntity )
194 continue;
195 Qgs3DExportObject *object = processGeometryRenderer( renderer, layer->name() + u"_"_s );
196 if ( !object )
197 continue;
198 if ( mExportTextures )
199 processEntityMaterial( parentEntity, object );
200 mObjects.push_back( object );
201 }
202 return mObjects.size() > prevSize;
203 }
204
205 else if ( rendererType == "vector" )
206 {
207 QgsVectorLayer3DRenderer *vectorLayerRenderer = dynamic_cast<QgsVectorLayer3DRenderer *>( abstractRenderer );
208 if ( vectorLayerRenderer )
209 {
210 const QgsAbstract3DSymbol *symbol = vectorLayerRenderer->symbol();
211 return symbol->exportGeometries( this, entity, layer->name() + u"_"_s );
212 }
213 else
214 return false;
215 }
216
217 else
218 {
219 // TODO: handle pointcloud/mesh/etc. layers
220 QgsDebugMsgLevel( u"Type '%1' of layer '%2' is not exportable."_s.arg( layer->name(), rendererType ), 2 );
221 return false;
222 }
223
224 return false;
225}
226
227void Qgs3DSceneExporter::processEntityMaterial( Qt3DCore::QEntity *entity, Qgs3DExportObject *object ) const
228{
229 Qt3DExtras::QPhongMaterial *phongMaterial = findTypedComponent<Qt3DExtras::QPhongMaterial>( entity );
230 if ( phongMaterial )
231 {
233 object->setupMaterial( &material );
234 }
235
236 Qt3DExtras::QDiffuseSpecularMaterial *diffuseMapMaterial = findTypedComponent<Qt3DExtras::QDiffuseSpecularMaterial>( entity );
237 if ( diffuseMapMaterial )
238 {
239 const Qt3DRender::QTexture2D *diffuseTexture = diffuseMapMaterial->diffuse().value<Qt3DRender::QTexture2D *>();
240 if ( diffuseTexture )
241 {
242 const QVector<Qt3DRender::QAbstractTextureImage *> textureImages = diffuseTexture->textureImages();
243 for ( const Qt3DRender::QAbstractTextureImage *tex : textureImages )
244 {
245 const QgsImageTexture *imageTexture = dynamic_cast<const QgsImageTexture *>( tex );
246 if ( imageTexture )
247 {
248 const QImage image = imageTexture->getImage();
249 object->setTextureImage( image );
250 break;
251 }
252 }
253 }
254 }
255}
256
257void Qgs3DSceneExporter::parseTerrain( QgsTerrainEntity *terrain, const QString &layerName )
258{
259 Qgs3DMapSettings *settings = terrain->mapSettings();
260 if ( !settings->terrainRenderingEnabled() )
261 return;
262
263 QgsChunkNode *node = terrain->rootNode();
264
265 QgsTerrainGenerator *generator = settings->terrainGenerator();
266 if ( !generator )
267 return;
268 QgsTerrainTileEntity *terrainTile = nullptr;
269 QgsTerrainTextureGenerator *textureGenerator = terrain->textureGenerator();
270 textureGenerator->waitForFinished();
271 const QSize oldResolution = textureGenerator->textureSize();
272 textureGenerator->setTextureSize( QSize( mTerrainTextureResolution, mTerrainTextureResolution ) );
273 switch ( generator->type() )
274 {
276 terrainTile = getDemTerrainEntity( terrain, node, settings->origin() );
277 parseDemTile( terrainTile, layerName + u"_"_s );
278 break;
280 terrainTile = getFlatTerrainEntity( terrain, node, settings->origin() );
281 parseFlatTile( terrainTile, layerName + u"_"_s );
282 break;
284 terrainTile = getMeshTerrainEntity( terrain, node, settings->origin() );
285 parseMeshTile( terrainTile, layerName + u"_"_s );
286 break;
287 // TODO: implement other terrain types
290 break;
291 }
292 textureGenerator->setTextureSize( oldResolution );
293}
294
295QgsTerrainTileEntity *Qgs3DSceneExporter::getFlatTerrainEntity( QgsTerrainEntity *terrain, QgsChunkNode *node, const QgsVector3D &mapOrigin )
296{
297 QgsFlatTerrainGenerator *generator = qgis::down_cast<QgsFlatTerrainGenerator *>( terrain->mapSettings()->terrainGenerator() );
298 FlatTerrainChunkLoader *flatTerrainLoader = qobject_cast<FlatTerrainChunkLoader *>( generator->createChunkLoader( node ) );
299 flatTerrainLoader->start();
300 if ( mExportTextures )
301 terrain->textureGenerator()->waitForFinished();
302 // the entity we created will be deallocated once the scene exporter is deallocated
303 Qt3DCore::QEntity *entity = flatTerrainLoader->createEntity( this );
304 QgsTerrainTileEntity *tileEntity = qobject_cast<QgsTerrainTileEntity *>( entity );
305
306 const QList<QgsGeoTransform *> transforms = entity->findChildren<QgsGeoTransform *>();
307 for ( QgsGeoTransform *transform : transforms )
308 {
309 transform->setOrigin( mapOrigin );
310 }
311
312 return tileEntity;
313}
314
315QgsTerrainTileEntity *Qgs3DSceneExporter::getDemTerrainEntity( QgsTerrainEntity *terrain, QgsChunkNode *node, const QgsVector3D &mapOrigin )
316{
317 // Just create a new tile (we don't need to export exact level of details as in the scene)
318 // create the entity synchronously and then it will be deleted once our scene exporter instance is deallocated
319 QgsDemTerrainGenerator *generator = qgis::down_cast<QgsDemTerrainGenerator *>( terrain->mapSettings()->terrainGenerator()->clone() );
320 generator->setResolution( mTerrainResolution );
321 QgsDemTerrainTileLoader *loader = qobject_cast<QgsDemTerrainTileLoader *>( generator->createChunkLoader( node ) );
322 loader->start();
323 generator->heightMapGenerator()->waitForFinished();
324 if ( mExportTextures )
325 terrain->textureGenerator()->waitForFinished();
326 QgsTerrainTileEntity *tileEntity = qobject_cast<QgsTerrainTileEntity *>( loader->createEntity( this ) );
327
328 const QList<QgsGeoTransform *> transforms = tileEntity->findChildren<QgsGeoTransform *>();
329 for ( QgsGeoTransform *transform : transforms )
330 {
331 transform->setOrigin( mapOrigin );
332 }
333
334 delete generator;
335 return tileEntity;
336}
337
338QgsTerrainTileEntity *Qgs3DSceneExporter::getMeshTerrainEntity( QgsTerrainEntity *terrain, QgsChunkNode *node, const QgsVector3D &mapOrigin )
339{
340 QgsMeshTerrainGenerator *generator = qgis::down_cast<QgsMeshTerrainGenerator *>( terrain->mapSettings()->terrainGenerator() );
341 QgsMeshTerrainTileLoader *loader = qobject_cast<QgsMeshTerrainTileLoader *>( generator->createChunkLoader( node ) );
342 loader->start();
343 // TODO: export textures
344 QgsTerrainTileEntity *tileEntity = qobject_cast<QgsTerrainTileEntity *>( loader->createEntity( this ) );
345
346 const QList<QgsGeoTransform *> transforms = tileEntity->findChildren<QgsGeoTransform *>();
347 for ( QgsGeoTransform *transform : transforms )
348 {
349 transform->setOrigin( mapOrigin );
350 }
351
352 return tileEntity;
353}
354
355void Qgs3DSceneExporter::parseFlatTile( QgsTerrainTileEntity *tileEntity, const QString &layerName )
356{
357 Qt3DRender::QGeometryRenderer *mesh = findTypedComponent<Qt3DRender::QGeometryRenderer>( tileEntity );
358 Qt3DCore::QTransform *transform = findTypedComponent<Qt3DCore::QTransform>( tileEntity );
359
360 Qt3DCore::QGeometry *geometry = mesh->geometry();
361 Qt3DExtras::QPlaneGeometry *tileGeometry = qobject_cast<Qt3DExtras::QPlaneGeometry *>( geometry );
362 if ( !tileGeometry )
363 {
364 QgsDebugError( "Qt3DExtras::QPlaneGeometry* is expected but something else was given" );
365 return;
366 }
367
368 // Generate vertice data
369 Qt3DCore::QAttribute *positionAttribute = tileGeometry->positionAttribute();
370 if ( !positionAttribute )
371 {
372 QgsDebugError( QString( "Cannot export '%1' - geometry has no position attribute!" ).arg( layerName ) );
373 return;
374 }
375 const QByteArray verticesBytes = getData( positionAttribute->buffer() );
376 if ( verticesBytes.isNull() )
377 {
378 QgsDebugError( QString( "Geometry for '%1' has position attribute with empty data!" ).arg( layerName ) );
379 return;
380 }
381 const QVector<float> positionBuffer = getAttributeData<float>( positionAttribute, verticesBytes );
382
383 // Generate index data
384 Qt3DCore::QAttribute *indexAttribute = tileGeometry->indexAttribute();
385 if ( !indexAttribute )
386 {
387 QgsDebugError( QString( "Cannot export '%1' - geometry has no index attribute!" ).arg( layerName ) );
388 return;
389 }
390 const QByteArray indexBytes = getData( indexAttribute->buffer() );
391 if ( indexBytes.isNull() )
392 {
393 QgsDebugError( QString( "Geometry for '%1' has index attribute with empty data!" ).arg( layerName ) );
394 return;
395 }
396 const QVector<uint> indexesBuffer = getIndexData( indexAttribute, indexBytes );
397
398 QString objectNamePrefix = layerName;
399 if ( objectNamePrefix != QString() )
400 objectNamePrefix += QString();
401
402 Qgs3DExportObject *object = new Qgs3DExportObject( getObjectName( objectNamePrefix + u"Flat_tile"_s ) );
403 mObjects.push_back( object );
404
405 object->setSmoothEdges( mSmoothEdges );
406 object->setupTriangle( positionBuffer, indexesBuffer, transform->matrix() );
407
408 if ( mExportNormals )
409 {
410 // Everts
411 QVector<float> normalsBuffer;
412 for ( int i = 0; i < positionBuffer.size(); i += 3 )
413 normalsBuffer << 0.0f << 1.0f << 0.0f;
414 object->setupNormalCoordinates( normalsBuffer, transform->matrix() );
415 }
416
417 Qt3DCore::QAttribute *texCoordsAttribute = tileGeometry->texCoordAttribute();
418 if ( mExportTextures && texCoordsAttribute )
419 {
420 // Reuse vertex buffer data for texture coordinates
421 const QVector<float> texCoords = getAttributeData<float>( texCoordsAttribute, verticesBytes );
422 object->setupTextureCoordinates( texCoords );
423
424 QgsTerrainTextureImage *textureImage = tileEntity->textureImage();
425 const QImage img = textureImage->getImage();
426 object->setTextureImage( img );
427 }
428}
429
430void Qgs3DSceneExporter::parseDemTile( QgsTerrainTileEntity *tileEntity, const QString &layerName )
431{
432 Qt3DRender::QGeometryRenderer *mesh = findTypedComponent<Qt3DRender::QGeometryRenderer>( tileEntity );
433 Qt3DCore::QTransform *transform = findTypedComponent<Qt3DCore::QTransform>( tileEntity );
434
435 Qt3DCore::QGeometry *geometry = mesh->geometry();
436 DemTerrainTileGeometry *tileGeometry = qobject_cast<DemTerrainTileGeometry *>( geometry );
437 if ( !tileGeometry )
438 {
439 QgsDebugError( "DemTerrainTileGeometry* is expected but something else was given" );
440 return;
441 }
442
443 Qt3DCore::QAttribute *positionAttribute = tileGeometry->positionAttribute();
444 const QByteArray positionBytes = positionAttribute->buffer()->data();
445 const QVector<float> positionBuffer = getAttributeData<float>( positionAttribute, positionBytes );
446
447 Qt3DCore::QAttribute *indexAttribute = tileGeometry->indexAttribute();
448 const QByteArray indexBytes = indexAttribute->buffer()->data();
449 const QVector<unsigned int> indexBuffer = getIndexData( indexAttribute, indexBytes );
450
451 Qgs3DExportObject *object = new Qgs3DExportObject( getObjectName( layerName + u"DEM_tile"_s ) );
452 mObjects.push_back( object );
453
454 object->setSmoothEdges( mSmoothEdges );
455 object->setupTriangle( positionBuffer, indexBuffer, transform->matrix() );
456
457 Qt3DCore::QAttribute *normalsAttributes = tileGeometry->normalAttribute();
458 if ( mExportNormals && normalsAttributes )
459 {
460 const QByteArray normalsBytes = normalsAttributes->buffer()->data();
461 const QVector<float> normalsBuffer = getAttributeData<float>( normalsAttributes, normalsBytes );
462 object->setupNormalCoordinates( normalsBuffer, transform->matrix() );
463 }
464
465 Qt3DCore::QAttribute *texCoordsAttribute = tileGeometry->texCoordsAttribute();
466 if ( mExportTextures && texCoordsAttribute )
467 {
468 const QByteArray texCoordsBytes = texCoordsAttribute->buffer()->data();
469 const QVector<float> texCoordsBuffer = getAttributeData<float>( texCoordsAttribute, texCoordsBytes );
470 object->setupTextureCoordinates( texCoordsBuffer );
471
472 QgsTerrainTextureImage *textureImage = tileEntity->textureImage();
473 const QImage img = textureImage->getImage();
474 object->setTextureImage( img );
475 }
476}
477
478void Qgs3DSceneExporter::parseMeshTile( QgsTerrainTileEntity *tileEntity, const QString &layerName )
479{
480 QString objectNamePrefix = layerName;
481 if ( objectNamePrefix != QString() )
482 objectNamePrefix += '_'_L1;
483
484 const QList<Qt3DRender::QGeometryRenderer *> renderers = tileEntity->findChildren<Qt3DRender::QGeometryRenderer *>();
485 for ( Qt3DRender::QGeometryRenderer *renderer : renderers )
486 {
487 Qgs3DExportObject *obj = processGeometryRenderer( renderer, objectNamePrefix );
488 if ( !obj )
489 continue;
490 mObjects << obj;
491 }
492}
493
494QVector<Qgs3DExportObject *> Qgs3DSceneExporter::processInstancedPointGeometry( Qt3DCore::QEntity *entity, const QString &objectNamePrefix )
495{
496 QVector<Qgs3DExportObject *> objects;
497 const QList<Qt3DCore::QGeometry *> geometriesList = entity->findChildren<Qt3DCore::QGeometry *>();
498 for ( Qt3DCore::QGeometry *geometry : geometriesList )
499 {
500 Qt3DCore::QAttribute *positionAttribute = findAttribute( geometry, Qt3DCore::QAttribute::defaultPositionAttributeName(), Qt3DCore::QAttribute::VertexAttribute );
501 Qt3DCore::QAttribute *indexAttribute = findAttribute( geometry, QString(), Qt3DCore::QAttribute::IndexAttribute );
502 if ( !positionAttribute || !indexAttribute )
503 {
504 QgsDebugError( QString( "Cannot export '%1' - geometry has no position or index attribute!" ).arg( objectNamePrefix ) );
505 continue;
506 }
507
508 const QByteArray vertexBytes = positionAttribute->buffer()->data();
509 const QByteArray indexBytes = indexAttribute->buffer()->data();
510 if ( vertexBytes.isNull() || indexBytes.isNull() )
511 {
512 QgsDebugError( QString( "Geometry for '%1' has position or index attribute with empty data!" ).arg( objectNamePrefix ) );
513 continue;
514 }
515
516 const QVector<float> positionData = getAttributeData<float>( positionAttribute, vertexBytes );
517 const QVector<uint> indexData = getIndexData( indexAttribute, indexBytes );
518
519 Qt3DCore::QAttribute *instanceDataAttribute = findAttribute( geometry, u"pos"_s, Qt3DCore::QAttribute::VertexAttribute );
520 if ( !instanceDataAttribute )
521 {
522 QgsDebugError( QString( "Cannot export '%1' - geometry has no instanceData attribute!" ).arg( objectNamePrefix ) );
523 continue;
524 }
525 const QByteArray instancePositionBytes = getData( instanceDataAttribute->buffer() );
526 if ( instancePositionBytes.isNull() )
527 {
528 QgsDebugError( QString( "Geometry for '%1' has instanceData attribute with empty data!" ).arg( objectNamePrefix ) );
529 continue;
530 }
531 QVector<float> instancePosition = getAttributeData<float>( instanceDataAttribute, instancePositionBytes );
532
533 for ( int i = 0; i < instancePosition.size(); i += 3 )
534 {
535 Qgs3DExportObject *object = new Qgs3DExportObject( getObjectName( objectNamePrefix + u"instance_point"_s ) );
536 objects.push_back( object );
537 QMatrix4x4 instanceTransform;
538 instanceTransform.translate( instancePosition[i], instancePosition[i + 1], instancePosition[i + 2] );
539 object->setupTriangle( positionData, indexData, instanceTransform );
540
541 object->setSmoothEdges( mSmoothEdges );
542
543 Qt3DCore::QAttribute *normalsAttribute = findAttribute( geometry, Qt3DCore::QAttribute::defaultNormalAttributeName(), Qt3DCore::QAttribute::VertexAttribute );
544 if ( mExportNormals && normalsAttribute )
545 {
546 // Reuse vertex bytes
547 const QVector<float> normalsData = getAttributeData<float>( normalsAttribute, vertexBytes );
548 object->setupNormalCoordinates( normalsData, instanceTransform );
549 }
550 }
551 }
552
553 return objects;
554}
555
556QVector<Qgs3DExportObject *> Qgs3DSceneExporter::processSceneLoaderGeometries( Qt3DRender::QSceneLoader *sceneLoader, const QString &objectNamePrefix )
557{
558 QVector<Qgs3DExportObject *> objects;
559 Qt3DCore::QEntity *sceneLoaderParent = qobject_cast<Qt3DCore::QEntity *>( sceneLoader->parent() );
560 Qt3DCore::QTransform *entityTransform = findTypedComponent<Qt3DCore::QTransform>( sceneLoaderParent );
561 QMatrix4x4 sceneTransform;
562 if ( entityTransform )
563 {
564 sceneTransform = entityTransform->matrix();
565 }
566 QStringList entityNames = sceneLoader->entityNames();
567 for ( const QString &entityName : entityNames )
568 {
569 Qt3DRender::QGeometryRenderer *mesh = qobject_cast<Qt3DRender::QGeometryRenderer *>( sceneLoader->component( entityName, Qt3DRender::QSceneLoader::GeometryRendererComponent ) );
570 Qgs3DExportObject *object = processGeometryRenderer( mesh, objectNamePrefix, sceneTransform );
571 if ( !object )
572 continue;
573 objects.push_back( object );
574 }
575 return objects;
576}
577
578Qgs3DExportObject *Qgs3DSceneExporter::processGeometryRenderer( Qt3DRender::QGeometryRenderer *geomRenderer, const QString &objectNamePrefix, const QMatrix4x4 &sceneTransform )
579{
580 // We only export triangles for now
581 if ( geomRenderer->primitiveType() != Qt3DRender::QGeometryRenderer::Triangles )
582 return nullptr;
583
584 Qt3DCore::QGeometry *geometry = geomRenderer->geometry();
585 if ( !geometry )
586 return nullptr;
587
588 // === Compute triangleIndexStartingIndiceToKeep according to duplicated features
589 //
590 // In the case of polygons, we have multiple feature geometries within the same geometry object (QgsTessellatedPolygonGeometry).
591 // The QgsTessellatedPolygonGeometry class holds the list of all feature ids included.
592 // To avoid exporting the same geometry (it can be included in multiple QgsTessellatedPolygonGeometry) more than once,
593 // we keep a list of already exported fid and compare with the fid of the current QgsTessellatedPolygonGeometry.
594 // As we cannot retrieve the specific geometry part for a featureId from the QgsTessellatedPolygonGeometry, we only reject
595 // the geometry if all the featureid are already present.
596 QVector<std::pair<uint, uint>> triangleIndexStartingIndiceToKeep;
597 QgsTessellatedPolygonGeometry *tessGeom = dynamic_cast<QgsTessellatedPolygonGeometry *>( geometry );
598 if ( tessGeom )
599 {
600 QVector<QgsFeatureId> featureIds = tessGeom->featureIds();
601
602 QSet<QgsFeatureId> tempFeatToAdd;
603 QVector<uint> triangleIndex = tessGeom->triangleIndexStartingIndices();
604
605 for ( int idx = 0; idx < featureIds.size(); idx++ )
606 {
607 const QgsFeatureId feat = featureIds[idx];
608 if ( !mExportedFeatureIds.contains( feat ) )
609 {
610 // add the feature (as it was unknown) to temp set and not to the mExportedFeatureIds (as featureIds can have the same id multiple times)
611 tempFeatToAdd += feat;
612
613 // keep the feature triangle indexes
614 const uint startIdx = triangleIndex[idx] * 3;
615 const uint endIdx = idx < triangleIndex.size() - 1 ? triangleIndex[idx + 1] * 3 : std::numeric_limits<uint>::max();
616
617 if ( startIdx < endIdx ) // keep only valid intervals
618 triangleIndexStartingIndiceToKeep.append( std::pair<uint, uint>( startIdx, endIdx ) );
619 }
620 }
621 mExportedFeatureIds += tempFeatToAdd;
622
623 if ( triangleIndexStartingIndiceToKeep.isEmpty() ) // all featureid are already exported
624 {
625 return nullptr;
626 }
627 }
628
629 // === Compute inherited scale and translation from child entity to parent
630 QMatrix4x4 transformMatrix = sceneTransform;
631 QObject *parent = geomRenderer->parent();
632 while ( parent )
633 {
634 Qt3DCore::QEntity *entity = qobject_cast<Qt3DCore::QEntity *>( parent );
635 Qt3DCore::QTransform *transform = findTypedComponent<Qt3DCore::QTransform>( entity );
636 if ( transform )
637 {
638 transformMatrix = transform->matrix() * transformMatrix;
639 }
640 parent = parent->parent();
641 }
642
643 Qt3DCore::QAttribute *positionAttribute = nullptr;
644 Qt3DCore::QAttribute *indexAttribute = nullptr;
645 QByteArray indexBytes, vertexBytes;
646 QVector<uint> indexDataTmp;
647 QVector<uint> indexData;
648 QVector<float> positionData;
649
650 // === Extract position data
651 positionAttribute = findAttribute( geometry, Qt3DCore::QAttribute::defaultPositionAttributeName(), Qt3DCore::QAttribute::VertexAttribute );
652 if ( !positionAttribute )
653 {
654 QgsDebugError( QString( "Cannot export '%1' - geometry has no position attribute!" ).arg( objectNamePrefix ) );
655 return nullptr;
656 }
657
658 vertexBytes = getData( positionAttribute->buffer() );
659 if ( vertexBytes.isNull() )
660 {
661 QgsDebugError( QString( "Will not export '%1' as geometry has empty position data!" ).arg( objectNamePrefix ) );
662 return nullptr;
663 }
664
665 positionData = getAttributeData<float>( positionAttribute, vertexBytes );
666
667 // === Search for face index data
668 QVector<Qt3DCore::QAttribute *> attributes = geometry->attributes();
669 for ( Qt3DCore::QAttribute *attribute : attributes )
670 {
671 if ( attribute->attributeType() == Qt3DCore::QAttribute::IndexAttribute )
672 {
673 indexAttribute = attribute;
674 indexBytes = getData( indexAttribute->buffer() );
675 if ( indexBytes.isNull() )
676 {
677 QgsDebugError( QString( "Geometry for '%1' has index attribute with empty data!" ).arg( objectNamePrefix ) );
678 }
679 else
680 {
681 indexDataTmp = getIndexData( indexAttribute, indexBytes );
682 break;
683 }
684 }
685 }
686
687 // for tessellated polygons that don't have index attributes, build them from positionData
688 if ( !indexAttribute )
689 {
690 for ( uint i = 0; i < static_cast<uint>( positionData.size() / 3 ); ++i )
691 {
692 indexDataTmp.push_back( i );
693 }
694 }
695
696 // === Filter face index data if needed
697 if ( triangleIndexStartingIndiceToKeep.isEmpty() )
698 {
699 // when geometry is NOT a QgsTessellatedPolygonGeometry, no filter, take them all
700 indexData.append( indexDataTmp );
701 }
702 else
703 {
704 // when geometry is a QgsTessellatedPolygonGeometry, filter according to triangleIndexStartingIndiceToKeep
705 int intervalIdx = 0;
706 const int triangleIndexStartingIndiceToKeepSize = triangleIndexStartingIndiceToKeep.size();
707 const uint indexDataTmpSize = static_cast<uint>( indexDataTmp.size() );
708 for ( uint i = 0; i < indexDataTmpSize; ++i )
709 {
710 uint idx = indexDataTmp[static_cast<int>( i )];
711 // search for valid triangle index interval
712 while ( intervalIdx < triangleIndexStartingIndiceToKeepSize
713 && idx > triangleIndexStartingIndiceToKeep[intervalIdx].first
714 && idx >= triangleIndexStartingIndiceToKeep[intervalIdx].second )
715 {
716 intervalIdx++;
717 }
718
719 // keep only the one within the triangle index interval
720 if ( intervalIdx < triangleIndexStartingIndiceToKeepSize
721 && idx >= triangleIndexStartingIndiceToKeep[intervalIdx].first
722 && idx < triangleIndexStartingIndiceToKeep[intervalIdx].second )
723 {
724 indexData.push_back( idx );
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