QGIS API Documentation 3.99.0-Master (26c88405ac0)
Loading...
Searching...
No Matches
qgs3dsceneexporter.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgs3dsceneexporter.cpp
3 --------------------------------------
4 Date : June 2020
5 Copyright : (C) 2020 by Belgacem Nedjima
6 Email : gb underscore nedjima at esi dot dz
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgs3dsceneexporter.h"
17
18#include <QVector>
19#include <Qt3DCore/QComponent>
20#include <Qt3DCore/QEntity>
21#include <Qt3DCore/QNode>
22
23#include "moc_qgs3dsceneexporter.cpp"
24
25#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
26#include <Qt3DRender/QAttribute>
27#include <Qt3DRender/QBuffer>
28#include <Qt3DRender/QGeometry>
29
30typedef Qt3DRender::QAttribute Qt3DQAttribute;
31typedef Qt3DRender::QBuffer Qt3DQBuffer;
32typedef Qt3DRender::QGeometry Qt3DQGeometry;
33#else
34#include <Qt3DCore/QAttribute>
35#include <Qt3DCore/QBuffer>
36#include <Qt3DCore/QGeometry>
37
38typedef Qt3DCore::QAttribute Qt3DQAttribute;
39typedef Qt3DCore::QBuffer Qt3DQBuffer;
40typedef Qt3DCore::QGeometry Qt3DQGeometry;
41#endif
42
43#include <Qt3DRender/QGeometryRenderer>
44#include <Qt3DExtras/QPlaneGeometry>
45#include <Qt3DCore/QTransform>
46#include <Qt3DExtras/QDiffuseSpecularMaterial>
47#include <Qt3DExtras/QTextureMaterial>
48#include <Qt3DRender/QTextureImage>
49#include <Qt3DRender/QTexture>
50#include <Qt3DRender/QMesh>
51#include <Qt3DRender/QSceneLoader>
52#include <Qt3DRender/QAbstractTexture>
53#include <Qt3DExtras/QCylinderGeometry>
54#include <Qt3DExtras/QConeGeometry>
55#include <Qt3DExtras/QSphereGeometry>
56#include <Qt3DExtras/QCuboidGeometry>
57#include <Qt3DExtras/QTorusGeometry>
58#include <Qt3DExtras/QExtrudedTextMesh>
59#include <Qt3DExtras/QPhongMaterial>
60#include <Qt3DRender/QAbstractTextureImage>
61
62#include <QByteArray>
63#include <QFile>
64#include <QTextStream>
65
68#include "qgsterrainentity.h"
69#include "qgschunknode.h"
70#include "qgsterraingenerator.h"
71#include "qgs3dmapsettings.h"
76#include "qgs3dexportobject.h"
79#include "qgsmeshterraingenerator.h"
80#include "qgsmeshterraintileloader_p.h"
81#include "qgsvectorlayer.h"
84#include "qgs3dutils.h"
85#include "qgsimagetexture.h"
87#include "qgsgeotransform.h"
88
89#include <numeric>
90
91template<typename T>
92QVector<T> getAttributeData( Qt3DQAttribute *attribute, const QByteArray &data )
93{
94 const uint bytesOffset = attribute->byteOffset();
95 const uint bytesStride = attribute->byteStride();
96 const uint vertexSize = attribute->vertexSize();
97 const uint dataSize = static_cast<uint>( data.size() );
98 QVector<T> result;
99
100 if ( bytesStride == 0 )
101 {
102 QgsDebugError( "bytesStride==0, the attribute probably was not set properly" );
103 return result;
104 }
105
106 const char *pData = data.constData();
107 for ( unsigned int i = bytesOffset; i < dataSize; i += bytesStride )
108 {
109 for ( unsigned int j = 0; j < vertexSize * sizeof( T ); j += sizeof( T ) )
110 {
111 T v;
112 memcpy( &v, pData + i + j, sizeof( T ) );
113 result.push_back( v );
114 }
115 }
116 return result;
117}
118
119template<typename T>
120QVector<uint> _getIndexDataImplementation( const QByteArray &data )
121{
122 QVector<uint> result;
123 const char *pData = data.constData();
124 for ( int i = 0; i < data.size(); i += sizeof( T ) )
125 {
126 T v;
127 memcpy( &v, pData + i, sizeof( T ) );
128 result.push_back( ( uint ) v );
129 }
130 return result;
131}
132
133QVector<uint> getIndexData( Qt3DQAttribute *indexAttribute, const QByteArray &data )
134{
135 switch ( indexAttribute->vertexBaseType() )
136 {
137 case Qt3DQAttribute::VertexBaseType::Int:
139 case Qt3DQAttribute::VertexBaseType::UnsignedInt:
141 case Qt3DQAttribute::VertexBaseType::Short:
143 case Qt3DQAttribute::VertexBaseType::UnsignedShort:
145 case Qt3DQAttribute::VertexBaseType::Byte:
147 case Qt3DQAttribute::VertexBaseType::UnsignedByte:
149 default:
150 QgsDebugError( "Probably trying to get index data using an attribute that has vertex data" );
151 break;
152 }
153 return QVector<uint>();
154}
155
156QByteArray getData( Qt3DQBuffer *buffer )
157{
158 QByteArray bytes = buffer->data();
159 if ( bytes.isNull() )
160 {
161 QgsDebugError( "QBuffer is null" );
162 }
163 return bytes;
164}
165
166Qt3DQAttribute *findAttribute( Qt3DQGeometry *geometry, const QString &name, Qt3DQAttribute::AttributeType type )
167{
168 QVector<Qt3DQAttribute *> attributes = geometry->attributes();
169 for ( Qt3DQAttribute *attribute : attributes )
170 {
171 if ( attribute->attributeType() != type )
172 continue;
173 if ( name.isEmpty() || attribute->name() == name )
174 return attribute;
175 }
176 return nullptr;
177}
178
179template<typename Component>
180Component *findTypedComponent( Qt3DCore::QEntity *entity )
181{
182 if ( !entity )
183 return nullptr;
184 QVector<Qt3DCore::QComponent *> components = entity->components();
185 for ( Qt3DCore::QComponent *component : components )
186 {
187 Component *typedComponent = qobject_cast<Component *>( component );
188 if ( typedComponent )
189 return typedComponent;
190 }
191 return nullptr;
192}
193
194bool Qgs3DSceneExporter::parseVectorLayerEntity( Qt3DCore::QEntity *entity, QgsVectorLayer *layer )
195{
196 QgsAbstract3DRenderer *abstractRenderer = layer->renderer3D();
197 const QString rendererType = abstractRenderer->type();
198
199 if ( rendererType == "rulebased" )
200 {
201 int prevSize = mObjects.size();
202 // Potential bug: meshes loaded using Qt3DRender::QSceneLoader will probably have wrong scale and translation
203 const QList<Qt3DRender::QGeometryRenderer *> renderers = entity->findChildren<Qt3DRender::QGeometryRenderer *>();
204 for ( Qt3DRender::QGeometryRenderer *renderer : renderers )
205 {
206 Qt3DCore::QEntity *parentEntity = qobject_cast<Qt3DCore::QEntity *>( renderer->parent() );
207 if ( !parentEntity )
208 continue;
209 Qgs3DExportObject *object = processGeometryRenderer( renderer, layer->name() + QStringLiteral( "_" ) );
210 if ( !object )
211 continue;
212 if ( mExportTextures )
213 processEntityMaterial( parentEntity, object );
214 mObjects.push_back( object );
215 }
216 return mObjects.size() > prevSize;
217 }
218
219 else if ( rendererType == "vector" )
220 {
221 QgsVectorLayer3DRenderer *vectorLayerRenderer = dynamic_cast<QgsVectorLayer3DRenderer *>( abstractRenderer );
222 if ( vectorLayerRenderer )
223 {
224 const QgsAbstract3DSymbol *symbol = vectorLayerRenderer->symbol();
225 return symbol->exportGeometries( this, entity, layer->name() + QStringLiteral( "_" ) );
226 }
227 else
228 return false;
229 }
230
231 else
232 {
233 // TODO: handle pointcloud/mesh/etc. layers
234 QgsDebugMsgLevel( QStringLiteral( "Type '%1' of layer '%2' is not exportable." ).arg( layer->name(), rendererType ), 2 );
235 return false;
236 }
237
238 return false;
239}
240
241void Qgs3DSceneExporter::processEntityMaterial( Qt3DCore::QEntity *entity, Qgs3DExportObject *object ) const
242{
243 Qt3DExtras::QPhongMaterial *phongMaterial = findTypedComponent<Qt3DExtras::QPhongMaterial>( entity );
244 if ( phongMaterial )
245 {
247 object->setupMaterial( &material );
248 }
249
250 Qt3DExtras::QDiffuseSpecularMaterial *diffuseMapMaterial = findTypedComponent<Qt3DExtras::QDiffuseSpecularMaterial>( entity );
251 if ( diffuseMapMaterial )
252 {
253 const Qt3DRender::QTexture2D *diffuseTexture = diffuseMapMaterial->diffuse().value<Qt3DRender::QTexture2D *>();
254 if ( diffuseTexture )
255 {
256 const QVector<Qt3DRender::QAbstractTextureImage *> textureImages = diffuseTexture->textureImages();
257 for ( const Qt3DRender::QAbstractTextureImage *tex : textureImages )
258 {
259 const QgsImageTexture *imageTexture = dynamic_cast<const QgsImageTexture *>( tex );
260 if ( imageTexture )
261 {
262 const QImage image = imageTexture->getImage();
263 object->setTextureImage( image );
264 break;
265 }
266 }
267 }
268 }
269}
270
271void Qgs3DSceneExporter::parseTerrain( QgsTerrainEntity *terrain, const QString &layerName )
272{
273 Qgs3DMapSettings *settings = terrain->mapSettings();
274 if ( !settings->terrainRenderingEnabled() )
275 return;
276
277 QgsChunkNode *node = terrain->rootNode();
278
279 QgsTerrainGenerator *generator = settings->terrainGenerator();
280 if ( !generator )
281 return;
282 QgsTerrainTileEntity *terrainTile = nullptr;
283 QgsTerrainTextureGenerator *textureGenerator = terrain->textureGenerator();
284 textureGenerator->waitForFinished();
285 const QSize oldResolution = textureGenerator->textureSize();
286 textureGenerator->setTextureSize( QSize( mTerrainTextureResolution, mTerrainTextureResolution ) );
287 switch ( generator->type() )
288 {
290 terrainTile = getDemTerrainEntity( terrain, node, settings->origin() );
291 parseDemTile( terrainTile, layerName + QStringLiteral( "_" ) );
292 break;
294 terrainTile = getFlatTerrainEntity( terrain, node, settings->origin() );
295 parseFlatTile( terrainTile, layerName + QStringLiteral( "_" ) );
296 break;
298 terrainTile = getMeshTerrainEntity( terrain, node, settings->origin() );
299 parseMeshTile( terrainTile, layerName + QStringLiteral( "_" ) );
300 break;
301 // TODO: implement other terrain types
304 break;
305 }
306 textureGenerator->setTextureSize( oldResolution );
307}
308
309QgsTerrainTileEntity *Qgs3DSceneExporter::getFlatTerrainEntity( QgsTerrainEntity *terrain, QgsChunkNode *node, const QgsVector3D &mapOrigin )
310{
311 QgsFlatTerrainGenerator *generator = qgis::down_cast<QgsFlatTerrainGenerator *>( terrain->mapSettings()->terrainGenerator() );
312 FlatTerrainChunkLoader *flatTerrainLoader = qobject_cast<FlatTerrainChunkLoader *>( generator->createChunkLoader( node ) );
313 flatTerrainLoader->start();
314 if ( mExportTextures )
315 terrain->textureGenerator()->waitForFinished();
316 // the entity we created will be deallocated once the scene exporter is deallocated
317 Qt3DCore::QEntity *entity = flatTerrainLoader->createEntity( this );
318 QgsTerrainTileEntity *tileEntity = qobject_cast<QgsTerrainTileEntity *>( entity );
319
320 const QList<QgsGeoTransform *> transforms = entity->findChildren<QgsGeoTransform *>();
321 for ( QgsGeoTransform *transform : transforms )
322 {
323 transform->setOrigin( mapOrigin );
324 }
325
326 return tileEntity;
327}
328
329QgsTerrainTileEntity *Qgs3DSceneExporter::getDemTerrainEntity( QgsTerrainEntity *terrain, QgsChunkNode *node, const QgsVector3D &mapOrigin )
330{
331 // Just create a new tile (we don't need to export exact level of details as in the scene)
332 // create the entity synchronously and then it will be deleted once our scene exporter instance is deallocated
333 QgsDemTerrainGenerator *generator = qgis::down_cast<QgsDemTerrainGenerator *>( terrain->mapSettings()->terrainGenerator()->clone() );
334 generator->setResolution( mTerrainResolution );
335 QgsDemTerrainTileLoader *loader = qobject_cast<QgsDemTerrainTileLoader *>( generator->createChunkLoader( node ) );
336 loader->start();
337 generator->heightMapGenerator()->waitForFinished();
338 if ( mExportTextures )
339 terrain->textureGenerator()->waitForFinished();
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 delete generator;
349 return tileEntity;
350}
351
352QgsTerrainTileEntity *Qgs3DSceneExporter::getMeshTerrainEntity( QgsTerrainEntity *terrain, QgsChunkNode *node, const QgsVector3D &mapOrigin )
353{
354 QgsMeshTerrainGenerator *generator = qgis::down_cast<QgsMeshTerrainGenerator *>( terrain->mapSettings()->terrainGenerator() );
355 QgsMeshTerrainTileLoader *loader = qobject_cast<QgsMeshTerrainTileLoader *>( generator->createChunkLoader( node ) );
356 loader->start();
357 // TODO: export textures
358 QgsTerrainTileEntity *tileEntity = qobject_cast<QgsTerrainTileEntity *>( loader->createEntity( this ) );
359
360 const QList<QgsGeoTransform *> transforms = tileEntity->findChildren<QgsGeoTransform *>();
361 for ( QgsGeoTransform *transform : transforms )
362 {
363 transform->setOrigin( mapOrigin );
364 }
365
366 return tileEntity;
367}
368
369void Qgs3DSceneExporter::parseFlatTile( QgsTerrainTileEntity *tileEntity, const QString &layerName )
370{
371 Qt3DRender::QGeometryRenderer *mesh = findTypedComponent<Qt3DRender::QGeometryRenderer>( tileEntity );
372 Qt3DCore::QTransform *transform = findTypedComponent<Qt3DCore::QTransform>( tileEntity );
373
374 Qt3DQGeometry *geometry = mesh->geometry();
375 Qt3DExtras::QPlaneGeometry *tileGeometry = qobject_cast<Qt3DExtras::QPlaneGeometry *>( geometry );
376 if ( !tileGeometry )
377 {
378 QgsDebugError( "Qt3DExtras::QPlaneGeometry* is expected but something else was given" );
379 return;
380 }
381
382 // Generate vertice data
383 Qt3DQAttribute *positionAttribute = tileGeometry->positionAttribute();
384 if ( !positionAttribute )
385 {
386 QgsDebugError( QString( "Cannot export '%1' - geometry has no position attribute!" ).arg( layerName ) );
387 return;
388 }
389 const QByteArray verticesBytes = getData( positionAttribute->buffer() );
390 if ( verticesBytes.isNull() )
391 {
392 QgsDebugError( QString( "Geometry for '%1' has position attribute with empty data!" ).arg( layerName ) );
393 return;
394 }
395 const QVector<float> positionBuffer = getAttributeData<float>( positionAttribute, verticesBytes );
396
397 // Generate index data
398 Qt3DQAttribute *indexAttribute = tileGeometry->indexAttribute();
399 if ( !indexAttribute )
400 {
401 QgsDebugError( QString( "Cannot export '%1' - geometry has no index attribute!" ).arg( layerName ) );
402 return;
403 }
404 const QByteArray indexBytes = getData( indexAttribute->buffer() );
405 if ( indexBytes.isNull() )
406 {
407 QgsDebugError( QString( "Geometry for '%1' has index attribute with empty data!" ).arg( layerName ) );
408 return;
409 }
410 const QVector<uint> indexesBuffer = getIndexData( indexAttribute, indexBytes );
411
412 QString objectNamePrefix = layerName;
413 if ( objectNamePrefix != QString() )
414 objectNamePrefix += QString();
415
416 Qgs3DExportObject *object = new Qgs3DExportObject( getObjectName( objectNamePrefix + QStringLiteral( "Flat_tile" ) ) );
417 mObjects.push_back( object );
418
419 object->setSmoothEdges( mSmoothEdges );
420 object->setupTriangle( positionBuffer, indexesBuffer, transform->matrix() );
421
422 if ( mExportNormals )
423 {
424 // Everts
425 QVector<float> normalsBuffer;
426 for ( int i = 0; i < positionBuffer.size(); i += 3 )
427 normalsBuffer << 0.0f << 1.0f << 0.0f;
428 object->setupNormalCoordinates( normalsBuffer, transform->matrix() );
429 }
430
431 Qt3DQAttribute *texCoordsAttribute = tileGeometry->texCoordAttribute();
432 if ( mExportTextures && texCoordsAttribute )
433 {
434 // Reuse vertex buffer data for texture coordinates
435 const QVector<float> texCoords = getAttributeData<float>( texCoordsAttribute, verticesBytes );
436 object->setupTextureCoordinates( texCoords );
437
438 QgsTerrainTextureImage *textureImage = tileEntity->textureImage();
439 const QImage img = textureImage->getImage();
440 object->setTextureImage( img );
441 }
442}
443
444void Qgs3DSceneExporter::parseDemTile( QgsTerrainTileEntity *tileEntity, const QString &layerName )
445{
446 Qt3DRender::QGeometryRenderer *mesh = findTypedComponent<Qt3DRender::QGeometryRenderer>( tileEntity );
447 Qt3DCore::QTransform *transform = findTypedComponent<Qt3DCore::QTransform>( tileEntity );
448
449 Qt3DQGeometry *geometry = mesh->geometry();
450 DemTerrainTileGeometry *tileGeometry = qobject_cast<DemTerrainTileGeometry *>( geometry );
451 if ( !tileGeometry )
452 {
453 QgsDebugError( "DemTerrainTileGeometry* is expected but something else was given" );
454 return;
455 }
456
457 Qt3DQAttribute *positionAttribute = tileGeometry->positionAttribute();
458 const QByteArray positionBytes = positionAttribute->buffer()->data();
459 const QVector<float> positionBuffer = getAttributeData<float>( positionAttribute, positionBytes );
460
461 Qt3DQAttribute *indexAttribute = tileGeometry->indexAttribute();
462 const QByteArray indexBytes = indexAttribute->buffer()->data();
463 const QVector<unsigned int> indexBuffer = getIndexData( indexAttribute, indexBytes );
464
465 Qgs3DExportObject *object = new Qgs3DExportObject( getObjectName( layerName + QStringLiteral( "DEM_tile" ) ) );
466 mObjects.push_back( object );
467
468 object->setSmoothEdges( mSmoothEdges );
469 object->setupTriangle( positionBuffer, indexBuffer, transform->matrix() );
470
471 Qt3DQAttribute *normalsAttributes = tileGeometry->normalAttribute();
472 if ( mExportNormals && normalsAttributes )
473 {
474 const QByteArray normalsBytes = normalsAttributes->buffer()->data();
475 const QVector<float> normalsBuffer = getAttributeData<float>( normalsAttributes, normalsBytes );
476 object->setupNormalCoordinates( normalsBuffer, transform->matrix() );
477 }
478
479 Qt3DQAttribute *texCoordsAttribute = tileGeometry->texCoordsAttribute();
480 if ( mExportTextures && texCoordsAttribute )
481 {
482 const QByteArray texCoordsBytes = texCoordsAttribute->buffer()->data();
483 const QVector<float> texCoordsBuffer = getAttributeData<float>( texCoordsAttribute, texCoordsBytes );
484 object->setupTextureCoordinates( texCoordsBuffer );
485
486 QgsTerrainTextureImage *textureImage = tileEntity->textureImage();
487 const QImage img = textureImage->getImage();
488 object->setTextureImage( img );
489 }
490}
491
492void Qgs3DSceneExporter::parseMeshTile( QgsTerrainTileEntity *tileEntity, const QString &layerName )
493{
494 QString objectNamePrefix = layerName;
495 if ( objectNamePrefix != QString() )
496 objectNamePrefix += QLatin1Char( '_' );
497
498 const QList<Qt3DRender::QGeometryRenderer *> renderers = tileEntity->findChildren<Qt3DRender::QGeometryRenderer *>();
499 for ( Qt3DRender::QGeometryRenderer *renderer : renderers )
500 {
501 Qgs3DExportObject *obj = processGeometryRenderer( renderer, objectNamePrefix );
502 if ( !obj )
503 continue;
504 mObjects << obj;
505 }
506}
507
508QVector<Qgs3DExportObject *> Qgs3DSceneExporter::processInstancedPointGeometry( Qt3DCore::QEntity *entity, const QString &objectNamePrefix )
509{
510 QVector<Qgs3DExportObject *> objects;
511 const QList<Qt3DQGeometry *> geometriesList = entity->findChildren<Qt3DQGeometry *>();
512 for ( Qt3DQGeometry *geometry : geometriesList )
513 {
514 Qt3DQAttribute *positionAttribute = findAttribute( geometry, Qt3DQAttribute::defaultPositionAttributeName(), Qt3DQAttribute::VertexAttribute );
515 Qt3DQAttribute *indexAttribute = findAttribute( geometry, QString(), Qt3DQAttribute::IndexAttribute );
516 if ( !positionAttribute || !indexAttribute )
517 {
518 QgsDebugError( QString( "Cannot export '%1' - geometry has no position or index attribute!" ).arg( objectNamePrefix ) );
519 continue;
520 }
521
522 const QByteArray vertexBytes = positionAttribute->buffer()->data();
523 const QByteArray indexBytes = indexAttribute->buffer()->data();
524 if ( vertexBytes.isNull() || indexBytes.isNull() )
525 {
526 QgsDebugError( QString( "Geometry for '%1' has position or index attribute with empty data!" ).arg( objectNamePrefix ) );
527 continue;
528 }
529
530 const QVector<float> positionData = getAttributeData<float>( positionAttribute, vertexBytes );
531 const QVector<uint> indexData = getIndexData( indexAttribute, indexBytes );
532
533 Qt3DQAttribute *instanceDataAttribute = findAttribute( geometry, QStringLiteral( "pos" ), Qt3DQAttribute::VertexAttribute );
534 if ( !instanceDataAttribute )
535 {
536 QgsDebugError( QString( "Cannot export '%1' - geometry has no instanceData attribute!" ).arg( objectNamePrefix ) );
537 continue;
538 }
539 const QByteArray instancePositionBytes = getData( instanceDataAttribute->buffer() );
540 if ( instancePositionBytes.isNull() )
541 {
542 QgsDebugError( QString( "Geometry for '%1' has instanceData attribute with empty data!" ).arg( objectNamePrefix ) );
543 continue;
544 }
545 QVector<float> instancePosition = getAttributeData<float>( instanceDataAttribute, instancePositionBytes );
546
547 for ( int i = 0; i < instancePosition.size(); i += 3 )
548 {
549 Qgs3DExportObject *object = new Qgs3DExportObject( getObjectName( objectNamePrefix + QStringLiteral( "instance_point" ) ) );
550 objects.push_back( object );
551 QMatrix4x4 instanceTransform;
552 instanceTransform.translate( instancePosition[i], instancePosition[i + 1], instancePosition[i + 2] );
553 object->setupTriangle( positionData, indexData, instanceTransform );
554
555 object->setSmoothEdges( mSmoothEdges );
556
557 Qt3DQAttribute *normalsAttribute = findAttribute( geometry, Qt3DQAttribute::defaultNormalAttributeName(), Qt3DQAttribute::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 Qt3DQGeometry *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] * 3;
629 const uint endIdx = idx < triangleIndex.size() - 1 ? triangleIndex[idx + 1] * 3 : 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 Qt3DQAttribute *positionAttribute = nullptr;
658 Qt3DQAttribute *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, Qt3DQAttribute::defaultPositionAttributeName(), Qt3DQAttribute::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<Qt3DQAttribute *> attributes = geometry->attributes();
683 for ( Qt3DQAttribute *attribute : attributes )
684 {
685 if ( attribute->attributeType() == Qt3DQAttribute::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 < indexDataTmpSize; ++i )
723 {
724 uint idx = indexDataTmp[static_cast<int>( i )];
725 // search for valid triangle index interval
726 while ( intervalIdx < triangleIndexStartingIndiceToKeepSize
727 && idx > triangleIndexStartingIndiceToKeep[intervalIdx].first
728 && idx >= triangleIndexStartingIndiceToKeep[intervalIdx].second )
729 {
730 intervalIdx++;
731 }
732
733 // keep only the one within the triangle index interval
734 if ( intervalIdx < triangleIndexStartingIndiceToKeepSize
735 && idx >= triangleIndexStartingIndiceToKeep[intervalIdx].first
736 && idx < triangleIndexStartingIndiceToKeep[intervalIdx].second )
737 {
738 indexData.push_back( idx );
739 }
740 }
741 }
742
743 // === Create Qgs3DExportObject
744 Qgs3DExportObject *object = new Qgs3DExportObject( getObjectName( objectNamePrefix + QStringLiteral( "mesh_geometry" ) ) );
745 object->setupTriangle( positionData, indexData, transformMatrix );
746
747 Qt3DQAttribute *normalsAttribute = findAttribute( geometry, Qt3DQAttribute::defaultNormalAttributeName(), Qt3DQAttribute::VertexAttribute );
748 if ( mExportNormals && normalsAttribute )
749 {
750 // Reuse vertex bytes
751 const QVector<float> normalsData = getAttributeData<float>( normalsAttribute, vertexBytes );
752 object->setupNormalCoordinates( normalsData, transformMatrix );
753 }
754
755 Qt3DQAttribute *texCoordsAttribute = findAttribute( geometry, Qt3DQAttribute::defaultTextureCoordinateAttributeName(), Qt3DQAttribute::VertexAttribute );
756 if ( mExportTextures && texCoordsAttribute )
757 {
758 // Reuse vertex bytes
759 const QVector<float> texCoordsData = getAttributeData<float>( texCoordsAttribute, vertexBytes );
760 object->setupTextureCoordinates( texCoordsData );
761 }
762
763 return object;
764}
765
766QVector<Qgs3DExportObject *> Qgs3DSceneExporter::processLines( Qt3DCore::QEntity *entity, const QString &objectNamePrefix )
767{
768 QVector<Qgs3DExportObject *> objs;
769 const QList<Qt3DRender::QGeometryRenderer *> renderers = entity->findChildren<Qt3DRender::QGeometryRenderer *>();
770 for ( Qt3DRender::QGeometryRenderer *renderer : renderers )
771 {
772 if ( renderer->primitiveType() != Qt3DRender::QGeometryRenderer::LineStripAdjacency )
773 continue;
774 Qt3DQGeometry *geom = renderer->geometry();
775 Qt3DQAttribute *positionAttribute = findAttribute( geom, Qt3DQAttribute::defaultPositionAttributeName(), Qt3DQAttribute::VertexAttribute );
776 Qt3DQAttribute *indexAttribute = findAttribute( geom, QString(), Qt3DQAttribute::IndexAttribute );
777 if ( !positionAttribute || !indexAttribute )
778 {
779 QgsDebugError( QString( "Cannot export '%1' - geometry has no position or index attribute!" ).arg( objectNamePrefix ) );
780 continue;
781 }
782
783 const QByteArray vertexBytes = getData( positionAttribute->buffer() );
784 const QByteArray indexBytes = getData( indexAttribute->buffer() );
785 if ( vertexBytes.isNull() || indexBytes.isNull() )
786 {
787 QgsDebugError( QString( "Geometry for '%1' has position or index attribute with empty data!" ).arg( objectNamePrefix ) );
788 continue;
789 }
790 const QVector<float> positionData = getAttributeData<float>( positionAttribute, vertexBytes );
791
792 Qgs3DExportObject *exportObject = new Qgs3DExportObject( getObjectName( objectNamePrefix + QStringLiteral( "line" ) ) );
793 exportObject->setupLine( positionData );
794
795 objs.push_back( exportObject );
796 }
797 return objs;
798}
799
800Qgs3DExportObject *Qgs3DSceneExporter::processPoints( Qt3DCore::QEntity *entity, const QString &objectNamePrefix )
801{
802 QVector<float> points;
803 const QList<Qt3DRender::QGeometryRenderer *> renderers = entity->findChildren<Qt3DRender::QGeometryRenderer *>();
804 for ( Qt3DRender::QGeometryRenderer *renderer : renderers )
805 {
806 Qt3DQGeometry *geometry = qobject_cast<QgsBillboardGeometry *>( renderer->geometry() );
807 if ( !geometry )
808 continue;
809 Qt3DQAttribute *positionAttribute = findAttribute( geometry, Qt3DQAttribute::defaultPositionAttributeName(), Qt3DQAttribute::VertexAttribute );
810 if ( !positionAttribute )
811 {
812 QgsDebugError( QString( "Cannot export '%1' - geometry has no position attribute!" ).arg( objectNamePrefix ) );
813 continue;
814 }
815 const QByteArray positionBytes = getData( positionAttribute->buffer() );
816 if ( positionBytes.isNull() )
817 {
818 QgsDebugError( QString( "Geometry for '%1' has position attribute with empty data!" ).arg( objectNamePrefix ) );
819 continue;
820 }
821 const QVector<float> positions = getAttributeData<float>( positionAttribute, positionBytes );
822 points << positions;
823 }
824 Qgs3DExportObject *obj = new Qgs3DExportObject( getObjectName( objectNamePrefix + QStringLiteral( "points" ) ) );
825 obj->setupPoint( points );
826 return obj;
827}
828
829bool Qgs3DSceneExporter::save( const QString &sceneName, const QString &sceneFolderPath, int precision ) const
830{
831 if ( mObjects.isEmpty() )
832 {
833 return false;
834 }
835
836 const QString objFilePath = QDir( sceneFolderPath ).filePath( sceneName + QStringLiteral( ".obj" ) );
837 const QString mtlFilePath = QDir( sceneFolderPath ).filePath( sceneName + QStringLiteral( ".mtl" ) );
838
839 QFile file( objFilePath );
840 if ( !file.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) )
841 {
842 QgsDebugError( QStringLiteral( "Scene can not be exported to '%1'. File access error." ).arg( objFilePath ) );
843 return false;
844 }
845 QFile mtlFile( mtlFilePath );
846 if ( !mtlFile.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) )
847 {
848 QgsDebugError( QStringLiteral( "Scene can not be exported to '%1'. File access error." ).arg( mtlFilePath ) );
849 return false;
850 }
851
852 float maxfloat = std::numeric_limits<float>::max(), minFloat = std::numeric_limits<float>::lowest();
853 float minX = maxfloat, minY = maxfloat, minZ = maxfloat, maxX = minFloat, maxY = minFloat, maxZ = minFloat;
854 for ( Qgs3DExportObject *obj : qAsConst( mObjects ) )
855 {
856 obj->objectBounds( minX, minY, minZ, maxX, maxY, maxZ );
857 }
858
859 float diffX = 1.0f, diffY = 1.0f, diffZ = 1.0f;
860 diffX = maxX - minX;
861 diffY = maxY - minY;
862 diffZ = maxZ - minZ;
863
864 const float centerX = ( minX + maxX ) / 2.0f;
865 const float centerY = ( minY + maxY ) / 2.0f;
866 const float centerZ = ( minZ + maxZ ) / 2.0f;
867
868 const float scale = std::max( diffX, std::max( diffY, diffZ ) );
869
870 QTextStream out( &file );
871 // set material library name
872 const QString mtlLibName = sceneName + ".mtl";
873 out << "mtllib " << mtlLibName << "\n";
874
875 QTextStream mtlOut( &mtlFile );
876 for ( Qgs3DExportObject *obj : qAsConst( mObjects ) )
877 {
878 if ( !obj )
879 continue;
880 // Set object name
881 const QString material = obj->saveMaterial( mtlOut, sceneFolderPath );
882 out << "o " << obj->name() << "\n";
883 if ( material != QString() )
884 out << "usemtl " << material << "\n";
885 obj->saveTo( out, scale / mScale, QVector3D( centerX, centerY, centerZ ), precision );
886 }
887
888 QgsDebugMsgLevel( QStringLiteral( "Scene exported to '%1'" ).arg( objFilePath ), 2 );
889 return true;
890}
891
892QString Qgs3DSceneExporter::getObjectName( const QString &name )
893{
894 QString ret = name;
895 if ( mUsedObjectNamesCounter.contains( name ) )
896 {
897 ret = QStringLiteral( "%1%2" ).arg( name ).arg( mUsedObjectNamesCounter[name] );
898 mUsedObjectNamesCounter[name]++;
899 }
900 else
901 mUsedObjectNamesCounter[name] = 2;
902 return ret;
903}
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:84
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:30
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.
Qt3DCore::QAttribute Qt3DQAttribute
Qt3DCore::QBuffer Qt3DQBuffer
QVector< T > getAttributeData(Qt3DQAttribute *attribute, const QByteArray &data)
Qt3DQAttribute * findAttribute(Qt3DQGeometry *geometry, const QString &name, Qt3DQAttribute::AttributeType type)
QVector< uint > getIndexData(Qt3DQAttribute *indexAttribute, const QByteArray &data)
QByteArray getData(Qt3DQBuffer *buffer)
QVector< uint > _getIndexDataImplementation(const QByteArray &data)
Component * findTypedComponent(Qt3DCore::QEntity *entity)
Qt3DCore::QGeometry Qt3DQGeometry
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:61
#define QgsDebugError(str)
Definition qgslogger.h:57