mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-04 00:04:03 -04:00
[i3s] Transcode Draco-encoded geometry to a GLTF 2.0 model
This commit is contained in:
parent
97ee003455
commit
8a93448a15
@ -15,7 +15,6 @@
|
|||||||
|
|
||||||
#include "qgsgltfutils.h"
|
#include "qgsgltfutils.h"
|
||||||
|
|
||||||
#include "qgscoordinatetransform.h"
|
|
||||||
#include "qgsexception.h"
|
#include "qgsexception.h"
|
||||||
#include "qgsmatrix4x4.h"
|
#include "qgsmatrix4x4.h"
|
||||||
#include "qgsconfig.h"
|
#include "qgsconfig.h"
|
||||||
@ -396,4 +395,384 @@ std::size_t QgsGltfUtils::sourceSceneForModel( const tinygltf::Model &model, boo
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void dumpDracoModelInfo( draco::Mesh *dracoMesh )
|
||||||
|
{
|
||||||
|
std::cout << "Decoded Draco Mesh:" << dracoMesh->num_points() << " points / " << dracoMesh->num_faces() << " faces" << std::endl;
|
||||||
|
draco::GeometryMetadata *geometryMetadata = dracoMesh->metadata();
|
||||||
|
|
||||||
|
std::cout << "Global Geometry Metadata:" << std::endl;
|
||||||
|
for ( const auto &entry : geometryMetadata->entries() )
|
||||||
|
{
|
||||||
|
std::cout << " Key: " << entry.first << ", Value: " << entry.second.data().size() << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "\nAttribute Metadata:" << std::endl;
|
||||||
|
for ( int32_t i = 0; i < dracoMesh->num_attributes(); ++i )
|
||||||
|
{
|
||||||
|
const draco::PointAttribute *attribute = dracoMesh->attribute( i );
|
||||||
|
if ( !attribute )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
std::cout << " Attribute ID: " << attribute->unique_id() << " / " << draco::PointAttribute::TypeToString( attribute->attribute_type() ) << std::endl;
|
||||||
|
if ( const draco::AttributeMetadata *attributeMetadata = geometryMetadata->attribute_metadata( attribute->unique_id() ) )
|
||||||
|
{
|
||||||
|
for ( const auto &entry : attributeMetadata->entries() )
|
||||||
|
{
|
||||||
|
std::cout << " Key: " << entry.first << ", Length: " << entry.second.data().size() << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool QgsGltfUtils::loadDracoModel( const QByteArray &data, const I3SNodeContext &context, tinygltf::Model &model, QString *errors )
|
||||||
|
{
|
||||||
|
//
|
||||||
|
// load the model in decoder and do basic sanity checks
|
||||||
|
//
|
||||||
|
|
||||||
|
draco::Decoder decoder;
|
||||||
|
draco::DecoderBuffer decoderBuffer;
|
||||||
|
decoderBuffer.Init( data.constData(), data.size() );
|
||||||
|
|
||||||
|
draco::StatusOr<draco::EncodedGeometryType> geometryTypeStatus = decoder.GetEncodedGeometryType( &decoderBuffer );
|
||||||
|
if ( !geometryTypeStatus.ok() )
|
||||||
|
{
|
||||||
|
if ( errors )
|
||||||
|
*errors = "Failed to get geometry type: " + QString( geometryTypeStatus.status().error_msg() );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ( geometryTypeStatus.value() != draco::EncodedGeometryType::TRIANGULAR_MESH )
|
||||||
|
{
|
||||||
|
if ( errors )
|
||||||
|
*errors = "Not a triangular mesh";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
draco::StatusOr<std::unique_ptr<draco::Mesh>> meshStatus = decoder.DecodeMeshFromBuffer( &decoderBuffer );
|
||||||
|
if ( !meshStatus.ok() )
|
||||||
|
{
|
||||||
|
if ( errors )
|
||||||
|
*errors = "Failed to decode mesh: " + QString( meshStatus.status().error_msg() );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<draco::Mesh> dracoMesh = std::move( meshStatus ).value();
|
||||||
|
|
||||||
|
draco::GeometryMetadata *geometryMetadata = dracoMesh->metadata();
|
||||||
|
if ( !geometryMetadata )
|
||||||
|
{
|
||||||
|
if ( errors )
|
||||||
|
*errors = "Geometry metadata missing";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int posAccessorIndex = -1;
|
||||||
|
int normalAccessorIndex = -1;
|
||||||
|
int uvAccessorIndex = -1;
|
||||||
|
int indicesAccessorIndex = -1;
|
||||||
|
|
||||||
|
//
|
||||||
|
// parse XYZ position coordinates
|
||||||
|
//
|
||||||
|
|
||||||
|
const draco::PointAttribute *posAttribute = dracoMesh->GetNamedAttribute( draco::GeometryAttribute::POSITION );
|
||||||
|
if ( posAttribute )
|
||||||
|
{
|
||||||
|
double scaleX = 1, scaleY = 1;
|
||||||
|
const draco::AttributeMetadata *posMetadata = geometryMetadata->attribute_metadata( posAttribute->unique_id() );
|
||||||
|
if ( posMetadata )
|
||||||
|
{
|
||||||
|
posMetadata->GetEntryDouble( "i3s-scale_x", &scaleX );
|
||||||
|
posMetadata->GetEntryDouble( "i3s-scale_y", &scaleY );
|
||||||
|
}
|
||||||
|
|
||||||
|
QgsVector3D nodeCenterLonLat = context.datasetToSceneTransform.transform( context.nodeCenterEcef, Qgis::TransformDirection::Reverse );
|
||||||
|
|
||||||
|
std::vector<unsigned char> posData( dracoMesh->num_points() * 3 * sizeof( float ) );
|
||||||
|
float *posPtr = reinterpret_cast<float *>( posData.data() );
|
||||||
|
|
||||||
|
float values[4];
|
||||||
|
for ( draco::PointIndex i( 0 ); i < dracoMesh->num_points(); ++i )
|
||||||
|
{
|
||||||
|
posAttribute->ConvertValue<float>( posAttribute->mapped_index( i ), posAttribute->num_components(), values );
|
||||||
|
|
||||||
|
// when using EPSG:4326, the X,Y coordinates are in degrees(!) relative to the node's center (in lat/lon degrees),
|
||||||
|
// but they are scaled (because they are several orders of magnitude smaller than Z coordinates).
|
||||||
|
// That scaling is applied so that Draco's compression works well.
|
||||||
|
// when using local CRS, scaling is not applied (not needed)
|
||||||
|
if ( context.isGlobalMode )
|
||||||
|
{
|
||||||
|
double lonDeg = double( values[0] ) * scaleX + nodeCenterLonLat.x();
|
||||||
|
double latDeg = double( values[1] ) * scaleY + nodeCenterLonLat.y();
|
||||||
|
double alt = double( values[2] ) + nodeCenterLonLat.z();
|
||||||
|
QgsVector3D ecef = context.datasetToSceneTransform.transform( QgsVector3D( lonDeg, latDeg, alt ) );
|
||||||
|
QgsVector3D localPos = ecef - context.nodeCenterEcef;
|
||||||
|
|
||||||
|
values[0] = static_cast<float>( localPos.x() );
|
||||||
|
values[1] = static_cast<float>( localPos.y() );
|
||||||
|
values[2] = static_cast<float>( localPos.z() );
|
||||||
|
}
|
||||||
|
|
||||||
|
posPtr[i.value() * 3 + 0] = values[0];
|
||||||
|
posPtr[i.value() * 3 + 1] = values[1];
|
||||||
|
posPtr[i.value() * 3 + 2] = values[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
tinygltf::Buffer posBuffer;
|
||||||
|
posBuffer.data = posData;
|
||||||
|
model.buffers.push_back( posBuffer );
|
||||||
|
|
||||||
|
tinygltf::BufferView posBufferView;
|
||||||
|
posBufferView.buffer = static_cast<int>( model.buffers.size() ) - 1;
|
||||||
|
posBufferView.byteOffset = 0;
|
||||||
|
posBufferView.byteLength = posData.size();
|
||||||
|
posBufferView.target = TINYGLTF_TARGET_ARRAY_BUFFER;
|
||||||
|
model.bufferViews.push_back( posBufferView );
|
||||||
|
|
||||||
|
tinygltf::Accessor posAccessor;
|
||||||
|
posAccessor.bufferView = static_cast<int>( model.bufferViews.size() ) - 1;
|
||||||
|
posAccessor.byteOffset = 0;
|
||||||
|
posAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT;
|
||||||
|
posAccessor.count = dracoMesh->num_points();
|
||||||
|
posAccessor.type = TINYGLTF_TYPE_VEC3;
|
||||||
|
model.accessors.push_back( posAccessor );
|
||||||
|
|
||||||
|
posAccessorIndex = static_cast<int>( model.accessors.size() ) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// parse normal vectors
|
||||||
|
//
|
||||||
|
|
||||||
|
const draco::PointAttribute *normalAttribute = dracoMesh->GetNamedAttribute( draco::GeometryAttribute::NORMAL );
|
||||||
|
if ( normalAttribute )
|
||||||
|
{
|
||||||
|
std::vector<unsigned char> normalData( dracoMesh->num_points() * 3 * sizeof( float ) );
|
||||||
|
float *normalPtr = reinterpret_cast<float *>( normalData.data() );
|
||||||
|
|
||||||
|
float values[3];
|
||||||
|
for ( draco::PointIndex i( 0 ); i < dracoMesh->num_points(); ++i )
|
||||||
|
{
|
||||||
|
normalAttribute->ConvertValue<float>( normalAttribute->mapped_index( i ), normalAttribute->num_components(), values );
|
||||||
|
|
||||||
|
normalPtr[i.value() * 3 + 0] = values[0];
|
||||||
|
normalPtr[i.value() * 3 + 1] = values[1];
|
||||||
|
normalPtr[i.value() * 3 + 2] = values[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
tinygltf::Buffer normalBuffer;
|
||||||
|
normalBuffer.data = normalData;
|
||||||
|
model.buffers.push_back( normalBuffer );
|
||||||
|
|
||||||
|
tinygltf::BufferView normalBufferView;
|
||||||
|
normalBufferView.buffer = static_cast<int>( model.buffers.size() ) - 1;
|
||||||
|
normalBufferView.byteOffset = 0;
|
||||||
|
normalBufferView.byteLength = normalData.size();
|
||||||
|
normalBufferView.target = TINYGLTF_TARGET_ARRAY_BUFFER;
|
||||||
|
model.bufferViews.push_back( normalBufferView );
|
||||||
|
|
||||||
|
tinygltf::Accessor normalAccessor;
|
||||||
|
normalAccessor.bufferView = static_cast<int>( model.bufferViews.size() ) - 1;
|
||||||
|
normalAccessor.byteOffset = 0;
|
||||||
|
normalAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT;
|
||||||
|
normalAccessor.count = dracoMesh->num_points();
|
||||||
|
normalAccessor.type = TINYGLTF_TYPE_VEC3;
|
||||||
|
model.accessors.push_back( normalAccessor );
|
||||||
|
|
||||||
|
normalAccessorIndex = static_cast<int>( model.accessors.size() ) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// parse UV texture coordinates
|
||||||
|
//
|
||||||
|
|
||||||
|
const draco::PointAttribute *uvAttribute = dracoMesh->GetNamedAttribute( draco::GeometryAttribute::TEX_COORD );
|
||||||
|
if ( uvAttribute )
|
||||||
|
{
|
||||||
|
std::vector<unsigned char> uvData( dracoMesh->num_points() * 2 * sizeof( float ) );
|
||||||
|
float *uvPtr = reinterpret_cast<float *>( uvData.data() );
|
||||||
|
|
||||||
|
// try to find UV region attribute - if it exists, we will need to adjust
|
||||||
|
// texture UV values based on its regions
|
||||||
|
const draco::PointAttribute *uvRegionAttribute = nullptr;
|
||||||
|
for ( int32_t i = 0; i < dracoMesh->num_attributes(); ++i )
|
||||||
|
{
|
||||||
|
const draco::PointAttribute *attribute = dracoMesh->attribute( i );
|
||||||
|
if ( !attribute )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
draco::AttributeMetadata *attributeMetadata = geometryMetadata->attribute_metadata( attribute->unique_id() );
|
||||||
|
if ( !attributeMetadata )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
std::string i3sAttributeType;
|
||||||
|
if ( attributeMetadata->GetEntryString( "i3s-attribute-type", &i3sAttributeType ) && i3sAttributeType == "uv-region" )
|
||||||
|
{
|
||||||
|
uvRegionAttribute = attribute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float values[2];
|
||||||
|
for ( draco::PointIndex i( 0 ); i < dracoMesh->num_points(); ++i )
|
||||||
|
{
|
||||||
|
uvAttribute->ConvertValue<float>( uvAttribute->mapped_index( i ), uvAttribute->num_components(), values );
|
||||||
|
|
||||||
|
if ( uvRegionAttribute )
|
||||||
|
{
|
||||||
|
// UV regions are 4 x uint16 per each vertex [uMin, vMin, uMax, vMax], and they define
|
||||||
|
// a sub-region within a texture to which UV coordinates of each vertex belong.
|
||||||
|
// I have no idea why there's such extra complication for clients... the final
|
||||||
|
// UV coordinates could have been easily calculated by the dataset producer.
|
||||||
|
uint16_t uvRegion[4];
|
||||||
|
uvRegionAttribute->ConvertValue<uint16_t>( uvRegionAttribute->mapped_index( i ), uvRegionAttribute->num_components(), uvRegion );
|
||||||
|
float uMin = uvRegion[0] / 65535.0;
|
||||||
|
float vMin = uvRegion[1] / 65535.f;
|
||||||
|
float uMax = uvRegion[2] / 65535.f;
|
||||||
|
float vMax = uvRegion[3] / 65535.f;
|
||||||
|
values[0] = uMin + values[0] * ( uMax - uMin );
|
||||||
|
values[1] = vMin + values[1] * ( vMax - vMin );
|
||||||
|
}
|
||||||
|
|
||||||
|
uvPtr[i.value() * 2 + 0] = values[0];
|
||||||
|
uvPtr[i.value() * 2 + 1] = values[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
tinygltf::Buffer uvBuffer;
|
||||||
|
uvBuffer.data = uvData;
|
||||||
|
model.buffers.push_back( uvBuffer );
|
||||||
|
|
||||||
|
tinygltf::BufferView uvBufferView;
|
||||||
|
uvBufferView.buffer = static_cast<int>( model.buffers.size() ) - 1;
|
||||||
|
uvBufferView.byteOffset = 0;
|
||||||
|
uvBufferView.byteLength = uvData.size();
|
||||||
|
uvBufferView.target = TINYGLTF_TARGET_ARRAY_BUFFER;
|
||||||
|
model.bufferViews.push_back( uvBufferView );
|
||||||
|
|
||||||
|
tinygltf::Accessor uvAccessor;
|
||||||
|
uvAccessor.bufferView = static_cast<int>( model.bufferViews.size() ) - 1;
|
||||||
|
uvAccessor.byteOffset = 0;
|
||||||
|
uvAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT;
|
||||||
|
uvAccessor.count = dracoMesh->num_points();
|
||||||
|
uvAccessor.type = TINYGLTF_TYPE_VEC2;
|
||||||
|
model.accessors.push_back( uvAccessor );
|
||||||
|
|
||||||
|
uvAccessorIndex = static_cast<int>( model.accessors.size() ) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// parse indices of triangles
|
||||||
|
//
|
||||||
|
|
||||||
|
// TODO: to save some memory, we could use only 1 or 2 bytes per vertex if the mesh is small enough
|
||||||
|
std::vector<unsigned char> indexData;
|
||||||
|
indexData.resize( dracoMesh->num_faces() * 3 * sizeof( quint32 ) );
|
||||||
|
Q_ASSERT( sizeof( dracoMesh->face( draco::FaceIndex( 0 ) )[0] ) == sizeof( quint32 ) );
|
||||||
|
memcpy( indexData.data(), &dracoMesh->face( draco::FaceIndex( 0 ) )[0], indexData.size() );
|
||||||
|
|
||||||
|
tinygltf::Buffer gltfIndexBuffer;
|
||||||
|
gltfIndexBuffer.data = indexData;
|
||||||
|
model.buffers.push_back( gltfIndexBuffer );
|
||||||
|
|
||||||
|
tinygltf::BufferView indexBufferView;
|
||||||
|
indexBufferView.buffer = static_cast<int>( model.buffers.size() ) - 1;
|
||||||
|
indexBufferView.byteLength = dracoMesh->num_faces() * 3 * sizeof( quint32 );
|
||||||
|
indexBufferView.byteOffset = 0;
|
||||||
|
indexBufferView.byteStride = 0;
|
||||||
|
indexBufferView.target = TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER;
|
||||||
|
model.bufferViews.emplace_back( std::move( indexBufferView ) );
|
||||||
|
|
||||||
|
tinygltf::Accessor indicesAccessor;
|
||||||
|
indicesAccessor.bufferView = static_cast<int>( model.bufferViews.size() ) - 1;
|
||||||
|
indicesAccessor.byteOffset = 0;
|
||||||
|
indicesAccessor.count = dracoMesh->num_faces() * 3;
|
||||||
|
indicesAccessor.type = TINYGLTF_TYPE_SCALAR;
|
||||||
|
indicesAccessor.componentType = TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT;
|
||||||
|
model.accessors.push_back( indicesAccessor );
|
||||||
|
|
||||||
|
indicesAccessorIndex = static_cast<int>( model.accessors.size() ) - 1;
|
||||||
|
|
||||||
|
//
|
||||||
|
// construct the GLTF model
|
||||||
|
//
|
||||||
|
|
||||||
|
tinygltf::Material material;
|
||||||
|
int materialIndex = loadMaterialFromMetadata( context.materialInfo, model );
|
||||||
|
|
||||||
|
tinygltf::Primitive primitive;
|
||||||
|
primitive.mode = TINYGLTF_MODE_TRIANGLES;
|
||||||
|
primitive.material = materialIndex;
|
||||||
|
primitive.indices = indicesAccessorIndex;
|
||||||
|
if ( posAccessorIndex != -1 )
|
||||||
|
primitive.attributes["POSITION"] = posAccessorIndex;
|
||||||
|
if ( normalAccessorIndex != -1 )
|
||||||
|
primitive.attributes["NORMAL"] = normalAccessorIndex;
|
||||||
|
if ( uvAccessorIndex != -1 )
|
||||||
|
primitive.attributes["TEXCOORD_0"] = uvAccessorIndex;
|
||||||
|
|
||||||
|
tinygltf::Mesh tiny_mesh;
|
||||||
|
tiny_mesh.primitives.push_back( primitive );
|
||||||
|
model.meshes.push_back( tiny_mesh );
|
||||||
|
|
||||||
|
tinygltf::Node node;
|
||||||
|
node.mesh = 0;
|
||||||
|
model.nodes.push_back( node );
|
||||||
|
|
||||||
|
tinygltf::Scene scene;
|
||||||
|
scene.nodes.push_back( 0 );
|
||||||
|
model.scenes.push_back( scene );
|
||||||
|
|
||||||
|
model.defaultScene = 0;
|
||||||
|
model.asset.version = "2.0";
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int QgsGltfUtils::loadMaterialFromMetadata( const QVariantMap &materialInfo, tinygltf::Model &model )
|
||||||
|
{
|
||||||
|
tinygltf::Material material;
|
||||||
|
material.name = "DefaultMaterial";
|
||||||
|
|
||||||
|
QVariantList colorList = materialInfo["pbrBaseColorFactor"].toList();
|
||||||
|
material.pbrMetallicRoughness.baseColorFactor = { colorList[0].toDouble(), colorList[1].toDouble(), colorList[2].toDouble(), colorList[3].toDouble() };
|
||||||
|
|
||||||
|
if ( materialInfo.contains( "pbrBaseColorTexture" ) )
|
||||||
|
{
|
||||||
|
QString baseColorTextureUri = materialInfo["pbrBaseColorTexture"].toString();
|
||||||
|
|
||||||
|
tinygltf::Image img;
|
||||||
|
img.uri = baseColorTextureUri.toStdString(); // file:/// or http:// ... will be fetched by QGIS
|
||||||
|
model.images.push_back( img );
|
||||||
|
|
||||||
|
tinygltf::Texture tex;
|
||||||
|
tex.source = static_cast<int>( model.images.size() ) - 1;
|
||||||
|
model.textures.push_back( tex );
|
||||||
|
|
||||||
|
material.pbrMetallicRoughness.baseColorTexture.index = static_cast<int>( model.textures.size() ) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( materialInfo.contains( "doubleSided" ) )
|
||||||
|
{
|
||||||
|
material.doubleSided = materialInfo["doubleSided"].toInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the new material to the model
|
||||||
|
model.materials.push_back( material );
|
||||||
|
|
||||||
|
return static_cast<int>( model.materials.size() ) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QgsGltfUtils::writeGltfModel( const tinygltf::Model &model, const QString &outputFilename )
|
||||||
|
{
|
||||||
|
tinygltf::TinyGLTF gltf;
|
||||||
|
bool res = gltf.WriteGltfSceneToFile( &model,
|
||||||
|
outputFilename.toStdString(),
|
||||||
|
false, // embedImages
|
||||||
|
true, // embedBuffers
|
||||||
|
false, // prettyPrint
|
||||||
|
true ); // writeBinary
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
///@endcond
|
///@endcond
|
||||||
|
@ -35,10 +35,11 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
|
|
||||||
|
#include "qgscoordinatetransform.h"
|
||||||
|
|
||||||
class QMatrix4x4;
|
class QMatrix4x4;
|
||||||
class QImage;
|
class QImage;
|
||||||
|
|
||||||
class QgsCoordinateTransform;
|
|
||||||
class QgsMatrix4x4;
|
class QgsMatrix4x4;
|
||||||
class QgsVector3D;
|
class QgsVector3D;
|
||||||
|
|
||||||
@ -166,6 +167,75 @@ class CORE_EXPORT QgsGltfUtils
|
|||||||
* If no scene is available, \a ok will be set to FALSE.
|
* If no scene is available, \a ok will be set to FALSE.
|
||||||
*/
|
*/
|
||||||
static std::size_t sourceSceneForModel( const tinygltf::Model &model, bool &ok );
|
static std::size_t sourceSceneForModel( const tinygltf::Model &model, bool &ok );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper structure with additional context for conversion of a Draco-encoded
|
||||||
|
* geometry of a I3S node.
|
||||||
|
* \since QGIS 4.0
|
||||||
|
*/
|
||||||
|
struct I3SNodeContext
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Material parsed from I3S material definition of the node. See
|
||||||
|
* loadMaterialFromMetadata() for more details about its content.
|
||||||
|
*/
|
||||||
|
QVariantMap materialInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A flag whether we are in "global" mode, i.e. the geometry's XY
|
||||||
|
* coordinates are lat/lon decimal degrees (in EPSG:4326).
|
||||||
|
* When not in global mode, we are using a projected CRS.
|
||||||
|
*/
|
||||||
|
bool isGlobalMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only applies when in global mode: transform from dataset's native CRS
|
||||||
|
* (lat/lon in degrees) to the scene CRS (ECEF - used in scene index).
|
||||||
|
*/
|
||||||
|
QgsCoordinateTransform datasetToSceneTransform;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only applies when in global mode: origin of the node's geometry
|
||||||
|
* (ECEF coordinates).
|
||||||
|
*/
|
||||||
|
QgsVector3D nodeCenterEcef;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a GLTF 2.0 model from I3S node's geometry in Draco file format.
|
||||||
|
* The function additionally needs the context of the I3S node, especially
|
||||||
|
* information about the material to be used.
|
||||||
|
*
|
||||||
|
* The function implements I3S-specific behaviors:
|
||||||
|
*
|
||||||
|
* - if position attribute contains "i3s-scale_x" and "i3s-scale_y" metadata,
|
||||||
|
* they will be used to scale XY position coordinates (used when XY are in degrees)
|
||||||
|
* - if there is a generic attribute with "i3s-attribute-type" metadata being "uv-region",
|
||||||
|
* the UV coordinates of each vertex are updated accordingly
|
||||||
|
*
|
||||||
|
* \since QGIS 4.0
|
||||||
|
*/
|
||||||
|
static bool loadDracoModel( const QByteArray &data, const I3SNodeContext &context, tinygltf::Model &model, QString *errors = nullptr );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a material into a model (including additions of texture and image objects)
|
||||||
|
* from a variant map representing GLTF 2.0 material. The following subset of properties
|
||||||
|
* is supported:
|
||||||
|
*
|
||||||
|
* - "pbrBaseColorFactor" - a list of 4 doubles (RGBA color)
|
||||||
|
* - "pbrBaseColorTexture" - a string with URI of a texture
|
||||||
|
* - "doubleSided" - a boolean indicating whether the material is double sided (no culling)
|
||||||
|
*
|
||||||
|
* \since QGIS 4.0
|
||||||
|
*/
|
||||||
|
static int loadMaterialFromMetadata( const QVariantMap &materialInfo, tinygltf::Model &model );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a model to a binary GLTF file (.glb)
|
||||||
|
* \since QGIS 4.0
|
||||||
|
*/
|
||||||
|
static bool writeGltfModel( const tinygltf::Model &model, const QString &outputFilename );
|
||||||
};
|
};
|
||||||
|
|
||||||
///@endcond
|
///@endcond
|
||||||
|
Loading…
x
Reference in New Issue
Block a user