Skip to content

Commit

Permalink
Add #66: Support KHR_draco_mesh_compression
Browse files Browse the repository at this point in the history
  • Loading branch information
spnda committed Aug 22, 2024
1 parent b853ee3 commit af0e3ca
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 40 deletions.
10 changes: 8 additions & 2 deletions include/fastgltf/core.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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<std::pair<std::string_view, Extensions>, 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 },
Expand Down Expand Up @@ -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);
Expand Down
25 changes: 24 additions & 1 deletion include/fastgltf/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1731,6 +1731,27 @@ namespace fastgltf {
}
};

struct DracoCompressedPrimitive {
std::size_t bufferView;
FASTGLTF_FG_PMR_NS::SmallVector<Attribute, 4> 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.
Expand All @@ -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<Optional<std::size_t>> mappings;

std::unique_ptr<DracoCompressedPrimitive> dracoCompression;

[[nodiscard]] auto findAttribute(std::string_view name) noexcept {
for (auto* it = attributes.begin(); it != attributes.end(); ++it) {
if (it->name == name)
Expand Down
123 changes: 86 additions & 37 deletions src/fastgltf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<crc32c(extensions::KHR_materials_variants)>: {
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<std::size_t>(variantIndex + 1)));
primitive.mappings[static_cast<std::size_t>(variantIndex)] = materialIndex;
}
}
break;
}
case force_consteval<crc32c(extensions::KHR_draco_mesh_compression)>: {
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<DracoCompressedPrimitive>();

std::uint64_t value;
if (auto error = dracoObject["bufferView"].get_uint64().get(value); error != SUCCESS) FASTGLTF_UNLIKELY {
return Error::InvalidGltf;
}
dracoCompression->bufferView = static_cast<std::size_t>(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;

Expand Down Expand Up @@ -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<std::size_t>(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;
}
Expand Down
49 changes: 49 additions & 0 deletions tests/extension_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down

0 comments on commit af0e3ca

Please sign in to comment.