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