24#include <Qt3DCore/QEntity>
26#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
27#include <Qt3DRender/QAttribute>
28#include <Qt3DRender/QBuffer>
29#include <Qt3DRender/QGeometry>
34#include <Qt3DCore/QAttribute>
35#include <Qt3DCore/QBuffer>
36#include <Qt3DCore/QGeometry>
42#include <Qt3DRender/QGeometryRenderer>
43#include <Qt3DRender/QTexture>
44#include <Qt3DExtras/QMetalRoughMaterial>
45#include <Qt3DExtras/QTextureMaterial>
51#define TINYGLTF_NO_STB_IMAGE
52#define TINYGLTF_NO_STB_IMAGE_WRITE
58static Qt3DQAttribute::VertexBaseType parseVertexBaseType(
int componentType )
60 switch ( componentType )
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;
80 return Qt3DQAttribute::UnsignedInt;
84static Qt3DRender::QAbstractTexture::Filter parseTextureFilter(
int filter )
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;
103 return Qt3DRender::QTexture2D::Nearest;
106static Qt3DRender::QTextureWrapMode::WrapMode parseTextureWrapMode(
int wrapMode )
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;
119 return Qt3DRender::QTextureWrapMode::Repeat;
123static Qt3DQAttribute *parseAttribute( tinygltf::Model &model,
int accessorIndex )
125 tinygltf::Accessor &accessor = model.accessors[accessorIndex];
126 tinygltf::BufferView &bv = model.bufferViews[accessor.bufferView];
127 tinygltf::Buffer &b = model.buffers[bv.buffer];
130 QByteArray byteArray(
reinterpret_cast<const char *
>( b.data.data() ),
131 static_cast<int>( b.data.size() ) );
133 buffer->setData( byteArray );
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 );
143 attribute->setBuffer( buffer );
144 attribute->setByteOffset( bv.byteOffset + accessor.byteOffset );
145 attribute->setByteStride( bv.byteStride );
146 attribute->setCount( accessor.count );
147 attribute->setVertexBaseType( parseVertexBaseType( accessor.componentType ) );
148 attribute->setVertexSize( tinygltf::GetNumComponentsInType( accessor.type ) );
154static Qt3DQAttribute *reprojectPositions( tinygltf::Model &model,
int accessorIndex,
const QgsGltf3DUtils::EntityTransform &transform,
const QgsVector3D &tileTranslationEcef, QMatrix4x4 *matrix )
156 tinygltf::Accessor &accessor = model.accessors[accessorIndex];
158 QVector<double> vx, vy, vz;
159 bool res = QgsGltfUtils::accessorToMapCoordinates( model, accessorIndex, transform.tileTransform, transform.ecefToTargetCrs, tileTranslationEcef, matrix, transform.gltfUpAxis, vx, vy, vz );
163 QByteArray byteArray;
164 byteArray.resize( accessor.count * 4 * 3 );
165 float *out =
reinterpret_cast<float *
>( byteArray.data() );
167 QgsVector3D sceneOrigin = transform.sceneOriginTargetCrs;
168 for (
int i = 0; i < static_cast<int>( accessor.count ); ++i )
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();
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 );
181 buffer->setData( byteArray );
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 );
199class TinyGltfTextureImageDataGenerator :
public Qt3DRender::QTextureImageDataGenerator
202 TinyGltfTextureImageDataGenerator( Qt3DRender::QTextureImageDataPtr imagePtr )
203 : mImagePtr( imagePtr ) {}
205 QT3D_FUNCTOR( TinyGltfTextureImageDataGenerator )
207 Qt3DRender::QTextureImageDataPtr operator()()
override
212 bool operator ==(
const QTextureImageDataGenerator &other )
const override
214 const TinyGltfTextureImageDataGenerator *otherFunctor = functor_cast<TinyGltfTextureImageDataGenerator>( &other );
215 return mImagePtr.get() == otherFunctor->mImagePtr.get();
218 Qt3DRender::QTextureImageDataPtr mImagePtr;
223class TinyGltfTextureImage :
public Qt3DRender::QAbstractTextureImage
227 TinyGltfTextureImage( tinygltf::Image &image )
229 Q_ASSERT( image.bits == 8 );
230 Q_ASSERT( image.component == 4 );
231 Q_ASSERT( image.pixel_type == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE );
233 imgDataPtr.reset(
new Qt3DRender::QTextureImageData );
234 imgDataPtr->setWidth( image.width );
235 imgDataPtr->setHeight( image.height );
236 imgDataPtr->setDepth( 1 );
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 );
244 imgDataPtr->setPixelType( QOpenGLTexture::UInt8 );
245 imgDataPtr->setTarget( QOpenGLTexture::Target2D );
248 Qt3DRender::QTextureImageDataGeneratorPtr dataGenerator()
const override
250 return Qt3DRender::QTextureImageDataGeneratorPtr(
new TinyGltfTextureImageDataGenerator( imgDataPtr ) );
253 Qt3DRender::QTextureImageDataPtr imgDataPtr;
258static QByteArray fetchUri(
const QUrl &url, QStringList *errors )
260 if ( url.scheme().startsWith(
"http" ) )
262 QNetworkRequest request = QNetworkRequest( url );
263 request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
264 request.setAttribute( QNetworkRequest::CacheSaveControlAttribute,
true );
270 *errors << QStringLiteral(
"Failed to download image: %1" ).arg( url.toString() );
278 else if ( url.isLocalFile() && QFile::exists( url.toLocalFile() ) )
280 QFile f( url.toLocalFile() );
281 if ( f.open( QIODevice::ReadOnly ) )
288 *errors << QStringLiteral(
"Unable to open image: %1" ).arg( url.toString() );
295static Qt3DRender::QMaterial *parseMaterial( tinygltf::Model &model,
int materialIndex, QString baseUri, QStringList *errors )
297 if ( materialIndex < 0 )
300 Qt3DExtras::QMetalRoughMaterial *defaultMaterial =
new Qt3DExtras::QMetalRoughMaterial;
301 defaultMaterial->setMetalness( 1 );
302 defaultMaterial->setRoughness( 1 );
303 defaultMaterial->setBaseColor( QColor::fromRgbF( 1, 1, 1 ) );
304 return defaultMaterial;
307 tinygltf::Material &material = model.materials[materialIndex];
308 tinygltf::PbrMetallicRoughness &pbr = material.pbrMetallicRoughness;
310 if ( pbr.baseColorTexture.index >= 0 )
312 tinygltf::Texture &tex = model.textures[pbr.baseColorTexture.index];
314 tinygltf::Image &img = model.images[tex.source];
316 if ( !img.uri.empty() )
318 QString imgUri = QString::fromStdString( img.uri );
319 QUrl url = QUrl( baseUri ).resolved( imgUri );
320 QByteArray ba = fetchUri( url, errors );
323 if ( !QgsGltfUtils::loadImageDataWithQImage( &img, -1,
nullptr,
nullptr, 0, 0, (
const unsigned char * ) ba.constData(), ba.size(),
nullptr ) )
326 *errors << QStringLiteral(
"Failed to load image: %1" ).arg( imgUri );
331 if ( img.image.empty() )
333 Qt3DExtras::QMetalRoughMaterial *pbrMaterial =
new Qt3DExtras::QMetalRoughMaterial;
334 pbrMaterial->setMetalness( pbr.metallicFactor );
335 pbrMaterial->setRoughness( pbr.roughnessFactor );
336 pbrMaterial->setBaseColor( QColor::fromRgbF( pbr.baseColorFactor[0], pbr.baseColorFactor[1], pbr.baseColorFactor[2], pbr.baseColorFactor[3] ) );
340 TinyGltfTextureImage *textureImage =
new TinyGltfTextureImage( img );
342 Qt3DRender::QTexture2D *texture =
new Qt3DRender::QTexture2D;
343 texture->addTextureImage( textureImage );
346 texture->setMinificationFilter( Qt3DRender::QTexture2D::Linear );
347 texture->setMagnificationFilter( Qt3DRender::QTexture2D::Linear );
349 if ( tex.sampler >= 0 )
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 );
365 Qt3DExtras::QTextureMaterial *mat =
new Qt3DExtras::QTextureMaterial;
366 mat->setTexture( texture );
370 Qt3DExtras::QMetalRoughMaterial *pbrMaterial =
new Qt3DExtras::QMetalRoughMaterial;
371 pbrMaterial->setMetalness( pbr.metallicFactor );
372 pbrMaterial->setRoughness( pbr.roughnessFactor );
373 pbrMaterial->setBaseColor( QColor::fromRgbF( pbr.baseColorFactor[0], pbr.baseColorFactor[1], pbr.baseColorFactor[2], pbr.baseColorFactor[3] ) );
378static QVector<Qt3DCore::QEntity *> parseNode( tinygltf::Model &model,
int nodeIndex,
const QgsGltf3DUtils::EntityTransform &transform,
const QgsVector3D &tileTranslationEcef, QString baseUri, QMatrix4x4 parentTransform, QStringList *errors )
380 tinygltf::Node &node = model.nodes[nodeIndex];
382 QVector<Qt3DCore::QEntity *> entities;
385 std::unique_ptr<QMatrix4x4> matrix = QgsGltfUtils::parseNodeTransform( node );
386 if ( !parentTransform.isIdentity() )
389 *matrix = parentTransform * *matrix;
392 matrix.reset(
new QMatrix4x4( parentTransform ) );
397 if ( node.mesh >= 0 )
399 tinygltf::Mesh &mesh = model.meshes[node.mesh];
401 for (
const tinygltf::Primitive &primitive : mesh.primitives )
403 if ( primitive.mode != TINYGLTF_MODE_TRIANGLES )
406 *errors << QStringLiteral(
"Unsupported mesh primitive: %1" ).arg( primitive.mode );
410 auto posIt = primitive.attributes.find(
"POSITION" );
411 Q_ASSERT( posIt != primitive.attributes.end() );
412 int positionAccessorIndex = posIt->second;
414 tinygltf::Accessor &posAccessor = model.accessors[positionAccessorIndex];
415 if ( posAccessor.componentType != TINYGLTF_PARAMETER_TYPE_FLOAT || posAccessor.type != TINYGLTF_TYPE_VEC3 )
418 *errors << QStringLiteral(
"Unsupported position accessor type: %1 / %2" ).arg( posAccessor.componentType ).arg( posAccessor.type );
424 Qt3DQAttribute *positionAttribute = reprojectPositions( model, positionAccessorIndex, transform, tileTranslationEcef, matrix.get() );
425 positionAttribute->setName( Qt3DQAttribute::defaultPositionAttributeName() );
426 geom->addAttribute( positionAttribute );
428 auto normalIt = primitive.attributes.find(
"NORMAL" );
429 if ( normalIt != primitive.attributes.end() )
431 int normalAccessorIndex = normalIt->second;
432 Qt3DQAttribute *normalAttribute = parseAttribute( model, normalAccessorIndex );
433 normalAttribute->setName( Qt3DQAttribute::defaultNormalAttributeName() );
434 geom->addAttribute( normalAttribute );
440 auto texIt = primitive.attributes.find(
"TEXCOORD_0" );
441 if ( texIt != primitive.attributes.end() )
443 int texAccessorIndex = texIt->second;
444 Qt3DQAttribute *texAttribute = parseAttribute( model, texAccessorIndex );
445 texAttribute->setName( Qt3DQAttribute::defaultTextureCoordinateAttributeName() );
446 geom->addAttribute( texAttribute );
450 if ( primitive.indices != -1 )
452 indexAttribute = parseAttribute( model, primitive.indices );
453 geom->addAttribute( indexAttribute );
456 Qt3DRender::QGeometryRenderer *geomRenderer =
new Qt3DRender::QGeometryRenderer;
457 geomRenderer->setGeometry( geom );
458 geomRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::Triangles );
459 geomRenderer->setVertexCount( indexAttribute ? indexAttribute->count() : model.accessors[positionAccessorIndex].count );
461 Qt3DRender::QMaterial *material = parseMaterial( model, primitive.material, baseUri, errors );
466 Qt3DCore::QEntity *primitiveEntity =
new Qt3DCore::QEntity;
467 primitiveEntity->addComponent( geomRenderer );
468 primitiveEntity->addComponent( material );
469 entities << primitiveEntity;
474 for (
int childNodeIndex : node.children )
476 entities << parseNode( model, childNodeIndex, transform, tileTranslationEcef, baseUri, matrix ? *matrix : QMatrix4x4(), errors );
483static Qt3DCore::QEntity *parseModel( tinygltf::Model &model,
const QgsGltf3DUtils::EntityTransform &transform, QString baseUri, QStringList *errors )
485 tinygltf::Scene &scene = model.scenes[model.defaultScene];
487 if ( scene.nodes.size() == 0 )
490 *errors <<
"No nodes present in the gltf data!";
494 const QgsVector3D tileTranslationEcef = QgsGltfUtils::extractTileTranslation( model );
496 Qt3DCore::QEntity *rootEntity =
new Qt3DCore::QEntity;
497 for (
const int nodeIndex : scene.nodes )
499 const QVector<Qt3DCore::QEntity *> entities = parseNode( model, nodeIndex, transform, tileTranslationEcef, baseUri, QMatrix4x4(), errors );
500 for ( Qt3DCore::QEntity *e : entities )
501 e->setParent( rootEntity );
507Qt3DCore::QEntity *QgsGltf3DUtils::gltfToEntity(
const QByteArray &data,
const QgsGltf3DUtils::EntityTransform &transform,
const QString &baseUri, QStringList *errors )
509 tinygltf::Model model;
510 QString gltfErrors, gltfWarnings;
512 bool res = QgsGltfUtils::loadGltfModel( data, model, &gltfErrors, &gltfWarnings );
513 if ( !gltfErrors.isEmpty() )
515 QgsDebugError( QStringLiteral(
"Error raised reading %1: %2" ).arg( baseUri, gltfErrors ) );
517 if ( !gltfWarnings.isEmpty() )
519 QgsDebugError( QStringLiteral(
"Warnings raised reading %1: %2" ).arg( baseUri, gltfWarnings ) );
525 errors->append( QStringLiteral(
"GLTF load error: " ) + gltfErrors );
530 return parseModel( model, transform, baseUri, errors );
534#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)
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...
double y() const
Returns Y coordinate.
double z() const
Returns Z coordinate.
double x() const
Returns X coordinate.
#define Q_NOWARN_DEPRECATED_POP
#define Q_NOWARN_DEPRECATED_PUSH
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)