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];
308 tinygltf::Image &img = model.images[tex.source];
310 if ( !img.uri.empty() )
312 QString imgUri = QString::fromStdString( img.uri );
313 QUrl url = QUrl( baseUri ).resolved( imgUri );
314 QByteArray ba = fetchUri( url, errors );
317 if ( !QgsGltfUtils::loadImageDataWithQImage( &img, -1,
nullptr,
nullptr, 0, 0, (
const unsigned char * ) ba.constData(), ba.size(),
nullptr ) )
320 *errors << QStringLiteral(
"Failed to load image: %1" ).arg( imgUri );
325 if ( img.image.empty() )
327 QgsMetalRoughMaterial *pbrMaterial =
new QgsMetalRoughMaterial;
328 pbrMaterial->setMetalness( pbr.metallicFactor );
329 pbrMaterial->setRoughness( pbr.roughnessFactor );
330 pbrMaterial->setBaseColor( QColor::fromRgbF( pbr.baseColorFactor[0], pbr.baseColorFactor[1], pbr.baseColorFactor[2], pbr.baseColorFactor[3] ) );
334 TinyGltfTextureImage *textureImage =
new TinyGltfTextureImage( img );
336 Qt3DRender::QTexture2D *texture =
new Qt3DRender::QTexture2D;
337 texture->addTextureImage( textureImage );
340 texture->setMinificationFilter( Qt3DRender::QTexture2D::Linear );
341 texture->setMagnificationFilter( Qt3DRender::QTexture2D::Linear );
343 if ( tex.sampler >= 0 )
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 );
359 QgsTextureMaterial *mat =
new QgsTextureMaterial;
360 mat->setTexture( texture );
367 QgsMetalRoughMaterial *pbrMaterial =
new QgsMetalRoughMaterial;
368 pbrMaterial->setMetalness( pbr.metallicFactor );
369 pbrMaterial->setRoughness( pbr.roughnessFactor );
370 pbrMaterial->setBaseColor( QColor::fromRgbF( pbr.baseColorFactor[0], pbr.baseColorFactor[1], pbr.baseColorFactor[2], pbr.baseColorFactor[3] ) );
375static QVector<Qt3DCore::QEntity *> parseNode( tinygltf::Model &model,
int nodeIndex,
const QgsGltf3DUtils::EntityTransform &transform,
const QgsVector3D &tileTranslationEcef, QString baseUri, QMatrix4x4 parentTransform, QStringList *errors )
377 tinygltf::Node &node = model.nodes[nodeIndex];
379 QVector<Qt3DCore::QEntity *> entities;
382 std::unique_ptr<QMatrix4x4> matrix = QgsGltfUtils::parseNodeTransform( node );
383 if ( !parentTransform.isIdentity() )
386 *matrix = parentTransform * *matrix;
389 matrix.reset(
new QMatrix4x4( parentTransform ) );
394 if ( node.mesh >= 0 )
396 tinygltf::Mesh &mesh = model.meshes[node.mesh];
398 for (
const tinygltf::Primitive &primitive : mesh.primitives )
400 if ( primitive.mode != TINYGLTF_MODE_TRIANGLES )
403 *errors << QStringLiteral(
"Unsupported mesh primitive: %1" ).arg( primitive.mode );
407 auto posIt = primitive.attributes.find(
"POSITION" );
408 Q_ASSERT( posIt != primitive.attributes.end() );
409 int positionAccessorIndex = posIt->second;
411 tinygltf::Accessor &posAccessor = model.accessors[positionAccessorIndex];
412 if ( posAccessor.componentType != TINYGLTF_PARAMETER_TYPE_FLOAT || posAccessor.type != TINYGLTF_TYPE_VEC3 )
415 *errors << QStringLiteral(
"Unsupported position accessor type: %1 / %2" ).arg( posAccessor.componentType ).arg( posAccessor.type );
419 QgsMaterial *material = parseMaterial( model, primitive.material, baseUri, errors );
428 Qt3DQAttribute *positionAttribute = reprojectPositions( model, positionAccessorIndex, transform, tileTranslationEcef, matrix.get() );
429 positionAttribute->setName( Qt3DQAttribute::defaultPositionAttributeName() );
430 geom->addAttribute( positionAttribute );
432 auto normalIt = primitive.attributes.find(
"NORMAL" );
433 if ( normalIt != primitive.attributes.end() )
435 int normalAccessorIndex = normalIt->second;
436 Qt3DQAttribute *normalAttribute = parseAttribute( model, normalAccessorIndex );
437 normalAttribute->setName( Qt3DQAttribute::defaultNormalAttributeName() );
438 geom->addAttribute( normalAttribute );
444 auto texIt = primitive.attributes.find(
"TEXCOORD_0" );
445 if ( texIt != primitive.attributes.end() )
447 int texAccessorIndex = texIt->second;
448 Qt3DQAttribute *texAttribute = parseAttribute( model, texAccessorIndex );
449 texAttribute->setName( Qt3DQAttribute::defaultTextureCoordinateAttributeName() );
450 geom->addAttribute( texAttribute );
454 if ( primitive.indices != -1 )
456 indexAttribute = parseAttribute( model, primitive.indices );
457 geom->addAttribute( indexAttribute );
460 Qt3DRender::QGeometryRenderer *geomRenderer =
new Qt3DRender::QGeometryRenderer;
461 geomRenderer->setGeometry( geom );
462 geomRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::Triangles );
463 geomRenderer->setVertexCount( indexAttribute ? indexAttribute->count() : model.accessors[positionAccessorIndex].count );
467 if ( normalIt == primitive.attributes.end() )
469 if ( QgsMetalRoughMaterial *pbrMat = qobject_cast<QgsMetalRoughMaterial *>( material ) )
471 pbrMat->setFlatShadingEnabled(
true );
475 Qt3DCore::QEntity *primitiveEntity =
new Qt3DCore::QEntity;
476 primitiveEntity->addComponent( geomRenderer );
477 primitiveEntity->addComponent( material );
478 entities << primitiveEntity;
483 for (
int childNodeIndex : node.children )
485 entities << parseNode( model, childNodeIndex, transform, tileTranslationEcef, baseUri, matrix ? *matrix : QMatrix4x4(), errors );
492Qt3DCore::QEntity *QgsGltf3DUtils::parsedGltfToEntity( tinygltf::Model &model,
const QgsGltf3DUtils::EntityTransform &transform, QString baseUri, QStringList *errors )
494 bool sceneOk =
false;
495 const std::size_t sceneIndex = QgsGltfUtils::sourceSceneForModel( model, sceneOk );
499 *errors <<
"No scenes present in the gltf data!";
503 tinygltf::Scene &scene = model.scenes[sceneIndex];
505 if ( scene.nodes.size() == 0 )
508 *errors <<
"No nodes present in the gltf data!";
512 const QgsVector3D tileTranslationEcef = QgsGltfUtils::extractTileTranslation( model );
514 Qt3DCore::QEntity *rootEntity =
new Qt3DCore::QEntity;
515 for (
const int nodeIndex : scene.nodes )
517 const QVector<Qt3DCore::QEntity *> entities = parseNode( model, nodeIndex, transform, tileTranslationEcef, baseUri, QMatrix4x4(), errors );
518 for ( Qt3DCore::QEntity *e : entities )
519 e->setParent( rootEntity );
525Qt3DCore::QEntity *QgsGltf3DUtils::gltfToEntity(
const QByteArray &data,
const QgsGltf3DUtils::EntityTransform &transform,
const QString &baseUri, QStringList *errors )
527 tinygltf::Model model;
528 QString gltfErrors, gltfWarnings;
530 bool res = QgsGltfUtils::loadGltfModel( data, model, &gltfErrors, &gltfWarnings );
531 if ( !gltfErrors.isEmpty() )
533 QgsDebugError( QStringLiteral(
"Error raised reading %1: %2" ).arg( baseUri, gltfErrors ) );
535 if ( !gltfWarnings.isEmpty() )
537 QgsDebugError( QStringLiteral(
"Warnings raised reading %1: %2" ).arg( baseUri, gltfWarnings ) );
543 errors->append( QStringLiteral(
"GLTF load error: " ) + gltfErrors );
548 return parsedGltfToEntity( model, transform, baseUri, errors );
552#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)