33#include <Qt3DCore/QAttribute>
34#include <Qt3DCore/QBuffer>
35#include <Qt3DCore/QEntity>
36#include <Qt3DCore/QGeometry>
37#include <Qt3DRender/QGeometryRenderer>
38#include <Qt3DRender/QTexture>
40using namespace Qt::StringLiterals;
44static Qt3DCore::QAttribute::VertexBaseType parseVertexBaseType(
int componentType )
46 switch ( componentType )
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;
66 return Qt3DCore::QAttribute::UnsignedInt;
70static Qt3DRender::QAbstractTexture::Filter parseTextureFilter(
int filter )
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;
89 return Qt3DRender::QTexture2D::Nearest;
92static Qt3DRender::QTextureWrapMode::WrapMode parseTextureWrapMode(
int wrapMode )
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;
105 return Qt3DRender::QTextureWrapMode::Repeat;
109static Qt3DCore::QAttribute *parseAttribute( tinygltf::Model &model,
int accessorIndex )
111 tinygltf::Accessor &accessor = model.accessors[accessorIndex];
112 tinygltf::BufferView &bv = model.bufferViews[accessor.bufferView];
113 tinygltf::Buffer &b = model.buffers[bv.buffer];
116 QByteArray byteArray(
reinterpret_cast<const char *
>( b.data.data() ),
117 static_cast<int>( b.data.size() ) );
118 Qt3DCore::QBuffer *buffer =
new Qt3DCore::QBuffer();
119 buffer->setData( byteArray );
121 Qt3DCore::QAttribute *attribute =
new Qt3DCore::QAttribute();
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 );
129 attribute->setBuffer( buffer );
130 attribute->setByteOffset( bv.byteOffset + accessor.byteOffset );
131 attribute->setByteStride( bv.byteStride );
132 attribute->setCount( accessor.count );
133 attribute->setVertexBaseType( parseVertexBaseType( accessor.componentType ) );
134 attribute->setVertexSize( tinygltf::GetNumComponentsInType( accessor.type ) );
140static Qt3DCore::QAttribute *reprojectPositions( tinygltf::Model &model,
int accessorIndex,
const QgsGltf3DUtils::EntityTransform &transform,
const QgsVector3D &tileTranslationEcef, QMatrix4x4 *matrix )
142 tinygltf::Accessor &accessor = model.accessors[accessorIndex];
144 QVector<double> vx, vy, vz;
145 bool res = QgsGltfUtils::accessorToMapCoordinates( model, accessorIndex, transform.tileTransform, transform.ecefToTargetCrs, tileTranslationEcef, matrix, transform.gltfUpAxis, vx, vy, vz );
149 QByteArray byteArray;
150 byteArray.resize( accessor.count * 4 * 3 );
151 float *out =
reinterpret_cast<float *
>( byteArray.data() );
153 QgsVector3D sceneOrigin = transform.chunkOriginTargetCrs;
154 for (
int i = 0; i < static_cast<int>( accessor.count ); ++i )
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();
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 );
165 Qt3DCore::QBuffer *buffer =
new Qt3DCore::QBuffer();
166 buffer->setData( byteArray );
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 );
180class TinyGltfTextureImageDataGenerator :
public Qt3DRender::QTextureImageDataGenerator
183 TinyGltfTextureImageDataGenerator( Qt3DRender::QTextureImageDataPtr imagePtr )
184 : mImagePtr( imagePtr ) {}
186 Qt3DRender::QTextureImageDataPtr operator()()
override
191 qintptr id()
const override
193 return reinterpret_cast<qintptr
>( &Qt3DCore::FunctorType<TinyGltfTextureImageDataGenerator>::id );
196 bool operator==(
const QTextureImageDataGenerator &other )
const override
198 const TinyGltfTextureImageDataGenerator *otherFunctor =
dynamic_cast<const TinyGltfTextureImageDataGenerator *
>( &other );
199 return otherFunctor && mImagePtr.get() == otherFunctor->mImagePtr.get();
202 Qt3DRender::QTextureImageDataPtr mImagePtr;
205class TinyGltfTextureImage :
public Qt3DRender::QAbstractTextureImage
209 TinyGltfTextureImage( tinygltf::Image &image )
211 Q_ASSERT( image.bits == 8 );
212 Q_ASSERT( image.component == 4 );
213 Q_ASSERT( image.pixel_type == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE );
215 imgDataPtr.reset(
new Qt3DRender::QTextureImageData );
216 imgDataPtr->setWidth( image.width );
217 imgDataPtr->setHeight( image.height );
218 imgDataPtr->setDepth( 1 );
219 imgDataPtr->setFaces( 1 );
220 imgDataPtr->setLayers( 1 );
221 imgDataPtr->setMipLevels( 1 );
222 QByteArray imageBytes(
reinterpret_cast<const char *
>( image.image.data() ), image.image.size() );
223 imgDataPtr->setData( imageBytes, 4 );
224 imgDataPtr->setFormat( QOpenGLTexture::RGBA8_UNorm );
225 imgDataPtr->setPixelFormat( QOpenGLTexture::BGRA );
226 imgDataPtr->setPixelType( QOpenGLTexture::UInt8 );
227 imgDataPtr->setTarget( QOpenGLTexture::Target2D );
230 Qt3DRender::QTextureImageDataGeneratorPtr dataGenerator()
const override
232 return Qt3DRender::QTextureImageDataGeneratorPtr(
new TinyGltfTextureImageDataGenerator( imgDataPtr ) );
235 Qt3DRender::QTextureImageDataPtr imgDataPtr;
240static QByteArray fetchUri(
const QUrl &url, QStringList *errors )
242 if ( url.scheme().startsWith(
"http" ) )
244 QNetworkRequest request = QNetworkRequest( url );
245 request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
246 request.setAttribute( QNetworkRequest::CacheSaveControlAttribute,
true );
252 *errors << u
"Failed to download image: %1"_s.arg( url.toString() );
260 else if ( url.isLocalFile() )
262 QString localFilePath = url.toLocalFile();
263 if ( localFilePath.contains(
".slpk/" ) )
265 const QStringList parts = localFilePath.split( u
".slpk/"_s );
266 if ( parts.size() == 2 )
268 QString slpkPath = parts[0] +
".slpk";
269 QString imagePath = parts[1];
271 QByteArray imageData;
279 *errors << u
"Unable to extract image '%1' from SLPK archive: %2"_s.arg( imagePath ).arg( slpkPath );
285 *errors << u
"Missing image path in SLPK archive: %1"_s.arg( localFilePath );
288 else if ( QFile::exists( localFilePath ) )
290 QFile f( localFilePath );
291 if ( f.open( QIODevice::ReadOnly ) )
299 *errors << u
"Unable to open image: %1"_s.arg( url.toString() );
306static QgsMaterial *parseMaterial( tinygltf::Model &model,
int materialIndex, QString baseUri, QStringList *errors )
308 if ( materialIndex < 0 )
311 QgsMetalRoughMaterial *defaultMaterial =
new QgsMetalRoughMaterial;
312 defaultMaterial->setMetalness( 1 );
313 defaultMaterial->setRoughness( 1 );
314 defaultMaterial->setBaseColor( QColor::fromRgbF( 1, 1, 1 ) );
315 return defaultMaterial;
318 tinygltf::Material &material = model.materials[materialIndex];
319 tinygltf::PbrMetallicRoughness &pbr = material.pbrMetallicRoughness;
321 if ( pbr.baseColorTexture.index >= 0 )
323 tinygltf::Texture &tex = model.textures[pbr.baseColorTexture.index];
326 if ( tex.source < 0 )
328 QgsMetalRoughMaterial *pbrMaterial =
new QgsMetalRoughMaterial;
329 pbrMaterial->setMetalness( pbr.metallicFactor );
330 pbrMaterial->setRoughness( pbr.roughnessFactor );
331 pbrMaterial->setBaseColor( QColor::fromRgbF( pbr.baseColorFactor[0], pbr.baseColorFactor[1], pbr.baseColorFactor[2], pbr.baseColorFactor[3] ) );
335 tinygltf::Image &img = model.images[tex.source];
337 if ( !img.uri.empty() )
339 QString imgUri = QString::fromStdString( img.uri );
340 QUrl url = QUrl( baseUri ).resolved( imgUri );
341 QByteArray ba = fetchUri( url, errors );
344 if ( !QgsGltfUtils::loadImageDataWithQImage( &img, -1,
nullptr,
nullptr, 0, 0, (
const unsigned char * ) ba.constData(), ba.size(),
nullptr ) )
347 *errors << u
"Failed to load image: %1"_s.arg( imgUri );
352 if ( img.image.empty() )
354 QgsMetalRoughMaterial *pbrMaterial =
new QgsMetalRoughMaterial;
355 pbrMaterial->setMetalness( pbr.metallicFactor );
356 pbrMaterial->setRoughness( pbr.roughnessFactor );
357 pbrMaterial->setBaseColor( QColor::fromRgbF( pbr.baseColorFactor[0], pbr.baseColorFactor[1], pbr.baseColorFactor[2], pbr.baseColorFactor[3] ) );
361 TinyGltfTextureImage *textureImage =
new TinyGltfTextureImage( img );
363 Qt3DRender::QTexture2D *texture =
new Qt3DRender::QTexture2D;
364 texture->addTextureImage( textureImage );
367 texture->setMinificationFilter( Qt3DRender::QTexture2D::Linear );
368 texture->setMagnificationFilter( Qt3DRender::QTexture2D::Linear );
370 if ( tex.sampler >= 0 )
372 tinygltf::Sampler &sampler = model.samplers[tex.sampler];
373 if ( sampler.minFilter >= 0 )
374 texture->setMinificationFilter( parseTextureFilter( sampler.minFilter ) );
375 if ( sampler.magFilter >= 0 )
376 texture->setMagnificationFilter( parseTextureFilter( sampler.magFilter ) );
377 Qt3DRender::QTextureWrapMode wrapMode;
378 wrapMode.setX( parseTextureWrapMode( sampler.wrapS ) );
379 wrapMode.setY( parseTextureWrapMode( sampler.wrapT ) );
380 texture->setWrapMode( wrapMode );
386 QgsTextureMaterial *mat =
new QgsTextureMaterial;
387 mat->setTexture( texture );
394 QgsMetalRoughMaterial *pbrMaterial =
new QgsMetalRoughMaterial;
395 pbrMaterial->setMetalness( pbr.metallicFactor );
396 pbrMaterial->setRoughness( pbr.roughnessFactor );
397 pbrMaterial->setBaseColor( QColor::fromRgbF( pbr.baseColorFactor[0], pbr.baseColorFactor[1], pbr.baseColorFactor[2], pbr.baseColorFactor[3] ) );
402static QVector<Qt3DCore::QEntity *> parseNode( tinygltf::Model &model,
int nodeIndex,
const QgsGltf3DUtils::EntityTransform &transform,
const QgsVector3D &tileTranslationEcef, QString baseUri, QMatrix4x4 parentTransform, QStringList *errors )
404 tinygltf::Node &node = model.nodes[nodeIndex];
406 QVector<Qt3DCore::QEntity *> entities;
409 std::unique_ptr<QMatrix4x4> matrix = QgsGltfUtils::parseNodeTransform( node );
410 if ( !parentTransform.isIdentity() )
413 *matrix = parentTransform * *matrix;
416 matrix = std::make_unique<QMatrix4x4>( parentTransform );
421 if ( node.mesh >= 0 )
423 tinygltf::Mesh &mesh = model.meshes[node.mesh];
425 for (
const tinygltf::Primitive &primitive : mesh.primitives )
427 if ( primitive.mode != TINYGLTF_MODE_TRIANGLES )
430 *errors << u
"Unsupported mesh primitive: %1"_s.arg( primitive.mode );
434 auto posIt = primitive.attributes.find(
"POSITION" );
435 Q_ASSERT( posIt != primitive.attributes.end() );
436 int positionAccessorIndex = posIt->second;
438 tinygltf::Accessor &posAccessor = model.accessors[positionAccessorIndex];
439 if ( posAccessor.componentType != TINYGLTF_PARAMETER_TYPE_FLOAT || posAccessor.type != TINYGLTF_TYPE_VEC3 )
442 *errors << u
"Unsupported position accessor type: %1 / %2"_s.arg( posAccessor.componentType ).arg( posAccessor.type );
446 QgsMaterial *material = parseMaterial( model, primitive.material, baseUri, errors );
453 Qt3DCore::QGeometry *geom =
new Qt3DCore::QGeometry;
455 Qt3DCore::QAttribute *positionAttribute = reprojectPositions( model, positionAccessorIndex, transform, tileTranslationEcef, matrix.get() );
456 positionAttribute->setName( Qt3DCore::QAttribute::defaultPositionAttributeName() );
457 geom->addAttribute( positionAttribute );
459 auto normalIt = primitive.attributes.find(
"NORMAL" );
460 if ( normalIt != primitive.attributes.end() )
462 int normalAccessorIndex = normalIt->second;
463 Qt3DCore::QAttribute *normalAttribute = parseAttribute( model, normalAccessorIndex );
464 normalAttribute->setName( Qt3DCore::QAttribute::defaultNormalAttributeName() );
465 geom->addAttribute( normalAttribute );
471 auto texIt = primitive.attributes.find(
"TEXCOORD_0" );
472 if ( texIt != primitive.attributes.end() )
474 int texAccessorIndex = texIt->second;
475 Qt3DCore::QAttribute *texAttribute = parseAttribute( model, texAccessorIndex );
476 texAttribute->setName( Qt3DCore::QAttribute::defaultTextureCoordinateAttributeName() );
477 geom->addAttribute( texAttribute );
480 Qt3DCore::QAttribute *indexAttribute =
nullptr;
481 if ( primitive.indices != -1 )
483 indexAttribute = parseAttribute( model, primitive.indices );
484 geom->addAttribute( indexAttribute );
487 Qt3DRender::QGeometryRenderer *geomRenderer =
new Qt3DRender::QGeometryRenderer;
488 geomRenderer->setGeometry( geom );
489 geomRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::Triangles );
490 geomRenderer->setVertexCount( indexAttribute ? indexAttribute->count() : model.accessors[positionAccessorIndex].count );
494 if ( normalIt == primitive.attributes.end() )
496 if ( QgsMetalRoughMaterial *pbrMat = qobject_cast<QgsMetalRoughMaterial *>( material ) )
498 pbrMat->setFlatShadingEnabled(
true );
502 Qt3DCore::QEntity *primitiveEntity =
new Qt3DCore::QEntity;
503 primitiveEntity->addComponent( geomRenderer );
504 primitiveEntity->addComponent( material );
505 entities << primitiveEntity;
510 for (
int childNodeIndex : node.children )
512 entities << parseNode( model, childNodeIndex, transform, tileTranslationEcef, baseUri, matrix ? *matrix : QMatrix4x4(), errors );
519Qt3DCore::QEntity *QgsGltf3DUtils::parsedGltfToEntity( tinygltf::Model &model,
const QgsGltf3DUtils::EntityTransform &transform, QString baseUri, QStringList *errors )
521 bool sceneOk =
false;
522 const std::size_t sceneIndex = QgsGltfUtils::sourceSceneForModel( model, sceneOk );
526 *errors <<
"No scenes present in the gltf data!";
530 tinygltf::Scene &scene = model.scenes[sceneIndex];
532 if ( scene.nodes.size() == 0 )
535 *errors <<
"No nodes present in the gltf data!";
539 const QgsVector3D tileTranslationEcef = QgsGltfUtils::extractTileTranslation( model );
541 Qt3DCore::QEntity *rootEntity =
new Qt3DCore::QEntity;
542 for (
const int nodeIndex : scene.nodes )
544 const QVector<Qt3DCore::QEntity *> entities = parseNode( model, nodeIndex, transform, tileTranslationEcef, baseUri, QMatrix4x4(), errors );
545 for ( Qt3DCore::QEntity *e : entities )
546 e->setParent( rootEntity );
552Qt3DCore::QEntity *QgsGltf3DUtils::gltfToEntity(
const QByteArray &data,
const QgsGltf3DUtils::EntityTransform &transform,
const QString &baseUri, QStringList *errors )
554 tinygltf::Model model;
555 QString gltfErrors, gltfWarnings;
557 bool res = QgsGltfUtils::loadGltfModel( data, model, &gltfErrors, &gltfWarnings );
558 if ( !gltfErrors.isEmpty() )
560 QgsDebugError( u
"Error raised reading %1: %2"_s.arg( baseUri, gltfErrors ) );
562 if ( !gltfWarnings.isEmpty() )
564 QgsDebugError( u
"Warnings raised reading %1: %2"_s.arg( baseUri, gltfWarnings ) );
570 errors->append( u
"GLTF load error: "_s + gltfErrors );
575 return parsedGltfToEntity( model, transform, baseUri, errors );
579#include "qgsgltf3dutils.moc"
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.
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...
double y() const
Returns Y coordinate.
double z() const
Returns Z coordinate.
double x() const
Returns X coordinate.
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).
bool operator==(const QgsFeatureIterator &fi1, const QgsFeatureIterator &fi2)
#define QgsDebugError(str)