QGIS API Documentation 4.3.0-Master (16649ce5cc6)
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
199 mObjects << processLines( entity, layer->name() + u"_"_s );
200
201 return mObjects.size() > prevSize;
202 }
203
204 else if ( rendererType == "vector" )
205 {
206 QgsVectorLayer3DRenderer *vectorLayerRenderer = dynamic_cast<QgsVectorLayer3DRenderer *>( abstractRenderer );
207 if ( vectorLayerRenderer )
208 {
209 const QgsAbstract3DSymbol *symbol = vectorLayerRenderer->symbol();
210 return symbol->exportGeometries( this, entity, layer->name() + u"_"_s );
211 }
212 else
213 return false;
214 }
215
216 else
217 {
218 // TODO: handle pointcloud/mesh/etc. layers
219 QgsDebugMsgLevel( u"Type '%1' of layer '%2' is not exportable."_s.arg( layer->name(), rendererType ), 2 );
220 return false;
221 }
222
223 return false;
224}
225
226void Qgs3DSceneExporter::processEntityMaterial( Qt3DCore::QEntity *entity, Qgs3DExportObject *object ) const
227{
228 Qt3DExtras::QPhongMaterial *phongMaterial = findTypedComponent<Qt3DExtras::QPhongMaterial>( entity );
229 if ( phongMaterial )
230 {
232 object->setupMaterial( &material );
233 }
234
235 Qt3DExtras::QDiffuseSpecularMaterial *diffuseMapMaterial = findTypedComponent<Qt3DExtras::QDiffuseSpecularMaterial>( entity );
236 if ( diffuseMapMaterial )
237 {
238 const Qt3DRender::QTexture2D *diffuseTexture = diffuseMapMaterial->diffuse().value<Qt3DRender::QTexture2D *>();
239 if ( diffuseTexture )
240 {
241 const QVector<Qt3DRender::QAbstractTextureImage *> textureImages = diffuseTexture->textureImages();
242 for ( const Qt3DRender::QAbstractTextureImage *tex : textureImages )
243 {
244 const QgsImageTexture *imageTexture = dynamic_cast<const QgsImageTexture *>( tex );
245 if ( imageTexture )
246 {
247 const QImage image = imageTexture->getImage();
248 object->setTextureImage( image );
249 break;
250 }
251 }
252 }
253 }
254}
255
256void Qgs3DSceneExporter::parseTerrain( QgsTerrainEntity *terrain, const QString &layerName )
257{
258 Qgs3DMapSettings *settings = terrain->mapSettings();
259 if ( !settings->terrainRenderingEnabled() || !mTerrainExportEnabled )
260 return;
261
262 QgsChunkNode *node = terrain->rootNode();
263
264 QgsTerrainGenerator *generator = settings->terrainGenerator();
265 if ( !generator )
266 return;
267 QgsTerrainTileEntity *terrainTile = nullptr;
268 QgsTerrainTextureGenerator *textureGenerator = terrain->textureGenerator();
269 textureGenerator->waitForFinished();
270 const QSize oldResolution = textureGenerator->textureSize();
271 textureGenerator->setTextureSize( QSize( mTerrainTextureResolution, mTerrainTextureResolution ) );
272 switch ( generator->type() )
273 {
275 terrainTile = getDemTerrainEntity( terrain, node, settings->origin() );
276 parseDemTile( terrainTile, layerName + u"_"_s );
277 break;
279 terrainTile = getFlatTerrainEntity( terrain, node, settings->origin() );
280 parseFlatTile( terrainTile, layerName + u"_"_s );
281 break;
283 terrainTile = getMeshTerrainEntity( terrain, node, settings->origin() );
284 parseMeshTile( terrainTile, layerName + u"_"_s );
285 break;
286 // TODO: implement other terrain types
289 break;
290 }
291 textureGenerator->setTextureSize( oldResolution );
292}
293
294QgsTerrainTileEntity *Qgs3DSceneExporter::getFlatTerrainEntity( QgsTerrainEntity *terrain, QgsChunkNode *node, const QgsVector3D &mapOrigin )
295{
296 QgsFlatTerrainGenerator *generator = qgis::down_cast<QgsFlatTerrainGenerator *>( terrain->mapSettings()->terrainGenerator() );
297 FlatTerrainChunkLoader *flatTerrainLoader = qobject_cast<FlatTerrainChunkLoader *>( generator->createChunkLoader( node ) );
298 flatTerrainLoader->start();
299 if ( mExportTextures )
300 terrain->textureGenerator()->waitForFinished();
301 // the entity we created will be deallocated once the scene exporter is deallocated
302 Qt3DCore::QEntity *entity = flatTerrainLoader->createEntity( this );
303 QgsTerrainTileEntity *tileEntity = qobject_cast<QgsTerrainTileEntity *>( entity );
304
305 const QList<QgsGeoTransform *> transforms = entity->findChildren<QgsGeoTransform *>();
306 for ( QgsGeoTransform *transform : transforms )
307 {
308 transform->setOrigin( mapOrigin );
309 }
310
311 return tileEntity;
312}
313
314QgsTerrainTileEntity *Qgs3DSceneExporter::getDemTerrainEntity( QgsTerrainEntity *terrain, QgsChunkNode *node, const QgsVector3D &mapOrigin )
315{
316 // Just create a new tile (we don't need to export exact level of details as in the scene)
317 // create the entity synchronously and then it will be deleted once our scene exporter instance is deallocated
318 QgsDemTerrainGenerator *generator = qgis::down_cast<QgsDemTerrainGenerator *>( terrain->mapSettings()->terrainGenerator()->clone() );
319 generator->setResolution( mTerrainResolution );
320 QgsDemTerrainTileLoader *loader = qobject_cast<QgsDemTerrainTileLoader *>( generator->createChunkLoader( node ) );
321 loader->start();
322 generator->heightMapGenerator()->waitForFinished();
323 if ( mExportTextures )
324 terrain->textureGenerator()->waitForFinished();
325 QgsTerrainTileEntity *tileEntity = qobject_cast<QgsTerrainTileEntity *>( loader->createEntity( this ) );
326
327 const QList<QgsGeoTransform *> transforms = tileEntity->findChildren<QgsGeoTransform *>();
328 for ( QgsGeoTransform *transform : transforms )
329 {
330 transform->setOrigin( mapOrigin );
331 }
332
333 delete generator;
334 return tileEntity;
335}
336
337QgsTerrainTileEntity *Qgs3DSceneExporter::getMeshTerrainEntity( QgsTerrainEntity *terrain, QgsChunkNode *node, const QgsVector3D &mapOrigin )
338{
339 QgsMeshTerrainGenerator *generator = qgis::down_cast<QgsMeshTerrainGenerator *>( terrain->mapSettings()->terrainGenerator() );
340 QgsMeshTerrainTileLoader *loader = qobject_cast<QgsMeshTerrainTileLoader *>( generator->createChunkLoader( node ) );
341 loader->start();
342 // TODO: export textures
343 QgsTerrainTileEntity *tileEntity = qobject_cast<QgsTerrainTileEntity *>( loader->createEntity( this ) );
344
345 const QList<QgsGeoTransform *> transforms = tileEntity->findChildren<QgsGeoTransform *>();
346 for ( QgsGeoTransform *transform : transforms )
347 {
348 transform->setOrigin( mapOrigin );
349 }
350
351 return tileEntity;
352}
353
354void Qgs3DSceneExporter::parseFlatTile( QgsTerrainTileEntity *tileEntity, const QString &layerName )
355{
356 Qt3DRender::QGeometryRenderer *mesh = findTypedComponent<Qt3DRender::QGeometryRenderer>( tileEntity );
357 Qt3DCore::QTransform *transform = findTypedComponent<Qt3DCore::QTransform>( tileEntity );
358
359 Qt3DCore::QGeometry *geometry = mesh->geometry();
360 Qt3DExtras::QPlaneGeometry *tileGeometry = qobject_cast<Qt3DExtras::QPlaneGeometry *>( geometry );
361 if ( !tileGeometry )
362 {
363 QgsDebugError( "Qt3DExtras::QPlaneGeometry* is expected but something else was given" );
364 return;
365 }
366
367 // Generate vertice data
368 Qt3DCore::QAttribute *positionAttribute = tileGeometry->positionAttribute();
369 if ( !positionAttribute )
370 {
371 QgsDebugError( QString( "Cannot export '%1' - geometry has no position attribute!" ).arg( layerName ) );
372 return;
373 }
374 const QByteArray verticesBytes = getData( positionAttribute->buffer() );
375 if ( verticesBytes.isNull() )
376 {
377 QgsDebugError( QString( "Geometry for '%1' has position attribute with empty data!" ).arg( layerName ) );
378 return;
379 }
380 const QVector<float> positionBuffer = getAttributeData<float>( positionAttribute, verticesBytes );
381
382 // Generate index data
383 Qt3DCore::QAttribute *indexAttribute = tileGeometry->indexAttribute();
384 if ( !indexAttribute )
385 {
386 QgsDebugError( QString( "Cannot export '%1' - geometry has no index attribute!" ).arg( layerName ) );
387 return;
388 }
389 const QByteArray indexBytes = getData( indexAttribute->buffer() );
390 if ( indexBytes.isNull() )
391 {
392 QgsDebugError( QString( "Geometry for '%1' has index attribute with empty data!" ).arg( layerName ) );
393 return;
394 }
395 const QVector<uint> indexesBuffer = getIndexData( indexAttribute, indexBytes );
396
397 QString objectNamePrefix = layerName;
398 if ( objectNamePrefix != QString() )
399 objectNamePrefix += QString();
400
401 Qgs3DExportObject *object = new Qgs3DExportObject( getObjectName( objectNamePrefix + u"Flat_tile"_s ) );
402 mObjects.push_back( object );
403
404 object->setSmoothEdges( mSmoothEdges );
405 object->setupTriangle( positionBuffer, indexesBuffer, transform->matrix() );
406
407 if ( mExportNormals )
408 {
409 // Everts
410 QVector<float> normalsBuffer;
411 for ( int i = 0; i < positionBuffer.size(); i += 3 )
412 normalsBuffer << 0.0f << 1.0f << 0.0f;
413 object->setupNormalCoordinates( normalsBuffer, transform->matrix() );
414 }
415
416 Qt3DCore::QAttribute *texCoordsAttribute = tileGeometry->texCoordAttribute();
417 if ( mExportTextures && texCoordsAttribute )
418 {
419 // Reuse vertex buffer data for texture coordinates
420 const QVector<float> texCoords = getAttributeData<float>( texCoordsAttribute, verticesBytes );
421 object->setupTextureCoordinates( texCoords );
422
423 QgsTerrainTextureImage *textureImage = tileEntity->textureImage();
424 const QImage img = textureImage->getImage();
425 object->setTextureImage( img );
426 }
427}
428
429void Qgs3DSceneExporter::parseDemTile( QgsTerrainTileEntity *tileEntity, const QString &layerName )
430{
431 Qt3DRender::QGeometryRenderer *mesh = findTypedComponent<Qt3DRender::QGeometryRenderer>( tileEntity );
432 Qt3DCore::QTransform *transform = findTypedComponent<Qt3DCore::QTransform>( tileEntity );
433
434 Qt3DCore::QGeometry *geometry = mesh->geometry();
435 DemTerrainTileGeometry *tileGeometry = qobject_cast<DemTerrainTileGeometry *>( geometry );
436 if ( !tileGeometry )
437 {
438 QgsDebugError( "DemTerrainTileGeometry* is expected but something else was given" );
439 return;
440 }
441
442 Qt3DCore::QAttribute *positionAttribute = tileGeometry->positionAttribute();
443 const QByteArray positionBytes = positionAttribute->buffer()->data();
444 const QVector<float> positionBuffer = getAttributeData<float>( positionAttribute, positionBytes );
445
446 Qt3DCore::QAttribute *indexAttribute = tileGeometry->indexAttribute();
447 const QByteArray indexBytes = indexAttribute->buffer()->data();
448 const QVector<unsigned int> indexBuffer = getIndexData( indexAttribute, indexBytes );
449
450 Qgs3DExportObject *object = new Qgs3DExportObject( getObjectName( layerName + u"DEM_tile"_s ) );
451 mObjects.push_back( object );
452
453 object->setSmoothEdges( mSmoothEdges );
454 object->setupTriangle( positionBuffer, indexBuffer, transform->matrix() );
455
456 Qt3DCore::QAttribute *normalsAttributes = tileGeometry->normalAttribute();
457 if ( mExportNormals && normalsAttributes )
458 {
459 const QByteArray normalsBytes = normalsAttributes->buffer()->data();
460 const QVector<float> normalsBuffer = getAttributeData<float>( normalsAttributes, normalsBytes );
461 object->setupNormalCoordinates( normalsBuffer, transform->matrix() );
462 }
463
464 Qt3DCore::QAttribute *texCoordsAttribute = tileGeometry->texCoordsAttribute();
465 if ( mExportTextures && texCoordsAttribute )
466 {
467 const QByteArray texCoordsBytes = texCoordsAttribute->buffer()->data();
468 const QVector<float> texCoordsBuffer = getAttributeData<float>( texCoordsAttribute, texCoordsBytes );
469 object->setupTextureCoordinates( texCoordsBuffer );
470
471 QgsTerrainTextureImage *textureImage = tileEntity->textureImage();
472 const QImage img = textureImage->getImage();
473 object->setTextureImage( img );
474 }
475}
476
477void Qgs3DSceneExporter::parseMeshTile( QgsTerrainTileEntity *tileEntity, const QString &layerName )
478{
479 QString objectNamePrefix = layerName;
480 if ( objectNamePrefix != QString() )
481 objectNamePrefix += '_'_L1;
482
483 const QList<Qt3DRender::QGeometryRenderer *> renderers = tileEntity->findChildren<Qt3DRender::QGeometryRenderer *>();
484 for ( Qt3DRender::QGeometryRenderer *renderer : renderers )
485 {
486 Qgs3DExportObject *obj = processGeometryRenderer( renderer, objectNamePrefix );
487 if ( !obj )
488 continue;
489 mObjects << obj;
490 }
491}
492
493QVector<Qgs3DExportObject *> Qgs3DSceneExporter::processInstancedPointGeometry( Qt3DCore::QEntity *entity, const QString &objectNamePrefix )
494{
495 // built-in Qt3D geometries (e.g. cylinder, plane, ...) assume Y axis going "up",
496 // They are rotated to have their Z axis goes "up", like the rest of the scene
497 // Retrieve the rotation matrix
498 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 );
499
500 QVector3D symbolScale;
501 QVector4D symbolRotation;
502 const QList<Qt3DRender::QMaterial *> materials = entity->findChildren<Qt3DRender::QMaterial *>();
503 for ( Qt3DRender::QMaterial *material : materials )
504 {
505 for ( const Qt3DRender::QParameter *parameter : material->parameters() )
506 {
507 if ( parameter->name() == "symbolScale"_L1 )
508 {
509 symbolScale = parameter->value().value<QVector3D>();
510 }
511 else if ( parameter->name() == "symbolRotation"_L1 )
512 {
513 symbolRotation = parameter->value().value<QVector4D>();
514 }
515 }
516 }
517
518 QQuaternion rotationQuat( symbolRotation.w(), symbolRotation.x(), symbolRotation.y(), symbolRotation.z() );
519
520 QVector<Qgs3DExportObject *> objects;
521 const QList<Qt3DCore::QGeometry *> geometriesList = entity->findChildren<Qt3DCore::QGeometry *>();
522 for ( Qt3DCore::QGeometry *geometry : geometriesList )
523 {
524 Qt3DCore::QAttribute *positionAttribute = findAttribute( geometry, Qt3DCore::QAttribute::defaultPositionAttributeName(), Qt3DCore::QAttribute::VertexAttribute );
525 Qt3DCore::QAttribute *indexAttribute = findAttribute( geometry, QString(), Qt3DCore::QAttribute::IndexAttribute );
526 if ( !positionAttribute || !indexAttribute )
527 {
528 QgsDebugError( QString( "Cannot export '%1' - geometry has no position or index attribute!" ).arg( objectNamePrefix ) );
529 continue;
530 }
531
532 Qt3DCore::QAttribute *instanceScaleAttribute = findAttribute( geometry, u"instanceScale"_s, Qt3DCore::QAttribute::VertexAttribute );
533 Qt3DCore::QAttribute *instanceRotationAttribute = findAttribute( geometry, u"instanceRotation"_s, Qt3DCore::QAttribute::VertexAttribute );
534
535 const QByteArray vertexBytes = positionAttribute->buffer()->data();
536 const QByteArray indexBytes = indexAttribute->buffer()->data();
537 if ( vertexBytes.isNull() || indexBytes.isNull() )
538 {
539 QgsDebugError( QString( "Geometry for '%1' has position or index attribute with empty data!" ).arg( objectNamePrefix ) );
540 continue;
541 }
542
543 const QVector<float> positionData = getAttributeData<float>( positionAttribute, vertexBytes );
544 const QVector<uint> indexData = getIndexData( indexAttribute, indexBytes );
545
546 const QVector<QVector3D> instanceScaleData = instanceScaleAttribute ? getAttributeData<QVector3D>( instanceScaleAttribute, instanceScaleAttribute->buffer()->data() ) : QVector<QVector3D>();
547 const QVector<QVector4D> instanceRotationData = instanceRotationAttribute ? getAttributeData<QVector4D>( instanceRotationAttribute, instanceRotationAttribute->buffer()->data() )
548 : QVector<QVector4D>();
549
550 Qt3DCore::QAttribute *instanceDataAttribute = findAttribute( geometry, u"instanceTranslation"_s, Qt3DCore::QAttribute::VertexAttribute );
551 if ( !instanceDataAttribute )
552 {
553 QgsDebugError( QString( "Cannot export '%1' - geometry has no instanceTranslation attribute!" ).arg( objectNamePrefix ) );
554 continue;
555 }
556 const QByteArray instancePositionBytes = getData( instanceDataAttribute->buffer() );
557 if ( instancePositionBytes.isNull() )
558 {
559 QgsDebugError( QString( "Geometry for '%1' has instanceTranslation attribute with empty data!" ).arg( objectNamePrefix ) );
560 continue;
561 }
562 QVector<float> instancePosition = getAttributeData<float>( instanceDataAttribute, instancePositionBytes );
563
564 int instanceIndex = 0;
565 for ( int i = 0; i < instancePosition.size(); i += 3, instanceIndex++ )
566 {
567 Qgs3DExportObject *object = new Qgs3DExportObject( getObjectName( objectNamePrefix + u"instance_point"_s ) );
568 objects.push_back( object );
569 QMatrix4x4 instanceTransform;
570 instanceTransform.translate( instancePosition[i], instancePosition[i + 1], instancePosition[i + 2] );
571
572 QQuaternion instanceRotation = rotationQuat;
573 if ( instanceRotationAttribute )
574 {
575 const QVector4D instanceRotationVec = instanceRotationData[instanceIndex];
576 instanceRotation = QQuaternion( instanceRotationVec.w(), instanceRotationVec.x(), instanceRotationVec.y(), instanceRotationVec.z() );
577 }
578 instanceTransform.rotate( instanceRotation );
579 instanceTransform.scale( instanceScaleAttribute ? instanceScaleData[instanceIndex] : symbolScale );
580 instanceTransform *= instanceMaterialTransform;
581 object->setupTriangle( positionData, indexData, instanceTransform );
582
583 object->setSmoothEdges( mSmoothEdges );
584
585 Qt3DCore::QAttribute *normalsAttribute = findAttribute( geometry, Qt3DCore::QAttribute::defaultNormalAttributeName(), Qt3DCore::QAttribute::VertexAttribute );
586 if ( mExportNormals && normalsAttribute )
587 {
588 // Reuse vertex bytes
589 const QVector<float> normalsData = getAttributeData<float>( normalsAttribute, vertexBytes );
590 object->setupNormalCoordinates( normalsData, instanceTransform );
591 }
592 }
593 }
594
595 return objects;
596}
597
598QVector<Qgs3DExportObject *> Qgs3DSceneExporter::processSceneLoaderGeometries( Qt3DRender::QSceneLoader *sceneLoader, const QString &objectNamePrefix )
599{
600 QVector<Qgs3DExportObject *> objects;
601 Qt3DCore::QEntity *sceneLoaderParent = qobject_cast<Qt3DCore::QEntity *>( sceneLoader->parent() );
602 Qt3DCore::QTransform *entityTransform = findTypedComponent<Qt3DCore::QTransform>( sceneLoaderParent );
603 QMatrix4x4 sceneTransform;
604 if ( entityTransform )
605 {
606 sceneTransform = entityTransform->matrix();
607 }
608 QStringList entityNames = sceneLoader->entityNames();
609 for ( const QString &entityName : entityNames )
610 {
611 Qt3DRender::QGeometryRenderer *mesh = qobject_cast<Qt3DRender::QGeometryRenderer *>( sceneLoader->component( entityName, Qt3DRender::QSceneLoader::GeometryRendererComponent ) );
612 Qgs3DExportObject *object = processGeometryRenderer( mesh, objectNamePrefix, sceneTransform );
613 if ( !object )
614 continue;
615 objects.push_back( object );
616 }
617 return objects;
618}
619
620Qgs3DExportObject *Qgs3DSceneExporter::processGeometryRenderer( Qt3DRender::QGeometryRenderer *geomRenderer, const QString &objectNamePrefix, const QMatrix4x4 &sceneTransform )
621{
622 // We only export triangles for now
623 if ( geomRenderer->primitiveType() != Qt3DRender::QGeometryRenderer::Triangles )
624 return nullptr;
625
626 Qt3DCore::QGeometry *geometry = geomRenderer->geometry();
627 if ( !geometry )
628 return nullptr;
629
630 if ( findAttribute( geometry, u"pointA"_s, Qt3DCore::QAttribute::VertexAttribute ) )
631 return nullptr;
632
633 // === Compute triangleIndexStartingIndiceToKeep according to duplicated features
634 //
635 // In the case of polygons, we have multiple feature geometries within the same geometry object (QgsTessellatedPolygonGeometry).
636 // The QgsTessellatedPolygonGeometry class holds the list of all feature ids included.
637 // To avoid exporting the same geometry (it can be included in multiple QgsTessellatedPolygonGeometry) more than once,
638 // we keep a list of already exported fid and compare with the fid of the current QgsTessellatedPolygonGeometry.
639 // As we cannot retrieve the specific geometry part for a featureId from the QgsTessellatedPolygonGeometry, we only reject
640 // the geometry if all the featureid are already present.
641 QVector<std::pair<uint, uint>> triangleIndexStartingIndiceToKeep;
642 QgsTessellatedPolygonGeometry *tessGeom = dynamic_cast<QgsTessellatedPolygonGeometry *>( geometry );
643 if ( tessGeom )
644 {
645 QVector<QgsFeatureId> featureIds = tessGeom->featureIds();
646
647 QSet<QgsFeatureId> tempFeatToAdd;
648 QVector<uint> triangleIndex = tessGeom->triangleIndexStartingIndices();
649
650 for ( int idx = 0; idx < featureIds.size(); idx++ )
651 {
652 const QgsFeatureId feat = featureIds[idx];
653 if ( !mExportedFeatureIds.contains( feat ) )
654 {
655 // add the feature (as it was unknown) to temp set and not to the mExportedFeatureIds (as featureIds can have the same id multiple times)
656 tempFeatToAdd += feat;
657
658 // keep the feature triangle indexes
659 const uint startIdx = triangleIndex[idx];
660 const uint endIdx = idx < triangleIndex.size() - 1 ? triangleIndex[idx + 1] : std::numeric_limits<uint>::max();
661
662 if ( startIdx < endIdx ) // keep only valid intervals
663 triangleIndexStartingIndiceToKeep.append( std::pair<uint, uint>( startIdx, endIdx ) );
664 }
665 }
666 mExportedFeatureIds += tempFeatToAdd;
667
668 if ( triangleIndexStartingIndiceToKeep.isEmpty() ) // all featureid are already exported
669 {
670 return nullptr;
671 }
672 }
673
674 // === Compute inherited scale and translation from child entity to parent
675 QMatrix4x4 transformMatrix = sceneTransform;
676 QObject *parent = geomRenderer->parent();
677 while ( parent )
678 {
679 Qt3DCore::QEntity *entity = qobject_cast<Qt3DCore::QEntity *>( parent );
680 Qt3DCore::QTransform *transform = findTypedComponent<Qt3DCore::QTransform>( entity );
681 if ( transform )
682 {
683 transformMatrix = transform->matrix() * transformMatrix;
684 }
685 parent = parent->parent();
686 }
687
688 Qt3DCore::QAttribute *positionAttribute = nullptr;
689 Qt3DCore::QAttribute *indexAttribute = nullptr;
690 QByteArray indexBytes, vertexBytes;
691 QVector<uint> indexDataTmp;
692 QVector<uint> indexData;
693 QVector<float> positionData;
694
695 // === Extract position data
696 positionAttribute = findAttribute( geometry, Qt3DCore::QAttribute::defaultPositionAttributeName(), Qt3DCore::QAttribute::VertexAttribute );
697 if ( !positionAttribute )
698 {
699 QgsDebugError( QString( "Cannot export '%1' - geometry has no position attribute!" ).arg( objectNamePrefix ) );
700 return nullptr;
701 }
702
703 vertexBytes = getData( positionAttribute->buffer() );
704 if ( vertexBytes.isNull() )
705 {
706 QgsDebugError( QString( "Will not export '%1' as geometry has empty position data!" ).arg( objectNamePrefix ) );
707 return nullptr;
708 }
709
710 positionData = getAttributeData<float>( positionAttribute, vertexBytes );
711
712 // === Search for face index data
713 QVector<Qt3DCore::QAttribute *> attributes = geometry->attributes();
714 for ( Qt3DCore::QAttribute *attribute : attributes )
715 {
716 if ( attribute->attributeType() == Qt3DCore::QAttribute::IndexAttribute )
717 {
718 indexAttribute = attribute;
719 indexBytes = getData( indexAttribute->buffer() );
720 if ( indexBytes.isNull() )
721 {
722 QgsDebugError( QString( "Geometry for '%1' has index attribute with empty data!" ).arg( objectNamePrefix ) );
723 }
724 else
725 {
726 indexDataTmp = getIndexData( indexAttribute, indexBytes );
727 break;
728 }
729 }
730 }
731
732 // for tessellated polygons that don't have index attributes, build them from positionData
733 if ( !indexAttribute )
734 {
735 for ( uint i = 0; i < static_cast<uint>( positionData.size() / 3 ); ++i )
736 {
737 indexDataTmp.push_back( i );
738 }
739 }
740
741 // === Filter face index data if needed
742 if ( triangleIndexStartingIndiceToKeep.isEmpty() )
743 {
744 // when geometry is NOT a QgsTessellatedPolygonGeometry, no filter, take them all
745 indexData.append( indexDataTmp );
746 }
747 else
748 {
749 // when geometry is a QgsTessellatedPolygonGeometry, filter according to triangleIndexStartingIndiceToKeep
750 int intervalIdx = 0;
751 const int triangleIndexStartingIndiceToKeepSize = triangleIndexStartingIndiceToKeep.size();
752 const uint indexDataTmpSize = static_cast<uint>( indexDataTmp.size() );
753 for ( uint i = 0; i + 2 < indexDataTmpSize; i += 3 )
754 {
755 const uint triangleIdx = i / 3;
756
757 // search for valid triangle index interval
758 while ( intervalIdx < triangleIndexStartingIndiceToKeepSize && triangleIdx >= triangleIndexStartingIndiceToKeep[intervalIdx].second )
759 {
760 intervalIdx++;
761 }
762
763 // keep only triangles within the triangle index interval
764 if ( intervalIdx < triangleIndexStartingIndiceToKeepSize && triangleIdx >= triangleIndexStartingIndiceToKeep[intervalIdx].first && triangleIdx < triangleIndexStartingIndiceToKeep[intervalIdx].second )
765 {
766 indexData.push_back( indexDataTmp[static_cast<int>( i )] );
767 indexData.push_back( indexDataTmp[static_cast<int>( i + 1 )] );
768 indexData.push_back( indexDataTmp[static_cast<int>( i + 2 )] );
769 }
770 }
771 }
772
773 // === Create Qgs3DExportObject
774 Qgs3DExportObject *object = new Qgs3DExportObject( getObjectName( objectNamePrefix + u"mesh_geometry"_s ) );
775 object->setupTriangle( positionData, indexData, transformMatrix );
776
777 Qt3DCore::QAttribute *normalsAttribute = findAttribute( geometry, Qt3DCore::QAttribute::defaultNormalAttributeName(), Qt3DCore::QAttribute::VertexAttribute );
778 if ( mExportNormals && normalsAttribute )
779 {
780 // Reuse vertex bytes
781 const QVector<float> normalsData = getAttributeData<float>( normalsAttribute, vertexBytes );
782 object->setupNormalCoordinates( normalsData, transformMatrix );
783 }
784
785 Qt3DCore::QAttribute *texCoordsAttribute = findAttribute( geometry, Qt3DCore::QAttribute::defaultTextureCoordinateAttributeName(), Qt3DCore::QAttribute::VertexAttribute );
786 if ( mExportTextures && texCoordsAttribute )
787 {
788 // Reuse vertex bytes
789 const QVector<float> texCoordsData = getAttributeData<float>( texCoordsAttribute, vertexBytes );
790 object->setupTextureCoordinates( texCoordsData );
791 }
792
793 return object;
794}
795
796QVector<Qgs3DExportObject *> Qgs3DSceneExporter::processLines( Qt3DCore::QEntity *entity, const QString &objectNamePrefix )
797{
798 QVector<Qgs3DExportObject *> objs;
799 const QList<Qt3DRender::QGeometryRenderer *> renderers = entity->findChildren<Qt3DRender::QGeometryRenderer *>();
800 for ( Qt3DRender::QGeometryRenderer *renderer : renderers )
801 {
802 Qt3DCore::QGeometry *geom = renderer->geometry();
803 Qt3DCore::QAttribute *pointAAttribute = findAttribute( geom, u"pointA"_s, Qt3DCore::QAttribute::VertexAttribute );
804 Qt3DCore::QAttribute *pointBAttribute = findAttribute( geom, u"pointB"_s, Qt3DCore::QAttribute::VertexAttribute );
805 Qt3DCore::QAttribute *pointCAttribute = findAttribute( geom, u"pointC"_s, Qt3DCore::QAttribute::VertexAttribute );
806 if ( !pointAAttribute || !pointBAttribute || pointCAttribute )
807 continue;
808
809 const QByteArray pointABytes = getData( pointAAttribute->buffer() );
810 const QByteArray pointBBytes = getData( pointBAttribute->buffer() );
811 if ( pointABytes.isNull() || pointBBytes.isNull() )
812 {
813 QgsDebugError( QString( "Geometry for '%1' has pointA or pointB attribute with empty data!" ).arg( objectNamePrefix ) );
814 continue;
815 }
816 const QVector<float> pointAData = getAttributeData<float>( pointAAttribute, pointABytes );
817 const QVector<float> pointBData = getAttributeData<float>( pointBAttribute, pointBBytes );
818
819 QVector<float> positionData;
820 positionData.reserve( pointAData.size() + pointBData.size() );
821 for ( int i = 0; i + 2 < pointAData.size(); i += 3 )
822 {
823 positionData << pointAData[i] << pointAData[i + 1] << pointAData[i + 2];
824 positionData << pointBData[i] << pointBData[i + 1] << pointBData[i + 2];
825 }
826
827 Qgs3DExportObject *exportObject = new Qgs3DExportObject( getObjectName( objectNamePrefix + u"line"_s ) );
828 exportObject->setupLine( positionData );
829
830 objs.push_back( exportObject );
831 }
832 return objs;
833}
834
835Qgs3DExportObject *Qgs3DSceneExporter::processPoints( Qt3DCore::QEntity *entity, const QString &objectNamePrefix )
836{
837 QVector<float> points;
838 const QList<Qt3DRender::QGeometryRenderer *> renderers = entity->findChildren<Qt3DRender::QGeometryRenderer *>();
839 for ( Qt3DRender::QGeometryRenderer *renderer : renderers )
840 {
841 Qt3DCore::QGeometry *geometry = qobject_cast<QgsBillboardGeometry *>( renderer->geometry() );
842 if ( !geometry )
843 continue;
844 Qt3DCore::QAttribute *positionAttribute = findAttribute( geometry, "instancePosition", Qt3DCore::QAttribute::VertexAttribute );
845 if ( !positionAttribute )
846 {
847 QgsDebugError( QString( "Cannot export '%1' - geometry has no position attribute!" ).arg( objectNamePrefix ) );
848 continue;
849 }
850 const QByteArray positionBytes = getData( positionAttribute->buffer() );
851 if ( positionBytes.isNull() )
852 {
853 QgsDebugError( QString( "Geometry for '%1' has position attribute with empty data!" ).arg( objectNamePrefix ) );
854 continue;
855 }
856 const QVector<float> positions = getAttributeData<float>( positionAttribute, positionBytes );
857 points << positions;
858 }
859 Qgs3DExportObject *obj = new Qgs3DExportObject( getObjectName( objectNamePrefix + u"points"_s ) );
860 obj->setupPoint( points );
861 return obj;
862}
863
864bool Qgs3DSceneExporter::save( QString sceneName, QString sceneFolderPath, const Qgis::Export3DSceneFormat &exportFormat, int precision ) const
865{
866 if ( mObjects.isEmpty() )
867 {
868 return false;
869 }
870
871 switch ( exportFormat )
872 {
874 return saveObj( sceneName, sceneFolderPath, precision );
876 return saveStl( sceneName, sceneFolderPath, precision );
877 }
878
880}
881
882bool Qgs3DSceneExporter::saveObj( QString sceneName, QString sceneFolderPath, int precision ) const
883{
884 const QString objFilePath = QDir( sceneFolderPath ).filePath( sceneName + u".obj"_s );
885 const QString mtlFilePath = QDir( sceneFolderPath ).filePath( sceneName + u".mtl"_s );
886
887 QFile file( objFilePath );
888 if ( !file.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) )
889 {
890 QgsDebugError( u"Scene can not be exported to '%1'. File access error."_s.arg( objFilePath ) );
891 return false;
892 }
893 QFile mtlFile( mtlFilePath );
894 if ( !mtlFile.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) )
895 {
896 QgsDebugError( u"Scene can not be exported to '%1'. File access error."_s.arg( mtlFilePath ) );
897 return false;
898 }
899
900 QVector3D center;
901 float scale;
902 getSceneCenterAndScale( center, scale );
903
904 QTextStream out( &file );
905 // set material library name
906 const QString mtlLibName = sceneName + ".mtl";
907 out << "mtllib " << mtlLibName << "\n";
908
909 QTextStream mtlOut( &mtlFile );
910 for ( Qgs3DExportObject *obj : std::as_const( mObjects ) )
911 {
912 if ( !obj )
913 continue;
914
915 const QString materialName = obj->saveMaterial( mtlOut, sceneFolderPath );
916 obj->saveTo( out, scale, center, Qgis::Export3DSceneFormat::Obj, precision, materialName );
917 }
918
919 QgsDebugMsgLevel( u"Scene exported to '%1'"_s.arg( objFilePath ), 2 );
920 return true;
921}
922
923bool Qgs3DSceneExporter::saveStl( QString sceneName, QString sceneFolderPath, int precision ) const
924{
925 const QString stlFilePath = QDir( sceneFolderPath ).filePath( sceneName + u".stl"_s );
926
927 QFile file( stlFilePath );
928 if ( !file.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) )
929 {
930 QgsDebugError( u"Scene can not be exported to '%1'. File access error."_s.arg( stlFilePath ) );
931 return false;
932 }
933
934 QVector3D center;
935 float scale;
936 getSceneCenterAndScale( center, scale );
937
938 QTextStream out( &file );
939
940 for ( Qgs3DExportObject *object : std::as_const( mObjects ) )
941 {
942 if ( !object )
943 continue;
944
945 object->saveTo( out, scale, center, Qgis::Export3DSceneFormat::StlAscii, precision );
946 }
947
948 QgsDebugMsgLevel( u"Scene exported to '%1'"_s.arg( stlFilePath ), 2 );
949 return true;
950}
951
952void Qgs3DSceneExporter::getSceneCenterAndScale( QVector3D &center, float &scale ) const
953{
954 const float minFloat = std::numeric_limits<float>::lowest();
955 const float maxfloat = std::numeric_limits<float>::max();
956
957 float minX = maxfloat;
958 float minY = maxfloat;
959 float minZ = maxfloat;
960 float maxX = minFloat;
961 float maxY = minFloat;
962 float maxZ = minFloat;
963 for ( Qgs3DExportObject *obj : std::as_const( mObjects ) )
964 {
965 obj->objectBounds( minX, minY, minZ, maxX, maxY, maxZ );
966 }
967
968 const float diffX = maxX - minX;
969 const float diffY = maxY - minY;
970 const float diffZ = maxZ - minZ;
971
972 center.setX( ( minX + maxX ) / 2.0f );
973 center.setY( ( minY + maxY ) / 2.0f );
974 center.setZ( ( minZ + maxZ ) / 2.0f );
975
976 scale = std::max( diffX, std::max( diffY, diffZ ) ) / mScale;
977}
978
979QString Qgs3DSceneExporter::getObjectName( const QString &name )
980{
981 QString ret = name;
982 if ( mUsedObjectNamesCounter.contains( name ) )
983 {
984 ret = u"%1%2"_s.arg( name ).arg( mUsedObjectNamesCounter[name] );
985 mUsedObjectNamesCounter[name]++;
986 }
987 else
988 mUsedObjectNamesCounter[name] = 2;
989 return ret;
990}
Export3DSceneFormat
The file format used when exporting a 3D scene.
Definition qgis.h:4618
@ StlAscii
STL ascii format.
Definition qgis.h:4620
@ Obj
Wavefront OBJ format.
Definition qgis.h:4619
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
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
void saveTo(QTextStream &out, float scale, const QVector3D &center, const Qgis::Export3DSceneFormat &exportFormat=Qgis::Export3DSceneFormat::Obj, int precision=6, QString materialName=QString()) const
Exports the 3D object to an OBJ or STL compatible output stream.
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).
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...
bool save(QString sceneName, QString sceneFolderPath, const Qgis::Export3DSceneFormat &exportFormat=Qgis::Export3DSceneFormat::Obj, int precision=6) const
Saves the scene to a file Returns false if the operation failed.
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.
#define BUILTIN_UNREACHABLE
Definition qgis.h:8365
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:80
#define QgsDebugError(str)
Definition qgslogger.h:71