QGIS API Documentation 3.99.0-Master (752b475928d)
Loading...
Searching...
No Matches
qgsgltf3dutils.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsgltf3dutils.cpp
3 --------------------------------------
4 Date : July 2023
5 Copyright : (C) 2023 by Martin Dobias
6 Email : wonder dot sk at gmail dot com
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
17#include "qgsgltf3dutils.h"
18
21#include "qgsgltfutils.h"
22#include "qgslogger.h"
24#include "qgstexturematerial.h"
25#include "qgsziputils.h"
26
27#include <Qt3DCore/QEntity>
28
29#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
30#include <Qt3DRender/QAttribute>
31#include <Qt3DRender/QBuffer>
32#include <Qt3DRender/QGeometry>
33typedef Qt3DRender::QAttribute Qt3DQAttribute;
34typedef Qt3DRender::QBuffer Qt3DQBuffer;
35typedef Qt3DRender::QGeometry Qt3DQGeometry;
36#else
37#include <Qt3DCore/QAttribute>
38#include <Qt3DCore/QBuffer>
39#include <Qt3DCore/QGeometry>
40typedef Qt3DCore::QAttribute Qt3DQAttribute;
41typedef Qt3DCore::QBuffer Qt3DQBuffer;
42typedef Qt3DCore::QGeometry Qt3DQGeometry;
43#endif
44
45#include <Qt3DRender/QGeometryRenderer>
46#include <Qt3DRender/QTexture>
47
48#include <QFile>
49#include <QFileInfo>
50#include <QMatrix4x4>
51#include <memory>
52
54
55static Qt3DQAttribute::VertexBaseType parseVertexBaseType( int componentType )
56{
57 switch ( componentType )
58 {
59 case TINYGLTF_COMPONENT_TYPE_BYTE:
60 return Qt3DQAttribute::Byte;
61 case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE:
62 return Qt3DQAttribute::UnsignedByte;
63 case TINYGLTF_COMPONENT_TYPE_SHORT:
64 return Qt3DQAttribute::Short;
65 case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT:
66 return Qt3DQAttribute::UnsignedShort;
67 case TINYGLTF_COMPONENT_TYPE_INT:
68 return Qt3DQAttribute::Int;
69 case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT:
70 return Qt3DQAttribute::UnsignedInt;
71 case TINYGLTF_COMPONENT_TYPE_FLOAT:
72 return Qt3DQAttribute::Float;
73 case TINYGLTF_COMPONENT_TYPE_DOUBLE:
74 return Qt3DQAttribute::Double;
75 }
76 Q_ASSERT( false );
77 return Qt3DQAttribute::UnsignedInt;
78}
79
80
81static Qt3DRender::QAbstractTexture::Filter parseTextureFilter( int filter )
82{
83 switch ( filter )
84 {
85 case TINYGLTF_TEXTURE_FILTER_NEAREST:
86 return Qt3DRender::QTexture2D::Nearest;
87 case TINYGLTF_TEXTURE_FILTER_LINEAR:
88 return Qt3DRender::QTexture2D::Linear;
89 case TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_NEAREST:
90 return Qt3DRender::QTexture2D::NearestMipMapNearest;
91 case TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_NEAREST:
92 return Qt3DRender::QTexture2D::LinearMipMapNearest;
93 case TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_LINEAR:
94 return Qt3DRender::QTexture2D::NearestMipMapLinear;
95 case TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_LINEAR:
96 return Qt3DRender::QTexture2D::LinearMipMapLinear;
97 }
98
99 // play it safe and handle malformed models
100 return Qt3DRender::QTexture2D::Nearest;
101}
102
103static Qt3DRender::QTextureWrapMode::WrapMode parseTextureWrapMode( int wrapMode )
104{
105 switch ( wrapMode )
106 {
107 case TINYGLTF_TEXTURE_WRAP_REPEAT:
108 return Qt3DRender::QTextureWrapMode::Repeat;
109 case TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE:
110 return Qt3DRender::QTextureWrapMode::ClampToEdge;
111 case TINYGLTF_TEXTURE_WRAP_MIRRORED_REPEAT:
112 return Qt3DRender::QTextureWrapMode::MirroredRepeat;
113 }
114 // some malformed GLTF models have incorrect texture wrap modes (eg
115 // https://qld.digitaltwin.terria.io/api/v0/data/b73ccb60-66ef-4470-8c3c-44af36c4d69b/CBD/tileset.json )
116 return Qt3DRender::QTextureWrapMode::Repeat;
117}
118
119
120static Qt3DQAttribute *parseAttribute( tinygltf::Model &model, int accessorIndex )
121{
122 tinygltf::Accessor &accessor = model.accessors[accessorIndex];
123 tinygltf::BufferView &bv = model.bufferViews[accessor.bufferView];
124 tinygltf::Buffer &b = model.buffers[bv.buffer];
125
126 // TODO: only ever create one QBuffer for a buffer even if it is used multiple times
127 QByteArray byteArray( reinterpret_cast<const char *>( b.data.data() ),
128 static_cast<int>( b.data.size() ) ); // makes a deep copy
129 Qt3DQBuffer *buffer = new Qt3DQBuffer();
130 buffer->setData( byteArray );
131
132 Qt3DQAttribute *attribute = new Qt3DQAttribute();
133
134 // "target" is optional, can be zero
135 if ( bv.target == TINYGLTF_TARGET_ARRAY_BUFFER )
136 attribute->setAttributeType( Qt3DQAttribute::VertexAttribute );
137 else if ( bv.target == TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER )
138 attribute->setAttributeType( Qt3DQAttribute::IndexAttribute );
139
140 attribute->setBuffer( buffer );
141 attribute->setByteOffset( bv.byteOffset + accessor.byteOffset );
142 attribute->setByteStride( bv.byteStride ); // could be zero, it seems that's fine (assuming packed)
143 attribute->setCount( accessor.count );
144 attribute->setVertexBaseType( parseVertexBaseType( accessor.componentType ) );
145 attribute->setVertexSize( tinygltf::GetNumComponentsInType( accessor.type ) );
146
147 return attribute;
148}
149
150
151static Qt3DQAttribute *reprojectPositions( tinygltf::Model &model, int accessorIndex, const QgsGltf3DUtils::EntityTransform &transform, const QgsVector3D &tileTranslationEcef, QMatrix4x4 *matrix )
152{
153 tinygltf::Accessor &accessor = model.accessors[accessorIndex];
154
155 QVector<double> vx, vy, vz;
156 bool res = QgsGltfUtils::accessorToMapCoordinates( model, accessorIndex, transform.tileTransform, transform.ecefToTargetCrs, tileTranslationEcef, matrix, transform.gltfUpAxis, vx, vy, vz );
157 if ( !res )
158 return nullptr;
159
160 QByteArray byteArray;
161 byteArray.resize( accessor.count * 4 * 3 );
162 float *out = reinterpret_cast<float *>( byteArray.data() );
163
164 QgsVector3D sceneOrigin = transform.chunkOriginTargetCrs;
165 for ( int i = 0; i < static_cast<int>( accessor.count ); ++i )
166 {
167 double x = vx[i] - sceneOrigin.x();
168 double y = vy[i] - sceneOrigin.y();
169 double z = ( vz[i] * transform.zValueScale ) + transform.zValueOffset - sceneOrigin.z();
170
171 out[i * 3 + 0] = static_cast<float>( x );
172 out[i * 3 + 1] = static_cast<float>( y );
173 out[i * 3 + 2] = static_cast<float>( z );
174 }
175
176 Qt3DQBuffer *buffer = new Qt3DQBuffer();
177 buffer->setData( byteArray );
178
179 Qt3DQAttribute *attribute = new Qt3DQAttribute();
180 attribute->setAttributeType( Qt3DQAttribute::VertexAttribute );
181 attribute->setBuffer( buffer );
182 attribute->setByteOffset( 0 );
183 attribute->setByteStride( 12 );
184 attribute->setCount( accessor.count );
185 attribute->setVertexBaseType( Qt3DQAttribute::Float );
186 attribute->setVertexSize( 3 );
187
188 return attribute;
189}
190
191class TinyGltfTextureImageDataGenerator : public Qt3DRender::QTextureImageDataGenerator
192{
193 public:
194 TinyGltfTextureImageDataGenerator( Qt3DRender::QTextureImageDataPtr imagePtr )
195 : mImagePtr( imagePtr ) {}
196
197 Qt3DRender::QTextureImageDataPtr operator()() override
198 {
199 return mImagePtr;
200 }
201
202 qintptr id() const override
203 {
204#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
205 return reinterpret_cast<qintptr>( &Qt3DRender::FunctorType<TinyGltfTextureImageDataGenerator>::id );
206#else
207 return reinterpret_cast<qintptr>( &Qt3DCore::FunctorType<TinyGltfTextureImageDataGenerator>::id );
208#endif
209 }
210
211 bool operator==( const QTextureImageDataGenerator &other ) const override
212 {
213 const TinyGltfTextureImageDataGenerator *otherFunctor = dynamic_cast<const TinyGltfTextureImageDataGenerator *>( &other );
214 return otherFunctor && mImagePtr.get() == otherFunctor->mImagePtr.get();
215 }
216
217 Qt3DRender::QTextureImageDataPtr mImagePtr;
218};
219
220class TinyGltfTextureImage : public Qt3DRender::QAbstractTextureImage
221{
222 Q_OBJECT
223 public:
224 TinyGltfTextureImage( tinygltf::Image &image )
225 {
226 Q_ASSERT( image.bits == 8 );
227 Q_ASSERT( image.component == 4 );
228 Q_ASSERT( image.pixel_type == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE );
229
230 imgDataPtr.reset( new Qt3DRender::QTextureImageData );
231 imgDataPtr->setWidth( image.width );
232 imgDataPtr->setHeight( image.height );
233 imgDataPtr->setDepth( 1 ); // not sure what this is
234 imgDataPtr->setFaces( 1 );
235 imgDataPtr->setLayers( 1 );
236 imgDataPtr->setMipLevels( 1 );
237 QByteArray imageBytes( reinterpret_cast<const char *>( image.image.data() ), image.image.size() );
238 imgDataPtr->setData( imageBytes, 4 );
239 imgDataPtr->setFormat( QOpenGLTexture::RGBA8_UNorm );
240 imgDataPtr->setPixelFormat( QOpenGLTexture::BGRA ); // when using tinygltf with STB_image, pixel format is QOpenGLTexture::RGBA
241 imgDataPtr->setPixelType( QOpenGLTexture::UInt8 );
242 imgDataPtr->setTarget( QOpenGLTexture::Target2D );
243 }
244
245 Qt3DRender::QTextureImageDataGeneratorPtr dataGenerator() const override
246 {
247 return Qt3DRender::QTextureImageDataGeneratorPtr( new TinyGltfTextureImageDataGenerator( imgDataPtr ) );
248 }
249
250 Qt3DRender::QTextureImageDataPtr imgDataPtr;
251};
252
253
254// TODO: move elsewhere
255static QByteArray fetchUri( const QUrl &url, QStringList *errors )
256{
257 if ( url.scheme().startsWith( "http" ) )
258 {
259 QNetworkRequest request = QNetworkRequest( url );
260 request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
261 request.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
262 QgsBlockingNetworkRequest networkRequest;
263 // TODO: setup auth, setup headers
264 if ( networkRequest.get( request ) != QgsBlockingNetworkRequest::NoError )
265 {
266 if ( errors )
267 *errors << QStringLiteral( "Failed to download image: %1" ).arg( url.toString() );
268 }
269 else
270 {
271 const QgsNetworkReplyContent content = networkRequest.reply();
272 return content.content();
273 }
274 }
275 else if ( url.isLocalFile() )
276 {
277 QString localFilePath = url.toLocalFile();
278 if ( localFilePath.contains( ".slpk/" ) ) // we need to extract the image from SLPK archive
279 {
280 const QStringList parts = localFilePath.split( QStringLiteral( ".slpk/" ) );
281 if ( parts.size() == 2 )
282 {
283 QString slpkPath = parts[0] + ".slpk";
284 QString imagePath = parts[1];
285
286 QByteArray imageData;
287 if ( QgsZipUtils::extractFileFromZip( slpkPath, imagePath, imageData ) )
288 {
289 return imageData;
290 }
291 else
292 {
293 if ( errors )
294 *errors << QStringLiteral( "Unable to extract image '%1' from SLPK archive: %2" ).arg( imagePath ).arg( slpkPath );
295 }
296 }
297 else
298 {
299 if ( errors )
300 *errors << QStringLiteral( "Missing image path in SLPK archive: %1" ).arg( localFilePath );
301 }
302 }
303 else if ( QFile::exists( localFilePath ) )
304 {
305 QFile f( localFilePath );
306 if ( f.open( QIODevice::ReadOnly ) )
307 {
308 return f.readAll();
309 }
310 }
311 else
312 {
313 if ( errors )
314 *errors << QStringLiteral( "Unable to open image: %1" ).arg( url.toString() );
315 }
316 }
317 return QByteArray();
318}
319
320// Returns NULLPTR if primitive should not be rendered
321static QgsMaterial *parseMaterial( tinygltf::Model &model, int materialIndex, QString baseUri, QStringList *errors )
322{
323 if ( materialIndex < 0 )
324 {
325 // material unspecified - using default
326 QgsMetalRoughMaterial *defaultMaterial = new QgsMetalRoughMaterial;
327 defaultMaterial->setMetalness( 1 );
328 defaultMaterial->setRoughness( 1 );
329 defaultMaterial->setBaseColor( QColor::fromRgbF( 1, 1, 1 ) );
330 return defaultMaterial;
331 }
332
333 tinygltf::Material &material = model.materials[materialIndex];
334 tinygltf::PbrMetallicRoughness &pbr = material.pbrMetallicRoughness;
335
336 if ( pbr.baseColorTexture.index >= 0 )
337 {
338 tinygltf::Texture &tex = model.textures[pbr.baseColorTexture.index];
339
340 // Source can be undefined if texture is provided by an extension
341 if ( tex.source < 0 )
342 {
343 QgsMetalRoughMaterial *pbrMaterial = new QgsMetalRoughMaterial;
344 pbrMaterial->setMetalness( pbr.metallicFactor ); // [0..1] or texture
345 pbrMaterial->setRoughness( pbr.roughnessFactor );
346 pbrMaterial->setBaseColor( QColor::fromRgbF( pbr.baseColorFactor[0], pbr.baseColorFactor[1], pbr.baseColorFactor[2], pbr.baseColorFactor[3] ) );
347 return pbrMaterial;
348 }
349
350 tinygltf::Image &img = model.images[tex.source];
351
352 if ( !img.uri.empty() )
353 {
354 QString imgUri = QString::fromStdString( img.uri );
355 QUrl url = QUrl( baseUri ).resolved( imgUri );
356 QByteArray ba = fetchUri( url, errors );
357 if ( !ba.isEmpty() )
358 {
359 if ( !QgsGltfUtils::loadImageDataWithQImage( &img, -1, nullptr, nullptr, 0, 0, ( const unsigned char * ) ba.constData(), ba.size(), nullptr ) )
360 {
361 if ( errors )
362 *errors << QStringLiteral( "Failed to load image: %1" ).arg( imgUri );
363 }
364 }
365 }
366
367 if ( img.image.empty() )
368 {
369 QgsMetalRoughMaterial *pbrMaterial = new QgsMetalRoughMaterial;
370 pbrMaterial->setMetalness( pbr.metallicFactor ); // [0..1] or texture
371 pbrMaterial->setRoughness( pbr.roughnessFactor );
372 pbrMaterial->setBaseColor( QColor::fromRgbF( pbr.baseColorFactor[0], pbr.baseColorFactor[1], pbr.baseColorFactor[2], pbr.baseColorFactor[3] ) );
373 return pbrMaterial;
374 }
375
376 TinyGltfTextureImage *textureImage = new TinyGltfTextureImage( img );
377
378 Qt3DRender::QTexture2D *texture = new Qt3DRender::QTexture2D;
379 texture->addTextureImage( textureImage ); // textures take the ownership of textureImage if has no parant
380
381 // let's use linear (rather than nearest) filtering by default to avoid blocky look of textures
382 texture->setMinificationFilter( Qt3DRender::QTexture2D::Linear );
383 texture->setMagnificationFilter( Qt3DRender::QTexture2D::Linear );
384
385 if ( tex.sampler >= 0 )
386 {
387 tinygltf::Sampler &sampler = model.samplers[tex.sampler];
388 if ( sampler.minFilter >= 0 )
389 texture->setMinificationFilter( parseTextureFilter( sampler.minFilter ) );
390 if ( sampler.magFilter >= 0 )
391 texture->setMagnificationFilter( parseTextureFilter( sampler.magFilter ) );
392 Qt3DRender::QTextureWrapMode wrapMode;
393 wrapMode.setX( parseTextureWrapMode( sampler.wrapS ) );
394 wrapMode.setY( parseTextureWrapMode( sampler.wrapT ) );
395 texture->setWrapMode( wrapMode );
396 }
397
398 // We should be using PBR material unless unlit material is requested using KHR_materials_unlit
399 // GLTF extension, but in various datasets that extension is not used (even though it should have been).
400 // In the future we may want to have a switch whether to use unlit material or PBR material...
401 QgsTextureMaterial *mat = new QgsTextureMaterial;
402 mat->setTexture( texture );
403 return mat;
404 }
405
406 if ( qgsDoubleNear( pbr.baseColorFactor[3], 0 ) )
407 return nullptr; // completely transparent primitive, just skip it
408
409 QgsMetalRoughMaterial *pbrMaterial = new QgsMetalRoughMaterial;
410 pbrMaterial->setMetalness( pbr.metallicFactor ); // [0..1] or texture
411 pbrMaterial->setRoughness( pbr.roughnessFactor );
412 pbrMaterial->setBaseColor( QColor::fromRgbF( pbr.baseColorFactor[0], pbr.baseColorFactor[1], pbr.baseColorFactor[2], pbr.baseColorFactor[3] ) );
413 return pbrMaterial;
414}
415
416
417static QVector<Qt3DCore::QEntity *> parseNode( tinygltf::Model &model, int nodeIndex, const QgsGltf3DUtils::EntityTransform &transform, const QgsVector3D &tileTranslationEcef, QString baseUri, QMatrix4x4 parentTransform, QStringList *errors )
418{
419 tinygltf::Node &node = model.nodes[nodeIndex];
420
421 QVector<Qt3DCore::QEntity *> entities;
422
423 // transform
424 std::unique_ptr<QMatrix4x4> matrix = QgsGltfUtils::parseNodeTransform( node );
425 if ( !parentTransform.isIdentity() )
426 {
427 if ( matrix )
428 *matrix = parentTransform * *matrix;
429 else
430 {
431 matrix = std::make_unique<QMatrix4x4>( parentTransform );
432 }
433 }
434
435 // mesh
436 if ( node.mesh >= 0 )
437 {
438 tinygltf::Mesh &mesh = model.meshes[node.mesh];
439
440 for ( const tinygltf::Primitive &primitive : mesh.primitives )
441 {
442 if ( primitive.mode != TINYGLTF_MODE_TRIANGLES )
443 {
444 if ( errors )
445 *errors << QStringLiteral( "Unsupported mesh primitive: %1" ).arg( primitive.mode );
446 continue;
447 }
448
449 auto posIt = primitive.attributes.find( "POSITION" );
450 Q_ASSERT( posIt != primitive.attributes.end() );
451 int positionAccessorIndex = posIt->second;
452
453 tinygltf::Accessor &posAccessor = model.accessors[positionAccessorIndex];
454 if ( posAccessor.componentType != TINYGLTF_PARAMETER_TYPE_FLOAT || posAccessor.type != TINYGLTF_TYPE_VEC3 )
455 {
456 if ( errors )
457 *errors << QStringLiteral( "Unsupported position accessor type: %1 / %2" ).arg( posAccessor.componentType ).arg( posAccessor.type );
458 continue;
459 }
460
461 QgsMaterial *material = parseMaterial( model, primitive.material, baseUri, errors );
462 if ( !material )
463 {
464 // primitive should be skipped, eg fully transparent material
465 continue;
466 }
467
468 Qt3DQGeometry *geom = new Qt3DQGeometry;
469
470 Qt3DQAttribute *positionAttribute = reprojectPositions( model, positionAccessorIndex, transform, tileTranslationEcef, matrix.get() );
471 positionAttribute->setName( Qt3DQAttribute::defaultPositionAttributeName() );
472 geom->addAttribute( positionAttribute );
473
474 auto normalIt = primitive.attributes.find( "NORMAL" );
475 if ( normalIt != primitive.attributes.end() )
476 {
477 int normalAccessorIndex = normalIt->second;
478 Qt3DQAttribute *normalAttribute = parseAttribute( model, normalAccessorIndex );
479 normalAttribute->setName( Qt3DQAttribute::defaultNormalAttributeName() );
480 geom->addAttribute( normalAttribute );
481
482 // TODO: we may need to transform normal vectors when we are altering positions
483 // (but quite often normals are actually note needed - e.g. when using textured data)
484 }
485
486 auto texIt = primitive.attributes.find( "TEXCOORD_0" );
487 if ( texIt != primitive.attributes.end() )
488 {
489 int texAccessorIndex = texIt->second;
490 Qt3DQAttribute *texAttribute = parseAttribute( model, texAccessorIndex );
491 texAttribute->setName( Qt3DQAttribute::defaultTextureCoordinateAttributeName() );
492 geom->addAttribute( texAttribute );
493 }
494
495 Qt3DQAttribute *indexAttribute = nullptr;
496 if ( primitive.indices != -1 )
497 {
498 indexAttribute = parseAttribute( model, primitive.indices );
499 geom->addAttribute( indexAttribute );
500 }
501
502 Qt3DRender::QGeometryRenderer *geomRenderer = new Qt3DRender::QGeometryRenderer;
503 geomRenderer->setGeometry( geom );
504 geomRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::Triangles ); // looks like same values as "mode"
505 geomRenderer->setVertexCount( indexAttribute ? indexAttribute->count() : model.accessors[positionAccessorIndex].count );
506
507 // if we are using PBR material, and normal vectors are not present in the data,
508 // they should be auto-generated by us (according to GLTF spec)
509 if ( normalIt == primitive.attributes.end() )
510 {
511 if ( QgsMetalRoughMaterial *pbrMat = qobject_cast<QgsMetalRoughMaterial *>( material ) )
512 {
513 pbrMat->setFlatShadingEnabled( true );
514 }
515 }
516
517 Qt3DCore::QEntity *primitiveEntity = new Qt3DCore::QEntity;
518 primitiveEntity->addComponent( geomRenderer );
519 primitiveEntity->addComponent( material );
520 entities << primitiveEntity;
521 }
522 }
523
524 // recursively add children
525 for ( int childNodeIndex : node.children )
526 {
527 entities << parseNode( model, childNodeIndex, transform, tileTranslationEcef, baseUri, matrix ? *matrix : QMatrix4x4(), errors );
528 }
529
530 return entities;
531}
532
533
534Qt3DCore::QEntity *QgsGltf3DUtils::parsedGltfToEntity( tinygltf::Model &model, const QgsGltf3DUtils::EntityTransform &transform, QString baseUri, QStringList *errors )
535{
536 bool sceneOk = false;
537 const std::size_t sceneIndex = QgsGltfUtils::sourceSceneForModel( model, sceneOk );
538 if ( !sceneOk )
539 {
540 if ( errors )
541 *errors << "No scenes present in the gltf data!";
542 return nullptr;
543 }
544
545 tinygltf::Scene &scene = model.scenes[sceneIndex];
546
547 if ( scene.nodes.size() == 0 )
548 {
549 if ( errors )
550 *errors << "No nodes present in the gltf data!";
551 return nullptr;
552 }
553
554 const QgsVector3D tileTranslationEcef = QgsGltfUtils::extractTileTranslation( model );
555
556 Qt3DCore::QEntity *rootEntity = new Qt3DCore::QEntity;
557 for ( const int nodeIndex : scene.nodes )
558 {
559 const QVector<Qt3DCore::QEntity *> entities = parseNode( model, nodeIndex, transform, tileTranslationEcef, baseUri, QMatrix4x4(), errors );
560 for ( Qt3DCore::QEntity *e : entities )
561 e->setParent( rootEntity );
562 }
563 return rootEntity;
564}
565
566
567Qt3DCore::QEntity *QgsGltf3DUtils::gltfToEntity( const QByteArray &data, const QgsGltf3DUtils::EntityTransform &transform, const QString &baseUri, QStringList *errors )
568{
569 tinygltf::Model model;
570 QString gltfErrors, gltfWarnings;
571
572 bool res = QgsGltfUtils::loadGltfModel( data, model, &gltfErrors, &gltfWarnings );
573 if ( !gltfErrors.isEmpty() )
574 {
575 QgsDebugError( QStringLiteral( "Error raised reading %1: %2" ).arg( baseUri, gltfErrors ) );
576 }
577 if ( !gltfWarnings.isEmpty() )
578 {
579 QgsDebugError( QStringLiteral( "Warnings raised reading %1: %2" ).arg( baseUri, gltfWarnings ) );
580 }
581 if ( !res )
582 {
583 if ( errors )
584 {
585 errors->append( QStringLiteral( "GLTF load error: " ) + gltfErrors );
586 }
587 return nullptr;
588 }
589
590 return parsedGltfToEntity( model, transform, baseUri, errors );
591}
592
593// For TinyGltfTextureImage
594#include "qgsgltf3dutils.moc"
595
A thread safe class for performing blocking (sync) network requests, with full support for QGIS proxy...
ErrorCode get(QNetworkRequest &request, bool forceRefresh=false, QgsFeedback *feedback=nullptr, RequestFlags requestFlags=QgsBlockingNetworkRequest::RequestFlags())
Performs a "get" operation on the specified request.
@ NoError
No error was encountered.
QgsNetworkReplyContent reply() const
Returns the content of the network reply, after a get(), post(), head() or put() request has been mad...
Base class for all materials used within QGIS 3D views.
Definition qgsmaterial.h:39
Encapsulates a network reply within a container which is inexpensive to copy and safe to pass between...
QByteArray content() const
Returns the reply content.
A 3D vector (similar to QVector3D) with the difference that it uses double precision instead of singl...
Definition qgsvector3d.h:30
double y() const
Returns Y coordinate.
Definition qgsvector3d.h:49
double z() const
Returns Z coordinate.
Definition qgsvector3d.h:51
double x() const
Returns X coordinate.
Definition qgsvector3d.h:47
static bool extractFileFromZip(const QString &zipFilename, const QString &filenameInZip, QByteArray &bytesOut)
Extracts a file from a zip archive, returns true on success.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6607
Qt3DCore::QAttribute Qt3DQAttribute
Qt3DCore::QBuffer Qt3DQBuffer
Qt3DCore::QGeometry Qt3DQGeometry
bool operator==(const QgsFeatureIterator &fi1, const QgsFeatureIterator &fi2)
#define QgsDebugError(str)
Definition qgslogger.h:57