QGIS API Documentation 4.1.0-Master (01362494303)
Loading...
Searching...
No Matches
qgs3dsceneexporter.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgs3dsceneexporter.cpp
3 --------------------------------------
4 Date : June 2020
5 Copyright : (C) 2020 by Belgacem Nedjima
6 Email : gb underscore nedjima at esi dot dz
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgs3dsceneexporter.h"
17
18#include "qgs3dexportobject.h"
19#include "qgs3dmapsettings.h"
20#include "qgs3dutils.h"
23#include "qgschunknode.h"
28#include "qgsgeotransform.h"
29#include "qgsimagetexture.h"
30#include "qgsmeshterraingenerator.h"
31#include "qgsmeshterraintileloader_p.h"
32#include "qgsterrainentity.h"
33#include "qgsterraingenerator.h"
38#include "qgsvectorlayer.h"
40
41#include <QByteArray>
42#include <QFile>
43#include <QString>
44#include <QTextStream>
45#include <QVector>
46#include <Qt3DCore/QAttribute>
47#include <Qt3DCore/QBuffer>
48#include <Qt3DCore/QComponent>
49#include <Qt3DCore/QEntity>
50#include <Qt3DCore/QGeometry>
51#include <Qt3DCore/QNode>
52#include <Qt3DCore/QTransform>
53#include <Qt3DExtras/QConeGeometry>
54#include <Qt3DExtras/QCuboidGeometry>
55#include <Qt3DExtras/QCylinderGeometry>
56#include <Qt3DExtras/QDiffuseSpecularMaterial>
57#include <Qt3DExtras/QExtrudedTextMesh>
58#include <Qt3DExtras/QPhongMaterial>
59#include <Qt3DExtras/QPlaneGeometry>
60#include <Qt3DExtras/QSphereGeometry>
61#include <Qt3DExtras/QTextureMaterial>
62#include <Qt3DExtras/QTorusGeometry>
63#include <Qt3DRender/QAbstractTexture>
64#include <Qt3DRender/QAbstractTextureImage>
65#include <Qt3DRender/QGeometryRenderer>
66#include <Qt3DRender/QMesh>
67#include <Qt3DRender/QParameter>
68#include <Qt3DRender/QSceneLoader>
69#include <Qt3DRender/QTexture>
70#include <Qt3DRender/QTextureImage>
71
72#include "moc_qgs3dsceneexporter.cpp"
73
74using namespace Qt::StringLiterals;
75
76template<typename T> QVector<T> getAttributeData( Qt3DCore::QAttribute *attribute, const QByteArray &data )
77{
78 const uint bytesOffset = attribute->byteOffset();
79 const uint bytesStride = attribute->byteStride();
80 const uint vertexSize = attribute->vertexSize();
81 const uint dataSize = static_cast<uint>( data.size() );
82 QVector<T> result;
83
84 if ( bytesStride == 0 )
85 {
86 QgsDebugError( "bytesStride==0, the attribute probably was not set properly" );
87 return result;
88 }
89
90 const char *pData = data.constData();
91 for ( unsigned int i = bytesOffset; i < dataSize; i += bytesStride )
92 {
93 for ( unsigned int j = 0; j < vertexSize * sizeof( T ); j += sizeof( T ) )
94 {
95 T v;
96 memcpy( &v, pData + i + j, sizeof( T ) );
97 result.push_back( v );
98 }
99 }
100 return result;
101}
102
103template<typename T> QVector<uint> _getIndexDataImplementation( const QByteArray &data )
104{
105 QVector<uint> result;
106 const char *pData = data.constData();
107 for ( int i = 0; i < data.size(); i += sizeof( T ) )
108 {
109 T v;
110 memcpy( &v, pData + i, sizeof( T ) );
111 result.push_back( ( uint ) v );
112 }
113 return result;
114}
115
116QVector<uint> getIndexData( Qt3DCore::QAttribute *indexAttribute, const QByteArray &data )
117{
118 switch ( indexAttribute->vertexBaseType() )
119 {
120 case Qt3DCore::QAttribute::VertexBaseType::Int:
122 case Qt3DCore::QAttribute::VertexBaseType::UnsignedInt:
124 case Qt3DCore::QAttribute::VertexBaseType::Short:
126 case Qt3DCore::QAttribute::VertexBaseType::UnsignedShort:
128 case Qt3DCore::QAttribute::VertexBaseType::Byte:
130 case Qt3DCore::QAttribute::VertexBaseType::UnsignedByte:
132 default:
133 QgsDebugError( "Probably trying to get index data using an attribute that has vertex data" );
134 break;
135 }
136 return QVector<uint>();
137}
138
139QByteArray getData( Qt3DCore::QBuffer *buffer )
140{
141 QByteArray bytes = buffer->data();
142 if ( bytes.isNull() )
143 {
144 QgsDebugError( "QBuffer is null" );
145 }
146 return bytes;
147}
148
149Qt3DCore::QAttribute *findAttribute( Qt3DCore::QGeometry *geometry, const QString &name, Qt3DCore::QAttribute::AttributeType type )
150{
151 QVector<Qt3DCore::QAttribute *> attributes = geometry->attributes();
152 for ( Qt3DCore::QAttribute *attribute : attributes )
153 {
154 if ( attribute->attributeType() != type )
155 continue;
156 if ( name.isEmpty() || attribute->name() == name )
157 return attribute;
158 }
159 return nullptr;
160}
161
162template<typename Component> Component *findTypedComponent( Qt3DCore::QEntity *entity )
163{
164 if ( !entity )
165 return nullptr;
166 QVector<Qt3DCore::QComponent *> components = entity->components();
167 for ( Qt3DCore::QComponent *component : components )
168 {
169 Component *typedComponent = qobject_cast<Component *>( component );
170 if ( typedComponent )
171 return typedComponent;
172 }
173 return nullptr;
174}
175
176bool Qgs3DSceneExporter::parseVectorLayerEntity( Qt3DCore::QEntity *entity, QgsVectorLayer *layer )
177{
178 QgsAbstract3DRenderer *abstractRenderer = layer->renderer3D();
179 const QString rendererType = abstractRenderer->type();
180
181 if ( rendererType == "rulebased" )
182 {
183 int prevSize = mObjects.size();
184 // Potential bug: meshes loaded using Qt3DRender::QSceneLoader will probably have wrong scale and translation
185 const QList<Qt3DRender::QGeometryRenderer *> renderers = entity->findChildren<Qt3DRender::QGeometryRenderer *>();
186 for ( Qt3DRender::QGeometryRenderer *renderer : renderers )
187 {
188 Qt3DCore::QEntity *parentEntity = qobject_cast<Qt3DCore::QEntity *>( renderer->parent() );
189 if ( !parentEntity )
190 continue;
191 Qgs3DExportObject *object = processGeometryRenderer( renderer, layer->name() + u"_"_s );
192 if ( !object )
193 continue;
194 if ( mExportTextures )
195 processEntityMaterial( parentEntity, object );
196 mObjects.push_back( object );
197 }
198 return mObjects.size() > prevSize;
199 }
200
201 else if ( rendererType == "vector" )
202 {
203 QgsVectorLayer3DRenderer *vectorLayerRenderer = dynamic_cast<QgsVectorLayer3DRenderer *>( abstractRenderer );
204 if ( vectorLayerRenderer )
205 {
206 const QgsAbstract3DSymbol *symbol = vectorLayerRenderer->symbol();
207 return symbol->exportGeometries( this, entity, layer->name() + u"_"_s );
208 }
209 else
210 return false;
211 }
212
213 else
214 {
215 // TODO: handle pointcloud/mesh/etc. layers
216 QgsDebugMsgLevel( u"Type '%1' of layer '%2' is not exportable."_s.arg( layer->name(), rendererType ), 2 );
217 return false;
218 }
219
220 return false;
221}
222
223void Qgs3DSceneExporter::processEntityMaterial( Qt3DCore::QEntity *entity, Qgs3DExportObject *object ) const
224{
225 Qt3DExtras::QPhongMaterial *phongMaterial = findTypedComponent<Qt3DExtras::QPhongMaterial>( entity );
226 if ( phongMaterial )
227 {
229 object->setupMaterial( &material );
230 }
231
232 Qt3DExtras::QDiffuseSpecularMaterial *diffuseMapMaterial = findTypedComponent<Qt3DExtras::QDiffuseSpecularMaterial>( entity );
233 if ( diffuseMapMaterial )
234 {
235 const Qt3DRender::QTexture2D *diffuseTexture = diffuseMapMaterial->diffuse().value<Qt3DRender::QTexture2D *>();
236 if ( diffuseTexture )
237 {
238 const QVector<Qt3DRender::QAbstractTextureImage *> textureImages = diffuseTexture->textureImages();
239 for ( const Qt3DRender::QAbstractTextureImage *tex : textureImages )
240 {
241 const QgsImageTexture *imageTexture = dynamic_cast<const QgsImageTexture *>( tex );
242 if ( imageTexture )
243 {
244 const QImage image = imageTexture->getImage();
245 object->setTextureImage( image );
246 break;
247 }
248 }
249 }
250 }
251}
252
253void Qgs3DSceneExporter::parseTerrain( QgsTerrainEntity *terrain, const QString &layerName )
254{
255 Qgs3DMapSettings *settings = terrain->mapSettings();
256 if ( !settings->terrainRenderingEnabled() || !mTerrainExportEnabled )
257 return;
258
259 QgsChunkNode *node = terrain->rootNode();
260
261 QgsTerrainGenerator *generator = settings->terrainGenerator();
262 if ( !generator )
263 return;
264 QgsTerrainTileEntity *terrainTile = nullptr;
265 QgsTerrainTextureGenerator *textureGenerator = terrain->textureGenerator();
266 textureGenerator->waitForFinished();
267 const QSize oldResolution = textureGenerator->textureSize();
268 textureGenerator->setTextureSize( QSize( mTerrainTextureResolution, mTerrainTextureResolution ) );
269 switch ( generator->type() )
270 {
272 terrainTile = getDemTerrainEntity( terrain, node, settings->origin() );
273 parseDemTile( terrainTile, layerName + u"_"_s );
274 break;
276 terrainTile = getFlatTerrainEntity( terrain, node, settings->origin() );
277 parseFlatTile( terrainTile, layerName + u"_"_s );
278 break;
280 terrainTile = getMeshTerrainEntity( terrain, node, settings->origin() );
281 parseMeshTile( terrainTile, layerName + u"_"_s );
282 break;
283 // TODO: implement other terrain types
286 break;
287 }
288 textureGenerator->setTextureSize( oldResolution );
289}
290
291QgsTerrainTileEntity *Qgs3DSceneExporter::getFlatTerrainEntity( QgsTerrainEntity *terrain, QgsChunkNode *node, const QgsVector3D &mapOrigin )
292{
293 QgsFlatTerrainGenerator *generator = qgis::down_cast<QgsFlatTerrainGenerator *>( terrain->mapSettings()->terrainGenerator() );
294 FlatTerrainChunkLoader *flatTerrainLoader = qobject_cast<FlatTerrainChunkLoader *>( generator->createChunkLoader( node ) );
295 flatTerrainLoader->start();
296 if ( mExportTextures )
297 terrain->textureGenerator()->waitForFinished();
298 // the entity we created will be deallocated once the scene exporter is deallocated
299 Qt3DCore::QEntity *entity = flatTerrainLoader->createEntity( this );
300 QgsTerrainTileEntity *tileEntity = qobject_cast<QgsTerrainTileEntity *>( entity );
301
302 const QList<QgsGeoTransform *> transforms = entity->findChildren<QgsGeoTransform *>();
303 for ( QgsGeoTransform *transform : transforms )
304 {
305 transform->setOrigin( mapOrigin );
306 }
307
308 return tileEntity;
309}
310
311QgsTerrainTileEntity *Qgs3DSceneExporter::getDemTerrainEntity( QgsTerrainEntity *terrain, QgsChunkNode *node, const QgsVector3D &mapOrigin )
312{
313 // Just create a new tile (we don't need to export exact level of details as in the scene)
314 // create the entity synchronously and then it will be deleted once our scene exporter instance is deallocated
315 QgsDemTerrainGenerator *generator = qgis::down_cast<QgsDemTerrainGenerator *>( terrain->mapSettings()->terrainGenerator()->clone() );
316 generator->setResolution( mTerrainResolution );
317 QgsDemTerrainTileLoader *loader = qobject_cast<QgsDemTerrainTileLoader *>( generator->createChunkLoader( node ) );
318 loader->start();
319 generator->heightMapGenerator()->waitForFinished();
320 if ( mExportTextures )
321 terrain->textureGenerator()->waitForFinished();
322 QgsTerrainTileEntity *tileEntity = qobject_cast<QgsTerrainTileEntity *>( loader->createEntity( this ) );
323
324 const QList<QgsGeoTransform *> transforms = tileEntity->findChildren<QgsGeoTransform *>();
325 for ( QgsGeoTransform *transform : transforms )
326 {
327 transform->setOrigin( mapOrigin );
328 }
329
330 delete generator;
331 return tileEntity;
332}
333
334QgsTerrainTileEntity *Qgs3DSceneExporter::getMeshTerrainEntity( QgsTerrainEntity *terrain, QgsChunkNode *node, const QgsVector3D &mapOrigin )
335{
336 QgsMeshTerrainGenerator *generator = qgis::down_cast<QgsMeshTerrainGenerator *>( terrain->mapSettings()->terrainGenerator() );
337 QgsMeshTerrainTileLoader *loader = qobject_cast<QgsMeshTerrainTileLoader *>( generator->createChunkLoader( node ) );
338 loader->start();
339 // TODO: export textures
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 return tileEntity;
349}
350
351void Qgs3DSceneExporter::parseFlatTile( QgsTerrainTileEntity *tileEntity, const QString &layerName )
352{
353 Qt3DRender::QGeometryRenderer *mesh = findTypedComponent<Qt3DRender::QGeometryRenderer>( tileEntity );
354 Qt3DCore::QTransform *transform = findTypedComponent<Qt3DCore::QTransform>( tileEntity );
355
356 Qt3DCore::QGeometry *geometry = mesh->geometry();
357 Qt3DExtras::QPlaneGeometry *tileGeometry = qobject_cast<Qt3DExtras::QPlaneGeometry *>( geometry );
358 if ( !tileGeometry )
359 {
360 QgsDebugError( "Qt3DExtras::QPlaneGeometry* is expected but something else was given" );
361 return;
362 }
363
364 // Generate vertice data
365 Qt3DCore::QAttribute *positionAttribute = tileGeometry->positionAttribute();
366 if ( !positionAttribute )
367 {
368 QgsDebugError( QString( "Cannot export '%1' - geometry has no position attribute!" ).arg( layerName ) );
369 return;
370 }
371 const QByteArray verticesBytes = getData( positionAttribute->buffer() );
372 if ( verticesBytes.isNull() )
373 {
374 QgsDebugError( QString( "Geometry for '%1' has position attribute with empty data!" ).arg( layerName ) );
375 return;
376 }
377 const QVector<float> positionBuffer = getAttributeData<float>( positionAttribute, verticesBytes );
378
379 // Generate index data
380 Qt3DCore::QAttribute *indexAttribute = tileGeometry->indexAttribute();
381 if ( !indexAttribute )
382 {
383 QgsDebugError( QString( "Cannot export '%1' - geometry has no index attribute!" ).arg( layerName ) );
384 return;
385 }
386 const QByteArray indexBytes = getData( indexAttribute->buffer() );
387 if ( indexBytes.isNull() )
388 {
389 QgsDebugError( QString( "Geometry for '%1' has index attribute with empty data!" ).arg( layerName ) );
390 return;
391 }
392 const QVector<uint> indexesBuffer = getIndexData( indexAttribute, indexBytes );
393
394 QString objectNamePrefix = layerName;
395 if ( objectNamePrefix != QString() )
396 objectNamePrefix += QString();
397
398 Qgs3DExportObject *object = new Qgs3DExportObject( getObjectName( objectNamePrefix + u"Flat_tile"_s ) );
399 mObjects.push_back( object );
400
401 object->setSmoothEdges( mSmoothEdges );
402 object->setupTriangle( positionBuffer, indexesBuffer, transform->matrix() );
403
404 if ( mExportNormals )
405 {
406 // Everts
407 QVector<float> normalsBuffer;
408 for ( int i = 0; i < positionBuffer.size(); i += 3 )
409 normalsBuffer << 0.0f << 1.0f << 0.0f;
410 object->setupNormalCoordinates( normalsBuffer, transform->matrix() );
411 }
412
413 Qt3DCore::QAttribute *texCoordsAttribute = tileGeometry->texCoordAttribute();
414 if ( mExportTextures && texCoordsAttribute )
415 {
416 // Reuse vertex buffer data for texture coordinates
417 const QVector<float> texCoords = getAttributeData<float>( texCoordsAttribute, verticesBytes );
418 object->setupTextureCoordinates( texCoords );
419
420 QgsTerrainTextureImage *textureImage = tileEntity->textureImage();
421 const QImage img = textureImage->getImage();
422 object->setTextureImage( img );
423 }
424}
425
426void Qgs3DSceneExporter::parseDemTile( QgsTerrainTileEntity *tileEntity, const QString &layerName )
427{
428 Qt3DRender::QGeometryRenderer *mesh = findTypedComponent<Qt3DRender::QGeometryRenderer>( tileEntity );
429 Qt3DCore::QTransform *transform = findTypedComponent<Qt3DCore::QTransform>( tileEntity );
430
431 Qt3DCore::QGeometry *geometry = mesh->geometry();
432 DemTerrainTileGeometry *tileGeometry = qobject_cast<DemTerrainTileGeometry *>( geometry );
433 if ( !tileGeometry )
434 {
435 QgsDebugError( "DemTerrainTileGeometry* is expected but something else was given" );
436 return;
437 }
438
439 Qt3DCore::QAttribute *positionAttribute = tileGeometry->positionAttribute();
440 const QByteArray positionBytes = positionAttribute->buffer()->data();
441 const QVector<float> positionBuffer = getAttributeData<float>( positionAttribute, positionBytes );
442
443 Qt3DCore::QAttribute *indexAttribute = tileGeometry->indexAttribute();
444 const QByteArray indexBytes = indexAttribute->buffer()->data();
445 const QVector<unsigned int> indexBuffer = getIndexData( indexAttribute, indexBytes );
446
447 Qgs3DExportObject *object = new Qgs3DExportObject( getObjectName( layerName + u"DEM_tile"_s ) );
448 mObjects.push_back( object );
449
450 object->setSmoothEdges( mSmoothEdges );
451 object->setupTriangle( positionBuffer, indexBuffer, transform->matrix() );
452
453 Qt3DCore::QAttribute *normalsAttributes = tileGeometry->normalAttribute();
454 if ( mExportNormals && normalsAttributes )
455 {
456 const QByteArray normalsBytes = normalsAttributes->buffer()->data();
457 const QVector<float> normalsBuffer = getAttributeData<float>( normalsAttributes, normalsBytes );
458 object->setupNormalCoordinates( normalsBuffer, transform->matrix() );
459 }
460
461 Qt3DCore::QAttribute *texCoordsAttribute = tileGeometry->texCoordsAttribute();
462 if ( mExportTextures && texCoordsAttribute )
463 {
464 const QByteArray texCoordsBytes = texCoordsAttribute->buffer()->data();
465 const QVector<float> texCoordsBuffer = getAttributeData<float>( texCoordsAttribute, texCoordsBytes );
466 object->setupTextureCoordinates( texCoordsBuffer );
467
468 QgsTerrainTextureImage *textureImage = tileEntity->textureImage();
469 const QImage img = textureImage->getImage();
470 object->setTextureImage( img );
471 }
472}
473
474void Qgs3DSceneExporter::parseMeshTile( QgsTerrainTileEntity *tileEntity, const QString &layerName )
475{
476 QString objectNamePrefix = layerName;
477 if ( objectNamePrefix != QString() )
478 objectNamePrefix += '_'_L1;
479
480 const QList<Qt3DRender::QGeometryRenderer *> renderers = tileEntity->findChildren<Qt3DRender::QGeometryRenderer *>();
481 for ( Qt3DRender::QGeometryRenderer *renderer : renderers )
482 {
483 Qgs3DExportObject *obj = processGeometryRenderer( renderer, objectNamePrefix );
484 if ( !obj )
485 continue;
486 mObjects << obj;
487 }
488}
489
490QVector<Qgs3DExportObject *> Qgs3DSceneExporter::processInstancedPointGeometry( Qt3DCore::QEntity *entity, const QString &objectNamePrefix )
491{
492 // built-in Qt3D geometries (e.g. cylinder, plane, ...) assume Y axis going "up",
493 // They are rotated to have their Z axis goes "up", like the rest of the scene
494 // Retrieve the rotation matrix
495 const QMatrix4x4 instanceMaterialTransform( 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0 );
496
497 QVector3D symbolScale;
498 QVector4D symbolRotation;
499 const QList<Qt3DRender::QMaterial *> materials = entity->findChildren<Qt3DRender::QMaterial *>();
500 for ( Qt3DRender::QMaterial *material : materials )
501 {
502 for ( const Qt3DRender::QParameter *parameter : material->parameters() )
503 {
504 if ( parameter->name() == "symbolScale"_L1 )
505 {
506 symbolScale = parameter->value().value<QVector3D>();
507 }
508 else if ( parameter->name() == "symbolRotation"_L1 )
509 {
510 symbolRotation = parameter->value().value<QVector4D>();
511 }
512 }
513 }
514
515 QQuaternion rotationQuat( symbolRotation.w(), symbolRotation.x(), symbolRotation.y(), symbolRotation.z() );
516
517 QVector<Qgs3DExportObject *> objects;
518 const QList<Qt3DCore::QGeometry *> geometriesList = entity->findChildren<Qt3DCore::QGeometry *>();
519 for ( Qt3DCore::QGeometry *geometry : geometriesList )
520 {
521 Qt3DCore::QAttribute *positionAttribute = findAttribute( geometry, Qt3DCore::QAttribute::defaultPositionAttributeName(), Qt3DCore::QAttribute::VertexAttribute );
522 Qt3DCore::QAttribute *indexAttribute = findAttribute( geometry, QString(), Qt3DCore::QAttribute::IndexAttribute );
523 if ( !positionAttribute || !indexAttribute )
524 {
525 QgsDebugError( QString( "Cannot export '%1' - geometry has no position or index attribute!" ).arg( objectNamePrefix ) );
526 continue;
527 }
528
529 Qt3DCore::QAttribute *instanceScaleAttribute = findAttribute( geometry, u"instanceScale"_s, Qt3DCore::QAttribute::VertexAttribute );
530 Qt3DCore::QAttribute *instanceRotationAttribute = findAttribute( geometry, u"instanceRotation"_s, Qt3DCore::QAttribute::VertexAttribute );
531
532 const QByteArray vertexBytes = positionAttribute->buffer()->data();
533 const QByteArray indexBytes = indexAttribute->buffer()->data();
534 if ( vertexBytes.isNull() || indexBytes.isNull() )
535 {
536 QgsDebugError( QString( "Geometry for '%1' has position or index attribute with empty data!" ).arg( objectNamePrefix ) );
537 continue;
538 }
539
540 const QVector<float> positionData = getAttributeData<float>( positionAttribute, vertexBytes );
541 const QVector<uint> indexData = getIndexData( indexAttribute, indexBytes );
542
543 const QVector<QVector3D> instanceScaleData = instanceScaleAttribute ? getAttributeData<QVector3D>( instanceScaleAttribute, instanceScaleAttribute->buffer()->data() ) : QVector<QVector3D>();
544 const QVector<QVector4D> instanceRotationData = instanceRotationAttribute ? getAttributeData<QVector4D>( instanceRotationAttribute, instanceRotationAttribute->buffer()->data() )
545 : QVector<QVector4D>();
546
547 Qt3DCore::QAttribute *instanceDataAttribute = findAttribute( geometry, u"instanceTranslation"_s, Qt3DCore::QAttribute::VertexAttribute );
548 if ( !instanceDataAttribute )
549 {
550 QgsDebugError( QString( "Cannot export '%1' - geometry has no instanceTranslation attribute!" ).arg( objectNamePrefix ) );
551 continue;
552 }
553 const QByteArray instancePositionBytes = getData( instanceDataAttribute->buffer() );
554 if ( instancePositionBytes.isNull() )
555 {
556 QgsDebugError( QString( "Geometry for '%1' has instanceTranslation attribute with empty data!" ).arg( objectNamePrefix ) );
557 continue;
558 }
559 QVector<float> instancePosition = getAttributeData<float>( instanceDataAttribute, instancePositionBytes );
560
561 int instanceIndex = 0;
562 for ( int i = 0; i < instancePosition.size(); i += 3, instanceIndex++ )
563 {
564 Qgs3DExportObject *object = new Qgs3DExportObject( getObjectName( objectNamePrefix + u"instance_point"_s ) );
565 objects.push_back( object );
566 QMatrix4x4 instanceTransform;
567 instanceTransform.translate( instancePosition[i], instancePosition[i + 1], instancePosition[i + 2] );
568
569 QQuaternion instanceRotation = rotationQuat;
570 if ( instanceRotationAttribute )
571 {
572 const QVector4D instanceRotationVec = instanceRotationData[instanceIndex];
573 instanceRotation = QQuaternion( instanceRotationVec.w(), instanceRotationVec.x(), instanceRotationVec.y(), instanceRotationVec.z() );
574 }
575 instanceTransform.rotate( instanceRotation );
576 instanceTransform.scale( instanceScaleAttribute ? instanceScaleData[instanceIndex] : symbolScale );
577 instanceTransform *= instanceMaterialTransform;
578 object->setupTriangle( positionData, indexData, instanceTransform );
579
580 object->setSmoothEdges( mSmoothEdges );
581
582 Qt3DCore::QAttribute *normalsAttribute = findAttribute( geometry, Qt3DCore::QAttribute::defaultNormalAttributeName(), Qt3DCore::QAttribute::VertexAttribute );
583 if ( mExportNormals && normalsAttribute )
584 {
585 // Reuse vertex bytes
586 const QVector<float> normalsData = getAttributeData<float>( normalsAttribute, vertexBytes );
587 object->setupNormalCoordinates( normalsData, instanceTransform );
588 }
589 }
590 }
591
592 return objects;
593}
594
595QVector<Qgs3DExportObject *> Qgs3DSceneExporter::processSceneLoaderGeometries( Qt3DRender::QSceneLoader *sceneLoader, const QString &objectNamePrefix )
596{
597 QVector<Qgs3DExportObject *> objects;
598 Qt3DCore::QEntity *sceneLoaderParent = qobject_cast<Qt3DCore::QEntity *>( sceneLoader->parent() );
599 Qt3DCore::QTransform *entityTransform = findTypedComponent<Qt3DCore::QTransform>( sceneLoaderParent );
600 QMatrix4x4 sceneTransform;
601 if ( entityTransform )
602 {
603 sceneTransform = entityTransform->matrix();
604 }
605 QStringList entityNames = sceneLoader->entityNames();
606 for ( const QString &entityName : entityNames )
607 {
608 Qt3DRender::QGeometryRenderer *mesh = qobject_cast<Qt3DRender::QGeometryRenderer *>( sceneLoader->component( entityName, Qt3DRender::QSceneLoader::GeometryRendererComponent ) );
609 Qgs3DExportObject *object = processGeometryRenderer( mesh, objectNamePrefix, sceneTransform );
610 if ( !object )
611 continue;
612 objects.push_back( object );
613 }
614 return objects;
615}
616
617Qgs3DExportObject *Qgs3DSceneExporter::processGeometryRenderer( Qt3DRender::QGeometryRenderer *geomRenderer, const QString &objectNamePrefix, const QMatrix4x4 &sceneTransform )
618{
619 // We only export triangles for now
620 if ( geomRenderer->primitiveType() != Qt3DRender::QGeometryRenderer::Triangles )
621 return nullptr;
622
623 Qt3DCore::QGeometry *geometry = geomRenderer->geometry();
624 if ( !geometry )
625 return nullptr;
626
627 // === Compute triangleIndexStartingIndiceToKeep according to duplicated features
628 //
629 // In the case of polygons, we have multiple feature geometries within the same geometry object (QgsTessellatedPolygonGeometry).
630 // The QgsTessellatedPolygonGeometry class holds the list of all feature ids included.
631 // To avoid exporting the same geometry (it can be included in multiple QgsTessellatedPolygonGeometry) more than once,
632 // we keep a list of already exported fid and compare with the fid of the current QgsTessellatedPolygonGeometry.
633 // As we cannot retrieve the specific geometry part for a featureId from the QgsTessellatedPolygonGeometry, we only reject
634 // the geometry if all the featureid are already present.
635 QVector<std::pair<uint, uint>> triangleIndexStartingIndiceToKeep;
636 QgsTessellatedPolygonGeometry *tessGeom = dynamic_cast<QgsTessellatedPolygonGeometry *>( geometry );
637 if ( tessGeom )
638 {
639 QVector<QgsFeatureId> featureIds = tessGeom->featureIds();
640
641 QSet<QgsFeatureId> tempFeatToAdd;
642 QVector<uint> triangleIndex = tessGeom->triangleIndexStartingIndices();
643
644 for ( int idx = 0; idx < featureIds.size(); idx++ )
645 {
646 const QgsFeatureId feat = featureIds[idx];
647 if ( !mExportedFeatureIds.contains( feat ) )
648 {
649 // add the feature (as it was unknown) to temp set and not to the mExportedFeatureIds (as featureIds can have the same id multiple times)
650 tempFeatToAdd += feat;
651
652 // keep the feature triangle indexes
653 const uint startIdx = triangleIndex[idx];
654 const uint endIdx = idx < triangleIndex.size() - 1 ? triangleIndex[idx + 1] : std::numeric_limits<uint>::max();
655
656 if ( startIdx < endIdx ) // keep only valid intervals
657 triangleIndexStartingIndiceToKeep.append( std::pair<uint, uint>( startIdx, endIdx ) );
658 }
659 }
660 mExportedFeatureIds += tempFeatToAdd;
661
662 if ( triangleIndexStartingIndiceToKeep.isEmpty() ) // all featureid are already exported
663 {
664 return nullptr;
665 }
666 }
667
668 // === Compute inherited scale and translation from child entity to parent
669 QMatrix4x4 transformMatrix = sceneTransform;
670 QObject *parent = geomRenderer->parent();
671 while ( parent )
672 {
673 Qt3DCore::QEntity *entity = qobject_cast<Qt3DCore::QEntity *>( parent );
674 Qt3DCore::QTransform *transform = findTypedComponent<Qt3DCore::QTransform>( entity );
675 if ( transform )
676 {
677 transformMatrix = transform->matrix() * transformMatrix;
678 }
679 parent = parent->parent();
680 }
681
682 Qt3DCore::QAttribute *positionAttribute = nullptr;
683 Qt3DCore::QAttribute *indexAttribute = nullptr;
684 QByteArray indexBytes, vertexBytes;
685 QVector<uint> indexDataTmp;
686 QVector<uint> indexData;
687 QVector<float> positionData;
688
689 // === Extract position data
690 positionAttribute = findAttribute( geometry, Qt3DCore::QAttribute::defaultPositionAttributeName(), Qt3DCore::QAttribute::VertexAttribute );
691 if ( !positionAttribute )
692 {
693 QgsDebugError( QString( "Cannot export '%1' - geometry has no position attribute!" ).arg( objectNamePrefix ) );
694 return nullptr;
695 }
696
697 vertexBytes = getData( positionAttribute->buffer() );
698 if ( vertexBytes.isNull() )
699 {
700 QgsDebugError( QString( "Will not export '%1' as geometry has empty position data!" ).arg( objectNamePrefix ) );
701 return nullptr;
702 }
703
704 positionData = getAttributeData<float>( positionAttribute, vertexBytes );
705
706 // === Search for face index data
707 QVector<Qt3DCore::QAttribute *> attributes = geometry->attributes();
708 for ( Qt3DCore::QAttribute *attribute : attributes )
709 {
710 if ( attribute->attributeType() == Qt3DCore::QAttribute::IndexAttribute )
711 {
712 indexAttribute = attribute;
713 indexBytes = getData( indexAttribute->buffer() );
714 if ( indexBytes.isNull() )
715 {
716 QgsDebugError( QString( "Geometry for '%1' has index attribute with empty data!" ).arg( objectNamePrefix ) );
717 }
718 else
719 {
720 indexDataTmp = getIndexData( indexAttribute, indexBytes );
721 break;
722 }
723 }
724 }
725
726 // for tessellated polygons that don't have index attributes, build them from positionData
727 if ( !indexAttribute )
728 {
729 for ( uint i = 0; i < static_cast<uint>( positionData.size() / 3 ); ++i )
730 {
731 indexDataTmp.push_back( i );
732 }
733 }
734
735 // === Filter face index data if needed
736 if ( triangleIndexStartingIndiceToKeep.isEmpty() )
737 {
738 // when geometry is NOT a QgsTessellatedPolygonGeometry, no filter, take them all
739 indexData.append( indexDataTmp );
740 }
741 else
742 {
743 // when geometry is a QgsTessellatedPolygonGeometry, filter according to triangleIndexStartingIndiceToKeep
744 int intervalIdx = 0;
745 const int triangleIndexStartingIndiceToKeepSize = triangleIndexStartingIndiceToKeep.size();
746 const uint indexDataTmpSize = static_cast<uint>( indexDataTmp.size() );
747 for ( uint i = 0; i + 2 < indexDataTmpSize; i += 3 )
748 {
749 const uint triangleIdx = i / 3;
750
751 // search for valid triangle index interval
752 while ( intervalIdx < triangleIndexStartingIndiceToKeepSize && triangleIdx >= triangleIndexStartingIndiceToKeep[intervalIdx].second )
753 {
754 intervalIdx++;
755 }
756
757 // keep only triangles within the triangle index interval
758 if ( intervalIdx < triangleIndexStartingIndiceToKeepSize && triangleIdx >= triangleIndexStartingIndiceToKeep[intervalIdx].first && triangleIdx < triangleIndexStartingIndiceToKeep[intervalIdx].second )
759 {
760 indexData.push_back( indexDataTmp[static_cast<int>( i )] );
761 indexData.push_back( indexDataTmp[static_cast<int>( i + 1 )] );
762 indexData.push_back( indexDataTmp[static_cast<int>( i + 2 )] );
763 }
764 }
765 }
766
767 // === Create Qgs3DExportObject
768 Qgs3DExportObject *object = new Qgs3DExportObject( getObjectName( objectNamePrefix + u"mesh_geometry"_s ) );
769 object->setupTriangle( positionData, indexData, transformMatrix );
770
771 Qt3DCore::QAttribute *normalsAttribute = findAttribute( geometry, Qt3DCore::QAttribute::defaultNormalAttributeName(), Qt3DCore::QAttribute::VertexAttribute );
772 if ( mExportNormals && normalsAttribute )
773 {
774 // Reuse vertex bytes
775 const QVector<float> normalsData = getAttributeData<float>( normalsAttribute, vertexBytes );
776 object->setupNormalCoordinates( normalsData, transformMatrix );
777 }
778
779 Qt3DCore::QAttribute *texCoordsAttribute = findAttribute( geometry, Qt3DCore::QAttribute::defaultTextureCoordinateAttributeName(), Qt3DCore::QAttribute::VertexAttribute );
780 if ( mExportTextures && texCoordsAttribute )
781 {
782 // Reuse vertex bytes
783 const QVector<float> texCoordsData = getAttributeData<float>( texCoordsAttribute, vertexBytes );
784 object->setupTextureCoordinates( texCoordsData );
785 }
786
787 return object;
788}
789
790QVector<Qgs3DExportObject *> Qgs3DSceneExporter::processLines( Qt3DCore::QEntity *entity, const QString &objectNamePrefix )
791{
792 QVector<Qgs3DExportObject *> objs;
793 const QList<Qt3DRender::QGeometryRenderer *> renderers = entity->findChildren<Qt3DRender::QGeometryRenderer *>();
794 for ( Qt3DRender::QGeometryRenderer *renderer : renderers )
795 {
796 if ( renderer->primitiveType() != Qt3DRender::QGeometryRenderer::LineStripAdjacency )
797 continue;
798 Qt3DCore::QGeometry *geom = renderer->geometry();
799 Qt3DCore::QAttribute *positionAttribute = findAttribute( geom, Qt3DCore::QAttribute::defaultPositionAttributeName(), Qt3DCore::QAttribute::VertexAttribute );
800 Qt3DCore::QAttribute *indexAttribute = findAttribute( geom, QString(), Qt3DCore::QAttribute::IndexAttribute );
801 if ( !positionAttribute || !indexAttribute )
802 {
803 QgsDebugError( QString( "Cannot export '%1' - geometry has no position or index attribute!" ).arg( objectNamePrefix ) );
804 continue;
805 }
806
807 const QByteArray vertexBytes = getData( positionAttribute->buffer() );
808 const QByteArray indexBytes = getData( indexAttribute->buffer() );
809 if ( vertexBytes.isNull() || indexBytes.isNull() )
810 {
811 QgsDebugError( QString( "Geometry for '%1' has position or index attribute with empty data!" ).arg( objectNamePrefix ) );
812 continue;
813 }
814 const QVector<float> positionData = getAttributeData<float>( positionAttribute, vertexBytes );
815
816 Qgs3DExportObject *exportObject = new Qgs3DExportObject( getObjectName( objectNamePrefix + u"line"_s ) );
817 exportObject->setupLine( positionData );
818
819 objs.push_back( exportObject );
820 }
821 return objs;
822}
823
824Qgs3DExportObject *Qgs3DSceneExporter::processPoints( Qt3DCore::QEntity *entity, const QString &objectNamePrefix )
825{
826 QVector<float> points;
827 const QList<Qt3DRender::QGeometryRenderer *> renderers = entity->findChildren<Qt3DRender::QGeometryRenderer *>();
828 for ( Qt3DRender::QGeometryRenderer *renderer : renderers )
829 {
830 Qt3DCore::QGeometry *geometry = qobject_cast<QgsBillboardGeometry *>( renderer->geometry() );
831 if ( !geometry )
832 continue;
833 Qt3DCore::QAttribute *positionAttribute = findAttribute( geometry, Qt3DCore::QAttribute::defaultPositionAttributeName(), Qt3DCore::QAttribute::VertexAttribute );
834 if ( !positionAttribute )
835 {
836 QgsDebugError( QString( "Cannot export '%1' - geometry has no position attribute!" ).arg( objectNamePrefix ) );
837 continue;
838 }
839 const QByteArray positionBytes = getData( positionAttribute->buffer() );
840 if ( positionBytes.isNull() )
841 {
842 QgsDebugError( QString( "Geometry for '%1' has position attribute with empty data!" ).arg( objectNamePrefix ) );
843 continue;
844 }
845 const QVector<float> positions = getAttributeData<float>( positionAttribute, positionBytes );
846 points << positions;
847 }
848 Qgs3DExportObject *obj = new Qgs3DExportObject( getObjectName( objectNamePrefix + u"points"_s ) );
849 obj->setupPoint( points );
850 return obj;
851}
852
853bool Qgs3DSceneExporter::save( const QString &sceneName, const QString &sceneFolderPath, int precision ) const
854{
855 if ( mObjects.isEmpty() )
856 {
857 return false;
858 }
859
860 const QString objFilePath = QDir( sceneFolderPath ).filePath( sceneName + u".obj"_s );
861 const QString mtlFilePath = QDir( sceneFolderPath ).filePath( sceneName + u".mtl"_s );
862
863 QFile file( objFilePath );
864 if ( !file.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) )
865 {
866 QgsDebugError( u"Scene can not be exported to '%1'. File access error."_s.arg( objFilePath ) );
867 return false;
868 }
869 QFile mtlFile( mtlFilePath );
870 if ( !mtlFile.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) )
871 {
872 QgsDebugError( u"Scene can not be exported to '%1'. File access error."_s.arg( mtlFilePath ) );
873 return false;
874 }
875
876 float maxfloat = std::numeric_limits<float>::max(), minFloat = std::numeric_limits<float>::lowest();
877 float minX = maxfloat, minY = maxfloat, minZ = maxfloat, maxX = minFloat, maxY = minFloat, maxZ = minFloat;
878 for ( Qgs3DExportObject *obj : qAsConst( mObjects ) )
879 {
880 obj->objectBounds( minX, minY, minZ, maxX, maxY, maxZ );
881 }
882
883 float diffX = 1.0f, diffY = 1.0f, diffZ = 1.0f;
884 diffX = maxX - minX;
885 diffY = maxY - minY;
886 diffZ = maxZ - minZ;
887
888 const float centerX = ( minX + maxX ) / 2.0f;
889 const float centerY = ( minY + maxY ) / 2.0f;
890 const float centerZ = ( minZ + maxZ ) / 2.0f;
891
892 const float scale = std::max( diffX, std::max( diffY, diffZ ) );
893
894 QTextStream out( &file );
895 // set material library name
896 const QString mtlLibName = sceneName + ".mtl";
897 out << "mtllib " << mtlLibName << "\n";
898
899 QTextStream mtlOut( &mtlFile );
900 for ( Qgs3DExportObject *obj : qAsConst( mObjects ) )
901 {
902 if ( !obj )
903 continue;
904 // Set object name
905 const QString material = obj->saveMaterial( mtlOut, sceneFolderPath );
906 out << "o " << obj->name() << "\n";
907 if ( material != QString() )
908 out << "usemtl " << material << "\n";
909 obj->saveTo( out, scale / mScale, QVector3D( centerX, centerY, centerZ ), precision );
910 }
911
912 QgsDebugMsgLevel( u"Scene exported to '%1'"_s.arg( objFilePath ), 2 );
913 return true;
914}
915
916QString Qgs3DSceneExporter::getObjectName( const QString &name )
917{
918 QString ret = name;
919 if ( mUsedObjectNamesCounter.contains( name ) )
920 {
921 ret = u"%1%2"_s.arg( name ).arg( mUsedObjectNamesCounter[name] );
922 mUsedObjectNamesCounter[name]++;
923 }
924 else
925 mUsedObjectNamesCounter[name] = 2;
926 return ret;
927}
Manages the data of each object of the scene (positions, normals, texture coordinates ....
void setupPoint(const QVector< float > &positionsBuffer)
sets point positions coordinates
QString saveMaterial(QTextStream &mtlOut, const QString &folder) const
saves the texture of the object and material information
QString name() const
Returns the object name.
void saveTo(QTextStream &out, float scale, const QVector3D &center, int precision=6) const
Saves the current object to the output stream while scaling the object and centering it to be visible...
void objectBounds(float &minX, float &minY, float &minZ, float &maxX, float &maxY, float &maxZ) const
Updates the box bounds explained with the current object bounds This expands the bounding box if the ...
void setupLine(const QVector< float > &positionsBuffer)
sets line indexes and positions coordinates
Definition of the world.
QgsTerrainGenerator * terrainGenerator() const
Returns the terrain generator.
bool terrainRenderingEnabled() const
Returns whether the 2D terrain surface will be rendered.
QgsVector3D origin() const
Returns coordinates in map CRS at which 3D scene has origin (0,0,0).
bool save(const QString &sceneName, const QString &sceneFolderPath, int precision=6) const
Saves the scene to a .obj file Returns false if the operation failed.
void parseTerrain(QgsTerrainEntity *terrain, const QString &layer)
Creates terrain export objects from the terrain entity.
float scale() const
Returns the scale of the exported 3D model.
bool parseVectorLayerEntity(Qt3DCore::QEntity *entity, QgsVectorLayer *layer)
Creates necessary export objects from entity if it represents valid vector layer entity Returns false...
static QgsPhongMaterialSettings phongMaterialFromQt3DComponent(Qt3DExtras::QPhongMaterial *material)
Returns phong material settings object based on the Qt3D material.
Base class for all renderers that participate in 3D views.
virtual QString type() const =0
Returns unique identifier of the renderer class (used to identify subclass).
Abstract base class for 3D symbols that are used by VectorLayer3DRenderer objects.
virtual bool exportGeometries(Qgs3DSceneExporter *exporter, Qt3DCore::QEntity *entity, const QString &objectNamePrefix) const
Exports the geometries contained within the hierarchy of entity.
QgsDemHeightMapGenerator * heightMapGenerator()
Returns height map generator object - takes care of extraction of elevations from the layer).
QgsChunkLoader * createChunkLoader(QgsChunkNode *node) const override
void setResolution(int resolution)
Sets resolution of the generator (how many elevation samples on one side of a terrain tile).
Terrain generator that creates a simple square flat area.
QgsChunkLoader * createChunkLoader(QgsChunkNode *node) const override
QImage getImage() const
Returns the image.
QString name
Definition qgsmaplayer.h:87
QgsAbstract3DRenderer * renderer3D() const
Returns 3D renderer associated with the layer.
Basic shading material used for rendering based on the Phong shading model with three color component...
Base class for generators of terrain.
@ QuantizedMesh
Terrain is built from quantized mesh tiles.
@ Dem
Terrain is built from raster layer with digital elevation model.
@ Online
Terrain is built from downloaded tiles with digital elevation model.
@ Mesh
Terrain is built from mesh layer with z value on vertices.
@ Flat
The whole terrain is flat area.
virtual Type type() const =0
What texture generator implementation is this.
QVector< QgsFeatureId > featureIds() const
Returns included feature ids.
QVector< uint > triangleIndexStartingIndices() const
Returns triangle index for features. For a feature featureIds()[i], matching triangles start at trian...
A 3D vector (similar to QVector3D) with the difference that it uses double precision instead of singl...
Definition qgsvector3d.h:33
3D renderer that renders all features of a vector layer with the same 3D symbol.
const QgsAbstract3DSymbol * symbol() const
Returns 3D symbol associated with the renderer.
Represents a vector layer which manages a vector based dataset.
QVector< uint > getIndexData(Qt3DCore::QAttribute *indexAttribute, const QByteArray &data)
Qt3DCore::QAttribute * findAttribute(Qt3DCore::QGeometry *geometry, const QString &name, Qt3DCore::QAttribute::AttributeType type)
QVector< uint > _getIndexDataImplementation(const QByteArray &data)
Component * findTypedComponent(Qt3DCore::QEntity *entity)
QVector< T > getAttributeData(Qt3DCore::QAttribute *attribute, const QByteArray &data)
QByteArray getData(Qt3DCore::QBuffer *buffer)
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63
#define QgsDebugError(str)
Definition qgslogger.h:59