Skip to content

Commit

Permalink
Add: KHR_materials_variants
Browse files Browse the repository at this point in the history
  • Loading branch information
spnda committed Feb 22, 2024
1 parent 5b819c6 commit a965f85
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 4 deletions.
9 changes: 7 additions & 2 deletions include/fastgltf/core.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,9 @@ namespace fastgltf {

// See https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_dispersion
KHR_materials_dispersion = 1 << 23,

// See https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_variants
KHR_materials_variants = 1 << 24,
};
// clang-format on

Expand Down Expand Up @@ -313,6 +316,7 @@ namespace fastgltf {
constexpr std::string_view KHR_materials_specular = "KHR_materials_specular";
constexpr std::string_view KHR_materials_transmission = "KHR_materials_transmission";
constexpr std::string_view KHR_materials_unlit = "KHR_materials_unlit";
constexpr std::string_view KHR_materials_variants = "KHR_materials_variants";
constexpr std::string_view KHR_materials_volume = "KHR_materials_volume";
constexpr std::string_view KHR_mesh_quantization = "KHR_mesh_quantization";
constexpr std::string_view KHR_texture_basisu = "KHR_texture_basisu";
Expand All @@ -331,9 +335,9 @@ 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 = 22;
static constexpr std::size_t SUPPORTED_EXTENSION_COUNT = 23;
#else
static constexpr std::size_t SUPPORTED_EXTENSION_COUNT = 21;
static constexpr std::size_t SUPPORTED_EXTENSION_COUNT = 22;
#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 },
Expand All @@ -350,6 +354,7 @@ namespace fastgltf {
{ extensions::KHR_materials_specular, Extensions::KHR_materials_specular },
{ extensions::KHR_materials_transmission, Extensions::KHR_materials_transmission },
{ extensions::KHR_materials_unlit, Extensions::KHR_materials_unlit },
{ extensions::KHR_materials_variants, Extensions::KHR_materials_variants },
{ extensions::KHR_materials_volume, Extensions::KHR_materials_volume },
{ extensions::KHR_mesh_quantization, Extensions::KHR_mesh_quantization },
{ extensions::KHR_texture_basisu, Extensions::KHR_texture_basisu },
Expand Down
13 changes: 12 additions & 1 deletion include/fastgltf/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1577,7 +1577,7 @@ namespace fastgltf {
}
return instancingAttributes.cend();
}
};
};

