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