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