struct Primitive {
using attribute_type = std::pair<FASTGLTF_STD_PMR_NS::string, std::size_t>;
Expand All @@ -1592,6 +1592,13 @@ namespace fastgltf {
Optional<std::size_t> indicesAccessor;
Optional<std::size_t> materialIndex;

/**
* 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.
*/
std::vector<Optional<std::size_t>> mappings;

[[nodiscard]] auto findAttribute(std::string_view name) noexcept {
for (auto it = attributes.begin(); it != attributes.end(); ++it) {
if (it->first == name)
Expand Down Expand Up @@ -2025,6 +2032,8 @@ namespace fastgltf {
std::vector<Skin> skins;
std::vector<Texture> textures;

std::vector<std::string> materialVariants;

// Keeps tracked of categories that were actually parsed.
Category availableCategories = Category::None;

Expand Down Expand Up @@ -2052,6 +2061,7 @@ namespace fastgltf {
scenes(std::move(other.scenes)),
skins(std::move(other.skins)),
textures(std::move(other.textures)),
materialVariants(std::move(other.materialVariants)),
availableCategories(other.availableCategories) {}

Asset& operator=(const Asset& other) = delete;
Expand All @@ -2077,6 +2087,7 @@ namespace fastgltf {
scenes = std::move(other.scenes);
skins = std::move(other.skins);
textures = std::move(other.textures);
materialVariants = std::move(other.materialVariants);
availableCategories = other.availableCategories;
return *this;
}
Expand Down
95 changes: 94 additions & 1 deletion src/fastgltf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2100,11 +2100,35 @@ fg::Error fg::Parser::parseExtensions(simdjson::dom::object& extensionsObject, A
if (auto error = extensionObject["lights"].get_array().get(lightsArray); error == SUCCESS) FASTGLTF_LIKELY {
if (auto lightsError = parseLights(lightsArray, asset); lightsError != Error::None)
return lightsError;
} else if (error != NO_SUCH_FIELD) FASTGLTF_UNLIKELY {
} else if (error != NO_SUCH_FIELD) {
return Error::InvalidGltf;
}
break;
}
case force_consteval<crc32c(extensions::KHR_materials_variants)>: {
if (!hasBit(config.extensions, Extensions::KHR_materials_variants))
break;

dom::array variantsArray;
if (auto arrayError = extensionObject["variants"].get_array().get(variantsArray); arrayError == SUCCESS) FASTGLTF_LIKELY {
asset.materialVariants.reserve(variantsArray.size());
for (auto variant : variantsArray) {
dom::object variantObject;
if (auto error = variant.get_object().get(variantObject); error == SUCCESS) {
std::string_view name;
if (variantObject["name"].get_string().get(name) != SUCCESS) {
return Error::InvalidGltf;
}
asset.materialVariants.emplace_back(name);
} else {
return Error::InvalidGltf;
}
}
} else {
return Error::InvalidGltf;
}
break;
}
}
}

Expand Down Expand Up @@ -3108,6 +3132,50 @@ fg::Error fg::Parser::parseMeshes(simdjson::dom::array& meshes, Asset& asset) {
return Error::InvalidGltf;
}

if (hasBit(config.extensions, Extensions::KHR_materials_variants)) {
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) {
return Error::InvalidGltf;
}

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 (error != NO_SUCH_FIELD) {
return Error::InvalidGltf;
}
}

mesh.primitives.emplace_back(std::move(primitive));
}
}
Expand Down Expand Up @@ -4685,6 +4753,19 @@ void fg::Exporter::writeMeshes(const Asset& asset, std::string& json) {
json += R"(,"type":)" + std::to_string(to_underlying(itp->type));
}

if (!itp->mappings.empty()) {
json += R"(,"extensions":{"KHR_materials_variants":{"mappings":[)";
// TODO: We should optimise to avoid writing multiple objects for the same material index
for (std::size_t i = 0; i < asset.materialVariants.size(); ++i) {
if (!itp->mappings[i].has_value())
continue;
if (json.back() == '}')
json += ',';
json += "{\"material\":" + std::to_string(itp->mappings[i].value()) + ",\"variants\":[" + std::to_string(i) + "]}";
}
json += "]}}";
}

json += '}';
++itp;
if (std::distance(it->primitives.begin(), itp) < it->primitives.size())
Expand Down Expand Up @@ -4990,6 +5071,18 @@ void fg::Exporter::writeExtensions(const fastgltf::Asset& asset, std::string& js

writeLights(asset, json);

if (!asset.materialVariants.empty()) {
if (json.back() != '{')
json += ',';
json += R"("KHR_materials_variants":{"variants":[)";
for (const auto& variant : asset.materialVariants) {
if (json.back() == '}')
json += ',';
json += R"({"name":")" + variant + "\"}";
}
json += "]}";
}

json += '}';
}

Expand Down
29 changes: 29 additions & 0 deletions tests/extension_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -280,3 +280,32 @@ TEST_CASE("Test KHR_materials_dispersion", "[gltf-loader]") {
REQUIRE(asset->materials.size() == 1);
REQUIRE(asset->materials.front().dispersion == 0.1f);
}

TEST_CASE("Test KHR_materials_variant", "[gltf-loader]") {
auto velvetSofa = sampleModels / "2.0" / "GlamVelvetSofa" / "glTF";
fastgltf::GltfDataBuffer jsonData;
REQUIRE(jsonData.loadFromFile(velvetSofa / "GlamVelvetSofa.gltf"));

fastgltf::Parser parser(fastgltf::Extensions::KHR_materials_variants | fastgltf::Extensions::KHR_texture_transform);
auto asset = parser.loadGltfJson(&jsonData, velvetSofa, fastgltf::Options::None);
REQUIRE(asset.error() == fastgltf::Error::None);
REQUIRE(fastgltf::validate(asset.get()) == fastgltf::Error::None);

REQUIRE(asset->materialVariants.size() == 5);
REQUIRE(asset->materialVariants[0] == "Champagne");
REQUIRE(asset->materialVariants[1] == "Navy");
REQUIRE(asset->materialVariants[2] == "Gray");
REQUIRE(asset->materialVariants[3] == "Black");
REQUIRE(asset->materialVariants[4] == "Pale Pink");

REQUIRE(asset->meshes.size() >= 2);
REQUIRE(asset->meshes[1].primitives.size() == 1);

auto& primitive = asset->meshes[1].primitives[0];
REQUIRE(primitive.mappings.size() == 5);
REQUIRE(primitive.mappings[0] == 2);
REQUIRE(primitive.mappings[1] == 3);
REQUIRE(primitive.mappings[2] == 4);
REQUIRE(primitive.mappings[3] == 5);
REQUIRE(primitive.mappings[4] == 6);
}

0 comments on commit a965f85

Please sign in to comment.