QGIS API Documentation 3.40.0-Bratislava (b56115d8743)
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 "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.sceneOriginTargetCrs;
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 // QGIS 3D uses base plane (X,-Z) with Y up - so flip the coordinates
170 out[i * 3 + 0] = static_cast< float >( x );
171 out[i * 3 + 1] = static_cast< float >( z );
172 out[i * 3 + 2] = static_cast< float >( -y );
173 }
174
175 Qt3DQBuffer *buffer = new Qt3DQBuffer();
176 buffer->setData( byteArray );
177
178 Qt3DQAttribute *attribute = new Qt3DQAttribute();
179 attribute->setAttributeType( Qt3DQAttribute::VertexAttribute );
180 attribute->setBuffer( buffer );
181 attribute->setByteOffset( 0 );
182 attribute->setByteStride( 12 );
183 attribute->setCount( accessor.count );
184 attribute->setVertexBaseType( Qt3DQAttribute::Float );
185 attribute->setVertexSize( 3 );
186
187 return attribute;
188}
189
190
191// QAbstractFunctor marked as deprecated in 5.15, but undeprecated for Qt 6.0. TODO -- remove when we require 6.0
193
194class TinyGltfTextureImageDataGenerator : public Qt3DRender::QTextureImageDataGenerator
195{
196 public:
197 TinyGltfTextureImageDataGenerator( Qt3DRender::QTextureImageDataPtr imagePtr )
198 : mImagePtr( imagePtr ) {}
199
200 QT3D_FUNCTOR( TinyGltfTextureImageDataGenerator )
201
202 Qt3DRender::QTextureImageDataPtr operator()() override
203 {
204 return mImagePtr;
205 }
206
207 bool operator ==( const QTextureImageDataGenerator &other ) const override
208 {
209 const TinyGltfTextureImageDataGenerator *otherFunctor = functor_cast<TinyGltfTextureImageDataGenerator>( &other );
210 return mImagePtr.get() == otherFunctor->mImagePtr.get();
211 }
212
213 Qt3DRender::QTextureImageDataPtr mImagePtr;
214};
215
217
218class TinyGltfTextureImage : public Qt3DRender::QAbstractTextureImage
219{
220 Q_OBJECT
221 public:
222 TinyGltfTextureImage( tinygltf::Image &image )
223 {
224 Q_ASSERT( image.bits == 8 );
225 Q_ASSERT( image.component == 4 );
226 Q_ASSERT( image.pixel_type == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE );
227
228 imgDataPtr.reset( new Qt3DRender::QTextureImageData );
229 imgDataPtr->setWidth( image.width );
230 imgDataPtr->setHeight( image.height );
231 imgDataPtr->setDepth( 1 ); // not sure what this is
232 imgDataPtr->setFaces( 1 );
233 imgDataPtr->setLayers( 1 );
234 imgDataPtr->setMipLevels( 1 );
235 QByteArray imageBytes( reinterpret_cast<const char *>( image.image.data() ), image.image.size() );
236 imgDataPtr->setData( imageBytes, 4 );
237 imgDataPtr->setFormat( QOpenGLTexture::RGBA8_UNorm );
238 imgDataPtr->setPixelFormat( QOpenGLTexture::BGRA ); // when using tinygltf with STB_image, pixel format is QOpenGLTexture::RGBA
239 imgDataPtr->setPixelType( QOpenGLTexture::UInt8 );
240 imgDataPtr->setTarget( QOpenGLTexture::Target2D );
241 }
242
243 Qt3DRender::QTextureImageDataGeneratorPtr dataGenerator() const override
244 {
245 return Qt3DRender::QTextureImageDataGeneratorPtr( new TinyGltfTextureImageDataGenerator( imgDataPtr ) );
246 }
247
248 Qt3DRender::QTextureImageDataPtr imgDataPtr;
249};
250
251
252// TODO: move elsewhere
253static QByteArray fetchUri( const QUrl &url, QStringList *errors )
254{
255 if ( url.scheme().startsWith( "http" ) )
256 {
257 QNetworkRequest request = QNetworkRequest( url );
258 request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
259 request.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
260 QgsBlockingNetworkRequest networkRequest;
261 // TODO: setup auth, setup headers
262 if ( networkRequest.get( request ) != QgsBlockingNetworkRequest::NoError )
263 {
264 if ( errors )
265 *errors << QStringLiteral( "Failed to download image: %1" ).arg( url.toString() );
266 }
267 else
268 {
269 const QgsNetworkReplyContent content = networkRequest.reply();
270 return content.content();
271 }
272 }
273 else if ( url.isLocalFile() && QFile::exists( url.toLocalFile() ) )
274 {
275 QFile f( url.toLocalFile() );
276 if ( f.open( QIODevice::ReadOnly ) )
277 {
278 return f.readAll();
279 }
280 else
281 {
282 if ( errors )
283 *errors << QStringLiteral( "Unable to open image: %1" ).arg( url.toString() );
284 }
285 }
286 return QByteArray();
287}
288
289// Returns NULLPTR if primitive should not be rendered
290static QgsMaterial *parseMaterial( tinygltf::Model &model, int materialIndex, QString baseUri, QStringList *errors )
291{
292 if ( materialIndex < 0 )
293 {
294 // material unspecified - using default
295 QgsMetalRoughMaterial *defaultMaterial = new QgsMetalRoughMaterial;
296 defaultMaterial->setMetalness( 1 );
297 defaultMaterial->setRoughness( 1 );
298 defaultMaterial->setBaseColor( QColor::fromRgbF( 1, 1, 1 ) );
299 return defaultMaterial;
300 }
301
302 tinygltf::Material &material = model.materials[materialIndex];
303 tinygltf::PbrMetallicRoughness &pbr = material.pbrMetallicRoughness;
304
305 if ( pbr.baseColorTexture.index >= 0 )
306 {
307 tinygltf::Texture &tex = model.textures[pbr.baseColorTexture.index];
308
309 tinygltf::Image &img = model.images[tex.source];
310
311 if ( !img.uri.empty() )
312 {
313 QString imgUri = QString::fromStdString( img.uri );
314 QUrl url = QUrl( baseUri ).resolved( imgUri );
315 QByteArray ba = fetchUri( url, errors );
316 if ( !ba.isEmpty() )
317 {
318 if ( !QgsGltfUtils::loadImageDataWithQImage( &img, -1, nullptr, nullptr, 0, 0, ( const unsigned char * ) ba.constData(), ba.size(), nullptr ) )
319 {
320 if ( errors )
321 *errors << QStringLiteral( "Failed to load image: %1" ).arg( imgUri );
322 }
323 }
324 }
325
326 if ( img.image.empty() )
327 {
328 QgsMetalRoughMaterial *pbrMaterial = new QgsMetalRoughMaterial;
329 pbrMaterial->setMetalness( pbr.metallicFactor ); // [0..1] or texture
330 pbrMaterial->setRoughness( pbr.roughnessFactor );
331 pbrMaterial->setBaseColor( QColor::fromRgbF( pbr.baseColorFactor[0], pbr.baseColorFactor[1], pbr.baseColorFactor[2], pbr.baseColorFactor[3] ) );
332 return pbrMaterial;
333 }
334
335 TinyGltfTextureImage *textureImage = new TinyGltfTextureImage( img );
336
337 Qt3DRender::QTexture2D *texture = new Qt3DRender::QTexture2D;
338 texture->addTextureImage( textureImage ); // textures take the ownership of textureImage if has no parant
339
340 // let's use linear (rather than nearest) filtering by default to avoid blocky look of textures
341 texture->setMinificationFilter( Qt3DRender::QTexture2D::Linear );
342 texture->setMagnificationFilter( Qt3DRender::QTexture2D::Linear );
343
344 if ( tex.sampler >= 0 )
345 {
346 tinygltf::Sampler &sampler = model.samplers[tex.sampler];
347 if ( sampler.minFilter >= 0 )
348 texture->setMinificationFilter( parseTextureFilter( sampler.minFilter ) );
349 if ( sampler.magFilter >= 0 )
350 texture->setMagnificationFilter( parseTextureFilter( sampler.magFilter ) );
351 Qt3DRender::QTextureWrapMode wrapMode;
352 wrapMode.setX( parseTextureWrapMode( sampler.wrapS ) );
353 wrapMode.setY( parseTextureWrapMode( sampler.wrapT ) );
354 texture->setWrapMode( wrapMode );
355 }
356
357 // We should be using PBR material unless unlit material is requested using KHR_materials_unlit
358 // GLTF extension, but in various datasets that extension is not used (even though it should have been).
359 // In the future we may want to have a switch whether to use unlit material or PBR material...
360 QgsTextureMaterial *mat = new QgsTextureMaterial;
361 mat->setTexture( texture );
362 return mat;
363 }
364
365 if ( qgsDoubleNear( pbr.baseColorFactor[3], 0 ) )
366 return nullptr; // completely transparent primitive, just skip it
367
368 QgsMetalRoughMaterial *pbrMaterial = new QgsMetalRoughMaterial;
369 pbrMaterial->setMetalness( pbr.metallicFactor ); // [0..1] or texture
370 pbrMaterial->setRoughness( pbr.roughnessFactor );
371 pbrMaterial->setBaseColor( QColor::fromRgbF( pbr.baseColorFactor[0], pbr.baseColorFactor[1], pbr.baseColorFactor[2], pbr.baseColorFactor[3] ) );
372 return pbrMaterial;
373}
374
375
376static QVector<Qt3DCore::QEntity *> parseNode( tinygltf::Model &model, int nodeIndex, const QgsGltf3DUtils::EntityTransform &transform, const QgsVector3D &tileTranslationEcef, QString baseUri, QMatrix4x4 parentTransform, QStringList *errors )
377{
378 tinygltf::Node &node = model.nodes[nodeIndex];
379
380 QVector<Qt3DCore::QEntity *> entities;
381
382 // transform
383 std::unique_ptr<QMatrix4x4> matrix = QgsGltfUtils::parseNodeTransform( node );
384 if ( !parentTransform.isIdentity() )
385 {
386 if ( matrix )
387 *matrix = parentTransform * *matrix;
388 else
389 {
390 matrix.reset( new QMatrix4x4( parentTransform ) );
391 }
392 }
393
394 // mesh
395 if ( node.mesh >= 0 )
396 {
397 tinygltf::Mesh &mesh = model.meshes[node.mesh];
398
399 for ( const tinygltf::Primitive &primitive : mesh.primitives )
400 {
401 if ( primitive.mode != TINYGLTF_MODE_TRIANGLES )
402 {
403 if ( errors )
404 *errors << QStringLiteral( "Unsupported mesh primitive: %1" ).arg( primitive.mode );
405 continue;
406 }
407
408 auto posIt = primitive.attributes.find( "POSITION" );
409 Q_ASSERT( posIt != primitive.attributes.end() );
410 int positionAccessorIndex = posIt->second;
411
412 tinygltf::Accessor &posAccessor = model.accessors[positionAccessorIndex];
413 if ( posAccessor.componentType != TINYGLTF_PARAMETER_TYPE_FLOAT || posAccessor.type != TINYGLTF_TYPE_VEC3 )
414 {
415 if ( errors )
416 *errors << QStringLiteral( "Unsupported position accessor type: %1 / %2" ).arg( posAccessor.componentType ).arg( posAccessor.type );
417 continue;
418 }
419
420 QgsMaterial *material = parseMaterial( model, primitive.material, baseUri, errors );
421 if ( !material )
422 {
423 // primitive should be skipped, eg fully transparent material
424 continue;
425 }
426
427 Qt3DQGeometry *geom = new Qt3DQGeometry;
428
429 Qt3DQAttribute *positionAttribute = reprojectPositions( model, positionAccessorIndex, transform, tileTranslationEcef, matrix.get() );
430 positionAttribute->setName( Qt3DQAttribute::defaultPositionAttributeName() );
431 geom->addAttribute( positionAttribute );
432
433 auto normalIt = primitive.attributes.find( "NORMAL" );
434 if ( normalIt != primitive.attributes.end() )
435 {
436 int normalAccessorIndex = normalIt->second;
437 Qt3DQAttribute *normalAttribute = parseAttribute( model, normalAccessorIndex );
438 normalAttribute->setName( Qt3DQAttribute::defaultNormalAttributeName() );
439 geom->addAttribute( normalAttribute );
440
441 // TODO: we may need to transform normal vectors when we are altering positions
442 // (but quite often normals are actually note needed - e.g. when using textured data)
443 }
444
445 auto texIt = primitive.attributes.find( "TEXCOORD_0" );
446 if ( texIt != primitive.attributes.end() )
447 {
448 int texAccessorIndex = texIt->second;
449 Qt3DQAttribute *texAttribute = parseAttribute( model, texAccessorIndex );
450 texAttribute->setName( Qt3DQAttribute::defaultTextureCoordinateAttributeName() );
451 geom->addAttribute( texAttribute );
452 }
453
454 Qt3DQAttribute *indexAttribute = nullptr;
455 if ( primitive.indices != -1 )
456 {
457 indexAttribute = parseAttribute( model, primitive.indices );
458 geom->addAttribute( indexAttribute );
459 }
460
461 Qt3DRender::QGeometryRenderer *geomRenderer = new Qt3DRender::QGeometryRenderer;
462 geomRenderer->setGeometry( geom );
463 geomRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::Triangles ); // looks like same values as "mode"
464 geomRenderer->setVertexCount( indexAttribute ? indexAttribute->count() : model.accessors[positionAccessorIndex].count );
465
466 // if we are using PBR material, and normal vectors are not present in the data,
467 // they should be auto-generated by us (according to GLTF spec)
468 if ( normalIt == primitive.attributes.end() )
469 {
470 if ( QgsMetalRoughMaterial *pbrMat = qobject_cast<QgsMetalRoughMaterial *>( material ) )
471 {
472 pbrMat->setFlatShadingEnabled( true );
473 }
474 }
475
476 Qt3DCore::QEntity *primitiveEntity = new Qt3DCore::QEntity;
477 primitiveEntity->addComponent( geomRenderer );
478 primitiveEntity->addComponent( material );
479 entities << primitiveEntity;
480 }
481 }
482
483 // recursively add children
484 for ( int childNodeIndex : node.children )
485 {
486 entities << parseNode( model, childNodeIndex, transform, tileTranslationEcef, baseUri, matrix ? *matrix : QMatrix4x4(), errors );
487 }
488
489 return entities;
490}
491
492
493Qt3DCore::QEntity *QgsGltf3DUtils::parsedGltfToEntity( tinygltf::Model &model, const QgsGltf3DUtils::EntityTransform &transform, QString baseUri, QStringList *errors )
494{
495 bool sceneOk = false;
496 const std::size_t sceneIndex = QgsGltfUtils::sourceSceneForModel( model, sceneOk );
497 if ( !sceneOk )
498 {
499 if ( errors )
500 *errors << "No scenes present in the gltf data!";
501 return nullptr;
502 }
503
504 tinygltf::Scene &scene = model.scenes[sceneIndex];
505
506 if ( scene.nodes.size() == 0 )
507 {
508 if ( errors )
509 *errors << "No nodes present in the gltf data!";
510 return nullptr;
511 }
512
513 const QgsVector3D tileTranslationEcef = QgsGltfUtils::extractTileTranslation( model );
514
515 Qt3DCore::QEntity *rootEntity = new Qt3DCore::QEntity;
516 for ( const int nodeIndex : scene.nodes )
517 {
518 const QVector<Qt3DCore::QEntity *> entities = parseNode( model, nodeIndex, transform, tileTranslationEcef, baseUri, QMatrix4x4(), errors );
519 for ( Qt3DCore::QEntity *e : entities )
520 e->setParent( rootEntity );
521 }
522 return rootEntity;
523}
524
525
526Qt3DCore::QEntity *QgsGltf3DUtils::gltfToEntity( const QByteArray &data, const QgsGltf3DUtils::EntityTransform &transform, const QString &baseUri, QStringList *errors )
527{
528 tinygltf::Model model;
529 QString gltfErrors, gltfWarnings;
530
531 bool res = QgsGltfUtils::loadGltfModel( data, model, &gltfErrors, &gltfWarnings );
532 if ( !gltfErrors.isEmpty() )
533 {
534 QgsDebugError( QStringLiteral( "Error raised reading %1: %2" ).arg( baseUri, gltfErrors ) );
535 }
536 if ( !gltfWarnings.isEmpty() )
537 {
538 QgsDebugError( QStringLiteral( "Warnings raised reading %1: %2" ).arg( baseUri, gltfWarnings ) );
539 }
540 if ( !res )
541 {
542 if ( errors )
543 {
544 errors->append( QStringLiteral( "GLTF load error: " ) + gltfErrors );
545 }
546 return nullptr;
547 }
548
549 return parsedGltfToEntity( model, transform, baseUri, errors );
550}
551
552// For TinyGltfTextureImage
553#include "qgsgltf3dutils.moc"
554
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:6494
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:6493
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:5917
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:38