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