25#include <Qt3DCore/QEntity>
27#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
28#include <Qt3DRender/QAttribute>
29#include <Qt3DRender/QBuffer>
30#include <Qt3DRender/QGeometry>
35#include <Qt3DCore/QAttribute>
36#include <Qt3DCore/QBuffer>
37#include <Qt3DCore/QGeometry>
43#include <Qt3DRender/QGeometryRenderer>
44#include <Qt3DRender/QTexture>
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 QgsMetalRoughMaterial *defaultMaterial =
new QgsMetalRoughMaterial;
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 QgsMetalRoughMaterial *pbrMaterial =
new QgsMetalRoughMaterial;
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 );
373 QgsMetalRoughMaterial *pbrMaterial =
new QgsMetalRoughMaterial;
374 pbrMaterial->setMetalness( pbr.metallicFactor );
375 pbrMaterial->setRoughness( pbr.roughnessFactor );
376 pbrMaterial->setBaseColor( QColor::fromRgbF( pbr.baseColorFactor[0], pbr.baseColorFactor[1], pbr.baseColorFactor[2], pbr.baseColorFactor[3] ) );
381static QVector<Qt3DCore::QEntity *> parseNode( tinygltf::Model &model,
int nodeIndex,
const QgsGltf3DUtils::EntityTransform &transform,
const QgsVector3D &tileTranslationEcef, QString baseUri, QMatrix4x4 parentTransform, QStringList *errors )
383 tinygltf::Node &node = model.nodes[nodeIndex];
385 QVector<Qt3DCore::QEntity *> entities;
388 std::unique_ptr<QMatrix4x4> matrix = QgsGltfUtils::parseNodeTransform( node );
389 if ( !parentTransform.isIdentity() )
392 *matrix = parentTransform * *matrix;
395 matrix.reset(
new QMatrix4x4( parentTransform ) );
400 if ( node.mesh >= 0 )
402 tinygltf::Mesh &mesh = model.meshes[node.mesh];
404 for (
const tinygltf::Primitive &primitive : mesh.primitives )
406 if ( primitive.mode != TINYGLTF_MODE_TRIANGLES )
409 *errors << QStringLiteral(
"Unsupported mesh primitive: %1" ).arg( primitive.mode );
413 auto posIt = primitive.attributes.find(
"POSITION" );
414 Q_ASSERT( posIt != primitive.attributes.end() );
415 int positionAccessorIndex = posIt->second;
417 tinygltf::Accessor &posAccessor = model.accessors[positionAccessorIndex];
418 if ( posAccessor.componentType != TINYGLTF_PARAMETER_TYPE_FLOAT || posAccessor.type != TINYGLTF_TYPE_VEC3 )
421 *errors << QStringLiteral(
"Unsupported position accessor type: %1 / %2" ).arg( posAccessor.componentType ).arg( posAccessor.type );
425 Qt3DRender::QMaterial *material = parseMaterial( model, primitive.material, baseUri, errors );
434 Qt3DQAttribute *positionAttribute = reprojectPositions( model, positionAccessorIndex, transform, tileTranslationEcef, matrix.get() );
435 positionAttribute->setName( Qt3DQAttribute::defaultPositionAttributeName() );
436 geom->addAttribute( positionAttribute );
438 auto normalIt = primitive.attributes.find(
"NORMAL" );
439 if ( normalIt != primitive.attributes.end() )
441 int normalAccessorIndex = normalIt->second;
442 Qt3DQAttribute *normalAttribute = parseAttribute( model, normalAccessorIndex );
443 normalAttribute->setName( Qt3DQAttribute::defaultNormalAttributeName() );
444 geom->addAttribute( normalAttribute );
450 auto texIt = primitive.attributes.find(
"TEXCOORD_0" );
451 if ( texIt != primitive.attributes.end() )
453 int texAccessorIndex = texIt->second;
454 Qt3DQAttribute *texAttribute = parseAttribute( model, texAccessorIndex );
455 texAttribute->setName( Qt3DQAttribute::defaultTextureCoordinateAttributeName() );
456 geom->addAttribute( texAttribute );
460 if ( primitive.indices != -1 )
462 indexAttribute = parseAttribute( model, primitive.indices );
463 geom->addAttribute( indexAttribute );
466 Qt3DRender::QGeometryRenderer *geomRenderer =
new Qt3DRender::QGeometryRenderer;
467 geomRenderer->setGeometry( geom );
468 geomRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::Triangles );
469 geomRenderer->setVertexCount( indexAttribute ? indexAttribute->count() : model.accessors[positionAccessorIndex].count );
473 if ( normalIt == primitive.attributes.end() )
475 if ( QgsMetalRoughMaterial *pbrMat = qobject_cast<QgsMetalRoughMaterial *>( material ) )
477 pbrMat->setFlatShadingEnabled(
true );
481 Qt3DCore::QEntity *primitiveEntity =
new Qt3DCore::QEntity;
482 primitiveEntity->addComponent( geomRenderer );
483 primitiveEntity->addComponent( material );
484 entities << primitiveEntity;
489 for (
int childNodeIndex : node.children )
491 entities << parseNode( model, childNodeIndex, transform, tileTranslationEcef, baseUri, matrix ? *matrix : QMatrix4x4(), errors );
498static Qt3DCore::QEntity *parseModel( tinygltf::Model &model,
const QgsGltf3DUtils::EntityTransform &transform, QString baseUri, QStringList *errors )
500 bool sceneOk =
false;
501 const std::size_t sceneIndex = QgsGltfUtils::sourceSceneForModel( model, sceneOk );
505 *errors <<
"No scenes present in the gltf data!";
509 tinygltf::Scene &scene = model.scenes[sceneIndex];
511 if ( scene.nodes.size() == 0 )
514 *errors <<
"No nodes present in the gltf data!";
518 const QgsVector3D tileTranslationEcef = QgsGltfUtils::extractTileTranslation( model );
520 Qt3DCore::QEntity *rootEntity =
new Qt3DCore::QEntity;
521 for (
const int nodeIndex : scene.nodes )
523 const QVector<Qt3DCore::QEntity *> entities = parseNode( model, nodeIndex, transform, tileTranslationEcef, baseUri, QMatrix4x4(), errors );
524 for ( Qt3DCore::QEntity *e : entities )
525 e->setParent( rootEntity );
531Qt3DCore::QEntity *QgsGltf3DUtils::gltfToEntity(
const QByteArray &data,
const QgsGltf3DUtils::EntityTransform &transform,
const QString &baseUri, QStringList *errors )
533 tinygltf::Model model;
534 QString gltfErrors, gltfWarnings;
536 bool res = QgsGltfUtils::loadGltfModel( data, model, &gltfErrors, &gltfWarnings );
537 if ( !gltfErrors.isEmpty() )
539 QgsDebugError( QStringLiteral(
"Error raised reading %1: %2" ).arg( baseUri, gltfErrors ) );
541 if ( !gltfWarnings.isEmpty() )
543 QgsDebugError( QStringLiteral(
"Warnings raised reading %1: %2" ).arg( baseUri, gltfWarnings ) );
549 errors->append( QStringLiteral(
"GLTF load error: " ) + gltfErrors );
554 return parseModel( model, transform, baseUri, errors );
558#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
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
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)