22#include <nlohmann/json.hpp>
38#include <QGenericMatrix>
41#include <QNetworkRequest>
44#include <QtCore/QBuffer>
46using namespace Qt::StringLiterals;
55 const double west = region[0].get<
double>() * 180 / M_PI;
56 const double south = region[1].get<
double>() * 180 / M_PI;
57 const double east = region[2].get<
double>() * 180 / M_PI;
58 const double north = region[3].get<
double>() * 180 / M_PI;
59 double minHeight = region[4].get<
double>();
60 double maxHeight = region[5].get<
double>();
62 return QgsBox3D( west, south, minHeight, east, north, maxHeight );
64 catch ( nlohmann::json::exception & )
72 if ( region.size() != 6 )
80 if ( box.size() != 12 )
86 for (
int i = 0; i < 3; ++i )
88 res.mCenter[i] = box[i].get<
double>();
90 for (
int i = 0; i < 9; ++i )
92 res.mHalfAxes[i] = box[i + 3].get<
double>();
96 catch ( nlohmann::json::exception & )
104 if ( box.size() != 12 )
112 if ( sphere.size() != 4 )
117 const double centerX = sphere[0].get<
double>();
118 const double centerY = sphere[1].get<
double>();
119 const double centerZ = sphere[2].get<
double>();
120 const double radius = sphere[3].get<
double>();
121 return QgsSphere( centerX, centerY, centerZ, radius );
123 catch ( nlohmann::json::exception & )
131 if ( sphere.size() != 4 )
144 const double uniformScale = std::max(
152 return QgsSphere( center.
x(), center.
y(), center.
z(), sphere.
radius() * uniformScale );
161 unsigned char magic[4];
164 quint32 featureTableJsonByteLength;
165 quint32 featureTableBinaryByteLength;
166 quint32 batchTableJsonByteLength;
167 quint32 batchTableBinaryByteLength;
171 if ( tileContent.size() <
static_cast<int>(
sizeof( b3dmHeader ) ) )
175 memcpy( &hdr, tileContent.constData(),
sizeof( b3dmHeader ) );
177 const QString featureTableJson( tileContent.mid(
sizeof( b3dmHeader ), hdr.featureTableJsonByteLength ) );
178 if ( !featureTableJson.isEmpty() )
182 const json featureTable = json::parse( featureTableJson.toStdString() );
183 if ( featureTable.contains(
"RTC_CENTER" ) )
185 const auto &rtcCenterJson = featureTable[
"RTC_CENTER"];
186 if ( rtcCenterJson.is_array() && rtcCenterJson.size() == 3 )
198 catch ( json::parse_error &ex )
200 QgsDebugError( u
"Error parsing feature table JSON: %1"_s.arg( ex.what() ) );
204 res.
gltf = tileContent.mid(
sizeof( b3dmHeader ) + hdr.featureTableJsonByteLength + hdr.featureTableBinaryByteLength + hdr.batchTableJsonByteLength + hdr.batchTableBinaryByteLength );
221static void computeEastNorthUpQuaternions(
const QVector<QVector3D> &positions,
const QgsVector3D &rtcCenter,
const QgsMatrix4x4 &tileTransform, QVector<QQuaternion> &rotations )
223 const int count = positions.size();
224 rotations.resize( count );
234 constexpr double delta = 0.001;
237 const int totalPts = 3 * count;
238 QVector<double> gx( totalPts ), gy( totalPts ), gz( totalPts );
242 QVector<double> px( count ), py( count ), pz( count );
243 for (
int i = 0; i < count; ++i )
245 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() );
254 ecefToGeodetic.transformCoords( count, px.data(), py.data(), pz.data() );
259 for (
int i = 0; i < count; ++i )
260 rotations[i] = QQuaternion();
266 for (
int i = 0; i < count; ++i )
274 gx[count + i] = px[i] + delta;
275 gy[count + i] = py[i];
276 gz[count + i] = pz[i];
279 gx[2 * count + i] = px[i];
280 gy[2 * count + i] = py[i] + delta;
281 gz[2 * count + i] = pz[i];
286 geodeticToEcef.transformCoords( totalPts, gx.data(), gy.data(), gz.data() );
290 for (
int i = 0; i < count; ++i )
291 rotations[i] = QQuaternion();
301 const double *td = tileTransform.
constData();
303 const QVector3D tileCol0(
static_cast<float>( td[0] ),
static_cast<float>( td[1] ),
static_cast<float>( td[2] ) );
304 const QVector3D tileCol1(
static_cast<float>( td[4] ),
static_cast<float>( td[5] ),
static_cast<float>( td[6] ) );
305 const QVector3D tileCol2(
static_cast<float>( td[8] ),
static_cast<float>( td[9] ),
static_cast<float>( td[10] ) );
308 const float len0 = tileCol0.length();
309 const float len1 = tileCol1.length();
310 const float len2 = tileCol2.length();
311 const bool hasTileRotation = len0 > 0 && len1 > 0 && len2 > 0 && !tileTransform.
isIdentity();
313 for (
int i = 0; i < count; ++i )
315 const QVector3D base(
static_cast<float>( gx[i] ),
static_cast<float>( gy[i] ),
static_cast<float>( gz[i] ) );
316 const QVector3D eastPt(
static_cast<float>( gx[count + i] ),
static_cast<float>( gy[count + i] ),
static_cast<float>( gz[count + i] ) );
317 const QVector3D northPt(
static_cast<float>( gx[2 * count + i] ),
static_cast<float>( gy[2 * count + i] ),
static_cast<float>( gz[2 * count + i] ) );
319 QVector3D east = ( eastPt - base ).normalized();
320 QVector3D north = ( northPt - base ).normalized();
321 QVector3D up = QVector3D::crossProduct( east, north ).normalized();
323 if ( hasTileRotation )
329 const QVector3D nc0 = tileCol0 / len0;
330 const QVector3D nc1 = tileCol1 / len1;
331 const QVector3D nc2 = tileCol2 / len2;
333 const QVector3D eastLocal( QVector3D::dotProduct( nc0, east ), QVector3D::dotProduct( nc1, east ), QVector3D::dotProduct( nc2, east ) );
334 const QVector3D upLocal( QVector3D::dotProduct( nc0, up ), QVector3D::dotProduct( nc1, up ), QVector3D::dotProduct( nc2, up ) );
345static QMatrix4x4 axisFlipMatrix(
Qgis::Axis gltfUpAxis )
348 switch ( gltfUpAxis )
352 F = QMatrix4x4( 1, 0, 0, 0, 0, 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, 1 );
364static bool parseExtMeshGpuInstancing(
365 const tinygltf::Model &model,
const tinygltf::Node &node, QVector<QVector3D> &translations, QVector<QQuaternion> &rotations, QVector<QVector3D> &scales,
int &instanceCount
368 auto extIt = node.extensions.find(
"EXT_mesh_gpu_instancing" );
369 if ( extIt == node.extensions.end() )
372 const tinygltf::Value &extValue = extIt->second;
373 if ( !extValue.IsObject() || !extValue.Has(
"attributes" ) )
376 const tinygltf::Value &attributes = extValue.Get(
"attributes" );
377 if ( !attributes.IsObject() )
383 auto readVec3Accessor = [&model, &instanceCount](
const tinygltf::Value &attributes,
const std::string &name, QVector<QVector3D> &out,
const QVector3D &defaultValue ) ->
bool {
384 if ( attributes.Has( name ) )
386 int accessorIdx = attributes.Get( name ).GetNumberAsInt();
387 if ( accessorIdx < 0 || accessorIdx >=
static_cast<int>( model.accessors.size() ) )
390 const tinygltf::Accessor &accessor = model.accessors[accessorIdx];
391 if ( accessor.type != TINYGLTF_TYPE_VEC3 || accessor.componentType != TINYGLTF_COMPONENT_TYPE_FLOAT )
394 const int count =
static_cast<int>( accessor.count );
395 if ( instanceCount == 0 )
396 instanceCount = count;
397 else if ( instanceCount != count )
400 const tinygltf::BufferView &bv = model.bufferViews[accessor.bufferView];
401 const tinygltf::Buffer &buf = model.buffers[bv.buffer];
402 const unsigned char *ptr = buf.data.data() + bv.byteOffset + accessor.byteOffset;
403 const int stride = bv.byteStride ?
static_cast<int>( bv.byteStride ) : 3 * static_cast<int>( sizeof( float ) );
406 const unsigned char *row = ptr;
407 for (
int i = 0; i < count; ++i, row += stride )
409 const float *fptr =
reinterpret_cast<const float *
>( row );
410 out[i] = QVector3D( fptr[0], fptr[1], fptr[2] );
416 if ( instanceCount > 0 )
417 out.fill( defaultValue, instanceCount );
422 if ( !readVec3Accessor( attributes,
"TRANSLATION", translations, QVector3D( 0, 0, 0 ) ) )
425 if ( !readVec3Accessor( attributes,
"SCALE", scales, QVector3D( 1, 1, 1 ) ) )
429 if ( attributes.Has(
"ROTATION" ) )
431 int accessorIdx = attributes.Get(
"ROTATION" ).GetNumberAsInt();
432 if ( accessorIdx < 0 || accessorIdx >=
static_cast<int>( model.accessors.size() ) )
435 const tinygltf::Accessor &accessor = model.accessors[accessorIdx];
436 if ( accessor.type != TINYGLTF_TYPE_VEC4 )
439 const int count =
static_cast<int>( accessor.count );
440 if ( instanceCount == 0 )
441 instanceCount = count;
442 else if ( instanceCount != count )
445 const tinygltf::BufferView &bv = model.bufferViews[accessor.bufferView];
446 const tinygltf::Buffer &buf = model.buffers[bv.buffer];
447 const unsigned char *ptr = buf.data.data() + bv.byteOffset + accessor.byteOffset;
449 rotations.resize( count );
451 if ( accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT )
453 const int stride = bv.byteStride ?
static_cast<int>( bv.byteStride ) : 4 * static_cast<int>( sizeof( float ) );
454 const unsigned char *row = ptr;
455 for (
int i = 0; i < count; ++i, row += stride )
457 const float *fptr =
reinterpret_cast<const float *
>( row );
459 rotations[i] = QQuaternion( fptr[3], fptr[0], fptr[1], fptr[2] );
462 else if ( accessor.componentType == TINYGLTF_COMPONENT_TYPE_SHORT )
464 const int stride = bv.byteStride ?
static_cast<int>( bv.byteStride ) : 4 * static_cast<int>( sizeof( short ) );
465 const unsigned char *row = ptr;
466 for (
int i = 0; i < count; ++i, row += stride )
468 const short *sptr =
reinterpret_cast<const short *
>( row );
470 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 );
473 else if ( accessor.componentType == TINYGLTF_COMPONENT_TYPE_BYTE )
475 const int stride = bv.byteStride ?
static_cast<int>( bv.byteStride ) : 4 * static_cast<int>( sizeof( char ) );
476 const unsigned char *row = ptr;
477 for (
int i = 0; i < count; ++i, row += stride )
479 const signed char *bptr =
reinterpret_cast<const signed char *
>( row );
481 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 );
491 if ( instanceCount > 0 )
492 rotations.fill( QQuaternion(), instanceCount );
496 if ( instanceCount > 0 )
498 if ( translations.isEmpty() )
499 translations.fill( QVector3D( 0, 0, 0 ), instanceCount );
500 if ( rotations.isEmpty() )
501 rotations.fill( QQuaternion(), instanceCount );
502 if ( scales.isEmpty() )
503 scales.fill( QVector3D( 1, 1, 1 ), instanceCount );
506 return instanceCount > 0;
511static QMatrix4x4 trsMatrix(
const QVector3D &t,
const QQuaternion &r,
const QVector3D &s )
522 const tinygltf::Model &model,
const std::optional<TileI3dmData> &tileInstancing,
Qgis::Axis gltfUpAxis,
const QgsMatrix4x4 &tileTransform,
const QgsVector3D &rtcCenter
525 QVector<QgsGltfUtils::InstancedPrimitive> result;
527 bool sceneOk =
false;
528 const std::size_t sceneIndex = QgsGltfUtils::sourceSceneForModel( model, sceneOk );
532 const tinygltf::Scene &scene = model.scenes[sceneIndex];
533 const QMatrix4x4 F = axisFlipMatrix( gltfUpAxis );
536 std::function<void(
int nodeIndex,
const QMatrix4x4 &parentTransform )> walkNode;
537 walkNode = [&](
int nodeIndex,
const QMatrix4x4 &parentTransform ) {
538 if ( nodeIndex < 0 || nodeIndex >=
static_cast<int>( model.nodes.size() ) )
541 const tinygltf::Node &node = model.nodes[nodeIndex];
544 std::unique_ptr<QMatrix4x4> localTransform = QgsGltfUtils::parseNodeTransform( node );
545 QMatrix4x4 M = parentTransform;
546 if ( localTransform )
547 M = parentTransform * *localTransform;
549 if ( node.mesh >= 0 )
551 const tinygltf::Mesh &mesh = model.meshes[node.mesh];
553 if ( tileInstancing.has_value() )
557 const QMatrix4x4 FM = F * M;
566 for (
int pIdx = 0; pIdx < static_cast<int>( mesh.primitives.size() ); ++pIdx )
568 QgsGltfUtils::InstancedPrimitive entry;
569 entry.meshIndex = node.mesh;
570 entry.primitiveIndex = pIdx;
571 entry.materialIndex = mesh.primitives[pIdx].material;
579 result.append( std::move( entry ) );
585 QVector<QVector3D> translations;
586 QVector<QQuaternion> rotations;
587 QVector<QVector3D> scales;
588 int instanceCount = 0;
590 if ( parseExtMeshGpuInstancing( model, node, translations, rotations, scales, instanceCount ) )
593 const QMatrix4x4 FM = F * M;
595 for (
int pIdx = 0; pIdx < static_cast<int>( mesh.primitives.size() ); ++pIdx )
597 QgsGltfUtils::InstancedPrimitive entry;
598 entry.meshIndex = node.mesh;
599 entry.primitiveIndex = pIdx;
600 entry.materialIndex = mesh.primitives[pIdx].material;
601 entry.instanceTransforms.resize( instanceCount );
603 for (
int i = 0; i < instanceCount; ++i )
605 entry.instanceTransforms[i] = FM * trsMatrix( translations[i], rotations[i], scales[i] );
608 result.append( std::move( entry ) );
616 for (
int childIndex : node.children )
618 walkNode( childIndex, M );
622 for (
int nodeIndex : scene.nodes )
624 walkNode( nodeIndex, QMatrix4x4() );
635 unsigned char magic[4];
638 quint32 featureTableJsonByteLength;
639 quint32 featureTableBinaryByteLength;
640 quint32 batchTableJsonByteLength;
641 quint32 batchTableBinaryByteLength;
647 if ( tileContent.size() <
static_cast<int>(
sizeof( i3dmHeader ) ) )
651 memcpy( &hdr, tileContent.constData(),
sizeof( i3dmHeader ) );
653 const int featureTableJsonOffset =
sizeof( i3dmHeader );
654 const int featureTableBinaryOffset = featureTableJsonOffset +
static_cast<int>( hdr.featureTableJsonByteLength );
657 const QString featureTableJson( tileContent.mid( featureTableJsonOffset, hdr.featureTableJsonByteLength ) );
658 if ( featureTableJson.isEmpty() )
664 int instanceCount = 0;
665 bool eastNorthUp =
false;
666 int positionByteOffset = -1;
667 int normalUpByteOffset = -1;
668 int normalRightByteOffset = -1;
669 int scaleByteOffset = -1;
670 int scaleNonUniformByteOffset = -1;
674 const json featureTable = json::parse( featureTableJson.toStdString() );
676 if ( !featureTable.contains(
"INSTANCES_LENGTH" ) )
678 QgsDebugError( u
"i3dm: INSTANCES_LENGTH not found in feature table"_s );
681 instanceCount = featureTable[
"INSTANCES_LENGTH"].get<
int>();
683 if ( featureTable.contains(
"RTC_CENTER" ) )
685 const auto &rtcCenterJson = featureTable[
"RTC_CENTER"];
686 if ( rtcCenterJson.is_array() && rtcCenterJson.size() == 3 )
694 if ( featureTable.contains(
"EAST_NORTH_UP" ) )
696 eastNorthUp = featureTable[
"EAST_NORTH_UP"].get<
bool>();
700 if ( featureTable.contains(
"POSITION" ) )
702 const auto &posJson = featureTable[
"POSITION"];
703 if ( posJson.is_object() && posJson.contains(
"byteOffset" ) )
704 positionByteOffset = posJson[
"byteOffset"].get<
int>();
706 if ( featureTable.contains(
"NORMAL_UP" ) )
708 const auto &nuJson = featureTable[
"NORMAL_UP"];
709 if ( nuJson.is_object() && nuJson.contains(
"byteOffset" ) )
710 normalUpByteOffset = nuJson[
"byteOffset"].get<
int>();
712 if ( featureTable.contains(
"NORMAL_RIGHT" ) )
714 const auto &nrJson = featureTable[
"NORMAL_RIGHT"];
715 if ( nrJson.is_object() && nrJson.contains(
"byteOffset" ) )
716 normalRightByteOffset = nrJson[
"byteOffset"].get<
int>();
718 if ( featureTable.contains(
"SCALE" ) )
720 const auto &sJson = featureTable[
"SCALE"];
721 if ( sJson.is_object() && sJson.contains(
"byteOffset" ) )
722 scaleByteOffset = sJson[
"byteOffset"].get<
int>();
724 if ( featureTable.contains(
"SCALE_NON_UNIFORM" ) )
726 const auto &snuJson = featureTable[
"SCALE_NON_UNIFORM"];
727 if ( snuJson.is_object() && snuJson.contains(
"byteOffset" ) )
728 scaleNonUniformByteOffset = snuJson[
"byteOffset"].get<
int>();
731 catch ( json::parse_error &ex )
733 QgsDebugError( u
"i3dm: error parsing feature table JSON: %1"_s.arg( ex.what() ) );
737 if ( instanceCount <= 0 || positionByteOffset < 0 )
739 QgsDebugError( u
"i3dm: invalid instance count or missing POSITION"_s );
743 const char *featureBinaryPtr = tileContent.constData() + featureTableBinaryOffset;
744 const int featureBinarySize =
static_cast<int>( hdr.featureTableBinaryByteLength );
751 const int requiredSize = positionByteOffset + instanceCount * 3 *
static_cast<int>(
sizeof( float ) );
752 if ( requiredSize > featureBinarySize )
754 QgsDebugError( u
"i3dm: POSITION data exceeds feature table binary size"_s );
758 const float *posPtr =
reinterpret_cast<const float *
>( featureBinaryPtr + positionByteOffset );
759 for (
int i = 0; i < instanceCount; ++i, posPtr += 3 )
761 instancing.
translations[i] = QVector3D( posPtr[0], posPtr[1], posPtr[2] );
766 if ( normalUpByteOffset >= 0 && normalRightByteOffset >= 0 )
768 const int requiredUp = normalUpByteOffset + instanceCount * 3 *
static_cast<int>(
sizeof( float ) );
769 const int requiredRight = normalRightByteOffset + instanceCount * 3 *
static_cast<int>(
sizeof( float ) );
770 if ( requiredUp > featureBinarySize || requiredRight > featureBinarySize )
772 QgsDebugError( u
"i3dm: NORMAL_UP/NORMAL_RIGHT data exceeds feature table binary size"_s );
775 instancing.
rotations.resize( instanceCount );
776 const float *upPtr =
reinterpret_cast<const float *
>( featureBinaryPtr + normalUpByteOffset );
777 const float *rightPtr =
reinterpret_cast<const float *
>( featureBinaryPtr + normalRightByteOffset );
778 for (
int i = 0; i < instanceCount; ++i, upPtr += 3, rightPtr += 3 )
780 const QVector3D normalUp( upPtr[0], upPtr[1], upPtr[2] );
781 const QVector3D
normalRight( rightPtr[0], rightPtr[1], rightPtr[2] );
785 else if ( eastNorthUp )
791 instancing.
rotations.fill( QQuaternion(), instanceCount );
795 instancing.
rotations.fill( QQuaternion(), instanceCount );
799 if ( scaleNonUniformByteOffset >= 0 )
801 const int required = scaleNonUniformByteOffset + instanceCount * 3 *
static_cast<int>(
sizeof( float ) );
802 if ( required > featureBinarySize )
804 QgsDebugError( u
"i3dm: SCALE_NON_UNIFORM data exceeds feature table binary size"_s );
807 instancing.
scales.resize( instanceCount );
808 const float *scalePtr =
reinterpret_cast<const float *
>( featureBinaryPtr + scaleNonUniformByteOffset );
809 for (
int i = 0; i < instanceCount; ++i, scalePtr += 3 )
811 instancing.
scales[i] = QVector3D( scalePtr[0], scalePtr[1], scalePtr[2] );
814 else if ( scaleByteOffset >= 0 )
816 const int required = scaleByteOffset + instanceCount *
static_cast<int>(
sizeof( float ) );
817 if ( required > featureBinarySize )
819 QgsDebugError( u
"i3dm: SCALE data exceeds feature table binary size"_s );
822 instancing.
scales.resize( instanceCount );
823 const float *scalePtr =
reinterpret_cast<const float *
>( featureBinaryPtr + scaleByteOffset );
824 for (
int i = 0; i < instanceCount; ++i )
826 const float s = scalePtr[i];
827 instancing.
scales[i] = QVector3D( s, s, s );
832 instancing.
scales.fill( QVector3D( 1, 1, 1 ), instanceCount );
838 const int gltfOffset = featureTableBinaryOffset
839 +
static_cast<int>( hdr.featureTableBinaryByteLength )
840 +
static_cast<int>( hdr.batchTableJsonByteLength )
841 +
static_cast<int>( hdr.batchTableBinaryByteLength );
842 QByteArray gltfContent = tileContent.mid( gltfOffset );
844 if ( hdr.gltfFormat == 1 )
846 res.
gltf = gltfContent;
848 else if ( hdr.gltfFormat == 0 )
850 QString gltfUri = QString::fromUtf8( gltfContent );
852 QUrl url = QUrl( baseUri ).resolved( gltfUri );
854 if ( url.scheme().startsWith(
"http" ) )
856 QNetworkRequest request = QNetworkRequest( url );
857 request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
858 request.setAttribute( QNetworkRequest::CacheSaveControlAttribute,
true );
863 QgsDebugError( u
"i3dm: Failed to download GLTF: %1"_s.arg( url.toString() ) );
871 else if ( url.isLocalFile() )
873 QString localFilePath = url.toLocalFile();
874 if ( QFile::exists( localFilePath ) )
876 QFile f( localFilePath );
877 if ( f.open( QIODevice::ReadOnly ) )
879 res.
gltf = f.readAll();
884 QgsDebugError( u
"i3dm: Failed to open local GLTF: %1"_s.arg( url.toString() ) );
890 QgsDebugError( u
"i3dm: Unknown gltf format: %1"_s.arg( hdr.gltfFormat ) );
896static QVector<QgsCesiumUtils::TileContents> extractGltfFromCmpt(
const QByteArray &tileContent,
int depth = 0 )
900 unsigned char magic[4];
906 QVector<QgsCesiumUtils::TileContents> result;
915 if ( tileContent.size() <
static_cast<int>(
sizeof( cmptHeader ) ) )
919 memcpy( &hdr, tileContent.constData(),
sizeof( cmptHeader ) );
921 if ( hdr.version != 1 )
923 QgsDebugError( u
"Unsupported cmpt version %1"_s.arg( hdr.version ) );
927 if (
static_cast<quint32
>( tileContent.size() ) < hdr.byteLength )
930 int offset =
static_cast<int>(
sizeof( cmptHeader ) );
931 for ( quint32 i = 0; i < hdr.tilesLength; ++i )
934 const quint32 innerByteLength = *
reinterpret_cast<const quint32 *
>( tileContent.constData() + offset + 8 );
936 if ( innerByteLength < 12 || offset +
static_cast<int>( innerByteLength ) >
static_cast<int>( hdr.byteLength ) )
938 QgsDebugError( u
"cmpt with bad inner tile (at index %1)"_s.arg( i ) );
942 const QByteArray innerTile = tileContent.mid( offset, innerByteLength );
944 if ( innerTile.startsWith( QByteArray(
"cmpt" ) ) )
946 result.append( extractGltfFromCmpt( innerTile, depth + 1 ) );
953 offset +=
static_cast<int>( innerByteLength );
963 if ( tileContent.startsWith( QByteArray(
"b3dm" ) ) )
970 else if ( tileContent.startsWith( QByteArray(
"glTF" ) ) )
972 res.
gltf = tileContent;
984 QVector<TileContents> result;
985 if ( tileContent.startsWith( QByteArray(
"b3dm" ) ) )
991 result.append( contents );
993 else if ( tileContent.startsWith( QByteArray(
"i3dm" ) ) )
995 TileContents contents = extractGltfFromI3dm( tileContent, baseUri );
996 result.append( contents );
998 else if ( tileContent.startsWith( QByteArray(
"glTF" ) ) )
1001 contents.
gltf = tileContent;
1002 result.append( contents );
1004 else if ( tileContent.startsWith( QByteArray(
"cmpt" ) ) )
1006 result = extractGltfFromCmpt( tileContent );
1010 QgsDebugError( u
"extractGltfFromTileContent: unknown tile format, size=%1, magic=%2"_s.arg( tileContent.size() ).arg( QString::fromLatin1( tileContent.left( 4 ) ) ) );
1017 if ( region.
width() > 20 || region.
height() > 20 )
1024 QVector< QgsVector3D > corners = region.
corners();
1025 QVector< double > x;
1027 QVector< double > y;
1029 QVector< double > z;
1031 for (
int i = 0; i < 8; ++i )
1034 x.append( corner.
x() );
1035 y.append( corner.
y() );
1036 z.append( corner.
z() );
1046 QgsDebugError( u
"Cannot transform region bounding volume"_s );
1049 const auto minMaxX = std::minmax_element( x.constBegin(), x.constEnd() );
1050 const auto minMaxY = std::minmax_element( y.constBegin(), y.constEnd() );
1051 const auto minMaxZ = std::minmax_element( z.constBegin(), z.constEnd() );
1065 QUrlQuery contentQuery( QUrl( contentUri ).query() );
1066 const QList<QPair<QString, QString>> baseUrlQueryItems = QUrlQuery( baseUrl.query() ).queryItems();
1067 for (
const QPair<QString, QString> &kv : baseUrlQueryItems )
1069 contentQuery.addQueryItem( kv.first, kv.second );
1071 QUrl newContentUrl( contentUri );
1072 newContentUrl.setQuery( contentQuery );
1073 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 QQuaternion quaternionFromNormalUpRight(const QVector3D &normalUp, const QVector3D &normalRight)
Builds a rotation quaternion from an "up" direction and a "right" direction, with the remaining basis...
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.