QGIS API Documentation 3.41.0-Master (af5edcb665c)
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.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 tinygltf::Image &img = model.images[tex.source];
309
310 if ( !img.uri.empty() )
311 {
312 QString imgUri = QString::fromStdString( img.uri );
313 QUrl url = QUrl( baseUri ).resolved( imgUri );
314 QByteArray ba = fetchUri( url, errors );
315 if ( !ba.isEmpty() )
316 {
317 if ( !QgsGltfUtils::loadImageDataWithQImage( &img, -1, nullptr, nullptr, 0, 0, ( const unsigned char * ) ba.constData(), ba.size(), nullptr ) )
318 {
319 if ( errors )
320 *errors << QStringLiteral( "Failed to load image: %1" ).arg( imgUri );
321 }
322 }
323 }
324
325 if ( img.image.empty() )
326 {
327 QgsMetalRoughMaterial *pbrMaterial = new QgsMetalRoughMaterial;
328 pbrMaterial->setMetalness( pbr.metallicFactor ); // [0..1] or texture
329 pbrMaterial->setRoughness( pbr.roughnessFactor );
330 pbrMaterial->setBaseColor( QColor::fromRgbF( pbr.baseColorFactor[0], pbr.baseColorFactor[1], pbr.baseColorFactor[2], pbr.baseColorFactor[3] ) );
331 return pbrMaterial;
332 }
333
334 TinyGltfTextureImage *textureImage = new TinyGltfTextureImage( img );
335
336 Qt3DRender::QTexture2D *texture = new Qt3DRender::QTexture2D;
337 texture->addTextureImage( textureImage ); // textures take the ownership of textureImage if has no parant
338
339 // let's use linear (rather than nearest) filtering by default to avoid blocky look of textures
340 texture->setMinificationFilter( Qt3DRender::QTexture2D::Linear );
341 texture->setMagnificationFilter( Qt3DRender::QTexture2D::Linear );
342
343 if ( tex.sampler >= 0 )
344 {
345 tinygltf::Sampler &sampler = model.samplers[tex.sampler];
346 if ( sampler.minFilter >= 0 )
347 texture->setMinificationFilter( parseTextureFilter( sampler.minFilter ) );
348 if ( sampler.magFilter >= 0 )
349 texture->setMagnificationFilter( parseTextureFilter( sampler.magFilter ) );
350 Qt3DRender::QTextureWrapMode wrapMode;
351 wrapMode.setX( parseTextureWrapMode( sampler.wrapS ) );
352 wrapMode.setY( parseTextureWrapMode( sampler.wrapT ) );
353 texture->setWrapMode( wrapMode );
354 }
355
356 // We should be using PBR material unless unlit material is requested using KHR_materials_unlit
357 // GLTF extension, but in various datasets that extension is not used (even though it should have been).
358 // In the future we may want to have a switch whether to use unlit material or PBR material...
359 QgsTextureMaterial *mat = new QgsTextureMaterial;
360 mat->setTexture( texture );
361 return mat;
362 }
363
364 if ( qgsDoubleNear( pbr.baseColorFactor[3], 0 ) )
365 return nullptr; // completely transparent primitive, just skip it
366
367 QgsMetalRoughMaterial *pbrMaterial = new QgsMetalRoughMaterial;
368 pbrMaterial->setMetalness( pbr.metallicFactor ); // [0..1] or texture
369 pbrMaterial->setRoughness( pbr.roughnessFactor );
370 pbrMaterial->setBaseColor( QColor::fromRgbF( pbr.baseColorFactor[0], pbr.baseColorFactor[1], pbr.baseColorFactor[2], pbr.baseColorFactor[3] ) );
371 return pbrMaterial;
372}
373
374
375static QVector<Qt3DCore::QEntity *> parseNode( tinygltf::Model &model, int nodeIndex, const QgsGltf3DUtils::EntityTransform &transform, const QgsVector3D &tileTranslationEcef, QString baseUri, QMatrix4x4 parentTransform, QStringList *errors )
376{
377 tinygltf::Node &node = model.nodes[nodeIndex];
378
379 QVector<Qt3DCore::QEntity *> entities;
380
381 // transform
382 std::unique_ptr<QMatrix4x4> matrix = QgsGltfUtils::parseNodeTransform( node );
383 if ( !parentTransform.isIdentity() )
384 {
385 if ( matrix )
386 *matrix = parentTransform * *matrix;
387 else
388 {
389 matrix.reset( new QMatrix4x4( parentTransform ) );
390 }
391 }
392
393 // mesh
394 if ( node.mesh >= 0 )
395 {
396 tinygltf::Mesh &mesh = model.meshes[node.mesh];
397
398 for ( const tinygltf::Primitive &primitive : mesh.primitives )
399 {
400 if ( primitive.mode != TINYGLTF_MODE_TRIANGLES )
401 {
402 if ( errors )
403 *errors << QStringLiteral( "Unsupported mesh primitive: %1" ).arg( primitive.mode );
404 continue;
405 }
406
407 auto posIt = primitive.attributes.find( "POSITION" );
408 Q_ASSERT( posIt != primitive.attributes.end() );
409 int positionAccessorIndex = posIt->second;
410
411 tinygltf::Accessor &posAccessor = model.accessors[positionAccessorIndex];
412 if ( posAccessor.componentType != TINYGLTF_PARAMETER_TYPE_FLOAT || posAccessor.type != TINYGLTF_TYPE_VEC3 )
413 {
414 if ( errors )
415 *errors << QStringLiteral( "Unsupported position accessor type: %1 / %2" ).arg( posAccessor.componentType ).arg( posAccessor.type );
416 continue;
417 }
418
419 QgsMaterial *material = parseMaterial( model, primitive.material, baseUri, errors );
420 if ( !material )
421 {
422 // primitive should be skipped, eg fully transparent material
423 continue;
424 }
425
426 Qt3DQGeometry *geom = new Qt3DQGeometry;
427
428 Qt3DQAttribute *positionAttribute = reprojectPositions( model, positionAccessorIndex, transform, tileTranslationEcef, matrix.get() );
429 positionAttribute->setName( Qt3DQAttribute::defaultPositionAttributeName() );
430 geom->addAttribute( positionAttribute );
431
432 auto normalIt = primitive.attributes.find( "NORMAL" );
433 if ( normalIt != primitive.attributes.end() )
434 {
435 int normalAccessorIndex = normalIt->second;
436 Qt3DQAttribute *normalAttribute = parseAttribute( model, normalAccessorIndex );
437 normalAttribute->setName( Qt3DQAttribute::defaultNormalAttributeName() );
438 geom->addAttribute( normalAttribute );
439
440 // TODO: we may need to transform normal vectors when we are altering positions
441 // (but quite often normals are actually note needed - e.g. when using textured data)
442 }
443
444 auto texIt = primitive.attributes.find( "TEXCOORD_0" );
445 if ( texIt != primitive.attributes.end() )
446 {
447 int texAccessorIndex = texIt->second;
448 Qt3DQAttribute *texAttribute = parseAttribute( model, texAccessorIndex );
449 texAttribute->setName( Qt3DQAttribute::defaultTextureCoordinateAttributeName() );
450 geom->addAttribute( texAttribute );
451 }
452
453 Qt3DQAttribute *indexAttribute = nullptr;
454 if ( primitive.indices != -1 )
455 {
456 indexAttribute = parseAttribute( model, primitive.indices );
457 geom->addAttribute( indexAttribute );
458 }
459
460 Qt3DRender::QGeometryRenderer *geomRenderer = new Qt3DRender::QGeometryRenderer;
461 geomRenderer->setGeometry( geom );
462 geomRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::Triangles ); // looks like same values as "mode"
463 geomRenderer->setVertexCount( indexAttribute ? indexAttribute->count() : model.accessors[positionAccessorIndex].count );
464
465 // if we are using PBR material, and normal vectors are not present in the data,
466 // they should be auto-generated by us (according to GLTF spec)
467 if ( normalIt == primitive.attributes.end() )
468 {
469 if ( QgsMetalRoughMaterial *pbrMat = qobject_cast<QgsMetalRoughMaterial *>( material ) )
470 {
471 pbrMat->setFlatShadingEnabled( true );
472 }
473 }
474
475 Qt3DCore::QEntity *primitiveEntity = new Qt3DCore::QEntity;
476 primitiveEntity->addComponent( geomRenderer );
477 primitiveEntity->addComponent( material );
478 entities << primitiveEntity;
479 }
480 }
481
482 // recursively add children
483 for ( int childNodeIndex : node.children )
484 {
485 entities << parseNode( model, childNodeIndex, transform, tileTranslationEcef, baseUri, matrix ? *matrix : QMatrix4x4(), errors );
486 }
487
488 return entities;
489}
490
491
492Qt3DCore::QEntity *QgsGltf3DUtils::parsedGltfToEntity( tinygltf::Model &model, const QgsGltf3DUtils::EntityTransform &transform, QString baseUri, QStringList *errors )
493{
494 bool sceneOk = false;
495 const std::size_t sceneIndex = QgsGltfUtils::sourceSceneForModel( model, sceneOk );
496 if ( !sceneOk )
497 {
498 if ( errors )
499 *errors << "No scenes present in the gltf data!";
500 return nullptr;
501 }
502
503 tinygltf::Scene &scene = model.scenes[sceneIndex];
504
505 if ( scene.nodes.size() == 0 )
506 {
507 if ( errors )
508 *errors << "No nodes present in the gltf data!";
509 return nullptr;
510 }
511
512 const QgsVector3D tileTranslationEcef = QgsGltfUtils::extractTileTranslation( model );
513
514 Qt3DCore::QEntity *rootEntity = new Qt3DCore::QEntity;
515 for ( const int nodeIndex : scene.nodes )
516 {
517 const QVector<Qt3DCore::QEntity *> entities = parseNode( model, nodeIndex, transform, tileTranslationEcef, baseUri, QMatrix4x4(), errors );
518 for ( Qt3DCore::QEntity *e : entities )
519 e->setParent( rootEntity );
520 }
521 return rootEntity;
522}
523
524
525Qt3DCore::QEntity *QgsGltf3DUtils::gltfToEntity( const QByteArray &data, const QgsGltf3DUtils::EntityTransform &transform, const QString &baseUri, QStringList *errors )
526{
527 tinygltf::Model model;
528 QString gltfErrors, gltfWarnings;
529
530 bool res = QgsGltfUtils::loadGltfModel( data, model, &gltfErrors, &gltfWarnings );
531 if ( !gltfErrors.isEmpty() )
532 {
533 QgsDebugError( QStringLiteral( "Error raised reading %1: %2" ).arg( baseUri, gltfErrors ) );
534 }
535 if ( !gltfWarnings.isEmpty() )
536 {
537 QgsDebugError( QStringLiteral( "Warnings raised reading %1: %2" ).arg( baseUri, gltfWarnings ) );
538 }
539 if ( !res )
540 {
541 if ( errors )
542 {
543 errors->append( QStringLiteral( "GLTF load error: " ) + gltfErrors );
544 }
545 return nullptr;
546 }
547
548 return parsedGltfToEntity( model, transform, baseUri, errors );
549}
550
551// For TinyGltfTextureImage
552#include "qgsgltf3dutils.moc"
553
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:6643
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:6642
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:6066
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