30#include <QRegularExpression>
32#define TINYGLTF_IMPLEMENTATION
37#define TINYGLTF_ENABLE_DRACO
40#define TINYGLTF_NO_STB_IMAGE
41#define TINYGLTF_NO_STB_IMAGE_WRITE
50bool QgsGltfUtils::accessorToMapCoordinates(
const tinygltf::Model &model,
int accessorIndex,
const QgsMatrix4x4 &tileTransform,
const QgsCoordinateTransform *ecefToTargetCrs,
const QgsVector3D &tileTranslationEcef,
const QMatrix4x4 *nodeTransform,
Qgis::Axis gltfUpAxis, QVector<double> &vx, QVector<double> &vy, QVector<double> &vz )
52 const tinygltf::Accessor &accessor = model.accessors[accessorIndex];
53 const tinygltf::BufferView &bv = model.bufferViews[accessor.bufferView];
54 const tinygltf::Buffer &b = model.buffers[bv.buffer];
56 if ( accessor.componentType != TINYGLTF_PARAMETER_TYPE_FLOAT || accessor.type != TINYGLTF_TYPE_VEC3 )
62 const unsigned char *ptr = b.data.data() + bv.byteOffset + accessor.byteOffset;
64 vx.resize( accessor.count );
65 vy.resize( accessor.count );
66 vz.resize( accessor.count );
67 double *vxOut = vx.data();
68 double *vyOut = vy.data();
69 double *vzOut = vz.data();
70 for (
int i = 0; i < static_cast<int>( accessor.count ); ++i )
72 const float *fptr =
reinterpret_cast<const float *
>( ptr );
73 QVector3D vOrig( fptr[0], fptr[1], fptr[2] );
76 vOrig = nodeTransform->map( vOrig );
83 QgsDebugError( QStringLiteral(
"X up translation not yet supported" ) );
84 v = tileTransform.
map( tileTranslationEcef );
91 QVector3D vFlip( vOrig.x(), -vOrig.z(), vOrig.y() );
92 v = tileTransform.
map(
QgsVector3D( vFlip ) + tileTranslationEcef );
98 v = tileTransform.
map(
QgsVector3D( vOrig ) + tileTranslationEcef );
108 ptr += bv.byteStride;
110 ptr += 3 *
sizeof( float );
113 if ( ecefToTargetCrs )
117 ecefToTargetCrs->
transformCoords( accessor.count, vx.data(), vy.data(), vz.data() );
128bool QgsGltfUtils::extractTextureCoordinates(
const tinygltf::Model &model,
int accessorIndex, QVector<float> &x, QVector<float> &y )
130 const tinygltf::Accessor &accessor = model.accessors[accessorIndex];
131 const tinygltf::BufferView &bv = model.bufferViews[accessor.bufferView];
132 const tinygltf::Buffer &b = model.buffers[bv.buffer];
134 if ( accessor.componentType != TINYGLTF_PARAMETER_TYPE_FLOAT || accessor.type != TINYGLTF_TYPE_VEC2 )
139 const unsigned char *ptr = b.data.data() + bv.byteOffset + accessor.byteOffset;
140 x.resize( accessor.count );
141 y.resize( accessor.count );
143 float *xOut = x.data();
144 float *yOut = y.data();
146 for ( std::size_t i = 0; i < accessor.count; i++ )
148 const float *fptr =
reinterpret_cast< const float *
>( ptr );
154 ptr += bv.byteStride;
156 ptr += 2 *
sizeof( float );
161QgsGltfUtils::ResourceType QgsGltfUtils::imageResourceType(
const tinygltf::Model &model,
int index )
163 const tinygltf::Image &img = model.images[index];
165 if ( !img.image.empty() )
167 return ResourceType::Embedded;
171 return ResourceType::Linked;
175QImage QgsGltfUtils::extractEmbeddedImage(
const tinygltf::Model &model,
int index )
177 const tinygltf::Image &img = model.images[index];
178 if ( !img.image.empty() )
179 return QImage( img.image.data(), img.width, img.height, QImage::Format_ARGB32 );
184QString QgsGltfUtils::linkedImagePath(
const tinygltf::Model &model,
int index )
186 const tinygltf::Image &img = model.images[index];
187 return QString::fromStdString( img.uri );
190std::unique_ptr<QMatrix4x4> QgsGltfUtils::parseNodeTransform(
const tinygltf::Node &node )
194 std::unique_ptr<QMatrix4x4> matrix;
195 if ( !node.matrix.empty() )
197 matrix = std::make_unique<QMatrix4x4>( );
198 float *mdata = matrix->data();
199 for (
int i = 0; i < 16; ++i )
200 mdata[i] =
static_cast< float >( node.matrix[i] );
202 else if ( node.translation.size() || node.rotation.size() || node.scale.size() )
204 matrix = std::make_unique<QMatrix4x4>( );
205 if ( node.scale.size() )
207 matrix->scale(
static_cast< float >( node.scale[0] ),
static_cast< float >( node.scale[1] ),
static_cast< float >( node.scale[2] ) );
209 if ( node.rotation.size() )
211 matrix->rotate( QQuaternion(
static_cast< float >( node.rotation[3] ),
static_cast< float >( node.rotation[0] ),
static_cast< float >( node.rotation[1] ),
static_cast< float >( node.rotation[2] ) ) );
213 if ( node.translation.size() )
215 matrix->translate(
static_cast< float >( node.translation[0] ),
static_cast< float >( node.translation[1] ),
static_cast< float >( node.translation[2] ) );
224 bool sceneOk =
false;
225 const std::size_t sceneIndex = QgsGltfUtils::sourceSceneForModel( model, sceneOk );
231 const tinygltf::Scene &scene = model.scenes[sceneIndex];
234 auto it = model.extensions.find(
"CESIUM_RTC" );
235 if ( it != model.extensions.end() )
237 const tinygltf::Value v = it->second;
238 if ( v.IsObject() && v.Has(
"center" ) )
240 const tinygltf::Value center = v.Get(
"center" );
241 if ( center.IsArray() && center.Size() == 3 )
243 tileTranslationEcef =
QgsVector3D( center.Get( 0 ).GetNumberAsDouble(), center.Get( 1 ).GetNumberAsDouble(), center.Get( 2 ).GetNumberAsDouble() );
248 if ( scene.nodes.size() == 0 )
251 int rootNodeIndex = scene.nodes[0];
252 tinygltf::Node &rootNode = model.nodes[rootNodeIndex];
254 if ( tileTranslationEcef.
isNull() && rootNode.translation.size() )
256 QgsVector3D rootTranslation( rootNode.translation[0], rootNode.translation[1], rootNode.translation[2] );
260 if ( rootTranslation.length() > 1e6 )
265 QgsDebugError( QStringLiteral(
"X up translation not yet supported" ) );
270 tileTranslationEcef =
QgsVector3D( rootTranslation.x(), -rootTranslation.z(), rootTranslation.y() );
271 rootNode.translation[0] = rootNode.translation[1] = rootNode.translation[2] = 0;
276 tileTranslationEcef =
QgsVector3D( rootTranslation.x(), rootTranslation.y(), rootTranslation.z() );
277 rootNode.translation[0] = rootNode.translation[1] = rootNode.translation[2] = 0;
284 return tileTranslationEcef;
288bool QgsGltfUtils::loadImageDataWithQImage(
289 tinygltf::Image *image,
const int image_idx, std::string *err,
290 std::string *warn,
int req_width,
int req_height,
291 const unsigned char *bytes,
int size,
void *user_data )
294 if ( req_width != 0 || req_height != 0 )
298 ( *err ) +=
"Expecting zero req_width/req_height.\n";
307 if ( !img.loadFromData( bytes, size ) )
312 "Unknown image format. QImage cannot decode image data for image[" +
313 std::to_string( image_idx ) +
"] name = \"" + image->name +
"\".\n";
318 if ( img.format() != QImage::Format_RGB32 && img.format() != QImage::Format_ARGB32 )
321 img.convertTo( QImage::Format_RGB32 );
324 image->width = img.width();
325 image->height = img.height();
326 image->component = 4;
328 image->pixel_type = TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE;
330 image->image.resize(
static_cast<size_t>( image->width * image->height * image->component ) *
size_t( image->bits / 8 ) );
331 std::copy( img.constBits(), img.constBits() +
static_cast< std::size_t
>( image->width ) * image->height * image->component * ( image->bits / 8 ), image->image.begin() );
336bool QgsGltfUtils::loadGltfModel(
const QByteArray &data, tinygltf::Model &model, QString *errors, QString *warnings )
338 tinygltf::TinyGLTF loader;
340 loader.SetImageLoader( QgsGltfUtils::loadImageDataWithQImage,
nullptr );
346 loader.SetParseStrictness( tinygltf::ParseStrictness::Permissive );
349 std::string err, warn;
352 if ( data.startsWith(
"glTF" ) )
354 if ( data.at( 4 ) == 1 )
356 *errors = QObject::tr(
"GLTF version 1 tiles cannot be loaded" );
359 res = loader.LoadBinaryFromMemory( &model, &err, &warn,
360 (
const unsigned char * )data.constData(), data.size(), baseDir );
364 res = loader.LoadASCIIFromString( &model, &err, &warn,
365 data.constData(), data.size(), baseDir );
369 *errors = QString::fromStdString( err );
372 *warnings = QString::fromStdString( warn );
375 const thread_local QRegularExpression rxFailedToLoadExternalUriForImage( QStringLiteral(
"Failed to load external 'uri' for image\\[\\d+\\] name = \".*?\"\\n?" ) );
376 warnings->replace( rxFailedToLoadExternalUriForImage, QString() );
377 const thread_local QRegularExpression rxFileNotFound( QStringLiteral(
"File not found : .*?\\n" ) );
378 warnings->replace( rxFileNotFound, QString() );
384std::size_t QgsGltfUtils::sourceSceneForModel(
const tinygltf::Model &model,
bool &ok )
387 if ( model.scenes.empty() )
393 int index = model.defaultScene;
394 if ( index >= 0 &&
static_cast< std::size_t
>( index ) < model.scenes.size() )
406void dumpDracoModelInfo( draco::Mesh *dracoMesh )
408 std::cout <<
"Decoded Draco Mesh:" << dracoMesh->num_points() <<
" points / " << dracoMesh->num_faces() <<
" faces" << std::endl;
409 draco::GeometryMetadata *geometryMetadata = dracoMesh->metadata();
411 std::cout <<
"Global Geometry Metadata:" << std::endl;
412 for (
const auto &entry : geometryMetadata->entries() )
414 std::cout <<
" Key: " << entry.first <<
", Value: " << entry.second.data().size() << std::endl;
417 std::cout <<
"\nAttribute Metadata:" << std::endl;
418 for ( int32_t i = 0; i < dracoMesh->num_attributes(); ++i )
420 const draco::PointAttribute *attribute = dracoMesh->attribute( i );
424 std::cout <<
" Attribute ID: " << attribute->unique_id() <<
" / " << draco::PointAttribute::TypeToString( attribute->attribute_type() ) << std::endl;
425 if (
const draco::AttributeMetadata *attributeMetadata = geometryMetadata->attribute_metadata( attribute->unique_id() ) )
427 for (
const auto &entry : attributeMetadata->entries() )
429 std::cout <<
" Key: " << entry.first <<
", Length: " << entry.second.data().size() << std::endl;
436bool QgsGltfUtils::loadDracoModel(
const QByteArray &data,
const I3SNodeContext &context, tinygltf::Model &model, QString *errors )
442 QByteArray dataExtracted;
443 if ( data.startsWith( QByteArray(
"\x1f\x8b", 2 ) ) )
448 *errors =
"Failed to decode gzipped model";
454 dataExtracted = data;
461 draco::Decoder decoder;
462 draco::DecoderBuffer decoderBuffer;
463 decoderBuffer.Init( dataExtracted.constData(), dataExtracted.size() );
465 draco::StatusOr<draco::EncodedGeometryType> geometryTypeStatus = decoder.GetEncodedGeometryType( &decoderBuffer );
466 if ( !geometryTypeStatus.ok() )
469 *errors =
"Failed to get geometry type: " + QString( geometryTypeStatus.status().error_msg() );
472 if ( geometryTypeStatus.value() != draco::EncodedGeometryType::TRIANGULAR_MESH )
475 *errors =
"Not a triangular mesh";
479 draco::StatusOr<std::unique_ptr<draco::Mesh>> meshStatus = decoder.DecodeMeshFromBuffer( &decoderBuffer );
480 if ( !meshStatus.ok() )
483 *errors =
"Failed to decode mesh: " + QString( meshStatus.status().error_msg() );
487 std::unique_ptr<draco::Mesh> dracoMesh = std::move( meshStatus ).value();
489 draco::GeometryMetadata *geometryMetadata = dracoMesh->metadata();
490 if ( !geometryMetadata )
493 *errors =
"Geometry metadata missing";
497 int posAccessorIndex = -1;
498 int normalAccessorIndex = -1;
499 int uvAccessorIndex = -1;
500 int indicesAccessorIndex = -1;
506 const draco::PointAttribute *posAttribute = dracoMesh->GetNamedAttribute( draco::GeometryAttribute::POSITION );
509 double scaleX = 1, scaleY = 1;
510 const draco::AttributeMetadata *posMetadata = geometryMetadata->attribute_metadata( posAttribute->unique_id() );
513 posMetadata->GetEntryDouble(
"i3s-scale_x", &scaleX );
514 posMetadata->GetEntryDouble(
"i3s-scale_y", &scaleY );
519 std::vector<unsigned char> posData( dracoMesh->num_points() * 3 *
sizeof(
float ) );
520 float *posPtr =
reinterpret_cast<float *
>( posData.data() );
523 for ( draco::PointIndex i( 0 ); i < dracoMesh->num_points(); ++i )
525 posAttribute->ConvertValue<
float>( posAttribute->mapped_index( i ), posAttribute->num_components(), values );
531 if ( context.isGlobalMode )
533 double lonDeg = double( values[0] ) * scaleX + nodeCenterLonLat.
x();
534 double latDeg = double( values[1] ) * scaleY + nodeCenterLonLat.
y();
535 double alt = double( values[2] ) + nodeCenterLonLat.
z();
537 QgsVector3D localPos = ecef - context.nodeCenterEcef;
539 values[0] =
static_cast<float>( localPos.
x() );
540 values[1] =
static_cast<float>( localPos.
y() );
541 values[2] =
static_cast<float>( localPos.
z() );
544 posPtr[i.value() * 3 + 0] = values[0];
545 posPtr[i.value() * 3 + 1] = values[1];
546 posPtr[i.value() * 3 + 2] = values[2];
549 tinygltf::Buffer posBuffer;
550 posBuffer.data = posData;
551 model.buffers.emplace_back( std::move( posBuffer ) );
553 tinygltf::BufferView posBufferView;
554 posBufferView.buffer =
static_cast<int>( model.buffers.size() ) - 1;
555 posBufferView.byteOffset = 0;
556 posBufferView.byteLength = posData.size();
557 posBufferView.target = TINYGLTF_TARGET_ARRAY_BUFFER;
558 model.bufferViews.emplace_back( std::move( posBufferView ) );
560 tinygltf::Accessor posAccessor;
561 posAccessor.bufferView =
static_cast<int>( model.bufferViews.size() ) - 1;
562 posAccessor.byteOffset = 0;
563 posAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT;
564 posAccessor.count = dracoMesh->num_points();
565 posAccessor.type = TINYGLTF_TYPE_VEC3;
566 model.accessors.emplace_back( std::move( posAccessor ) );
568 posAccessorIndex =
static_cast<int>( model.accessors.size() ) - 1;
575 const draco::PointAttribute *normalAttribute = dracoMesh->GetNamedAttribute( draco::GeometryAttribute::NORMAL );
576 if ( normalAttribute )
578 std::vector<unsigned char> normalData( dracoMesh->num_points() * 3 *
sizeof(
float ) );
579 float *normalPtr =
reinterpret_cast<float *
>( normalData.data() );
582 for ( draco::PointIndex i( 0 ); i < dracoMesh->num_points(); ++i )
584 normalAttribute->ConvertValue<
float>( normalAttribute->mapped_index( i ), normalAttribute->num_components(), values );
586 normalPtr[i.value() * 3 + 0] = values[0];
587 normalPtr[i.value() * 3 + 1] = values[1];
588 normalPtr[i.value() * 3 + 2] = values[2];
591 tinygltf::Buffer normalBuffer;
592 normalBuffer.data = normalData;
593 model.buffers.emplace_back( std::move( normalBuffer ) );
595 tinygltf::BufferView normalBufferView;
596 normalBufferView.buffer =
static_cast<int>( model.buffers.size() ) - 1;
597 normalBufferView.byteOffset = 0;
598 normalBufferView.byteLength = normalData.size();
599 normalBufferView.target = TINYGLTF_TARGET_ARRAY_BUFFER;
600 model.bufferViews.emplace_back( std::move( normalBufferView ) );
602 tinygltf::Accessor normalAccessor;
603 normalAccessor.bufferView =
static_cast<int>( model.bufferViews.size() ) - 1;
604 normalAccessor.byteOffset = 0;
605 normalAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT;
606 normalAccessor.count = dracoMesh->num_points();
607 normalAccessor.type = TINYGLTF_TYPE_VEC3;
608 model.accessors.emplace_back( std::move( normalAccessor ) );
610 normalAccessorIndex =
static_cast<int>( model.accessors.size() ) - 1;
617 const draco::PointAttribute *uvAttribute = dracoMesh->GetNamedAttribute( draco::GeometryAttribute::TEX_COORD );
620 std::vector<unsigned char> uvData( dracoMesh->num_points() * 2 *
sizeof(
float ) );
621 float *uvPtr =
reinterpret_cast<float *
>( uvData.data() );
625 const draco::PointAttribute *uvRegionAttribute =
nullptr;
626 for ( int32_t i = 0; i < dracoMesh->num_attributes(); ++i )
628 const draco::PointAttribute *attribute = dracoMesh->attribute( i );
632 draco::AttributeMetadata *attributeMetadata = geometryMetadata->attribute_metadata( attribute->unique_id() );
633 if ( !attributeMetadata )
636 std::string i3sAttributeType;
637 if ( attributeMetadata->GetEntryString(
"i3s-attribute-type", &i3sAttributeType ) && i3sAttributeType ==
"uv-region" )
639 uvRegionAttribute = attribute;
644 for ( draco::PointIndex i( 0 ); i < dracoMesh->num_points(); ++i )
646 uvAttribute->ConvertValue<
float>( uvAttribute->mapped_index( i ), uvAttribute->num_components(), values );
648 if ( uvRegionAttribute )
654 uint16_t uvRegion[4];
655 uvRegionAttribute->ConvertValue<uint16_t>( uvRegionAttribute->mapped_index( i ), uvRegionAttribute->num_components(), uvRegion );
656 float uMin =
static_cast<float>( uvRegion[0] ) / 65535.f;
657 float vMin =
static_cast<float>( uvRegion[1] ) / 65535.f;
658 float uMax =
static_cast<float>( uvRegion[2] ) / 65535.f;
659 float vMax =
static_cast<float>( uvRegion[3] ) / 65535.f;
660 values[0] = uMin + values[0] * ( uMax - uMin );
661 values[1] = vMin + values[1] * ( vMax - vMin );
664 uvPtr[i.value() * 2 + 0] = values[0];
665 uvPtr[i.value() * 2 + 1] = values[1];
668 tinygltf::Buffer uvBuffer;
669 uvBuffer.data = uvData;
670 model.buffers.emplace_back( std::move( uvBuffer ) );
672 tinygltf::BufferView uvBufferView;
673 uvBufferView.buffer =
static_cast<int>( model.buffers.size() ) - 1;
674 uvBufferView.byteOffset = 0;
675 uvBufferView.byteLength = uvData.size();
676 uvBufferView.target = TINYGLTF_TARGET_ARRAY_BUFFER;
677 model.bufferViews.emplace_back( std::move( uvBufferView ) ) ;
679 tinygltf::Accessor uvAccessor;
680 uvAccessor.bufferView =
static_cast<int>( model.bufferViews.size() ) - 1;
681 uvAccessor.byteOffset = 0;
682 uvAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT;
683 uvAccessor.count = dracoMesh->num_points();
684 uvAccessor.type = TINYGLTF_TYPE_VEC2;
685 model.accessors.emplace_back( std::move( uvAccessor ) );
687 uvAccessorIndex =
static_cast<int>( model.accessors.size() ) - 1;
695 std::vector<unsigned char> indexData;
696 indexData.resize( dracoMesh->num_faces() * 3 *
sizeof( quint32 ) );
697 Q_ASSERT(
sizeof( dracoMesh->face( draco::FaceIndex( 0 ) )[0] ) ==
sizeof( quint32 ) );
698 memcpy( indexData.data(), &dracoMesh->face( draco::FaceIndex( 0 ) )[0], indexData.size() );
700 tinygltf::Buffer gltfIndexBuffer;
701 gltfIndexBuffer.data = indexData;
702 model.buffers.emplace_back( std::move( gltfIndexBuffer ) );
704 tinygltf::BufferView indexBufferView;
705 indexBufferView.buffer =
static_cast<int>( model.buffers.size() ) - 1;
706 indexBufferView.byteLength = dracoMesh->num_faces() * 3 *
sizeof( quint32 );
707 indexBufferView.byteOffset = 0;
708 indexBufferView.byteStride = 0;
709 indexBufferView.target = TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER;
710 model.bufferViews.emplace_back( std::move( indexBufferView ) );
712 tinygltf::Accessor indicesAccessor;
713 indicesAccessor.bufferView =
static_cast<int>( model.bufferViews.size() ) - 1;
714 indicesAccessor.byteOffset = 0;
715 indicesAccessor.count = dracoMesh->num_faces() * 3;
716 indicesAccessor.type = TINYGLTF_TYPE_SCALAR;
717 indicesAccessor.componentType = TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT;
718 model.accessors.emplace_back( std::move( indicesAccessor ) );
720 indicesAccessorIndex =
static_cast<int>( model.accessors.size() ) - 1;
726 tinygltf::Material material;
727 int materialIndex = loadMaterialFromMetadata( context.materialInfo, model );
729 tinygltf::Primitive primitive;
730 primitive.mode = TINYGLTF_MODE_TRIANGLES;
731 primitive.material = materialIndex;
732 primitive.indices = indicesAccessorIndex;
733 if ( posAccessorIndex != -1 )
734 primitive.attributes[
"POSITION"] = posAccessorIndex;
735 if ( normalAccessorIndex != -1 )
736 primitive.attributes[
"NORMAL"] = normalAccessorIndex;
737 if ( uvAccessorIndex != -1 )
738 primitive.attributes[
"TEXCOORD_0"] = uvAccessorIndex;
740 tinygltf::Mesh tiny_mesh;
741 tiny_mesh.primitives.emplace_back( std::move( primitive ) );
742 model.meshes.emplace_back( std::move( tiny_mesh ) );
746 model.nodes.emplace_back( std::move( node ) );
748 tinygltf::Scene scene;
749 scene.nodes.push_back( 0 );
750 model.scenes.emplace_back( std::move( scene ) );
752 model.defaultScene = 0;
753 model.asset.version =
"2.0";
759bool QgsGltfUtils::loadDracoModel(
const QByteArray &data,
const I3SNodeContext &context, tinygltf::Model &model, QString *errors )
765 *errors =
"Cannot load geometry - QGIS was built without Draco library.";
771int QgsGltfUtils::loadMaterialFromMetadata(
const QVariantMap &materialInfo, tinygltf::Model &model )
773 tinygltf::Material material;
774 material.name =
"DefaultMaterial";
776 QVariantList colorList = materialInfo[
"pbrBaseColorFactor"].toList();
777 material.pbrMetallicRoughness.baseColorFactor = { colorList[0].toDouble(), colorList[1].toDouble(), colorList[2].toDouble(), colorList[3].toDouble() };
779 if ( materialInfo.contains(
"pbrBaseColorTexture" ) )
781 QString baseColorTextureUri = materialInfo[
"pbrBaseColorTexture"].toString();
784 img.uri = baseColorTextureUri.toStdString();
785 model.images.emplace_back( std::move( img ) );
787 tinygltf::Texture tex;
788 tex.source =
static_cast<int>( model.images.size() ) - 1;
789 model.textures.emplace_back( std::move( tex ) );
791 material.pbrMetallicRoughness.baseColorTexture.index =
static_cast<int>( model.textures.size() ) - 1;
794 if ( materialInfo.contains(
"doubleSided" ) )
796 material.doubleSided = materialInfo[
"doubleSided"].toInt();
800 model.materials.emplace_back( std::move( material ) );
802 return static_cast<int>( model.materials.size() ) - 1;
805bool QgsGltfUtils::writeGltfModel(
const tinygltf::Model &model,
const QString &outputFilename )
807 tinygltf::TinyGLTF gltf;
808 bool res = gltf.WriteGltfSceneToFile( &model,
809 outputFilename.toStdString(),
819 const QVariantMap tileMetadata = tile.
metadata();
821 materialInfo = tileMetadata[QStringLiteral(
"material" )].toMap();
@ Geocentric
Geocentric CRS.
@ Reverse
Reverse/inverse transform (from destination to source).
Represents a coordinate reference system (CRS).
Qgis::CrsType type() const
Returns the type of the CRS.
Contains information about the context in which a coordinate transform is executed.
Custom exception class for Coordinate Reference System related exceptions.
A simple 4x4 matrix implementation useful for transformation in 3D space.
QgsVector3D map(const QgsVector3D &vector) const
Matrix-vector multiplication (vector is converted to homogeneous coordinates [X,Y,...
QgsVector3D center() const
Returns the vector to the center of the box.
QgsOrientedBox3D box() const
Returns the volume's oriented box.
Represents an individual tile from a tiled scene data source.
const QgsTiledSceneBoundingVolume & boundingVolume() const
Returns the bounding volume for the tile.
QVariantMap metadata() const
Returns additional metadata attached to the tile.
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.
bool isNull() const
Returns true if all three coordinates are zero.
double x() const
Returns X coordinate.
static bool decodeGzip(const QByteArray &bytesIn, QByteArray &bytesOut)
Decodes gzip byte stream, returns true on success.
#define QgsDebugError(str)