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