22#include <nlohmann/json.hpp>
37#include <QGenericMatrix>
40#include <QNetworkRequest>
43#include <QtCore/QBuffer>
45using namespace Qt::StringLiterals;
54 const double west = region[0].get<
double>() * 180 / M_PI;
55 const double south = region[1].get<
double>() * 180 / M_PI;
56 const double east = region[2].get<
double>() * 180 / M_PI;
57 const double north = region[3].get<
double>() * 180 / M_PI;
58 double minHeight = region[4].get<
double>();
59 double maxHeight = region[5].get<
double>();
61 return QgsBox3D( west, south, minHeight, east, north, maxHeight );
63 catch ( nlohmann::json::exception & )
71 if ( region.size() != 6 )
79 if ( box.size() != 12 )
85 for (
int i = 0; i < 3; ++i )
87 res.mCenter[i] = box[i].get<
double>();
89 for (
int i = 0; i < 9; ++i )
91 res.mHalfAxes[i] = box[i + 3].get<
double>();
95 catch ( nlohmann::json::exception & )
103 if ( box.size() != 12 )
111 if ( sphere.size() != 4 )
116 const double centerX = sphere[0].get<
double>();
117 const double centerY = sphere[1].get<
double>();
118 const double centerZ = sphere[2].get<
double>();
119 const double radius = sphere[3].get<
double>();
120 return QgsSphere( centerX, centerY, centerZ, radius );
122 catch ( nlohmann::json::exception & )
130 if ( sphere.size() != 4 )
143 const double uniformScale = std::max(
151 return QgsSphere( center.
x(), center.
y(), center.
z(), sphere.
radius() * uniformScale );
160 unsigned char magic[4];
163 quint32 featureTableJsonByteLength;
164 quint32 featureTableBinaryByteLength;
165 quint32 batchTableJsonByteLength;
166 quint32 batchTableBinaryByteLength;
170 if ( tileContent.size() <
static_cast<int>(
sizeof( b3dmHeader ) ) )
174 memcpy( &hdr, tileContent.constData(),
sizeof( b3dmHeader ) );
176 const QString featureTableJson( tileContent.mid(
sizeof( b3dmHeader ), hdr.featureTableJsonByteLength ) );
177 if ( !featureTableJson.isEmpty() )
181 const json featureTable = json::parse( featureTableJson.toStdString() );
182 if ( featureTable.contains(
"RTC_CENTER" ) )
184 const auto &rtcCenterJson = featureTable[
"RTC_CENTER"];
185 if ( rtcCenterJson.is_array() && rtcCenterJson.size() == 3 )
197 catch ( json::parse_error &ex )
199 QgsDebugError( u
"Error parsing feature table JSON: %1"_s.arg( ex.what() ) );
203 res.
gltf = tileContent.mid(
sizeof( b3dmHeader ) + hdr.featureTableJsonByteLength + hdr.featureTableBinaryByteLength + hdr.batchTableJsonByteLength + hdr.batchTableBinaryByteLength );
207static QQuaternion quaternionFromNormalUpRight(
const QVector3D &normalUp,
const QVector3D &normalRight )
209 const QVector3D right = normalRight.normalized();
210 const QVector3D up = normalUp.normalized();
213 const QVector3D forward = QVector3D::crossProduct( up, right ).normalized();
217 float matData[9] = { right.x(), forward.x(), up.x(), right.y(), forward.y(), up.y(), right.z(), forward.z(), up.z() };
218 QMatrix3x3 rotMatrix( matData );
219 return QQuaternion::fromRotationMatrix( rotMatrix );
235static void computeEastNorthUpQuaternions(
const QVector<QVector3D> &positions,
const QgsVector3D &rtcCenter,
const QgsMatrix4x4 &tileTransform, QVector<QQuaternion> &rotations )
237 const int count = positions.size();
238 rotations.resize( count );
248 constexpr double delta = 0.001;
251 const int totalPts = 3 * count;
252 QVector<double> gx( totalPts ), gy( totalPts ), gz( totalPts );
256 QVector<double> px( count ), py( count ), pz( count );
257 for (
int i = 0; i < count; ++i )
259 const QgsVector3D posLocal(
static_cast<double>( positions[i].x() ) + rtcCenter.
x(),
static_cast<double>( positions[i].y() ) + rtcCenter.
y(),
static_cast<double>( positions[i].z() ) + rtcCenter.
z() );
268 ecefToGeodetic.transformCoords( count, px.data(), py.data(), pz.data() );
273 for (
int i = 0; i < count; ++i )
274 rotations[i] = QQuaternion();
280 for (
int i = 0; i < count; ++i )
288 gx[count + i] = px[i] + delta;
289 gy[count + i] = py[i];
290 gz[count + i] = pz[i];
293 gx[2 * count + i] = px[i];
294 gy[2 * count + i] = py[i] + delta;
295 gz[2 * count + i] = pz[i];
300 geodeticToEcef.transformCoords( totalPts, gx.data(), gy.data(), gz.data() );
304 for (
int i = 0; i < count; ++i )
305 rotations[i] = QQuaternion();
315 const double *td = tileTransform.
constData();
317 const QVector3D tileCol0(
static_cast<float>( td[0] ),
static_cast<float>( td[1] ),
static_cast<float>( td[2] ) );
318 const QVector3D tileCol1(
static_cast<float>( td[4] ),
static_cast<float>( td[5] ),
static_cast<float>( td[6] ) );
319 const QVector3D tileCol2(
static_cast<float>( td[8] ),
static_cast<float>( td[9] ),
static_cast<float>( td[10] ) );
322 const float len0 = tileCol0.length();
323 const float len1 = tileCol1.length();
324 const float len2 = tileCol2.length();
325 const bool hasTileRotation = len0 > 0 && len1 > 0 && len2 > 0 && !tileTransform.
isIdentity();
327 for (
int i = 0; i < count; ++i )
329 const QVector3D base(
static_cast<float>( gx[i] ),
static_cast<float>( gy[i] ),
static_cast<float>( gz[i] ) );
330 const QVector3D eastPt(
static_cast<float>( gx[count + i] ),
static_cast<float>( gy[count + i] ),
static_cast<float>( gz[count + i] ) );
331 const QVector3D northPt(
static_cast<float>( gx[2 * count + i] ),
static_cast<float>( gy[2 * count + i] ),
static_cast<float>( gz[2 * count + i] ) );
333 QVector3D east = ( eastPt - base ).normalized();
334 QVector3D north = ( northPt - base ).normalized();
335 QVector3D up = QVector3D::crossProduct( east, north ).normalized();
337 if ( hasTileRotation )
343 const QVector3D nc0 = tileCol0 / len0;
344 const QVector3D nc1 = tileCol1 / len1;
345 const QVector3D nc2 = tileCol2 / len2;
347 const QVector3D eastLocal( QVector3D::dotProduct( nc0, east ), QVector3D::dotProduct( nc1, east ), QVector3D::dotProduct( nc2, east ) );
348 const QVector3D upLocal( QVector3D::dotProduct( nc0, up ), QVector3D::dotProduct( nc1, up ), QVector3D::dotProduct( nc2, up ) );
353 rotations[i] = quaternionFromNormalUpRight( up, east );
359static QMatrix4x4 axisFlipMatrix(
Qgis::Axis gltfUpAxis )
362 switch ( gltfUpAxis )
366 F = QMatrix4x4( 1, 0, 0, 0, 0, 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, 1 );
378static bool parseExtMeshGpuInstancing(
379 const tinygltf::Model &model,
const tinygltf::Node &node, QVector<QVector3D> &translations, QVector<QQuaternion> &rotations, QVector<QVector3D> &scales,
int &instanceCount
382 auto extIt = node.extensions.find(
"EXT_mesh_gpu_instancing" );
383 if ( extIt == node.extensions.end() )
386 const tinygltf::Value &extValue = extIt->second;
387 if ( !extValue.IsObject() || !extValue.Has(
"attributes" ) )
390 const tinygltf::Value &attributes = extValue.Get(
"attributes" );
391 if ( !attributes.IsObject() )
397 auto readVec3Accessor = [&model, &instanceCount](
const tinygltf::Value &attributes,
const std::string &name, QVector<QVector3D> &out,
const QVector3D &defaultValue ) ->
bool {
398 if ( attributes.Has( name ) )
400 int accessorIdx = attributes.Get( name ).GetNumberAsInt();
401 if ( accessorIdx < 0 || accessorIdx >=
static_cast<int>( model.accessors.size() ) )
404 const tinygltf::Accessor &accessor = model.accessors[accessorIdx];
405 if ( accessor.type != TINYGLTF_TYPE_VEC3 || accessor.componentType != TINYGLTF_COMPONENT_TYPE_FLOAT )
408 const int count =
static_cast<int>( accessor.count );
409 if ( instanceCount == 0 )
410 instanceCount = count;
411 else if ( instanceCount != count )
414 const tinygltf::BufferView &bv = model.bufferViews[accessor.bufferView];
415 const tinygltf::Buffer &buf = model.buffers[bv.buffer];
416 const unsigned char *ptr = buf.data.data() + bv.byteOffset + accessor.byteOffset;
417 const int stride = bv.byteStride ?
static_cast<int>( bv.byteStride ) : 3 * static_cast<int>( sizeof( float ) );
420 const unsigned char *row = ptr;
421 for (
int i = 0; i < count; ++i, row += stride )
423 const float *fptr =
reinterpret_cast<const float *
>( row );
424 out[i] = QVector3D( fptr[0], fptr[1], fptr[2] );
430 if ( instanceCount > 0 )
431 out.fill( defaultValue, instanceCount );
436 if ( !readVec3Accessor( attributes,
"TRANSLATION", translations, QVector3D( 0, 0, 0 ) ) )
439 if ( !readVec3Accessor( attributes,
"SCALE", scales, QVector3D( 1, 1, 1 ) ) )
443 if ( attributes.Has(
"ROTATION" ) )
445 int accessorIdx = attributes.Get(
"ROTATION" ).GetNumberAsInt();
446 if ( accessorIdx < 0 || accessorIdx >=
static_cast<int>( model.accessors.size() ) )
449 const tinygltf::Accessor &accessor = model.accessors[accessorIdx];
450 if ( accessor.type != TINYGLTF_TYPE_VEC4 )
453 const int count =
static_cast<int>( accessor.count );
454 if ( instanceCount == 0 )
455 instanceCount = count;
456 else if ( instanceCount != count )
459 const tinygltf::BufferView &bv = model.bufferViews[accessor.bufferView];
460 const tinygltf::Buffer &buf = model.buffers[bv.buffer];
461 const unsigned char *ptr = buf.data.data() + bv.byteOffset + accessor.byteOffset;
463 rotations.resize( count );
465 if ( accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT )
467 const int stride = bv.byteStride ?
static_cast<int>( bv.byteStride ) : 4 * static_cast<int>( sizeof( float ) );
468 const unsigned char *row = ptr;
469 for (
int i = 0; i < count; ++i, row += stride )
471 const float *fptr =
reinterpret_cast<const float *
>( row );
473 rotations[i] = QQuaternion( fptr[3], fptr[0], fptr[1], fptr[2] );
476 else if ( accessor.componentType == TINYGLTF_COMPONENT_TYPE_SHORT )
478 const int stride = bv.byteStride ?
static_cast<int>( bv.byteStride ) : 4 * static_cast<int>( sizeof( short ) );
479 const unsigned char *row = ptr;
480 for (
int i = 0; i < count; ++i, row += stride )
482 const short *sptr =
reinterpret_cast<const short *
>( row );
484 rotations[i] = QQuaternion(
static_cast<float>( sptr[3] ) / 32767.0f,
static_cast<float>( sptr[0] ) / 32767.0f,
static_cast<float>( sptr[1] ) / 32767.0f,
static_cast<float>( sptr[2] ) / 32767.0f );
487 else if ( accessor.componentType == TINYGLTF_COMPONENT_TYPE_BYTE )
489 const int stride = bv.byteStride ?
static_cast<int>( bv.byteStride ) : 4 * static_cast<int>( sizeof( char ) );
490 const unsigned char *row = ptr;
491 for (
int i = 0; i < count; ++i, row += stride )
493 const signed char *bptr =
reinterpret_cast<const signed char *
>( row );
495 rotations[i] = QQuaternion(
static_cast<float>( bptr[3] ) / 127.0f,
static_cast<float>( bptr[0] ) / 127.0f,
static_cast<float>( bptr[1] ) / 127.0f,
static_cast<float>( bptr[2] ) / 127.0f );
505 if ( instanceCount > 0 )
506 rotations.fill( QQuaternion(), instanceCount );
510 if ( instanceCount > 0 )
512 if ( translations.isEmpty() )
513 translations.fill( QVector3D( 0, 0, 0 ), instanceCount );
514 if ( rotations.isEmpty() )
515 rotations.fill( QQuaternion(), instanceCount );
516 if ( scales.isEmpty() )
517 scales.fill( QVector3D( 1, 1, 1 ), instanceCount );
520 return instanceCount > 0;
525static QMatrix4x4 trsMatrix(
const QVector3D &t,
const QQuaternion &r,
const QVector3D &s )
536 const tinygltf::Model &model,
const std::optional<TileI3dmData> &tileInstancing,
Qgis::Axis gltfUpAxis,
const QgsMatrix4x4 &tileTransform,
const QgsVector3D &rtcCenter
539 QVector<QgsGltfUtils::InstancedPrimitive> result;
541 bool sceneOk =
false;
542 const std::size_t sceneIndex = QgsGltfUtils::sourceSceneForModel( model, sceneOk );
546 const tinygltf::Scene &scene = model.scenes[sceneIndex];
547 const QMatrix4x4 F = axisFlipMatrix( gltfUpAxis );
550 std::function<void(
int nodeIndex,
const QMatrix4x4 &parentTransform )> walkNode;
551 walkNode = [&](
int nodeIndex,
const QMatrix4x4 &parentTransform ) {
552 if ( nodeIndex < 0 || nodeIndex >=
static_cast<int>( model.nodes.size() ) )
555 const tinygltf::Node &node = model.nodes[nodeIndex];
558 std::unique_ptr<QMatrix4x4> localTransform = QgsGltfUtils::parseNodeTransform( node );
559 QMatrix4x4 M = parentTransform;
560 if ( localTransform )
561 M = parentTransform * *localTransform;
563 if ( node.mesh >= 0 )
565 const tinygltf::Mesh &mesh = model.meshes[node.mesh];
567 if ( tileInstancing.has_value() )
571 const QMatrix4x4 FM = F * M;
580 for (
int pIdx = 0; pIdx < static_cast<int>( mesh.primitives.size() ); ++pIdx )
582 QgsGltfUtils::InstancedPrimitive entry;
583 entry.meshIndex = node.mesh;
584 entry.primitiveIndex = pIdx;
585 entry.materialIndex = mesh.primitives[pIdx].material;
593 result.append( std::move( entry ) );
599 QVector<QVector3D> translations;
600 QVector<QQuaternion> rotations;
601 QVector<QVector3D> scales;
602 int instanceCount = 0;
604 if ( parseExtMeshGpuInstancing( model, node, translations, rotations, scales, instanceCount ) )
607 const QMatrix4x4 FM = F * M;
609 for (
int pIdx = 0; pIdx < static_cast<int>( mesh.primitives.size() ); ++pIdx )
611 QgsGltfUtils::InstancedPrimitive entry;
612 entry.meshIndex = node.mesh;
613 entry.primitiveIndex = pIdx;
614 entry.materialIndex = mesh.primitives[pIdx].material;
615 entry.instanceTransforms.resize( instanceCount );
617 for (
int i = 0; i < instanceCount; ++i )
619 entry.instanceTransforms[i] = FM * trsMatrix( translations[i], rotations[i], scales[i] );
622 result.append( std::move( entry ) );
630 for (
int childIndex : node.children )
632 walkNode( childIndex, M );
636 for (
int nodeIndex : scene.nodes )
638 walkNode( nodeIndex, QMatrix4x4() );
649 unsigned char magic[4];
652 quint32 featureTableJsonByteLength;
653 quint32 featureTableBinaryByteLength;
654 quint32 batchTableJsonByteLength;
655 quint32 batchTableBinaryByteLength;
661 if ( tileContent.size() <
static_cast<int>(
sizeof( i3dmHeader ) ) )
665 memcpy( &hdr, tileContent.constData(),
sizeof( i3dmHeader ) );
667 const int featureTableJsonOffset =
sizeof( i3dmHeader );
668 const int featureTableBinaryOffset = featureTableJsonOffset +
static_cast<int>( hdr.featureTableJsonByteLength );
671 const QString featureTableJson( tileContent.mid( featureTableJsonOffset, hdr.featureTableJsonByteLength ) );
672 if ( featureTableJson.isEmpty() )
678 int instanceCount = 0;
679 bool eastNorthUp =
false;
680 int positionByteOffset = -1;
681 int normalUpByteOffset = -1;
682 int normalRightByteOffset = -1;
683 int scaleByteOffset = -1;
684 int scaleNonUniformByteOffset = -1;
688 const json featureTable = json::parse( featureTableJson.toStdString() );
690 if ( !featureTable.contains(
"INSTANCES_LENGTH" ) )
692 QgsDebugError( u
"i3dm: INSTANCES_LENGTH not found in feature table"_s );
695 instanceCount = featureTable[
"INSTANCES_LENGTH"].get<
int>();
697 if ( featureTable.contains(
"RTC_CENTER" ) )
699 const auto &rtcCenterJson = featureTable[
"RTC_CENTER"];
700 if ( rtcCenterJson.is_array() && rtcCenterJson.size() == 3 )
708 if ( featureTable.contains(
"EAST_NORTH_UP" ) )
710 eastNorthUp = featureTable[
"EAST_NORTH_UP"].get<
bool>();
714 if ( featureTable.contains(
"POSITION" ) )
716 const auto &posJson = featureTable[
"POSITION"];
717 if ( posJson.is_object() && posJson.contains(
"byteOffset" ) )
718 positionByteOffset = posJson[
"byteOffset"].get<
int>();
720 if ( featureTable.contains(
"NORMAL_UP" ) )
722 const auto &nuJson = featureTable[
"NORMAL_UP"];
723 if ( nuJson.is_object() && nuJson.contains(
"byteOffset" ) )
724 normalUpByteOffset = nuJson[
"byteOffset"].get<
int>();
726 if ( featureTable.contains(
"NORMAL_RIGHT" ) )
728 const auto &nrJson = featureTable[
"NORMAL_RIGHT"];
729 if ( nrJson.is_object() && nrJson.contains(
"byteOffset" ) )
730 normalRightByteOffset = nrJson[
"byteOffset"].get<
int>();
732 if ( featureTable.contains(
"SCALE" ) )
734 const auto &sJson = featureTable[
"SCALE"];
735 if ( sJson.is_object() && sJson.contains(
"byteOffset" ) )
736 scaleByteOffset = sJson[
"byteOffset"].get<
int>();
738 if ( featureTable.contains(
"SCALE_NON_UNIFORM" ) )
740 const auto &snuJson = featureTable[
"SCALE_NON_UNIFORM"];
741 if ( snuJson.is_object() && snuJson.contains(
"byteOffset" ) )
742 scaleNonUniformByteOffset = snuJson[
"byteOffset"].get<
int>();
745 catch ( json::parse_error &ex )
747 QgsDebugError( u
"i3dm: error parsing feature table JSON: %1"_s.arg( ex.what() ) );
751 if ( instanceCount <= 0 || positionByteOffset < 0 )
753 QgsDebugError( u
"i3dm: invalid instance count or missing POSITION"_s );
757 const char *featureBinaryPtr = tileContent.constData() + featureTableBinaryOffset;
758 const int featureBinarySize =
static_cast<int>( hdr.featureTableBinaryByteLength );
765 const int requiredSize = positionByteOffset + instanceCount * 3 *
static_cast<int>(
sizeof( float ) );
766 if ( requiredSize > featureBinarySize )
768 QgsDebugError( u
"i3dm: POSITION data exceeds feature table binary size"_s );
772 const float *posPtr =
reinterpret_cast<const float *
>( featureBinaryPtr + positionByteOffset );
773 for (
int i = 0; i < instanceCount; ++i, posPtr += 3 )
775 instancing.
translations[i] = QVector3D( posPtr[0], posPtr[1], posPtr[2] );
780 if ( normalUpByteOffset >= 0 && normalRightByteOffset >= 0 )
782 const int requiredUp = normalUpByteOffset + instanceCount * 3 *
static_cast<int>(
sizeof( float ) );
783 const int requiredRight = normalRightByteOffset + instanceCount * 3 *
static_cast<int>(
sizeof( float ) );
784 if ( requiredUp > featureBinarySize || requiredRight > featureBinarySize )
786 QgsDebugError( u
"i3dm: NORMAL_UP/NORMAL_RIGHT data exceeds feature table binary size"_s );
789 instancing.
rotations.resize( instanceCount );
790 const float *upPtr =
reinterpret_cast<const float *
>( featureBinaryPtr + normalUpByteOffset );
791 const float *rightPtr =
reinterpret_cast<const float *
>( featureBinaryPtr + normalRightByteOffset );
792 for (
int i = 0; i < instanceCount; ++i, upPtr += 3, rightPtr += 3 )
794 const QVector3D normalUp( upPtr[0], upPtr[1], upPtr[2] );
795 const QVector3D
normalRight( rightPtr[0], rightPtr[1], rightPtr[2] );
796 instancing.
rotations[i] = quaternionFromNormalUpRight( normalUp, normalRight );
799 else if ( eastNorthUp )
805 instancing.
rotations.fill( QQuaternion(), instanceCount );
809 instancing.
rotations.fill( QQuaternion(), instanceCount );
813 if ( scaleNonUniformByteOffset >= 0 )
815 const int required = scaleNonUniformByteOffset + instanceCount * 3 *
static_cast<int>(
sizeof( float ) );
816 if ( required > featureBinarySize )
818 QgsDebugError( u
"i3dm: SCALE_NON_UNIFORM data exceeds feature table binary size"_s );
821 instancing.
scales.resize( instanceCount );
822 const float *scalePtr =
reinterpret_cast<const float *
>( featureBinaryPtr + scaleNonUniformByteOffset );
823 for (
int i = 0; i < instanceCount; ++i, scalePtr += 3 )
825 instancing.
scales[i] = QVector3D( scalePtr[0], scalePtr[1], scalePtr[2] );
828 else if ( scaleByteOffset >= 0 )
830 const int required = scaleByteOffset + instanceCount *
static_cast<int>(
sizeof( float ) );
831 if ( required > featureBinarySize )
833 QgsDebugError( u
"i3dm: SCALE data exceeds feature table binary size"_s );
836 instancing.
scales.resize( instanceCount );
837 const float *scalePtr =
reinterpret_cast<const float *
>( featureBinaryPtr + scaleByteOffset );
838 for (
int i = 0; i < instanceCount; ++i )
840 const float s = scalePtr[i];
841 instancing.
scales[i] = QVector3D( s, s, s );
846 instancing.
scales.fill( QVector3D( 1, 1, 1 ), instanceCount );
852 const int gltfOffset = featureTableBinaryOffset
853 +
static_cast<int>( hdr.featureTableBinaryByteLength )
854 +
static_cast<int>( hdr.batchTableJsonByteLength )
855 +
static_cast<int>( hdr.batchTableBinaryByteLength );
856 QByteArray gltfContent = tileContent.mid( gltfOffset );
858 if ( hdr.gltfFormat == 1 )
860 res.
gltf = gltfContent;
862 else if ( hdr.gltfFormat == 0 )
864 QString gltfUri = QString::fromUtf8( gltfContent );
866 QUrl url = QUrl( baseUri ).resolved( gltfUri );
868 if ( url.scheme().startsWith(
"http" ) )
870 QNetworkRequest request = QNetworkRequest( url );
871 request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
872 request.setAttribute( QNetworkRequest::CacheSaveControlAttribute,
true );
877 QgsDebugError( u
"i3dm: Failed to download GLTF: %1"_s.arg( url.toString() ) );
885 else if ( url.isLocalFile() )
887 QString localFilePath = url.toLocalFile();
888 if ( QFile::exists( localFilePath ) )
890 QFile f( localFilePath );
891 if ( f.open( QIODevice::ReadOnly ) )
893 res.
gltf = f.readAll();
898 QgsDebugError( u
"i3dm: Failed to open local GLTF: %1"_s.arg( url.toString() ) );
904 QgsDebugError( u
"i3dm: Unknown gltf format: %1"_s.arg( hdr.gltfFormat ) );
910static QVector<QgsCesiumUtils::TileContents> extractGltfFromCmpt(
const QByteArray &tileContent,
int depth = 0 )
914 unsigned char magic[4];
920 QVector<QgsCesiumUtils::TileContents> result;
929 if ( tileContent.size() <
static_cast<int>(
sizeof( cmptHeader ) ) )
933 memcpy( &hdr, tileContent.constData(),
sizeof( cmptHeader ) );
935 if ( hdr.version != 1 )
937 QgsDebugError( u
"Unsupported cmpt version %1"_s.arg( hdr.version ) );
941 if (
static_cast<quint32
>( tileContent.size() ) < hdr.byteLength )
944 int offset =
static_cast<int>(
sizeof( cmptHeader ) );
945 for ( quint32 i = 0; i < hdr.tilesLength; ++i )
948 const quint32 innerByteLength = *
reinterpret_cast<const quint32 *
>( tileContent.constData() + offset + 8 );
950 if ( innerByteLength < 12 || offset +
static_cast<int>( innerByteLength ) >
static_cast<int>( hdr.byteLength ) )
952 QgsDebugError( u
"cmpt with bad inner tile (at index %1)"_s.arg( i ) );
956 const QByteArray innerTile = tileContent.mid( offset, innerByteLength );
958 if ( innerTile.startsWith( QByteArray(
"cmpt" ) ) )
960 result.append( extractGltfFromCmpt( innerTile, depth + 1 ) );
967 offset +=
static_cast<int>( innerByteLength );
977 if ( tileContent.startsWith( QByteArray(
"b3dm" ) ) )
984 else if ( tileContent.startsWith( QByteArray(
"glTF" ) ) )
986 res.
gltf = tileContent;
998 QVector<TileContents> result;
999 if ( tileContent.startsWith( QByteArray(
"b3dm" ) ) )
1005 result.append( contents );
1007 else if ( tileContent.startsWith( QByteArray(
"i3dm" ) ) )
1009 TileContents contents = extractGltfFromI3dm( tileContent, baseUri );
1010 result.append( contents );
1012 else if ( tileContent.startsWith( QByteArray(
"glTF" ) ) )
1015 contents.
gltf = tileContent;
1016 result.append( contents );
1018 else if ( tileContent.startsWith( QByteArray(
"cmpt" ) ) )
1020 result = extractGltfFromCmpt( tileContent );
1024 QgsDebugError( u
"extractGltfFromTileContent: unknown tile format, size=%1, magic=%2"_s.arg( tileContent.size() ).arg( QString::fromLatin1( tileContent.left( 4 ) ) ) );
1031 if ( region.
width() > 20 || region.
height() > 20 )
1038 QVector< QgsVector3D > corners = region.
corners();
1039 QVector< double > x;
1041 QVector< double > y;
1043 QVector< double > z;
1045 for (
int i = 0; i < 8; ++i )
1048 x.append( corner.
x() );
1049 y.append( corner.
y() );
1050 z.append( corner.
z() );
1060 QgsDebugError( u
"Cannot transform region bounding volume"_s );
1063 const auto minMaxX = std::minmax_element( x.constBegin(), x.constEnd() );
1064 const auto minMaxY = std::minmax_element( y.constBegin(), y.constEnd() );
1065 const auto minMaxZ = std::minmax_element( z.constBegin(), z.constEnd() );
1079 QUrlQuery contentQuery( QUrl( contentUri ).query() );
1080 const QList<QPair<QString, QString>> baseUrlQueryItems = QUrlQuery( baseUrl.query() ).queryItems();
1081 for (
const QPair<QString, QString> &kv : baseUrlQueryItems )
1083 contentQuery.addQueryItem( kv.first, kv.second );
1085 QUrl newContentUrl( contentUri );
1086 newContentUrl.setQuery( contentQuery );
1087 return newContentUrl.toString();
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...
A 3-dimensional box composed of x, y, z coordinates.
QVector< QgsVector3D > corners() const
Returns an array of all box corners as 3D vectors.
double width() const
Returns the width of the box.
double height() const
Returns the height of the box.
static QgsSphere parseSphere(const json &sphere)
Parses a sphere object from a Cesium JSON document.
static B3DMContents extractGltfFromB3dm(const QByteArray &tileContent)
Extracts GLTF binary data and other contents from the legacy b3dm (Batched 3D Model) tile format.
static QString appendQueryFromBaseUrl(const QString &contentUri, const QUrl &baseUrl)
Copies any query items from the base URL to the content URI - to replicate undocumented Cesium JS beh...
static QgsOrientedBox3D parseBox(const json &box)
Parses a box object from a Cesium JSON document to an oriented bounding box.
static QVector< QgsGltfUtils::InstancedPrimitive > resolveInstancing(const tinygltf::Model &model, const std::optional< TileI3dmData > &tileInstancing, Qgis::Axis gltfUpAxis, const QgsMatrix4x4 &tileTransform, const QgsVector3D &rtcCenter)
Resolves instancing from either i3dm data or EXT_mesh_gpu_instancing.
static QgsTiledSceneBoundingVolume boundingVolumeFromRegion(const QgsBox3D ®ion, const QgsCoordinateTransformContext &transformContext)
Calculates oriented bounding box in EPSG:4978 from "region" defined with min/max lat/lon coordinates ...
static QgsBox3D parseRegion(const json ®ion)
Parses a region object from a Cesium JSON object to a 3D box.
static QgsSphere transformSphere(const QgsSphere &sphere, const QgsMatrix4x4 &transform)
Applies a transform to a sphere.
static QVector< QgsCesiumUtils::TileContents > extractTileContent(const QByteArray &tileContent, const QString &baseUri=QString())
Parses tile content and returns a list of TileContents.
static Q_DECL_DEPRECATED TileContents extractGltfFromTileContent(const QByteArray &tileContent)
Parses tile content.
Represents a coordinate reference system (CRS).
Contains information about the context in which a coordinate transform is executed.
Custom exception class for Coordinate Reference System related exceptions.
static json jsonFromVariant(const QVariant &v)
Converts a QVariant v to a json object.
A simple 4x4 matrix implementation useful for transformation in 3D space.
bool isIdentity() const
Returns whether this matrix is an identity matrix.
QgsVector3D map(const QgsVector3D &vector) const
Matrix-vector multiplication (vector is converted to homogeneous coordinates [X,Y,...
const double * constData() const
Returns pointer to the matrix data (stored in column-major order).
Encapsulates a network reply within a container which is inexpensive to copy and safe to pass between...
QByteArray content() const
Returns the reply content.
Represents a oriented (rotated) box in 3 dimensions.
static QgsOrientedBox3D fromBox3D(const QgsBox3D &box)
Constructs an oriented box from an axis-aligned bounding box.
A spherical geometry object.
QgsVector3D centerVector() const
Returns the vector to the center of the sphere.
double radius() const
Returns the radius of the sphere.
Represents a bounding volume for a tiled scene.
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.
void setZ(double z)
Sets Z coordinate.
double x() const
Returns X coordinate.
void setX(double x)
Sets X coordinate.
void setY(double y)
Sets Y coordinate.
bool ANALYSIS_EXPORT normalRight(Vector3D *v1, Vector3D *result, double length)
Assigns the vector 'result', which is normal to the vector 'v1', on the right side of v1 and has leng...
#define QgsDebugError(str)
Encapsulates the contents of a B3DM file.
QByteArray gltf
GLTF binary content.
QgsVector3D rtcCenter
Optional RTC center.
Encapsulates the contents of a 3D tile.
QgsVector3D rtcCenter
Center position of relative-to-center coordinates (when used).
QByteArray gltf
GLTF binary content.
std::optional< TileI3dmData > instancing
Optional instancing data, populated for i3dm tiles.
Raw per-instance data parsed from an i3dm feature table of a single tile.
QVector< QVector3D > translations
ECEF-relative positions (Z-up), relative to RTC_CENTER.
QVector< QVector3D > scales
Per-axis scale - (1,1,1) if unspecified.
int instanceCount
Number of instances.
bool eastNorthUp
Whether EAST_NORTH_UP rotations should be computed (deferred until tile transform is available).
QVector< QQuaternion > rotations
Quaternion (x,y,z,w) - identity if unspecified.