26#include <Qt3DCore/QEntity>
28#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
29#include <Qt3DRender/QAttribute>
30#include <Qt3DRender/QBuffer>
31#include <Qt3DRender/QGeometry>
36#include <Qt3DCore/QAttribute>
37#include <Qt3DCore/QBuffer>
38#include <Qt3DCore/QGeometry>
44#include <Qt3DRender/QGeometryRenderer>
45#include <Qt3DRender/QTexture>
53static Qt3DQAttribute::VertexBaseType parseVertexBaseType(
int componentType )
55 switch ( componentType )
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;
75 return Qt3DQAttribute::UnsignedInt;
79static Qt3DRender::QAbstractTexture::Filter parseTextureFilter(
int filter )
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;
98 return Qt3DRender::QTexture2D::Nearest;
101static Qt3DRender::QTextureWrapMode::WrapMode parseTextureWrapMode(
int wrapMode )
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;
114 return Qt3DRender::QTextureWrapMode::Repeat;
118static Qt3DQAttribute *parseAttribute( tinygltf::Model &model,
int accessorIndex )
120 tinygltf::Accessor &accessor = model.accessors[accessorIndex];
121 tinygltf::BufferView &bv = model.bufferViews[accessor.bufferView];
122 tinygltf::Buffer &b = model.buffers[bv.buffer];
125 QByteArray byteArray(
reinterpret_cast<const char *
>( b.data.data() ),
126 static_cast<int>( b.data.size() ) );
128 buffer->setData( byteArray );
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 );
138 attribute->setBuffer( buffer );
139 attribute->setByteOffset( bv.byteOffset + accessor.byteOffset );
140 attribute->setByteStride( bv.byteStride );
141 attribute->setCount( accessor.count );
142 attribute->setVertexBaseType( parseVertexBaseType( accessor.componentType ) );
143 attribute->setVertexSize( tinygltf::GetNumComponentsInType( accessor.type ) );
149static Qt3DQAttribute *reprojectPositions( tinygltf::Model &model,
int accessorIndex,
const QgsGltf3DUtils::EntityTransform &transform,
const QgsVector3D &tileTranslationEcef, QMatrix4x4 *matrix )
151 tinygltf::Accessor &accessor = model.accessors[accessorIndex];
153 QVector<double> vx, vy, vz;
154 bool res = QgsGltfUtils::accessorToMapCoordinates( model, accessorIndex, transform.tileTransform, transform.ecefToTargetCrs, tileTranslationEcef, matrix, transform.gltfUpAxis, vx, vy, vz );
158 QByteArray byteArray;
159 byteArray.resize( accessor.count * 4 * 3 );
160 float *out =
reinterpret_cast<float *
>( byteArray.data() );
162 QgsVector3D sceneOrigin = transform.chunkOriginTargetCrs;
163 for (
int i = 0; i < static_cast<int>( accessor.count ); ++i )
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();
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 );
175 buffer->setData( byteArray );
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 );
193class TinyGltfTextureImageDataGenerator :
public Qt3DRender::QTextureImageDataGenerator
196 TinyGltfTextureImageDataGenerator( Qt3DRender::QTextureImageDataPtr imagePtr )
197 : mImagePtr( imagePtr ) {}
199 QT3D_FUNCTOR( TinyGltfTextureImageDataGenerator )
201 Qt3DRender::QTextureImageDataPtr operator()()
override
206 bool operator==(
const QTextureImageDataGenerator &other )
const override
208 const TinyGltfTextureImageDataGenerator *otherFunctor = functor_cast<TinyGltfTextureImageDataGenerator>( &other );
209 return mImagePtr.get() == otherFunctor->mImagePtr.get();
212 Qt3DRender::QTextureImageDataPtr mImagePtr;
217class TinyGltfTextureImage :
public Qt3DRender::QAbstractTextureImage
221 TinyGltfTextureImage( tinygltf::Image &image )
223 Q_ASSERT( image.bits == 8 );
224 Q_ASSERT( image.component == 4 );
225 Q_ASSERT( image.pixel_type == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE );
227 imgDataPtr.reset(
new Qt3DRender::QTextureImageData );
228 imgDataPtr->setWidth( image.width );
229 imgDataPtr->setHeight( image.height );
230 imgDataPtr->setDepth( 1 );
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 );
238 imgDataPtr->setPixelType( QOpenGLTexture::UInt8 );
239 imgDataPtr->setTarget( QOpenGLTexture::Target2D );
242 Qt3DRender::QTextureImageDataGeneratorPtr dataGenerator()
const override
244 return Qt3DRender::QTextureImageDataGeneratorPtr(
new TinyGltfTextureImageDataGenerator( imgDataPtr ) );
247 Qt3DRender::QTextureImageDataPtr imgDataPtr;
252static QByteArray fetchUri(
const QUrl &url, QStringList *errors )
254 if ( url.scheme().startsWith(
"http" ) )
256 QNetworkRequest request = QNetworkRequest( url );
257 request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
258 request.setAttribute( QNetworkRequest::CacheSaveControlAttribute,
true );
264 *errors << QStringLiteral(
"Failed to download image: %1" ).arg( url.toString() );
272 else if ( url.isLocalFile() && QFile::exists( url.toLocalFile() ) )
274 QFile f( url.toLocalFile() );
275 if ( f.open( QIODevice::ReadOnly ) )
282 *errors << QStringLiteral(
"Unable to open image: %1" ).arg( url.toString() );
289static QgsMaterial *parseMaterial( tinygltf::Model &model,
int materialIndex, QString baseUri, QStringList *errors )
291 if ( materialIndex < 0 )
294 QgsMetalRoughMaterial *defaultMaterial =
new QgsMetalRoughMaterial;
295 defaultMaterial->setMetalness( 1 );
296 defaultMaterial->setRoughness( 1 );
297 defaultMaterial->setBaseColor( QColor::fromRgbF( 1, 1, 1 ) );
298 return defaultMaterial;
301 tinygltf::Material &material = model.materials[materialIndex];
302 tinygltf::PbrMetallicRoughness &pbr = material.pbrMetallicRoughness;
304 if ( pbr.baseColorTexture.index >= 0 )
306 tinygltf::Texture &tex = model.textures[pbr.baseColorTexture.index];
309 if ( tex.source < 0 )
311 QgsMetalRoughMaterial *pbrMaterial =
new QgsMetalRoughMaterial;
312 pbrMaterial->setMetalness( pbr.metallicFactor );
313 pbrMaterial->setRoughness( pbr.roughnessFactor );
314 pbrMaterial->setBaseColor( QColor::fromRgbF( pbr.baseColorFactor[0], pbr.baseColorFactor[1], pbr.baseColorFactor[2], pbr.baseColorFactor[3] ) );
318 tinygltf::Image &img = model.images[tex.source];
320 if ( !img.uri.empty() )
322 QString imgUri = QString::fromStdString( img.uri );
323 QUrl url = QUrl( baseUri ).resolved( imgUri );
324 QByteArray ba = fetchUri( url, errors );
327 if ( !QgsGltfUtils::loadImageDataWithQImage( &img, -1,
nullptr,
nullptr, 0, 0, (
const unsigned char * ) ba.constData(), ba.size(),
nullptr ) )
330 *errors << QStringLiteral(
"Failed to load image: %1" ).arg( imgUri );
335 if ( img.image.empty() )
337 QgsMetalRoughMaterial *pbrMaterial =
new QgsMetalRoughMaterial;
338 pbrMaterial->setMetalness( pbr.metallicFactor );
339 pbrMaterial->setRoughness( pbr.roughnessFactor );
340 pbrMaterial->setBaseColor( QColor::fromRgbF( pbr.baseColorFactor[0], pbr.baseColorFactor[1], pbr.baseColorFactor[2], pbr.baseColorFactor[3] ) );
344 TinyGltfTextureImage *textureImage =
new TinyGltfTextureImage( img );
346 Qt3DRender::QTexture2D *texture =
new Qt3DRender::QTexture2D;
347 texture->addTextureImage( textureImage );
350 texture->setMinificationFilter( Qt3DRender::QTexture2D::Linear );
351 texture->setMagnificationFilter( Qt3DRender::QTexture2D::Linear );
353 if ( tex.sampler >= 0 )
355 tinygltf::Sampler &sampler = model.samplers[tex.sampler];
356 if ( sampler.minFilter >= 0 )
357 texture->setMinificationFilter( parseTextureFilter( sampler.minFilter ) );
358 if ( sampler.magFilter >= 0 )
359 texture->setMagnificationFilter( parseTextureFilter( sampler.magFilter ) );
360 Qt3DRender::QTextureWrapMode wrapMode;
361 wrapMode.setX( parseTextureWrapMode( sampler.wrapS ) );
362 wrapMode.setY( parseTextureWrapMode( sampler.wrapT ) );
363 texture->setWrapMode( wrapMode );
369 QgsTextureMaterial *mat =
new QgsTextureMaterial;
370 mat->setTexture( texture );
377 QgsMetalRoughMaterial *pbrMaterial =
new QgsMetalRoughMaterial;
378 pbrMaterial->setMetalness( pbr.metallicFactor );
379 pbrMaterial->setRoughness( pbr.roughnessFactor );
380 pbrMaterial->setBaseColor( QColor::fromRgbF( pbr.baseColorFactor[0], pbr.baseColorFactor[1], pbr.baseColorFactor[2], pbr.baseColorFactor[3] ) );
385static QVector<Qt3DCore::QEntity *> parseNode( tinygltf::Model &model,
int nodeIndex,
const QgsGltf3DUtils::EntityTransform &transform,
const QgsVector3D &tileTranslationEcef, QString baseUri, QMatrix4x4 parentTransform, QStringList *errors )
387 tinygltf::Node &node = model.nodes[nodeIndex];
389 QVector<Qt3DCore::QEntity *> entities;
392 std::unique_ptr<QMatrix4x4> matrix = QgsGltfUtils::parseNodeTransform( node );
393 if ( !parentTransform.isIdentity() )
396 *matrix = parentTransform * *matrix;
399 matrix.reset(
new QMatrix4x4( parentTransform ) );
404 if ( node.mesh >= 0 )
406 tinygltf::Mesh &mesh = model.meshes[node.mesh];
408 for (
const tinygltf::Primitive &primitive : mesh.primitives )
410 if ( primitive.mode != TINYGLTF_MODE_TRIANGLES )
413 *errors << QStringLiteral(
"Unsupported mesh primitive: %1" ).arg( primitive.mode );
417 auto posIt = primitive.attributes.find(
"POSITION" );
418 Q_ASSERT( posIt != primitive.attributes.end() );
419 int positionAccessorIndex = posIt->second;
421 tinygltf::Accessor &posAccessor = model.accessors[positionAccessorIndex];
422 if ( posAccessor.componentType != TINYGLTF_PARAMETER_TYPE_FLOAT || posAccessor.type != TINYGLTF_TYPE_VEC3 )
425 *errors << QStringLiteral(
"Unsupported position accessor type: %1 / %2" ).arg( posAccessor.componentType ).arg( posAccessor.type );
429 QgsMaterial *material = parseMaterial( model, primitive.material, baseUri, errors );
438 Qt3DQAttribute *positionAttribute = reprojectPositions( model, positionAccessorIndex, transform, tileTranslationEcef, matrix.get() );
439 positionAttribute->setName( Qt3DQAttribute::defaultPositionAttributeName() );
440 geom->addAttribute( positionAttribute );
442 auto normalIt = primitive.attributes.find(
"NORMAL" );
443 if ( normalIt != primitive.attributes.end() )
445 int normalAccessorIndex = normalIt->second;
446 Qt3DQAttribute *normalAttribute = parseAttribute( model, normalAccessorIndex );
447 normalAttribute->setName( Qt3DQAttribute::defaultNormalAttributeName() );
448 geom->addAttribute( normalAttribute );
454 auto texIt = primitive.attributes.find(
"TEXCOORD_0" );
455 if ( texIt != primitive.attributes.end() )
457 int texAccessorIndex = texIt->second;
458 Qt3DQAttribute *texAttribute = parseAttribute( model, texAccessorIndex );
459 texAttribute->setName( Qt3DQAttribute::defaultTextureCoordinateAttributeName() );
460 geom->addAttribute( texAttribute );
464 if ( primitive.indices != -1 )
466 indexAttribute = parseAttribute( model, primitive.indices );
467 geom->addAttribute( indexAttribute );
470 Qt3DRender::QGeometryRenderer *geomRenderer =
new Qt3DRender::QGeometryRenderer;
471 geomRenderer->setGeometry( geom );
472 geomRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::Triangles );
473 geomRenderer->setVertexCount( indexAttribute ? indexAttribute->count() : model.accessors[positionAccessorIndex].count );
477 if ( normalIt == primitive.attributes.end() )
479 if ( QgsMetalRoughMaterial *pbrMat = qobject_cast<QgsMetalRoughMaterial *>( material ) )
481 pbrMat->setFlatShadingEnabled(
true );
485 Qt3DCore::QEntity *primitiveEntity =
new Qt3DCore::QEntity;
486 primitiveEntity->addComponent( geomRenderer );
487 primitiveEntity->addComponent( material );
488 entities << primitiveEntity;
493 for (
int childNodeIndex : node.children )
495 entities << parseNode( model, childNodeIndex, transform, tileTranslationEcef, baseUri, matrix ? *matrix : QMatrix4x4(), errors );
502Qt3DCore::QEntity *QgsGltf3DUtils::parsedGltfToEntity( tinygltf::Model &model,
const QgsGltf3DUtils::EntityTransform &transform, QString baseUri, QStringList *errors )
504 bool sceneOk =
false;
505 const std::size_t sceneIndex = QgsGltfUtils::sourceSceneForModel( model, sceneOk );
509 *errors <<
"No scenes present in the gltf data!";
513 tinygltf::Scene &scene = model.scenes[sceneIndex];
515 if ( scene.nodes.size() == 0 )
518 *errors <<
"No nodes present in the gltf data!";
522 const QgsVector3D tileTranslationEcef = QgsGltfUtils::extractTileTranslation( model );
524 Qt3DCore::QEntity *rootEntity =
new Qt3DCore::QEntity;
525 for (
const int nodeIndex : scene.nodes )
527 const QVector<Qt3DCore::QEntity *> entities = parseNode( model, nodeIndex, transform, tileTranslationEcef, baseUri, QMatrix4x4(), errors );
528 for ( Qt3DCore::QEntity *e : entities )
529 e->setParent( rootEntity );
535Qt3DCore::QEntity *QgsGltf3DUtils::gltfToEntity(
const QByteArray &data,
const QgsGltf3DUtils::EntityTransform &transform,
const QString &baseUri, QStringList *errors )
537 tinygltf::Model model;
538 QString gltfErrors, gltfWarnings;
540 bool res = QgsGltfUtils::loadGltfModel( data, model, &gltfErrors, &gltfWarnings );
541 if ( !gltfErrors.isEmpty() )
543 QgsDebugError( QStringLiteral(
"Error raised reading %1: %2" ).arg( baseUri, gltfErrors ) );
545 if ( !gltfWarnings.isEmpty() )
547 QgsDebugError( QStringLiteral(
"Warnings raised reading %1: %2" ).arg( baseUri, gltfWarnings ) );
553 errors->append( QStringLiteral(
"GLTF load error: " ) + gltfErrors );
558 return parsedGltfToEntity( model, transform, baseUri, errors );
562#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...
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)