diff --git a/include/fastgltf/core.hpp b/include/fastgltf/core.hpp index 391c9b590..7af30404f 100644 --- a/include/fastgltf/core.hpp +++ b/include/fastgltf/core.hpp @@ -212,6 +212,9 @@ namespace fastgltf { // See https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_accessor_float64 KHR_accessor_float64 = 1 << 25, + + // See https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_draco_mesh_compression + KHR_draco_mesh_compression = 1 << 26, }; // clang-format on @@ -315,6 +318,7 @@ namespace fastgltf { constexpr std::string_view EXT_meshopt_compression = "EXT_meshopt_compression"; constexpr std::string_view EXT_texture_webp = "EXT_texture_webp"; constexpr std::string_view KHR_accessor_float64 = "KHR_accessor_float64"; + constexpr std::string_view KHR_draco_mesh_compression = "KHR_draco_mesh_compression"; constexpr std::string_view KHR_lights_punctual = "KHR_lights_punctual"; constexpr std::string_view KHR_materials_anisotropy = "KHR_materials_anisotropy"; constexpr std::string_view KHR_materials_clearcoat = "KHR_materials_clearcoat"; @@ -345,15 +349,16 @@ namespace fastgltf { // value used for enabling/disabling the loading of it. This also represents all extensions that // fastgltf supports and understands. #if FASTGLTF_ENABLE_DEPRECATED_EXT - static constexpr std::size_t SUPPORTED_EXTENSION_COUNT = 24; + static constexpr std::size_t SUPPORTED_EXTENSION_COUNT = 25; #else - static constexpr std::size_t SUPPORTED_EXTENSION_COUNT = 23; + static constexpr std::size_t SUPPORTED_EXTENSION_COUNT = 24; #endif static constexpr std::array, SUPPORTED_EXTENSION_COUNT> extensionStrings = {{ { extensions::EXT_mesh_gpu_instancing, Extensions::EXT_mesh_gpu_instancing }, { extensions::EXT_meshopt_compression, Extensions::EXT_meshopt_compression }, { extensions::EXT_texture_webp, Extensions::EXT_texture_webp }, { extensions::KHR_accessor_float64, Extensions::KHR_accessor_float64 }, + { extensions::KHR_draco_mesh_compression, Extensions::KHR_draco_mesh_compression }, { extensions::KHR_lights_punctual, Extensions::KHR_lights_punctual }, { extensions::KHR_materials_anisotropy, Extensions::KHR_materials_anisotropy }, { extensions::KHR_materials_clearcoat, Extensions::KHR_materials_clearcoat }, @@ -811,6 +816,7 @@ namespace fastgltf { Error parseLights(simdjson::dom::array& array, Asset& asset); Error parseMaterialExtensions(simdjson::dom::object& object, Material& material); Error parseMaterials(simdjson::dom::array& array, Asset& asset); + Error parsePrimitiveExtensions(simdjson::dom::object& object, Primitive& primitive); Error parseMeshes(simdjson::dom::array& array, Asset& asset); Error parseNodes(simdjson::dom::array& array, Asset& asset); Error parseSamplers(simdjson::dom::array& array, Asset& asset); diff --git a/include/fastgltf/types.hpp b/include/fastgltf/types.hpp index cdeab7afe..4879057d7 100644 --- a/include/fastgltf/types.hpp +++ b/include/fastgltf/types.hpp @@ -1731,6 +1731,27 @@ namespace fastgltf { } }; + struct DracoCompressedPrimitive { + std::size_t bufferView; + FASTGLTF_FG_PMR_NS::SmallVector attributes; + + [[nodiscard]] auto findAttribute(std::string_view name) noexcept { + for (auto* it = attributes.begin(); it != attributes.end(); ++it) { + if (it->name == name) + return it; + } + return attributes.end(); + } + + [[nodiscard]] auto findAttribute(std::string_view name) const noexcept { + for (const auto* it = attributes.cbegin(); it != attributes.cend(); ++it) { + if (it->name == name) + return it; + } + return attributes.cend(); + } + }; + FASTGLTF_EXPORT struct Primitive { // Instead of a map, we have a list of attributes here. Each pair contains // the name of the attribute and the corresponding accessor index. @@ -1745,10 +1766,12 @@ namespace fastgltf { /** * Represents the mappings data from KHR_material_variants. * Use the variant index to index into this array to get the corresponding material index to use. - * If the optional has no value, the normal materialIndex should be used as a fallback. + * If this vector is empty, the normal materialIndex should be used as a fallback. */ std::vector> mappings; + std::unique_ptr dracoCompression; + [[nodiscard]] auto findAttribute(std::string_view name) noexcept { for (auto* it = attributes.begin(); it != attributes.end(); ++it) { if (it->name == name) diff --git a/src/fastgltf.cpp b/src/fastgltf.cpp index b9dbebf66..a2a03c088 100644 --- a/src/fastgltf.cpp +++ b/src/fastgltf.cpp @@ -3250,6 +3250,89 @@ fg::Error fg::Parser::parseMaterials(simdjson::dom::array& materials, Asset& ass return Error::None; } +fastgltf::Error fg::Parser::parsePrimitiveExtensions(simdjson::dom::object& object, Primitive& primitive) { + using namespace simdjson; + + for (auto extension : object) { + auto keyHash = crcStringFunction(extension.key); + + switch (keyHash) { + case force_consteval: { + if (!hasBit(config.extensions, Extensions::KHR_materials_variants)) + break; + + dom::object variantObject; + if (auto variantsError = extension.value.get_object().get(variantObject); variantsError != SUCCESS) { + return Error::InvalidGltf; + } + + dom::array mappingsArray; + if (variantObject["mappings"].get_array().get(mappingsArray) != SUCCESS) FASTGLTF_UNLIKELY { + return Error::InvalidGltf; + } + + for (auto mapping : mappingsArray) { + dom::object mappingObject; + if (mapping.get_object().get(mappingObject) != SUCCESS) FASTGLTF_UNLIKELY { + return Error::InvalidGltf; + } + + std::uint64_t materialIndex; + if (mappingObject["material"].get_uint64().get(materialIndex) != SUCCESS) FASTGLTF_UNLIKELY { + return Error::InvalidGltf; + } + + dom::array variantsArray; + if (mappingObject["variants"].get_array().get(variantsArray) != SUCCESS) FASTGLTF_UNLIKELY { + return Error::InvalidGltf; + } + for (auto variant : variantsArray) { + std::uint64_t variantIndex; + if (variant.get_uint64().get(variantIndex) != SUCCESS) FASTGLTF_UNLIKELY { + return Error::InvalidGltf; + } + + primitive.mappings.resize(max(primitive.mappings.size(), + static_cast(variantIndex + 1))); + primitive.mappings[static_cast(variantIndex)] = materialIndex; + } + } + break; + } + case force_consteval: { + if (!hasBit(config.extensions, Extensions::KHR_draco_mesh_compression)) + break; + + dom::object dracoObject; + if (auto dracoError = extension.value.get_object().get(dracoObject); dracoError != SUCCESS) { + return Error::InvalidGltf; + } + + auto dracoCompression = std::make_unique(); + + std::uint64_t value; + if (auto error = dracoObject["bufferView"].get_uint64().get(value); error != SUCCESS) FASTGLTF_UNLIKELY { + return Error::InvalidGltf; + } + dracoCompression->bufferView = static_cast(value); + + dom::object attributesObject; + if (dracoObject["attributes"].get_object().get(attributesObject) != SUCCESS) FASTGLTF_UNLIKELY { + return Error::InvalidGltf; + } + if (auto attributesError = parseAttributes(attributesObject, dracoCompression->attributes); attributesError != Error::None) { + return attributesError; + } + + primitive.dracoCompression = std::move(dracoCompression); + break; + } + } + } + + return Error::None; +} + fg::Error fg::Parser::parseMeshes(simdjson::dom::array& meshes, Asset& asset) { using namespace simdjson; @@ -3320,45 +3403,11 @@ fg::Error fg::Parser::parseMeshes(simdjson::dom::array& meshes, Asset& asset) { return Error::InvalidGltf; } - if (hasBit(config.extensions, Extensions::KHR_materials_variants)) { + if (hasBit(config.extensions, Extensions::KHR_materials_variants) || hasBit(config.extensions, Extensions::KHR_draco_mesh_compression)) { dom::object extensionsObject; if (auto error = primitiveObject["extensions"].get_object().get(extensionsObject); error == SUCCESS) { - dom::object extensionObject; - if (auto variantsError = extensionsObject[extensions::KHR_materials_variants].get_object().get(extensionObject); variantsError == SUCCESS) { - dom::array mappingsArray; - if (extensionObject["mappings"].get_array().get(mappingsArray) != SUCCESS) FASTGLTF_UNLIKELY { - return Error::InvalidGltf; - } - - for (auto mapping : mappingsArray) { - dom::object mappingObject; - if (mapping.get_object().get(mappingObject) != SUCCESS) FASTGLTF_UNLIKELY { - return Error::InvalidGltf; - } - - std::uint64_t materialIndex; - if (mappingObject["material"].get_uint64().get(materialIndex) != SUCCESS) FASTGLTF_UNLIKELY { - return Error::InvalidGltf; - } - - dom::array variantsArray; - if (mappingObject["variants"].get_array().get(variantsArray) != SUCCESS) FASTGLTF_UNLIKELY { - return Error::InvalidGltf; - } - for (auto variant : variantsArray) { - std::uint64_t variantIndex; - if (variant.get_uint64().get(variantIndex) != SUCCESS) FASTGLTF_UNLIKELY { - return Error::InvalidGltf; - } - - primitive.mappings.resize(max(primitive.mappings.size(), - static_cast(variantIndex + 1))); - primitive.mappings[std::size_t(variantIndex)] = materialIndex; - } - } - } else if (variantsError != NO_SUCH_FIELD) { - return Error::InvalidGltf; - } + if (auto extensionError = parsePrimitiveExtensions(extensionsObject, primitive); extensionError != Error::None) + return extensionError; } else if (error != NO_SUCH_FIELD) { return Error::InvalidGltf; } diff --git a/tests/extension_tests.cpp b/tests/extension_tests.cpp index d871eabcf..20c5ab87e 100644 --- a/tests/extension_tests.cpp +++ b/tests/extension_tests.cpp @@ -101,6 +101,55 @@ TEST_CASE("Extension EXT_meshopt_compression", "[gltf-loader]") { } } +TEST_CASE("Extension KHR_draco_mesh_compression", "[gltf-loader]") { + auto brainStem = sampleModels / "2.0" / "BrainStem" / "glTF-Draco"; + fastgltf::GltfFileStream jsonData(brainStem / "BrainStem.gltf"); + REQUIRE(jsonData.isOpen()); + + fastgltf::Parser parser(fastgltf::Extensions::KHR_draco_mesh_compression); + auto asset = parser.loadGltfJson(jsonData, brainStem, fastgltf::Options::None); + REQUIRE(asset.error() == fastgltf::Error::None); + REQUIRE(fastgltf::validate(asset.get()) == fastgltf::Error::None); + + REQUIRE(!asset->meshes.empty()); + REQUIRE(asset->meshes[0].primitives.size() >= 2); + + { + auto& draco = asset->meshes[0].primitives[0].dracoCompression; + REQUIRE(draco); + REQUIRE(draco->bufferView == 4); + + REQUIRE(draco->attributes[0].name == "JOINTS_0"); + REQUIRE(draco->attributes[0].accessorIndex == 0); + + REQUIRE(draco->attributes[1].name == "NORMAL"); + REQUIRE(draco->attributes[1].accessorIndex == 1); + + REQUIRE(draco->attributes[2].name == "POSITION"); + REQUIRE(draco->attributes[2].accessorIndex == 2); + + REQUIRE(draco->attributes[3].name == "WEIGHTS_0"); + REQUIRE(draco->attributes[3].accessorIndex == 3); + } + { + auto& draco = asset->meshes[0].primitives[1].dracoCompression; + REQUIRE(draco); + REQUIRE(draco->bufferView == 5); + + REQUIRE(draco->attributes[0].name == "JOINTS_0"); + REQUIRE(draco->attributes[0].accessorIndex == 0); + + REQUIRE(draco->attributes[1].name == "NORMAL"); + REQUIRE(draco->attributes[1].accessorIndex == 1); + + REQUIRE(draco->attributes[2].name == "POSITION"); + REQUIRE(draco->attributes[2].accessorIndex == 2); + + REQUIRE(draco->attributes[3].name == "WEIGHTS_0"); + REQUIRE(draco->attributes[3].accessorIndex == 3); + } +} + TEST_CASE("Extension KHR_lights_punctual", "[gltf-loader]") { SECTION("Point light") { auto lightsLamp = sampleModels / "2.0" / "LightsPunctualLamp" / "glTF";