From 84e56447f11e2b2cafc4d3700a845a8f409112c3 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Tue, 7 Dec 2021 16:31:05 +0100 Subject: [PATCH 01/35] {Cgltf,TinyGltf}Importer: test phongMaterialFallback config default value --- src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp | 2 +- .../TinyGltfImporter/Test/TinyGltfImporterTest.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp b/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp index 75cea04d8..3187ea709 100644 --- a/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp +++ b/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp @@ -3814,7 +3814,7 @@ void CgltfImporterTest::materialPhongFallback() { Containers::Pointer importer = _manager.instantiate("CgltfImporter"); /* phongMaterialFallback should be on by default */ - //importer->configuration().setValue("phongMaterialFallback", true); + CORRADE_VERIFY(importer->configuration().value("phongMaterialFallback")); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(CGLTFIMPORTER_TEST_DIR, "material-phong-fallback.gltf"))); diff --git a/src/MagnumPlugins/TinyGltfImporter/Test/TinyGltfImporterTest.cpp b/src/MagnumPlugins/TinyGltfImporter/Test/TinyGltfImporterTest.cpp index c525d1745..569382769 100644 --- a/src/MagnumPlugins/TinyGltfImporter/Test/TinyGltfImporterTest.cpp +++ b/src/MagnumPlugins/TinyGltfImporter/Test/TinyGltfImporterTest.cpp @@ -3451,7 +3451,7 @@ void TinyGltfImporterTest::materialPhongFallback() { Containers::Pointer importer = _manager.instantiate("TinyGltfImporter"); /* phongMaterialFallback should be on by default */ - //importer->configuration().setValue("phongMaterialFallback", true); + CORRADE_VERIFY(importer->configuration().value("phongMaterialFallback")); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(CGLTFIMPORTER_TEST_DIR, "material-phong-fallback.gltf"))); From cfcc9b91aab431354e0f0dbe566b63526c3ad195 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Mon, 13 Dec 2021 21:33:12 +0100 Subject: [PATCH 02/35] {Cgltf,TinyGltf}Importer: doc++ --- src/MagnumPlugins/CgltfImporter/CgltfImporter.h | 2 +- src/MagnumPlugins/TinyGltfImporter/TinyGltfImporter.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MagnumPlugins/CgltfImporter/CgltfImporter.h b/src/MagnumPlugins/CgltfImporter/CgltfImporter.h index c5b524b07..eb4a1a376 100644 --- a/src/MagnumPlugins/CgltfImporter/CgltfImporter.h +++ b/src/MagnumPlugins/CgltfImporter/CgltfImporter.h @@ -259,7 +259,7 @@ Import of morph data is not supported at the moment. @ref VertexFormat::Vector2sNormalized (which includes the additional types specified by [KHR_mesh_quantization](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_mesh_quantization/README.md)). The data are by default Y-flipped on import unless - @cb{.conf} textureCoordinateYFlipInMaterial @ce is either explicitly + @cb{.ini} textureCoordinateYFlipInMaterial @ce is either explicitly enabled, or if the file contains non-normalized integer or normalized signed integer texture coordinates (which can't easily be flipped). In that case texture coordinate data are kept as-is and materials provide a texture diff --git a/src/MagnumPlugins/TinyGltfImporter/TinyGltfImporter.h b/src/MagnumPlugins/TinyGltfImporter/TinyGltfImporter.h index 4a0887bd0..52912a441 100644 --- a/src/MagnumPlugins/TinyGltfImporter/TinyGltfImporter.h +++ b/src/MagnumPlugins/TinyGltfImporter/TinyGltfImporter.h @@ -267,7 +267,7 @@ Import of morph data is not supported at the moment. @ref VertexFormat::Vector2sNormalized (which includes the additional types specified by [KHR_mesh_quantization](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_mesh_quantization/README.md)). The data are by default Y-flipped on import unless - @cb{.conf} textureCoordinateYFlipInMaterial @ce is either explicitly + @cb{.ini} textureCoordinateYFlipInMaterial @ce is either explicitly enabled, or if the file contains non-normalized integer or normalized signed integer texture coordinates (which can't easily be flipped). In that case texture coordinate data are kept as-is and materials provide a texture From 6ea63216d199af1201a577649d689698e8b03524 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Thu, 16 Dec 2021 12:00:20 +0100 Subject: [PATCH 03/35] {Cgltf,TinyGltf}Importer: fix unlit material test Still works, yay --- src/MagnumPlugins/CgltfImporter/Test/material-unlit.gltf | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/MagnumPlugins/CgltfImporter/Test/material-unlit.gltf b/src/MagnumPlugins/CgltfImporter/Test/material-unlit.gltf index 99f4997c1..c49ff208f 100644 --- a/src/MagnumPlugins/CgltfImporter/Test/material-unlit.gltf +++ b/src/MagnumPlugins/CgltfImporter/Test/material-unlit.gltf @@ -15,9 +15,10 @@ "index": 1 } }, - "note": "to verify Type::PbrSpecularGlossiness gets removed too", - "KHR_materials_pbrSpecularGlossiness": {}, "extensions": { + "KHR_materials_pbrSpecularGlossiness": { + "note": "to verify Type::PbrSpecularGlossiness gets removed too" + }, "KHR_materials_unlit": {} } } @@ -36,6 +37,7 @@ } ], "extensionsUsed": [ + "KHR_materials_pbrSpecularGlossiness", "KHR_materials_unlit" ] } From 3056bc68db159c4d9b23a36b68beda9ed83b846b Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Mon, 13 Dec 2021 21:37:20 +0100 Subject: [PATCH 04/35] AssimpImporter: make this warning more intuitive - this shouldn't be an error, it doesn't fail the import - with really large names, the printed size can be small, making this message very confusing --- src/MagnumPlugins/AssimpImporter/AssimpImporter.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/MagnumPlugins/AssimpImporter/AssimpImporter.cpp b/src/MagnumPlugins/AssimpImporter/AssimpImporter.cpp index a786f7d38..f071195bc 100644 --- a/src/MagnumPlugins/AssimpImporter/AssimpImporter.cpp +++ b/src/MagnumPlugins/AssimpImporter/AssimpImporter.cpp @@ -1617,7 +1617,8 @@ Containers::Optional AssimpImporter::doMaterial(const UnsignedInt /* +1 is null byte for the key */ if(valueSize + keyString.size() + 1 + sizeof(MaterialAttributeType) > sizeof(MaterialAttributeData)) { - Error{} << "Trade::AssimpImporter::material(): property" << keyString << "is too large with" << valueSize << "bytes, skipping"; + Warning{} << "Trade::AssimpImporter::material(): property" << keyString << + "is too large with" << valueSize + keyString.size() << "bytes, skipping"; continue; } From 0ae42b4143dc48bcb766d9cf50ad6b163e2869b3 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Mon, 13 Dec 2021 21:37:49 +0100 Subject: [PATCH 05/35] AssimpImporter: test large material name/size Why didn't I test this? --- .../Test/AssimpImporterTest.cpp | 20 ++++++++++++++----- .../AssimpImporter/Test/material-raw.fbx | 2 ++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/MagnumPlugins/AssimpImporter/Test/AssimpImporterTest.cpp b/src/MagnumPlugins/AssimpImporter/Test/AssimpImporterTest.cpp index faa9adfa3..2915b6a74 100644 --- a/src/MagnumPlugins/AssimpImporter/Test/AssimpImporterTest.cpp +++ b/src/MagnumPlugins/AssimpImporter/Test/AssimpImporterTest.cpp @@ -2254,7 +2254,13 @@ void AssimpImporterTest::materialRaw() { CORRADE_VERIFY(importer->openFile(Utility::Directory::join(ASSIMPIMPORTER_TEST_DIR, "material-raw.fbx"))); CORRADE_COMPARE(importer->materialCount(), 2); - Containers::Optional material = importer->material("Custom_Types"); + Containers::Optional material; + std::ostringstream out; + { + Warning redirectWarning{&out}; + material = importer->material("Custom_Types"); + } + CORRADE_VERIFY(material); CORRADE_COMPARE(material->types(), MaterialType{}); CORRADE_COMPARE(material->layerCount(), 1); @@ -2288,9 +2294,11 @@ void AssimpImporterTest::materialRaw() { CORRADE_COMPARE(material->attribute(2), 0.25f); } - if(_assimpVersion < 500) + if(_assimpVersion < 500) { CORRADE_WARN("This version of Assimp doesn't import raw FBX material properties."); - else { + + CORRADE_VERIFY(out.str().empty()); + } else { /* Raw attributes taken directly from the FBX file, prefixed with "$raw.". Seems to be the only importer that supports that. */ { @@ -2298,12 +2306,14 @@ void AssimpImporterTest::materialRaw() { CORRADE_COMPARE(material->attributeType(3), MaterialAttributeType::Vector3); CORRADE_COMPARE(material->attribute(3), (Vector3{0.1f, 0.2f, 0.3f})); } { - CORRADE_EXPECT_FAIL_IF(_assimpVersion < 500, - "This version of Assimp doesn't import raw FBX material properties."); CORRADE_COMPARE(material->attributeName(4), "$raw.SomeString"_s); CORRADE_COMPARE(material->attributeType(4), MaterialAttributeType::String); CORRADE_COMPARE(material->attribute(4), "Ministry of Finance (Turkmenistan)"); } + + CORRADE_COMPARE(out.str(), + "Trade::AssimpImporter::material(): property $raw.LongNameLongNameLongNameLongNameLongNameLongNameLongName is too large with 67 bytes, skipping\n" + "Trade::AssimpImporter::material(): property $raw.LongValue is too large with 70 bytes, skipping\n"); } if(_assimpVersion < 410) diff --git a/src/MagnumPlugins/AssimpImporter/Test/material-raw.fbx b/src/MagnumPlugins/AssimpImporter/Test/material-raw.fbx index 43fd1fa4a..04118aeb1 100644 --- a/src/MagnumPlugins/AssimpImporter/Test/material-raw.fbx +++ b/src/MagnumPlugins/AssimpImporter/Test/material-raw.fbx @@ -356,6 +356,8 @@ Objects: { P: "Opacity", "double", "Number", "",0.25 P: "SomeColor", "Vector3D", "", "",0.1,0.2,0.3 P: "SomeString", "KString", "", "","Ministry of Finance (Turkmenistan)" + P: "LongNameLongNameLongNameLongNameLongNameLongNameLongName", "KString", "", "","Test" + P: "LongValue", "KString", "", "","LongValueLongValueLongValueLongValueLongValueLongValue" } } } From 56dc4d9b5d9259412fcfb71f6e9566d7df6c501a Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Mon, 13 Dec 2021 21:37:57 +0100 Subject: [PATCH 06/35] AssimpImporter: cleanup --- src/MagnumPlugins/AssimpImporter/Test/AssimpImporterTest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MagnumPlugins/AssimpImporter/Test/AssimpImporterTest.cpp b/src/MagnumPlugins/AssimpImporter/Test/AssimpImporterTest.cpp index 2915b6a74..62a9f7157 100644 --- a/src/MagnumPlugins/AssimpImporter/Test/AssimpImporterTest.cpp +++ b/src/MagnumPlugins/AssimpImporter/Test/AssimpImporterTest.cpp @@ -2011,7 +2011,7 @@ void AssimpImporterTest::materialWhiteAmbientTexture() { CORRADE_COMPARE(importer->textureCount(), 1); CORRADE_VERIFY(material->hasAttribute(MaterialAttribute::AmbientTexture)); /* It shouldn't be complaining about white ambient in this case */ - CORRADE_COMPARE(out.str(), ""); + CORRADE_VERIFY(out.str().empty()); } void AssimpImporterTest::materialMultipleTextures() { From 354621ba85c531d62d5de88f2e6d4d139f96541e Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Mon, 13 Dec 2021 21:46:25 +0100 Subject: [PATCH 07/35] CgltfImporter: simplify extension parsing This handles invalid primitives (those are not thoroughly checked by jsmn) the same way as cgltf. Numbers are parsed up until the first invalid characters, or 0 if there are no valid characters. --- .../CgltfImporter/CgltfImporter.cpp | 39 +++++++++---------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp index 79a52e845..fb6b7cf3e 100644 --- a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp +++ b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp @@ -208,34 +208,25 @@ std::string decodeUri(Containers::StringView uri) { return decoded; } -struct JsonToken { - jsmntype_t type; - Containers::StringView str; - std::size_t size; -}; - -Containers::Array parseJson(Containers::StringView str) { +Containers::Array parseJson(Containers::StringView str) { jsmn_parser parser{0, 0, 0}; Int numTokens = jsmn_parse(&parser, str.data(), str.size(), nullptr, 0); /* All JSON strings we're parsing come from cgltf and should already have passed jsmn parsing */ CORRADE_INTERNAL_ASSERT(numTokens >= 0); - Containers::Array jsmnTokens{std::size_t(numTokens)}; + Containers::Array tokens{std::size_t(numTokens)}; jsmn_init(&parser); - numTokens = jsmn_parse(&parser, str.data(), str.size(), jsmnTokens.data(), numTokens); - CORRADE_INTERNAL_ASSERT(std::size_t(numTokens) == jsmnTokens.size()); - - Containers::Array tokens{jsmnTokens.size()}; - for(Int i = 0; i != numTokens; ++i) { - tokens[i].type = jsmnTokens[i].type; - tokens[i].str = str.slice(jsmnTokens[i].start, jsmnTokens[i].end); - tokens[i].size = jsmnTokens[i].size; - } + numTokens = jsmn_parse(&parser, str.data(), str.size(), tokens.data(), numTokens); + CORRADE_INTERNAL_ASSERT(std::size_t(numTokens) == tokens.size()); return tokens; } +Containers::StringView tokenString(Containers::StringView json, const jsmntok_t& token) { + return json.slice(token.start, token.end); +} + } struct CgltfImporter::Document { @@ -2585,11 +2576,17 @@ Containers::Optional CgltfImporter::doTexture(const UnsignedInt id) for(std::size_t i = 0; i != tex.extensions_count && imageId == ~0u; ++i) { for(const auto& ext: extensions) { if(tex.extensions[i].name == ext) { + const Containers::StringView json = tex.extensions[i].data; const auto tokens = parseJson(tex.extensions[i].data); - if(tokens.size() == 3 && tokens[0].type == JSMN_OBJECT && tokens[1].type == JSMN_STRING && tokens[1].str == "source" && tokens[2].type == JSMN_PRIMITIVE) { - std::size_t parsed = 0; - const Int source = std::stoi(tokens[2].str, &parsed); - if(parsed != tokens[2].str.size() || source < 0 || UnsignedInt(source) >= _d->data->images_count) { + /* This is checked by cgltf */ + CORRADE_INTERNAL_ASSERT(!tokens.empty() && tokens[0].type == JSMN_OBJECT); + /* @todo This assumes there is only one key in the object. + There could be unrelated things in there like "note". */ + if(tokens.size() == 3 && tokens[1].type == JSMN_STRING && + tokenString(json, tokens[1]) == "source" && tokens[2].type == JSMN_PRIMITIVE) + { + const Int source = cgltf_json_to_int(&tokens[2], reinterpret_cast(json.data())); + if(source < 0 || UnsignedInt(source) >= _d->data->images_count) { Error{} << "Trade::CgltfImporter::texture():" << ext << "image" << source << "out of bounds for" << _d->data->images_count << "images"; return Containers::NullOpt; } From 63d7be946b01d6c5191acf425e24f282a12eb95f Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Mon, 13 Dec 2021 21:49:45 +0100 Subject: [PATCH 08/35] CgltfImporter: use StringView for texture attribute names In preparation for importing custom material texture attributes from unknown extensions. Using raw names is identical to the built-in enum values, no changes to tests needed. --- .../CgltfImporter/CgltfImporter.cpp | 78 +++++++++---------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp index fb6b7cf3e..02ae5ab38 100644 --- a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp +++ b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp @@ -313,7 +313,7 @@ struct CgltfImporter::Document { implicitly as we can't perform Y-flip directly on the data. */ bool textureCoordinateYFlipInMaterial = false; - void materialTexture(const cgltf_texture_view& texture, Containers::Array& attributes, MaterialAttribute attribute, MaterialAttribute matrixAttribute, MaterialAttribute coordinateAttribute) const; + void materialTexture(const cgltf_texture_view& texture, Containers::Array& attributes, Containers::StringView attribute, Containers::StringView matrixAttribute, Containers::StringView coordinateAttribute) const; bool open = false; @@ -2179,7 +2179,7 @@ std::string CgltfImporter::doMaterialName(const UnsignedInt id) { return _d->decodeString(_d->data->materials[id].name); } -void CgltfImporter::Document::materialTexture(const cgltf_texture_view& texture, Containers::Array& attributes, const MaterialAttribute attribute, const MaterialAttribute matrixAttribute, const MaterialAttribute coordinateAttribute) const { +void CgltfImporter::Document::materialTexture(const cgltf_texture_view& texture, Containers::Array& attributes, Containers::StringView attribute, Containers::StringView matrixAttribute, Containers::StringView coordinateAttribute) const { CORRADE_INTERNAL_ASSERT(texture.texture); UnsignedInt texCoord = texture.texcoord; @@ -2234,7 +2234,7 @@ void CgltfImporter::Document::materialTexture(const cgltf_texture_view& texture, /* In some cases (when dealing with packed textures), we're parsing & adding texture coordinates and matrix multiple times, but adding the packed texture ID just once. In other cases the attribute is invalid. */ - if(attribute != MaterialAttribute{}) { + if(!attribute.isEmpty()) { const UnsignedInt textureId = texture.texture - data->textures; arrayAppend(attributes, InPlaceInit, attribute, textureId); } @@ -2282,27 +2282,27 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i _d->materialTexture( material.pbr_metallic_roughness.base_color_texture, attributes, - MaterialAttribute::BaseColorTexture, - MaterialAttribute::BaseColorTextureMatrix, - MaterialAttribute::BaseColorTextureCoordinates); + "BaseColorTexture"_s, + "BaseColorTextureMatrix"_s, + "BaseColorTextureCoordinates"_s); } if(material.pbr_metallic_roughness.metallic_roughness_texture.texture) { _d->materialTexture( material.pbr_metallic_roughness.metallic_roughness_texture, attributes, - MaterialAttribute::NoneRoughnessMetallicTexture, - MaterialAttribute::MetalnessTextureMatrix, - MaterialAttribute::MetalnessTextureCoordinates); + "NoneRoughnessMetallicTexture"_s, + "MetalnessTextureMatrix"_s, + "MetalnessTextureCoordinates"_s); /* Add the matrix/coordinates attributes also for the roughness texture, but skip adding the texture ID again */ _d->materialTexture( material.pbr_metallic_roughness.metallic_roughness_texture, attributes, - MaterialAttribute{}, - MaterialAttribute::RoughnessTextureMatrix, - MaterialAttribute::RoughnessTextureCoordinates); + {}, + "RoughnessTextureMatrix"_s, + "RoughnessTextureCoordinates"_s); } /** @todo Support for KHR_materials_specular? This adds an explicit @@ -2337,27 +2337,27 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i _d->materialTexture( material.pbr_specular_glossiness.diffuse_texture, attributes, - MaterialAttribute::DiffuseTexture, - MaterialAttribute::DiffuseTextureMatrix, - MaterialAttribute::DiffuseTextureCoordinates); + "DiffuseTexture"_s, + "DiffuseTextureMatrix"_s, + "DiffuseTextureCoordinates"_s); } if(material.pbr_specular_glossiness.specular_glossiness_texture.texture) { _d->materialTexture( material.pbr_specular_glossiness.specular_glossiness_texture, attributes, - MaterialAttribute::SpecularGlossinessTexture, - MaterialAttribute::SpecularTextureMatrix, - MaterialAttribute::SpecularTextureCoordinates); + "SpecularGlossinessTexture"_s, + "SpecularTextureMatrix"_s, + "SpecularTextureCoordinates"_s); /* Add the matrix/coordinates attributes also for the glossiness texture, but skip adding the texture ID again */ _d->materialTexture( material.pbr_specular_glossiness.specular_glossiness_texture, attributes, - MaterialAttribute{}, - MaterialAttribute::GlossinessTextureMatrix, - MaterialAttribute::GlossinessTextureCoordinates); + {}, + "GlossinessTextureMatrix"_s, + "GlossinessTextureCoordinates"_s); } } @@ -2370,9 +2370,9 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i _d->materialTexture( material.normal_texture, attributes, - MaterialAttribute::NormalTexture, - MaterialAttribute::NormalTextureMatrix, - MaterialAttribute::NormalTextureCoordinates); + "NormalTexture"_s, + "NormalTextureMatrix"_s, + "NormalTextureCoordinates"_s); if(material.normal_texture.scale != 1.0f) arrayAppend(attributes, InPlaceInit, @@ -2385,9 +2385,9 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i _d->materialTexture( material.occlusion_texture, attributes, - MaterialAttribute::OcclusionTexture, - MaterialAttribute::OcclusionTextureMatrix, - MaterialAttribute::OcclusionTextureCoordinates); + "OcclusionTexture"_s, + "OcclusionTextureMatrix"_s, + "OcclusionTextureCoordinates"_s); /* cgltf exposes the strength multiplier as scale */ if(material.occlusion_texture.scale != 1.0f) @@ -2406,9 +2406,9 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i _d->materialTexture( material.emissive_texture, attributes, - MaterialAttribute::EmissiveTexture, - MaterialAttribute::EmissiveTextureMatrix, - MaterialAttribute::EmissiveTextureCoordinates); + "EmissiveTexture"_s, + "EmissiveTextureMatrix"_s, + "EmissiveTextureCoordinates"_s); } /* Phong material fallback for backwards compatibility */ @@ -2474,9 +2474,9 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i _d->materialTexture( material.clearcoat.clearcoat_texture, attributes, - MaterialAttribute::LayerFactorTexture, - MaterialAttribute::LayerFactorTextureMatrix, - MaterialAttribute::LayerFactorTextureCoordinates); + "LayerFactorTexture"_s, + "LayerFactorTextureMatrix"_s, + "LayerFactorTextureCoordinates"_s); } arrayAppend(attributes, InPlaceInit, @@ -2487,9 +2487,9 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i _d->materialTexture( material.clearcoat.clearcoat_roughness_texture, attributes, - MaterialAttribute::RoughnessTexture, - MaterialAttribute::RoughnessTextureMatrix, - MaterialAttribute::RoughnessTextureCoordinates); + "RoughnessTexture"_s, + "RoughnessTextureMatrix"_s, + "RoughnessTextureCoordinates"_s); /* The extension description doesn't mention it, but the schema says the clearcoat roughness is actually in the G channel: @@ -2503,9 +2503,9 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i _d->materialTexture( material.clearcoat.clearcoat_normal_texture, attributes, - MaterialAttribute::NormalTexture, - MaterialAttribute::NormalTextureMatrix, - MaterialAttribute::NormalTextureCoordinates); + "NormalTexture"_s, + "NormalTextureMatrix"_s, + "NormalTextureCoordinates"_s); if(material.clearcoat.clearcoat_normal_texture.scale != 1.0f) arrayAppend(attributes, InPlaceInit, From 3d1ed998d78d7f7d6c15f863c61314efee362bf6 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Mon, 13 Dec 2021 22:02:56 +0100 Subject: [PATCH 09/35] CgltfImporter: import unknown material extensions as custom layers Each extension gets its own layer, extension object properties are imported as custom attributes. To keep our sanity, the following types are not imported, we just print a warning and ignore the attribute: - arrays with size 0 or > 4 - non-primitive arrays (strings, objects, nested arrays) - bool arrays, not supported by MaterialAttributeType and awkward to parse when mixed with numbers - objects that don't end in "Texture" and resolve to a textureInfo (according to the glTF schema) type --- .../CgltfImporter/CgltfImporter.cpp | 314 ++++++++++++++++++ .../CgltfImporter/Test/CMakeLists.txt | 1 + .../CgltfImporter/Test/CgltfImporterTest.cpp | 267 ++++++++++++++- .../CgltfImporter/Test/material-raw.gltf | 139 ++++++++ 4 files changed, 720 insertions(+), 1 deletion(-) create mode 100644 src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf diff --git a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp index 02ae5ab38..aef3c7baa 100644 --- a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp +++ b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp @@ -27,6 +27,7 @@ #include "CgltfImporter.h" #include /* std::stable_sort() */ +#include #include #include #include @@ -41,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -227,6 +229,14 @@ Containers::StringView tokenString(Containers::StringView json, const jsmntok_t& return json.slice(token.start, token.end); } +std::size_t skipJson(Containers::ArrayView tokens, size_t start = 0) { + const int skipped = cgltf_skip_json(tokens, int(start)); + /* This only happens for tokens with type JSMN_UNDEFINED, which we should + never get for valid JSON files */ + CORRADE_INTERNAL_ASSERT(skipped >= 0); + return skipped; +} + } struct CgltfImporter::Document { @@ -2514,6 +2524,310 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i } } + + /* Stable-sort extensions by name so that we can easily find duplicates and + overwrite lexically preceding extensions. This matches cgltf parsing + behaviour. */ + Containers::Array extensionOrder{material.extensions_count}; + for(UnsignedInt i = 0; i < extensionOrder.size(); ++i) + extensionOrder[i] = i; + + std::stable_sort(extensionOrder.begin(), extensionOrder.end(), [&](UnsignedInt a, UnsignedInt b) { + return std::strcmp(material.extensions[a].name, material.extensions[b].name) < 0; + }); + + /* Mark duplicates, those will be skipped later */ + for(UnsignedInt i = 0; i + 1 < extensionOrder.size(); ++i) { + const cgltf_extension& current = material.extensions[extensionOrder[i]]; + const cgltf_extension& next = material.extensions[extensionOrder[i + 1]]; + if(std::strcmp(current.name, next.name) == 0) + extensionOrder[i] = ~0u; + } + + /* Import unrecognized extension attributes as custom attributes, one + layer per extension */ + for(UnsignedInt e: extensionOrder) { + if(e == ~0u) continue; + + const cgltf_extension& extension = material.extensions[e]; + + /** @todo Should empty extension names be ignored? jsmn allows them. */ + const Containers::StringView extensionName = extension.name; + /* +1 is the key null byte. +3 are the '#' layer prefix, the layer null + byte and the length. */ + if(" LayerName"_s.size() + 1 + extensionName.size() + 3 + sizeof(MaterialAttributeType) > sizeof(MaterialAttributeData)) { + Warning{} << "Trade::CgltfImporter::material(): extension name" << extensionName << + "is too long with" << extensionName.size() << "characters, skipping"; + continue; + } + + const Containers::StringView json = extension.data; + const auto tokens = parseJson(json); + + Containers::Array extensionAttributes; + Containers::arrayReserve(extensionAttributes, tokens[0].size); + + /* First token is the extension object. This is checked by cgltf. If + the object is empty, tokens.size() is 1. */ + CORRADE_INTERNAL_ASSERT(!tokens.empty() && tokens[0].type == JSMN_OBJECT); + size_t i = 1; + while(i + 1 < tokens.size()) { + /* Keys are always strings and followed by a value. This is checked + by jsmn. */ + CORRADE_INTERNAL_ASSERT(tokens[i].type == JSMN_STRING && tokens[i].size == 1); + + Containers::StringView name = tokenString(json, tokens[i]); + /** @todo Should empty attribute names be ignored? jsmn allows + them. */ + + ++i; + const jsmntok_t& attribute = tokens[i]; + const Containers::StringView value = tokenString(json, attribute); + + /* We only need temporary storage for parsing single primitives + (bool, Float, Int, UnsignedInt) or Vector2/3/4 of primitives. + Arrays of other types/sizes are ignored, so we know the upper + limit on the data size. If it's a string, MaterialAttributeData + expects a pointer to a StringView. */ + char attributeData[16]; + Containers::StringView attributeView; + MaterialAttributeType type{}; + std::size_t valueSize{}; + if(attribute.type == JSMN_OBJECT) { + /* Parse glTF textureInfo objects. Any objects without the + correct suffix and type are ignored. */ + if(name.size() > 7 && name.hasSuffix("Texture")) { + cgltf_texture_view textureView{}; + const int next = cgltf_parse_json_texture_view(&_d->options, tokens, i, + reinterpret_cast(json.data()), &textureView); + + /* cgltf_parse_json_texture_view() casts and saves the + index + 1 as cgltf_texture*. 0 indicates there was no + index property. It's mandatory, so we check for it. */ + if(next < 0 || !textureView.texture) { + Warning{} << "Trade::CgltfImporter::material(): property" << name << "has invalid texture object type, skipping"; + i = skipJson(tokens, i); + + } else { + const std::size_t index = std::size_t(textureView.texture) - 1; + if(index >= _d->data->images_count) { + Error{} << "Trade::CgltfImporter::material():" << name << "index" << index << "out of bounds for" << _d->data->textures_count << "textures"; + return Containers::NullOpt; + } + + /* materialTexture() expects a fixed up texture pointer + in cgltf_texture_view, normally done by cgltf */ + textureView.texture = &_d->data->textures[index]; + /** @todo Check for too large material data in + materialTexture(). Move the check to a function. */ + Containers::String nameBuffer{NoInit, name.size()*2 + 6 + 11}; + Utility::formatInto(nameBuffer, "{}Matrix{}Coordinates", name, name); + _d->materialTexture( + textureView, + extensionAttributes, + name, + nameBuffer.prefix(name.size() + 6), + nameBuffer.suffix(name.size() + 6)); + + /** @todo If there are ever extensions that reference + texture types other than textureInfo and + normalTextureInfo, we might have to be a bit + smarter here, e.g. detect occlusionTextureInfo and + suffix with "Strength" instead. cgltf parses both + "strength" and "scale" into the same scale element. */ + if(textureView.scale != 1.0f) { + /** @todo Test that this is skipped for 1.0 */ + Utility::formatInto(nameBuffer, "{}Scale", name); + arrayAppend(extensionAttributes, InPlaceInit, + nameBuffer.prefix(name.size() + 5), textureView.scale); + } + + i = next; + } + + } else { + Warning{} << "Trade::CgltfImporter::material(): property" << name << "has non-texture object type, skipping"; + i = skipJson(tokens, i); + } + + /* A primitive is anything that's not a string, object or array. We + ignore non-primitive arrays, so we can handle both in one place. */ + } else if(attribute.type == JSMN_PRIMITIVE || attribute.type == JSMN_ARRAY) { + const std::size_t start = i + size_t(attribute.type == JSMN_ARRAY); + /* Primitive token size is 0, but we can't use max() or we'd + allow empty arrays */ + const std::size_t count = attribute.type == JSMN_PRIMITIVE ? 1 : attribute.size; + + /* Using a lambda here to make aborting early easier */ + auto attributeType = [&]() -> MaterialAttributeType { + /* No use importing arbitrarily-sized arrays of primitives, + those are currently not used in any glTF extension */ + if(count < 1 || count > 4) return MaterialAttributeType{}; + + MaterialAttributeType finalType{}; + /* This still works for non-primitive array members (like + objects or nested arrays), because we instantly abort + when we find one of those, so we never need more than + four tokens */ + for(const jsmntok_t& token: tokens.slice(start, start + count)) { + if(token.type != JSMN_PRIMITIVE) + return MaterialAttributeType{}; + + const auto value = tokenString(json, token); + + /* Jsmn only checks the first character, which allows + some invalid values like nnn. We perform some basic + type detection and invalid values result in 0. This + matches cgltf behaviour. */ + constexpr Containers::StringView FloatIndicators = ".eE"_s; + MaterialAttributeType type{}; + if(value == "true"_s || value == "false"_s) { + /* MaterialAttributeType has no bool vectors, and + converting to a number needlessly complicates + parsing later. So far there is no glTF extension + that uses bool vectors. */ + if(count > 1) + return MaterialAttributeType{}; + type = MaterialAttributeType::Bool; + + } else if(std::find_first_of(value.begin(), value.end(), FloatIndicators.begin(), FloatIndicators.end()) != value.end()) { + type = MaterialAttributeType::Float; + } else if(value.hasPrefix("-"_s)) { + type = MaterialAttributeType::Int; + } else if(value != "null"_s) { + type = MaterialAttributeType::UnsignedInt; + } else + return MaterialAttributeType{}; + + if(finalType == MaterialAttributeType{}) + finalType = type; + + /* Upgrade mixed primitive types to a more generic type */ + if(finalType != type) { + if(type == MaterialAttributeType::Float || finalType == MaterialAttributeType::Float) { + finalType = MaterialAttributeType::Float; + } else if(type == MaterialAttributeType::Int || finalType == MaterialAttributeType::Int) { + finalType = MaterialAttributeType::Int; + } else { + /* Only UnsignedInt left, but both types would + be identical. Can't be Bool because we + abort for bool arrays with size > 1. */ + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + } + } + + CORRADE_INTERNAL_ASSERT(finalType != MaterialAttributeType{}); + return finalType; + }; + + type = attributeType(); + + if(type != MaterialAttributeType{}) { + if(type == MaterialAttributeType::Float) { + constexpr MaterialAttributeType vectorType[4] { + MaterialAttributeType::Float, MaterialAttributeType::Vector2, + MaterialAttributeType::Vector3, MaterialAttributeType::Vector4 + }; + type = vectorType[count - 1]; + + Vector4& data = *reinterpret_cast(attributeData); + for(std::size_t j = 0; j != count; ++j) + data[j] = cgltf_json_to_float(&tokens[start + j], reinterpret_cast(json.data())); + + } else if(type == MaterialAttributeType::Int) { + constexpr MaterialAttributeType vectorType[4] { + MaterialAttributeType::Int, MaterialAttributeType::Vector2i, + MaterialAttributeType::Vector3i, MaterialAttributeType::Vector4i + }; + type = vectorType[count - 1]; + + Vector4i& data = *reinterpret_cast(attributeData); + for(std::size_t j = 0; j != count; ++j) + data[j] = cgltf_json_to_int(&tokens[start + j], reinterpret_cast(json.data())); + + } else if(type == MaterialAttributeType::UnsignedInt) { + constexpr MaterialAttributeType vectorType[4] { + MaterialAttributeType::UnsignedInt, MaterialAttributeType::Vector2ui, + MaterialAttributeType::Vector3ui, MaterialAttributeType::Vector4ui + }; + type = vectorType[count - 1]; + + Vector4ui& data = *reinterpret_cast(attributeData); + for(std::size_t j = 0; j != count; ++j) + data[j] = cgltf_json_to_int(&tokens[start + j], reinterpret_cast(json.data())); + + } else if(type == MaterialAttributeType::Bool) { + bool& data = *reinterpret_cast(attributeData); + data = cgltf_json_to_bool(&tokens[start], reinterpret_cast(json.data())); + + } else + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + + valueSize = materialAttributeTypeSize(type); + i += attribute.size + 1; + + } else { + Warning{} << "Trade::CgltfImporter::material(): property" << name << "has unsupported type, skipping"; + i = skipJson(tokens, i); + } + + } else if(attribute.type == JSMN_STRING) { + attributeView = value; + type = MaterialAttributeType::String; + /* +2 is null byte + size */ + valueSize = value.size() + 2; + ++i; + + /* JSMN_UNDEFINED, should never happen for valid JSON files */ + } else + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + + /* Ignored and warning printed, or attributes already added + (texture object). Nothing left to do. */ + if(type == MaterialAttributeType{}) + continue; + + CORRADE_INTERNAL_ASSERT(valueSize); + + /* +1 is null byte for the key */ + if(valueSize + name.size() + 1 + sizeof(MaterialAttributeType) > sizeof(MaterialAttributeData)) { + Warning{} << "Trade::CgltfImporter::material(): property" << name << + "is too large with" << valueSize + name.size() << "bytes, skipping"; + continue; + } + + /** @todo Handle duplicate attributes */ + /* Uppercase attribute names are reserved. This shouldn't really + happen since virtually all existing glTF extension attributes + are lowercase, but better to be safe. Can't use + String::nullTerminatedView() here because JSON tokens are not + null-terminated. */ + Containers::String nameLowercase; + if(!name.isEmpty() && std::isupper(static_cast(name.front()))) { + nameLowercase = name; + nameLowercase[0] = std::tolower(static_cast(name.front())); + name = nameLowercase; + } + + arrayAppend(extensionAttributes, InPlaceInit, name, type, + type == MaterialAttributeType::String ? static_cast(&attributeView) : static_cast(attributeData)); + } + + /* Only add a layer if the extension contains any attributes */ + if(!extensionAttributes.empty()) { + /* Uppercase layer names are reserved. Since all extension names + start with an uppercase vendor identifier, making the first + character lower-case seems silly, so we use a unique prefix. */ + Containers::String layerName{NoInit, extensionName.size() + 1}; + Utility::formatInto(layerName, "#{}", extensionName); + + arrayAppend(layers, UnsignedInt(attributes.size())); + arrayAppend(attributes, InPlaceInit, MaterialAttribute::LayerName, layerName); + arrayAppend(attributes, Containers::arrayView(extensionAttributes)); + } + } + /* If there's any layer, add the final attribute count */ arrayAppend(layers, UnsignedInt(attributes.size())); diff --git a/src/MagnumPlugins/CgltfImporter/Test/CMakeLists.txt b/src/MagnumPlugins/CgltfImporter/Test/CMakeLists.txt index 283e4428d..b5523dfcd 100644 --- a/src/MagnumPlugins/CgltfImporter/Test/CMakeLists.txt +++ b/src/MagnumPlugins/CgltfImporter/Test/CMakeLists.txt @@ -134,6 +134,7 @@ corrade_add_test(CgltfImporterTest material-invalid-pbr-specular-oob.gltf material-metallicroughness.gltf material-phong-fallback.gltf + material-raw.gltf material-specularglossiness.gltf material-texcoord-flip.bin material-texcoord-flip.gltf diff --git a/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp b/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp index 3187ea709..811c782a1 100644 --- a/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp +++ b/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp @@ -150,6 +150,8 @@ struct CgltfImporterTest: TestSuite::Tester { void materialUnlit(); void materialClearCoat(); void materialPhongFallback(); + void materialRaw(); + void materialRawOutOfBounds(); void materialOutOfBounds(); void materialInvalidAlphaMode(); @@ -769,7 +771,9 @@ CgltfImporterTest::CgltfImporterTest() { &CgltfImporterTest::materialCommon, &CgltfImporterTest::materialUnlit, &CgltfImporterTest::materialClearCoat, - &CgltfImporterTest::materialPhongFallback}); + &CgltfImporterTest::materialPhongFallback, + &CgltfImporterTest::materialRaw, + &CgltfImporterTest::materialRawOutOfBounds}); addInstancedTests({&CgltfImporterTest::materialOutOfBounds}, Containers::arraySize(MaterialOutOfBoundsData)); @@ -3931,6 +3935,267 @@ void CgltfImporterTest::materialPhongFallback() { } } +void CgltfImporterTest::materialRaw() { + Containers::Pointer importer = _manager.instantiate("CgltfImporter"); + importer->configuration().setValue("phongMaterialFallback", false); + + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(CGLTFIMPORTER_TEST_DIR, + "material-raw.gltf"))); + + Containers::Optional material; + std::ostringstream out; + { + Warning redirectWarning{&out}; + material = importer->material("raw"); + } + + CORRADE_VERIFY(material); + /* Standard layer + clear coat + 3 unknown extensions */ + CORRADE_COMPARE(material->types(), MaterialType::PbrMetallicRoughness|MaterialType::PbrClearCoat); + CORRADE_COMPARE(material->layerCount(), 2 + 3); + + /* Standard layer import still works */ + { + CORRADE_ITERATION("base layer"); + CORRADE_COMPARE(material->attributeCount(0), 3); + + { + constexpr MaterialAttribute name = MaterialAttribute::BaseColor; + CORRADE_VERIFY(material->hasAttribute(0, name)); + CORRADE_COMPARE(material->attributeType(0, name), MaterialAttributeType::Vector4); + CORRADE_COMPARE(material->attribute(0, name), (Color4{0.8f, 0.2f, 0.4f, 0.3f})); + } { + constexpr MaterialAttribute name = MaterialAttribute::BaseColorTexture; + CORRADE_VERIFY(material->hasAttribute(0, name)); + CORRADE_COMPARE(material->attributeType(0, name), MaterialAttributeType::UnsignedInt); + CORRADE_COMPARE(material->attribute(0, name), 0u); + } { + constexpr MaterialAttribute name = MaterialAttribute::DoubleSided; + CORRADE_VERIFY(material->hasAttribute(0, name)); + CORRADE_COMPARE(material->attributeType(0, name), MaterialAttributeType::Bool); + CORRADE_COMPARE(material->attribute(0, name), true); + } + + /* Known extension layer import still works */ + } { + constexpr MaterialLayer layer = MaterialLayer::ClearCoat; + CORRADE_ITERATION(layer); + CORRADE_VERIFY(material->hasLayer(layer)); + /* +1 for the layer name */ + CORRADE_COMPARE(material->attributeCount(layer), 2 + 1); + + { + constexpr MaterialAttribute name = MaterialAttribute::LayerFactor; + CORRADE_VERIFY(material->hasAttribute(layer, name)); + CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Float); + CORRADE_COMPARE(material->attribute(layer, name), 0.5f); + } { + constexpr MaterialAttribute name = MaterialAttribute::Roughness; + CORRADE_VERIFY(material->hasAttribute(layer, name)); + CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Float); + CORRADE_COMPARE(material->attribute(layer, name), 0.0f); + } + + /* Extension with an empty name */ + } { + constexpr Containers::StringView layer = "#"_s; + CORRADE_ITERATION("empty name"); + CORRADE_VERIFY(material->hasLayer(layer)); + CORRADE_COMPARE(material->attributeCount(layer), 1 + 1); + + /* Unknown extension with a textureInfo object */ + } { + constexpr Containers::StringView layer = "#MGNM_material_snake"_s; + CORRADE_ITERATION(layer); + CORRADE_VERIFY(material->hasLayer(layer)); + CORRADE_COMPARE(material->attributeCount(layer), 7 + 1); + + { + constexpr Containers::StringView name = "snakeFactor"_s; + CORRADE_VERIFY(material->hasAttribute(layer, name)); + CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Float); + CORRADE_COMPARE(material->attribute(layer, name), 6.6f); + } { + constexpr Containers::StringView name = "snakeTexture"_s; + CORRADE_VERIFY(material->hasAttribute(layer, name)); + CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::UnsignedInt); + CORRADE_COMPARE(material->attribute(layer, name), 1u); + } { + constexpr Containers::StringView name = "snakeTextureMatrix"_s; + CORRADE_VERIFY(material->hasAttribute(layer, name)); + CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Matrix3x3); + CORRADE_COMPARE(material->attribute(layer, name), (Matrix3{ + {0.33f, 0.0f, 0.0f}, + {0.0f, 0.44f, 0.0f}, + {0.5f, 1.06f, 1.0f} + })); + } { + constexpr Containers::StringView name = "snakeTextureCoordinates"_s; + CORRADE_VERIFY(material->hasAttribute(layer, name)); + CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::UnsignedInt); + CORRADE_COMPARE(material->attribute(layer, name), 3u); + } { + constexpr Containers::StringView name = "snakeTextureScale"_s; + CORRADE_VERIFY(material->hasAttribute(layer, name)); + CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Float); + CORRADE_COMPARE(material->attribute(layer, name), 0.2f); + } { + constexpr Containers::StringView name = "type"_s; + CORRADE_VERIFY(material->hasAttribute(layer, name)); + CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::String); + CORRADE_COMPARE(material->attribute(layer, name), "BASIC"_s); + } { + constexpr Containers::StringView name = "coiled"_s; + CORRADE_VERIFY(material->hasAttribute(layer, name)); + CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Bool); + CORRADE_COMPARE(material->attribute(layer, name), true); + } + + /* Unknown extension with all supported types */ + } { + /** @todo Empty attribute names (which become "#") cause weird sorting + issues. The attribute is moved before the layer name, causing the + layer offset to be wrong. What exactly is happening there? */ + constexpr Containers::StringView layer = "#MGNM_material_type_zoo"_s; + CORRADE_ITERATION(layer); + CORRADE_VERIFY(material->hasLayer(layer)); + CORRADE_COMPARE(material->attributeCount(layer), 21 + 1); + + { + constexpr Containers::StringView name = "boolTrue"_s; + CORRADE_VERIFY(material->hasAttribute(layer, name)); + CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Bool); + CORRADE_COMPARE(material->attribute(layer, name), true); + } { + constexpr Containers::StringView name = "boolFalse"_s; + CORRADE_VERIFY(material->hasAttribute(layer, name)); + CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Bool); + CORRADE_COMPARE(material->attribute(layer, name), false); + } { + constexpr Containers::StringView name = "int"_s; + CORRADE_VERIFY(material->hasAttribute(layer, name)); + CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Int); + CORRADE_COMPARE(material->attribute(layer, name), -7992835); + } { + constexpr Containers::StringView name = "unsignedInt"_s; + CORRADE_VERIFY(material->hasAttribute(layer, name)); + CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::UnsignedInt); + CORRADE_COMPARE(material->attribute(layer, name), 109835761u); + } { + constexpr Containers::StringView name = "float"_s; + CORRADE_VERIFY(material->hasAttribute(layer, name)); + CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Float); + CORRADE_COMPARE(material->attribute(layer, name), 4.321f); + } { + constexpr Containers::StringView name = "string"_s; + CORRADE_VERIFY(material->hasAttribute(layer, name)); + CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::String); + CORRADE_COMPARE(material->attribute(layer, name), "Ribbit -- ribbit"_s); + } { + constexpr Containers::StringView name = "emptyString"_s; + CORRADE_VERIFY(material->hasAttribute(layer, name)); + CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::String); + CORRADE_COMPARE(material->attribute(layer, name), ""_s); + } { + constexpr Containers::StringView name = "uppercaseName"_s; + CORRADE_VERIFY(material->hasAttribute(layer, name)); + CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Bool); + CORRADE_COMPARE(material->attribute(layer, name), true); + } { + constexpr Containers::StringView name = "vec1"_s; + CORRADE_VERIFY(material->hasAttribute(layer, name)); + CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Float); + CORRADE_COMPARE(material->attribute(layer, name), 9.1f); + } { + constexpr Containers::StringView name = "vec2"_s; + CORRADE_VERIFY(material->hasAttribute(layer, name)); + CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Vector2); + CORRADE_COMPARE(material->attribute(layer, name), (Vector2{-9.1f, 8.2f})); + } { + constexpr Containers::StringView name = "vec3"_s; + CORRADE_VERIFY(material->hasAttribute(layer, name)); + CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Vector3); + CORRADE_COMPARE(material->attribute(layer, name), (Vector3{9.0f, 8.0f, 7.3f})); + } { + constexpr Containers::StringView name = "vec4"_s; + CORRADE_VERIFY(material->hasAttribute(layer, name)); + CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Vector4); + CORRADE_COMPARE(material->attribute(layer, name), (Vector4{-9.0f, 8.0f, 7.3f, -6.0f})); + } { + constexpr Containers::StringView name = "vec1i"_s; + CORRADE_VERIFY(material->hasAttribute(layer, name)); + CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Int); + CORRADE_COMPARE(material->attribute(layer, name), -9); + } { + constexpr Containers::StringView name = "vec2i"_s; + CORRADE_VERIFY(material->hasAttribute(layer, name)); + CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Vector2i); + CORRADE_COMPARE(material->attribute(layer, name), (Vector2i{9, -8})); + } { + constexpr Containers::StringView name = "vec3i"_s; + CORRADE_VERIFY(material->hasAttribute(layer, name)); + CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Vector3i); + CORRADE_COMPARE(material->attribute(layer, name), (Vector3i{-9, 8, 7})); + } { + constexpr Containers::StringView name = "vec4i"_s; + CORRADE_VERIFY(material->hasAttribute(layer, name)); + CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Vector4i); + CORRADE_COMPARE(material->attribute(layer, name), (Vector4i{-9, -8, -7, -6})); + } { + constexpr Containers::StringView name = "vec1ui"_s; + CORRADE_VERIFY(material->hasAttribute(layer, name)); + CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::UnsignedInt); + CORRADE_COMPARE(material->attribute(layer, name), 9u); + } { + constexpr Containers::StringView name = "vec2ui"_s; + CORRADE_VERIFY(material->hasAttribute(layer, name)); + CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Vector2ui); + CORRADE_COMPARE(material->attribute(layer, name), (Vector2ui{9u, 8u})); + } { + constexpr Containers::StringView name = "vec3ui"_s; + CORRADE_VERIFY(material->hasAttribute(layer, name)); + CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Vector3ui); + CORRADE_COMPARE(material->attribute(layer, name), (Vector3ui{9u, 8u, 7u})); + } { + constexpr Containers::StringView name = "vec4ui"_s; + CORRADE_VERIFY(material->hasAttribute(layer, name)); + CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Vector4ui); + CORRADE_COMPARE(material->attribute(layer, name), (Vector4ui{9u, 8u, 7u, 6u})); + } + } + + CORRADE_COMPARE(out.str(), + "Trade::CgltfImporter::material(): property null has unsupported type, skipping\n" + "Trade::CgltfImporter::material(): property emptyArray has unsupported type, skipping\n" + "Trade::CgltfImporter::material(): property oversizedArray has unsupported type, skipping\n" + "Trade::CgltfImporter::material(): property stringArray has unsupported type, skipping\n" + "Trade::CgltfImporter::material(): property boolArray has unsupported type, skipping\n" + "Trade::CgltfImporter::material(): property mixedStringArray has unsupported type, skipping\n" + "Trade::CgltfImporter::material(): property mixedBoolArray has unsupported type, skipping\n" + "Trade::CgltfImporter::material(): property mixedObjectArray has unsupported type, skipping\n" + "Trade::CgltfImporter::material(): property nonTextureObject has non-texture object type, skipping\n" + "Trade::CgltfImporter::material(): property Texture has non-texture object type, skipping\n" + "Trade::CgltfImporter::material(): property invalidTexture has invalid texture object type, skipping\n" + "Trade::CgltfImporter::material(): property anIncrediblyLongNameThatSadlyWontFitPaddingPaddingPadding!! is too large with 63 bytes, skipping\n" + "Trade::CgltfImporter::material(): property aValueThatWontFit is too large with 84 bytes, skipping\n" + "Trade::CgltfImporter::material(): extension name VENDOR_material_thisnameiswaytoolongforalayername! is too long with 50 characters, skipping\n"); +} + +void CgltfImporterTest::materialRawOutOfBounds() { + Containers::Pointer importer = _manager.instantiate("CgltfImporter"); + + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(CGLTFIMPORTER_TEST_DIR, + "material-raw.gltf"))); + + std::ostringstream out; + { + Error redirectError{&out}; + CORRADE_VERIFY(!importer->material("raw out-of-bounds")); + } + + CORRADE_COMPARE(out.str(), "Trade::CgltfImporter::material(): snakeTexture index 2 out of bounds for 2 textures\n"); +} + void CgltfImporterTest::materialOutOfBounds() { auto&& data = MaterialOutOfBoundsData[testCaseInstanceId()]; setTestCaseDescription(data.name); diff --git a/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf b/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf new file mode 100644 index 000000000..4078f8601 --- /dev/null +++ b/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf @@ -0,0 +1,139 @@ +{ + "asset": { + "version": "2.0" + }, + "images": [ + { + "uri": "basecolor.png" + }, + { + "uri": "snake.snk" + } + ], + "materials": [ + { + "name": "raw out-of-bounds", + "extensions": { + "MGNM_material_snake": { + "snakeTexture": { + "index": 2 + } + } + } + }, + { + "name": "raw", + "pbrMetallicRoughness": { + "baseColorFactor": [ 0.8, 0.2, 0.4, 0.3 ], + "baseColorTexture": { + "index": 0 + } + }, + "doubleSided": true, + "extensions": { + "MGNM_material_snake": { + "note": "should be overwritten by later duplicate extension", + "snakeFactor": 3.3, + "thisWillDisappear": true + }, + "KHR_materials_clearcoat": { + "note": "should be imported as a predefined layer", + "clearcoatFactor": 0.5 + }, + "MGNM_material_snake": { + "snakeFactor": 6.6, + "snakeTexture": { + "index": 1, + "texCoord": 3, + "snakeCoord": "ignored, not parsed by cgltf", + "scale": 0.2, + "extensions": { + "KHR_texture_transform": { + "offset": [0.5, -0.5], + "scale": [0.33, 0.44] + } + } + }, + "type": "BASIC", + "coiled": true + }, + "MGNM_material_type_zoo": { + "null": null, + "boolTrue": true, + "boolFalse": false, + "int": -7992835, + "unsignedInt": 109835761, + "float": 4.321, + "string": "Ribbit -- ribbit", + "emptyString": "", + "UppercaseName": true , + "vec1": [9.1], + "vec2": [ + -9.1 , + 8.2 ], + "vec3": [9, 8, 7.3], + "vec4": [-9, 8, 7.3, -6], + "vec1i": [-9], + "vec2i": [9, -8], + "vec3i": [-9, 8, 7], + "vec4i": [-9, -8, -7, -6], + "vec1ui": [9], + "vec2ui": [9, 8], + "vec3ui": [9, 8, 7], + "vec4ui": [9, 8, 7, 6], + "emptyArray": [], + "oversizedArray": [9.1, 8.2, 7.3, 6.4, 5.5], + "stringArray": ["one", "two", "", "four"], + "boolArray": [true, false], + "mixedStringArray": ["1", "2", 3, "4"], + "mixedBoolArray": [1, 2, 3, false], + "mixedObjectArray": [1, 2, 3, {"4": "5", "6": "7"}], + "nonTextureObject": { + "these": {"should": "all"}, + "be": ["ignored"] + }, + "Texture": { + "note": "this should be ignored", + "index": 1 + }, + "invalidTexture": { + "note": "index is mandatory", + "indeks": 0 + }, + "anIncrediblyLongNameThatSadlyWontFitPaddingPaddingPadding!!": 0, + "aValueThatWontFit": "1 pound ground turkey, 1 medium-sized onion, 1 cup celery (diced)", + "": "Empty attributes are allowed" + }, + "VENDOR_empty_extension_object": {}, + "VENDOR_material_thisnameiswaytoolongforalayername!": { + "wow": ["so", "long"] + }, + "": { + "note": "Empty extension names are allowed" + } + } + } + ], + "samplers": [ + {} + ], + "textures": [ + { + "sampler": 0, + "source": 0 + }, + { + "sampler": 0, + "source": 1 + } + ], + "extensionsUsed": [ + "KHR_materials_clearcoat", + "MGNM_material_snake", + "MGNM_material_type_zoo", + "VENDOR_empty_extension_object", + "VENDOR_material_thisnameiswaytoolongforalayername!", + "", + "KHR_texture_transform" + ] +} From d9f5422856c6cd47c2161760eff1a310557298a3 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Tue, 14 Dec 2021 01:02:09 +0100 Subject: [PATCH 10/35] CgltfImporter: import material extensions handled by cgltf Treat them like unknown extensions and import their attributes with the original glTF names. Some of these may turn into a standard MaterialAttribute or MaterialLayer in the future, but for the time being this makes them accessible without changes to magnum. Tests will follow in another commit. --- .../CgltfImporter/CgltfImporter.cpp | 144 +++++++++++++++++- 1 file changed, 143 insertions(+), 1 deletion(-) diff --git a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp index aef3c7baa..0d51bb382 100644 --- a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp +++ b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp @@ -646,6 +646,12 @@ void CgltfImporter::doOpenData(Containers::Array&& data, const DataFlags d "KHR_mesh_quantization"_s, "KHR_texture_basisu"_s, "KHR_texture_transform"_s, + /* Parsed by cgltf and imported as custom material data */ + "KHR_materials_ior"_s, + "KHR_materials_sheen"_s, + "KHR_materials_specular"_s, + "KHR_materials_transmission"_s, + "KHR_materials_volume"_s, /* Manually parsed */ "GOOGLE_texture_basis"_s, "MSFT_texture_dds"_s @@ -2317,7 +2323,8 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i /** @todo Support for KHR_materials_specular? This adds an explicit F0 (texture) and a scalar factor (texture) for the entire specular - reflection to a metallic/roughness material. */ + reflection to a metallic/roughness material. Currently imported as + a custom layer below. */ } /* Specular/glossiness material */ @@ -2524,6 +2531,141 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i } } + /* Import extensions with non-standard layer/attribute types that are + already parsed by cgltf and hence don't appear in the extension list + anymore. We use the original attribute names as found in the extension + specifications. */ + if(material.has_ior) { + arrayAppend(layers, UnsignedInt(attributes.size())); + arrayAppend(attributes, InPlaceInit, MaterialAttribute::LayerName, "KHR_materials_ior"_s); + + arrayAppend(attributes, InPlaceInit, "ior", material.ior.ior); + } + + if(material.has_specular) { + arrayAppend(layers, UnsignedInt(attributes.size())); + arrayAppend(attributes, InPlaceInit, MaterialAttribute::LayerName, "KHR_materials_specular"_s); + + arrayAppend(attributes, InPlaceInit, + "specularFactor"_s, + material.specular.specular_factor); + + if(material.specular.specular_texture.texture) { + _d->materialTexture( + material.specular.specular_texture, + attributes, + "specularTexture"_s, + "specularTextureMatrix"_s, + "specularTextureCoordinates"_s); + + /* Specular strength is stored in the alpha channel */ + arrayAppend(attributes, InPlaceInit, + "specularTextureSwizzle"_s, + MaterialTextureSwizzle::A); + } + + const Vector3 specularColorFactor = Vector3::from(material.specular.specular_color_factor); + if(specularColorFactor != Vector3{1.0f}) + arrayAppend(attributes, InPlaceInit, + "specularColorFactor"_s, + specularColorFactor); + + if(material.specular.specular_color_texture.texture) + _d->materialTexture( + material.specular.specular_color_texture, + attributes, + "specularColorTexture"_s, + "specularColorTextureMatrix"_s, + "specularColorTextureCoordinates"_s); + } + + if(material.has_transmission) { + arrayAppend(layers, UnsignedInt(attributes.size())); + arrayAppend(attributes, InPlaceInit, MaterialAttribute::LayerName, "KHR_materials_transmission"_s); + + arrayAppend(attributes, InPlaceInit, + "transmissionFactor"_s, + material.transmission.transmission_factor); + + if(material.transmission.transmission_texture.texture) + _d->materialTexture( + material.transmission.transmission_texture, + attributes, + "transmissionTexture"_s, + "transmissionTextureMatrix"_s, + "transmissionTextureCoordinates"_s); + } + + if(material.has_volume) { + arrayAppend(layers, UnsignedInt(attributes.size())); + arrayAppend(attributes, InPlaceInit, MaterialAttribute::LayerName, "KHR_materials_volume"_s); + + arrayAppend(attributes, InPlaceInit, + "thicknessFactor"_s, + material.volume.thickness_factor); + + if(material.volume.thickness_texture.texture) { + _d->materialTexture( + material.volume.thickness_texture, + attributes, + "thicknessTexture"_s, + "thicknessTextureMatrix"_s, + "thicknessTextureCoordinates"_s); + + /* Thickness is stored in the green channel */ + arrayAppend(attributes, InPlaceInit, + "thicknessTextureSwizzle"_s, + MaterialTextureSwizzle::G); + } + + /* Default spec value is +infinity, but cgltf uses FLT_MAX */ + if(material.volume.attenuation_distance != std::numeric_limits::max()) + arrayAppend(attributes, InPlaceInit, + "attenuationDistance"_s, + material.volume.attenuation_distance); + + const Vector3 attenuationColor = Vector3::from(material.volume.attenuation_color); + if(attenuationColor != Vector3{1.0f}) + arrayAppend(attributes, InPlaceInit, + "attenuationColor"_s, + attenuationColor); + } + + if(material.has_sheen) { + arrayAppend(layers, UnsignedInt(attributes.size())); + arrayAppend(attributes, InPlaceInit, MaterialAttribute::LayerName, "KHR_materials_sheen"_s); + + const Vector3 sheenColorFactor = Vector3::from(material.sheen.sheen_color_factor); + arrayAppend(attributes, InPlaceInit, + "sheenColorFactor"_s, + sheenColorFactor); + + if(material.sheen.sheen_color_texture.texture) + _d->materialTexture( + material.sheen.sheen_color_texture, + attributes, + "sheenColorTexture"_s, + "sheenColorTextureMatrix"_s, + "sheenColorTextureCoordinates"_s); + + arrayAppend(attributes, InPlaceInit, + "sheenRoughnessFactor"_s, + material.sheen.sheen_roughness_factor); + + if(material.sheen.sheen_roughness_texture.texture) { + _d->materialTexture( + material.sheen.sheen_roughness_texture, + attributes, + "sheenRoughnessTexture"_s, + "sheenRoughnessTextureMatrix"_s, + "sheenRoughnessTextureCoordinates"_s); + + /* Sheen roughness is stored in the alpha channel */ + arrayAppend(attributes, InPlaceInit, + "sheenRoughnessTextureSwizzle"_s, + MaterialTextureSwizzle::A); + } + } /* Stable-sort extensions by name so that we can easily find duplicates and overwrite lexically preceding extensions. This matches cgltf parsing From 385cc00ae2adc38be1247254bbef07fe05c35c4d Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Tue, 14 Dec 2021 11:26:35 +0100 Subject: [PATCH 11/35] CgltfImporter: add docs for unrecognized material extensions --- .../CgltfImporter/CgltfImporter.h | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/MagnumPlugins/CgltfImporter/CgltfImporter.h b/src/MagnumPlugins/CgltfImporter/CgltfImporter.h index eb4a1a376..e30a8b346 100644 --- a/src/MagnumPlugins/CgltfImporter/CgltfImporter.h +++ b/src/MagnumPlugins/CgltfImporter/CgltfImporter.h @@ -314,6 +314,31 @@ fail. types, and a new layer with clearcoat properties is added - Custom texture coordinate sets as well as [KHR_texture_transform](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_texture_transform/README.md) properties are imported on all textures. +- Unrecognized material extensions are imported as custom layers with a `#` + prefix. Extension properties are imported with their raw names and types, + the following of which are supported: + - @ref MaterialAttributeType::String, @ref MaterialAttributeType::Bool, + @ref MaterialAttributeType::Float, @ref MaterialAttributeType::Int, + @ref MaterialAttributeType::UnsignedInt + - arrays as @ref MaterialAttributeType::Vector2/ + @ref MaterialAttributeType::Vector3/ + @ref MaterialAttributeType::Vector4, + MaterialAttributeType::Vector2i/@ref MaterialAttributeType::Vector3i/ + @ref MaterialAttributeType::Vector4i, MaterialAttributeType::Vector2ui/ + @ref MaterialAttributeType::Vector3ui/ + @ref MaterialAttributeType::Vector4ui. + Empty arrays, arrays of size 5 or higher as well as arrays containing + anything that isn't a number are ignored. + - [textureInfo](https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#reference-textureinfo) + objects, including all attributes handled for regular textures. Texture + attributes are suffixed to the name of the object: e.g. if an extension + has a `someTexture` property, the texture index, matrix, coordinate set + and scale would be imported as `someTexture`, `someTextureMatrix`, + `someTextureCoordinates` and `someTextureScale`. Non-texture object + types are ignored. + If you handle any of these custom material extensions, it may make sense + to enable the @cb{.ini} ignoreRequiredExtensions @ce + @ref Trade-CgltfImporter-configuration "configuration option". - If the on-by-default @cb{.ini} phongMaterialFallback @ce @ref Trade-CgltfImporter-configuration "configuration option" is enabled, the importer provides a Phong fallback for backwards From 40cf41de2b56b0df433afc4b1d3ffc592f906468 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Tue, 14 Dec 2021 12:04:25 +0100 Subject: [PATCH 12/35] CgltfImporter: ignore empty extension and attribute names This causes funny sorting issues inside MaterialData, and shouldn't happen in practice anyway --- .../CgltfImporter/CgltfImporter.cpp | 13 +++++++++--- .../CgltfImporter/Test/CgltfImporterTest.cpp | 21 +++++++------------ .../CgltfImporter/Test/material-raw.gltf | 11 ++++++---- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp index 0d51bb382..74d87a950 100644 --- a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp +++ b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp @@ -2693,8 +2693,12 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i const cgltf_extension& extension = material.extensions[e]; - /** @todo Should empty extension names be ignored? jsmn allows them. */ const Containers::StringView extensionName = extension.name; + if(extensionName.isEmpty()) { + Warning{} << "Trade::CgltfImporter::material(): extension with an empty name, skipping"; + continue; + } + /* +1 is the key null byte. +3 are the '#' layer prefix, the layer null byte and the length. */ if(" LayerName"_s.size() + 1 + extensionName.size() + 3 + sizeof(MaterialAttributeType) > sizeof(MaterialAttributeData)) { @@ -2719,8 +2723,11 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i CORRADE_INTERNAL_ASSERT(tokens[i].type == JSMN_STRING && tokens[i].size == 1); Containers::StringView name = tokenString(json, tokens[i]); - /** @todo Should empty attribute names be ignored? jsmn allows - them. */ + if(name.isEmpty()) { + Warning{} << "Trade::CgltfImporter::material(): property with an empty name, skipping"; + i = skipJson(tokens, i + 1); + continue; + } ++i; const jsmntok_t& attribute = tokens[i]; diff --git a/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp b/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp index 811c782a1..6ea5d16ff 100644 --- a/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp +++ b/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp @@ -3950,9 +3950,9 @@ void CgltfImporterTest::materialRaw() { } CORRADE_VERIFY(material); - /* Standard layer + clear coat + 3 unknown extensions */ + /* Standard layer + clear coat + 2 unknown extensions */ CORRADE_COMPARE(material->types(), MaterialType::PbrMetallicRoughness|MaterialType::PbrClearCoat); - CORRADE_COMPARE(material->layerCount(), 2 + 3); + CORRADE_COMPARE(material->layerCount(), 2 + 2); /* Standard layer import still works */ { @@ -3996,13 +3996,6 @@ void CgltfImporterTest::materialRaw() { CORRADE_COMPARE(material->attribute(layer, name), 0.0f); } - /* Extension with an empty name */ - } { - constexpr Containers::StringView layer = "#"_s; - CORRADE_ITERATION("empty name"); - CORRADE_VERIFY(material->hasLayer(layer)); - CORRADE_COMPARE(material->attributeCount(layer), 1 + 1); - /* Unknown extension with a textureInfo object */ } { constexpr Containers::StringView layer = "#MGNM_material_snake"_s; @@ -4053,13 +4046,10 @@ void CgltfImporterTest::materialRaw() { /* Unknown extension with all supported types */ } { - /** @todo Empty attribute names (which become "#") cause weird sorting - issues. The attribute is moved before the layer name, causing the - layer offset to be wrong. What exactly is happening there? */ constexpr Containers::StringView layer = "#MGNM_material_type_zoo"_s; CORRADE_ITERATION(layer); CORRADE_VERIFY(material->hasLayer(layer)); - CORRADE_COMPARE(material->attributeCount(layer), 21 + 1); + CORRADE_COMPARE(material->attributeCount(layer), 20 + 1); { constexpr Containers::StringView name = "boolTrue"_s; @@ -4164,7 +4154,11 @@ void CgltfImporterTest::materialRaw() { } } + /* No MGNM_material_forbidden_types layer because all attributes are + invalid types or too large and skipped */ + CORRADE_COMPARE(out.str(), + "Trade::CgltfImporter::material(): extension with an empty name, skipping\n" "Trade::CgltfImporter::material(): property null has unsupported type, skipping\n" "Trade::CgltfImporter::material(): property emptyArray has unsupported type, skipping\n" "Trade::CgltfImporter::material(): property oversizedArray has unsupported type, skipping\n" @@ -4178,6 +4172,7 @@ void CgltfImporterTest::materialRaw() { "Trade::CgltfImporter::material(): property invalidTexture has invalid texture object type, skipping\n" "Trade::CgltfImporter::material(): property anIncrediblyLongNameThatSadlyWontFitPaddingPaddingPadding!! is too large with 63 bytes, skipping\n" "Trade::CgltfImporter::material(): property aValueThatWontFit is too large with 84 bytes, skipping\n" + "Trade::CgltfImporter::material(): property with an empty name, skipping\n" "Trade::CgltfImporter::material(): extension name VENDOR_material_thisnameiswaytoolongforalayername! is too long with 50 characters, skipping\n"); } diff --git a/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf b/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf index 4078f8601..a14ff9694 100644 --- a/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf +++ b/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf @@ -58,7 +58,6 @@ "coiled": true }, "MGNM_material_type_zoo": { - "null": null, "boolTrue": true, "boolFalse": false, "int": -7992835, @@ -80,7 +79,10 @@ "vec1ui": [9], "vec2ui": [9, 8], "vec3ui": [9, 8, 7], - "vec4ui": [9, 8, 7, 6], + "vec4ui": [9, 8, 7, 6] + }, + "MGNM_material_forbidden_types": { + "null": null, "emptyArray": [], "oversizedArray": [9.1, 8.2, 7.3, 6.4, 5.5], "stringArray": ["one", "two", "", "four"], @@ -102,14 +104,14 @@ }, "anIncrediblyLongNameThatSadlyWontFitPaddingPaddingPadding!!": 0, "aValueThatWontFit": "1 pound ground turkey, 1 medium-sized onion, 1 cup celery (diced)", - "": "Empty attributes are allowed" + "": "Empty attributes are ignored" }, "VENDOR_empty_extension_object": {}, "VENDOR_material_thisnameiswaytoolongforalayername!": { "wow": ["so", "long"] }, "": { - "note": "Empty extension names are allowed" + "note": "Empty extension names are ignored" } } } @@ -131,6 +133,7 @@ "KHR_materials_clearcoat", "MGNM_material_snake", "MGNM_material_type_zoo", + "MGNM_material_forbidden_types", "VENDOR_empty_extension_object", "VENDOR_material_thisnameiswaytoolongforalayername!", "", From f5592c289ce2b701942dbe6b618a917606caa373 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Tue, 14 Dec 2021 12:06:44 +0100 Subject: [PATCH 13/35] CgltfImporter: check texture material attribute size --- .../CgltfImporter/CgltfImporter.cpp | 52 ++++++++++++------- .../CgltfImporter/Test/CgltfImporterTest.cpp | 4 ++ .../CgltfImporter/Test/material-raw.gltf | 11 ++++ 3 files changed, 49 insertions(+), 18 deletions(-) diff --git a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp index 74d87a950..6cdf193f6 100644 --- a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp +++ b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp @@ -2195,6 +2195,29 @@ std::string CgltfImporter::doMaterialName(const UnsignedInt id) { return _d->decodeString(_d->data->materials[id].name); } +namespace { + +bool checkMaterialAttributeSize(Containers::StringView name, MaterialAttributeType type, const void* value = nullptr) { + std::size_t valueSize; + if(type == MaterialAttributeType::String) { + CORRADE_INTERNAL_ASSERT(value); + /* +2 are null byte and size */ + valueSize = static_cast(value)->size() + 2; + } else + valueSize = materialAttributeTypeSize(type); + + /* +1 is the key null byte */ + if(valueSize + name.size() + 1 + sizeof(MaterialAttributeType) > sizeof(MaterialAttributeData)) { + Warning{} << "Trade::CgltfImporter::material(): property" << name << + "is too large with" << valueSize + name.size() << "bytes, skipping"; + return false; + } + + return true; +} + +} + void CgltfImporter::Document::materialTexture(const cgltf_texture_view& texture, Containers::Array& attributes, Containers::StringView attribute, Containers::StringView matrixAttribute, Containers::StringView coordinateAttribute) const { CORRADE_INTERNAL_ASSERT(texture.texture); @@ -2204,7 +2227,7 @@ void CgltfImporter::Document::materialTexture(const cgltf_texture_view& texture, unflip them back, apply the transform (which assumes origin at bottom left and Y down) and then flip the result again. Sanity of the following verified with https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/TextureTransformTest */ - if(texture.has_transform) { + if(texture.has_transform && checkMaterialAttributeSize(matrixAttribute, MaterialAttributeType::Matrix3x3)) { Matrix3 matrix; /* If material needs an Y-flip, the mesh doesn't have the texture @@ -2236,7 +2259,9 @@ void CgltfImporter::Document::materialTexture(const cgltf_texture_view& texture, /* In case the material had no texture transformation but still needs an Y-flip, put it there */ - if(!texture.has_transform && textureCoordinateYFlipInMaterial) { + if(!texture.has_transform && textureCoordinateYFlipInMaterial && + checkMaterialAttributeSize(matrixAttribute, MaterialAttributeType::Matrix3x3)) + { arrayAppend(attributes, InPlaceInit, matrixAttribute, Matrix3::translation(Vector2::yAxis(1.0f))* Matrix3::scaling(Vector2::yScale(-1.0f))); @@ -2244,13 +2269,13 @@ void CgltfImporter::Document::materialTexture(const cgltf_texture_view& texture, /* Add texture coordinate set if non-zero. The KHR_texture_transform could be modifying it, so do that after */ - if(texCoord != 0) + if(texCoord != 0 && checkMaterialAttributeSize(coordinateAttribute, MaterialAttributeType::UnsignedInt)) arrayAppend(attributes, InPlaceInit, coordinateAttribute, texCoord); /* In some cases (when dealing with packed textures), we're parsing & adding texture coordinates and matrix multiple times, but adding the packed texture ID just once. In other cases the attribute is invalid. */ - if(!attribute.isEmpty()) { + if(!attribute.isEmpty() && checkMaterialAttributeSize(attribute, MaterialAttributeType::UnsignedInt)) { const UnsignedInt textureId = texture.texture - data->textures; arrayAppend(attributes, InPlaceInit, attribute, textureId); } @@ -2741,7 +2766,6 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i char attributeData[16]; Containers::StringView attributeView; MaterialAttributeType type{}; - std::size_t valueSize{}; if(attribute.type == JSMN_OBJECT) { /* Parse glTF textureInfo objects. Any objects without the correct suffix and type are ignored. */ @@ -2767,8 +2791,7 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i /* materialTexture() expects a fixed up texture pointer in cgltf_texture_view, normally done by cgltf */ textureView.texture = &_d->data->textures[index]; - /** @todo Check for too large material data in - materialTexture(). Move the check to a function. */ + Containers::String nameBuffer{NoInit, name.size()*2 + 6 + 11}; Utility::formatInto(nameBuffer, "{}Matrix{}Coordinates", name, name); _d->materialTexture( @@ -2913,7 +2936,6 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); - valueSize = materialAttributeTypeSize(type); i += attribute.size + 1; } else { @@ -2924,8 +2946,6 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i } else if(attribute.type == JSMN_STRING) { attributeView = value; type = MaterialAttributeType::String; - /* +2 is null byte + size */ - valueSize = value.size() + 2; ++i; /* JSMN_UNDEFINED, should never happen for valid JSON files */ @@ -2937,14 +2957,11 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i if(type == MaterialAttributeType{}) continue; - CORRADE_INTERNAL_ASSERT(valueSize); + const void* valuePointer = type == MaterialAttributeType::String ? + static_cast(&attributeView) : static_cast(attributeData); - /* +1 is null byte for the key */ - if(valueSize + name.size() + 1 + sizeof(MaterialAttributeType) > sizeof(MaterialAttributeData)) { - Warning{} << "Trade::CgltfImporter::material(): property" << name << - "is too large with" << valueSize + name.size() << "bytes, skipping"; + if(!checkMaterialAttributeSize(name, type, valuePointer)) continue; - } /** @todo Handle duplicate attributes */ /* Uppercase attribute names are reserved. This shouldn't really @@ -2959,8 +2976,7 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i name = nameLowercase; } - arrayAppend(extensionAttributes, InPlaceInit, name, type, - type == MaterialAttributeType::String ? static_cast(&attributeView) : static_cast(attributeData)); + arrayAppend(extensionAttributes, InPlaceInit, name, type, valuePointer); } /* Only add a layer if the extension contains any attributes */ diff --git a/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp b/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp index 6ea5d16ff..73dea3455 100644 --- a/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp +++ b/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp @@ -4172,6 +4172,10 @@ void CgltfImporterTest::materialRaw() { "Trade::CgltfImporter::material(): property invalidTexture has invalid texture object type, skipping\n" "Trade::CgltfImporter::material(): property anIncrediblyLongNameThatSadlyWontFitPaddingPaddingPadding!! is too large with 63 bytes, skipping\n" "Trade::CgltfImporter::material(): property aValueThatWontFit is too large with 84 bytes, skipping\n" + "Trade::CgltfImporter::material(): property alsoTestingThisWithAnOverlyElongatedNameButThisTimeForATextureMatrix is too large with 104 bytes, skipping\n" + "Trade::CgltfImporter::material(): property alsoTestingThisWithAnOverlyElongatedNameButThisTimeForATextureCoordinates is too large with 77 bytes, skipping\n" + "Trade::CgltfImporter::material(): property alsoTestingThisWithAnOverlyElongatedNameButThisTimeForATexture is too large with 66 bytes, skipping\n" + "Trade::CgltfImporter::material(): property alsoTestingThisWithAnOverlyElongatedNameButThisTimeForATextureScale is too large with 71 bytes, skipping\n" "Trade::CgltfImporter::material(): property with an empty name, skipping\n" "Trade::CgltfImporter::material(): extension name VENDOR_material_thisnameiswaytoolongforalayername! is too long with 50 characters, skipping\n"); } diff --git a/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf b/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf index a14ff9694..c273ed15c 100644 --- a/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf +++ b/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf @@ -104,6 +104,17 @@ }, "anIncrediblyLongNameThatSadlyWontFitPaddingPaddingPadding!!": 0, "aValueThatWontFit": "1 pound ground turkey, 1 medium-sized onion, 1 cup celery (diced)", + "alsoTestingThisWithAnOverlyElongatedNameButThisTimeForATexture": { + "index": 1, + "texCoord": 3, + "scale": 0.2, + "extensions": { + "KHR_texture_transform": { + "offset": [0.5, -0.5], + "scale": [0.33, 0.44] + } + } + }, "": "Empty attributes are ignored" }, "VENDOR_empty_extension_object": {}, From c5756b7a6eabdc66381e5c46cdc3c57383c5d5ad Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Tue, 14 Dec 2021 12:08:06 +0100 Subject: [PATCH 14/35] CgltfImporter: test default scale being skipped for custom texture attributes Also no need for those extra attributes, all types are checked in MGNM_material_type_zoo --- .../CgltfImporter/CgltfImporter.cpp | 7 ++++--- .../CgltfImporter/Test/CgltfImporterTest.cpp | 17 +++++++---------- .../CgltfImporter/Test/material-raw.gltf | 6 ++++-- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp index 6cdf193f6..34498c67b 100644 --- a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp +++ b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp @@ -2808,10 +2808,11 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i suffix with "Strength" instead. cgltf parses both "strength" and "scale" into the same scale element. */ if(textureView.scale != 1.0f) { - /** @todo Test that this is skipped for 1.0 */ Utility::formatInto(nameBuffer, "{}Scale", name); - arrayAppend(extensionAttributes, InPlaceInit, - nameBuffer.prefix(name.size() + 5), textureView.scale); + const Containers::StringView scaleName = nameBuffer.prefix(name.size() + 5); + if(checkMaterialAttributeSize(scaleName, MaterialAttributeType::Float)) + arrayAppend(extensionAttributes, InPlaceInit, + scaleName, textureView.scale); } i = next; diff --git a/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp b/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp index 73dea3455..e6b01d222 100644 --- a/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp +++ b/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp @@ -4001,7 +4001,7 @@ void CgltfImporterTest::materialRaw() { constexpr Containers::StringView layer = "#MGNM_material_snake"_s; CORRADE_ITERATION(layer); CORRADE_VERIFY(material->hasLayer(layer)); - CORRADE_COMPARE(material->attributeCount(layer), 7 + 1); + CORRADE_COMPARE(material->attributeCount(layer), 6 + 1); { constexpr Containers::StringView name = "snakeFactor"_s; @@ -4033,18 +4033,15 @@ void CgltfImporterTest::materialRaw() { CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Float); CORRADE_COMPARE(material->attribute(layer, name), 0.2f); } { - constexpr Containers::StringView name = "type"_s; + constexpr Containers::StringView name = "defaultScaleTexture"_s; CORRADE_VERIFY(material->hasAttribute(layer, name)); - CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::String); - CORRADE_COMPARE(material->attribute(layer, name), "BASIC"_s); - } { - constexpr Containers::StringView name = "coiled"_s; - CORRADE_VERIFY(material->hasAttribute(layer, name)); - CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Bool); - CORRADE_COMPARE(material->attribute(layer, name), true); + CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::UnsignedInt); + CORRADE_COMPARE(material->attribute(layer, name), 1u); } - /* Unknown extension with all supported types */ + /* No defaultScaleTextureScale because the scale is 1.0 */ + + /* Unknown extension with all other supported types */ } { constexpr Containers::StringView layer = "#MGNM_material_type_zoo"_s; CORRADE_ITERATION(layer); diff --git a/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf b/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf index c273ed15c..1b9db352e 100644 --- a/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf +++ b/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf @@ -54,8 +54,10 @@ } } }, - "type": "BASIC", - "coiled": true + "defaultScaleTexture": { + "index": 1, + "scale": 1.0 + } }, "MGNM_material_type_zoo": { "boolTrue": true, From b9c7a210406fc1369c30321a5177134bd4247d75 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Tue, 14 Dec 2021 12:08:14 +0100 Subject: [PATCH 15/35] CgltfImporter: minor --- src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp index 34498c67b..047699d79 100644 --- a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp +++ b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp @@ -923,7 +923,7 @@ Containers::Optional CgltfImporter::doAnimation(UnsignedInt id) { /* Calculate total track count. If merging all animations together, this is the sum of all clip track counts. */ std::size_t trackCount = 0; - for(const cgltf_animation& animation : animations) { + for(const cgltf_animation& animation: animations) { for(std::size_t i = 0; i != animation.channels_count; ++i) { /* Skip animations without a target node. See comment below. */ if(animation.channels[i].target_node) @@ -935,7 +935,7 @@ Containers::Optional CgltfImporter::doAnimation(UnsignedInt id) { bool hadToRenormalize = false; std::size_t trackId = 0; Containers::Array tracks{trackCount}; - for(const cgltf_animation& animation : animations) { + for(const cgltf_animation& animation: animations) { for(std::size_t i = 0; i != animation.channels_count; ++i) { const cgltf_animation_channel& channel = animation.channels[i]; const cgltf_animation_sampler& sampler = *channel.sampler; From 1bd21de4143fbdf736ed001561095fe61dadebbd Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Tue, 14 Dec 2021 12:41:32 +0100 Subject: [PATCH 16/35] CgltfImporter: fix memory leak cgltf allocates memory for extensions inside cgltf_parse_json_texture_view(). We don't use that, so we can free it right away. --- src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp index 047699d79..a01cfcdf0 100644 --- a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp +++ b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp @@ -2773,6 +2773,10 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i cgltf_texture_view textureView{}; const int next = cgltf_parse_json_texture_view(&_d->options, tokens, i, reinterpret_cast(json.data()), &textureView); + /* Free memory allocated by cgltf. We're only interested in + KHR_texture_transform and that's already parsed into + textureView.transform. */ + cgltf_free_extensions(_d->data, textureView.extensions, textureView.extensions_count); /* cgltf_parse_json_texture_view() casts and saves the index + 1 as cgltf_texture*. 0 indicates there was no From db8b395fcc2efd1b90ac3a04adbb3028534f5bfe Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Tue, 14 Dec 2021 16:52:03 +0100 Subject: [PATCH 17/35] CgltfImporter: always import custom extension factors We treat these as "raw" material layers, so just import whatever is there without divining too much about their meaning --- .../CgltfImporter/CgltfImporter.cpp | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp index a01cfcdf0..26d59fb0f 100644 --- a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp +++ b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp @@ -2590,10 +2590,9 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i } const Vector3 specularColorFactor = Vector3::from(material.specular.specular_color_factor); - if(specularColorFactor != Vector3{1.0f}) - arrayAppend(attributes, InPlaceInit, - "specularColorFactor"_s, - specularColorFactor); + arrayAppend(attributes, InPlaceInit, + "specularColorFactor"_s, + specularColorFactor); if(material.specular.specular_color_texture.texture) _d->materialTexture( @@ -2643,17 +2642,17 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i MaterialTextureSwizzle::G); } - /* Default spec value is +infinity, but cgltf uses FLT_MAX */ - if(material.volume.attenuation_distance != std::numeric_limits::max()) - arrayAppend(attributes, InPlaceInit, - "attenuationDistance"_s, - material.volume.attenuation_distance); + /* Default spec value is infinity but cgltf uses FLT_MAX, fix it */ + const Float attenuationDistance = material.volume.attenuation_distance == std::numeric_limits::max() ? + Constants::inf() : material.volume.attenuation_distance; + arrayAppend(attributes, InPlaceInit, + "attenuationDistance"_s, + attenuationDistance); const Vector3 attenuationColor = Vector3::from(material.volume.attenuation_color); - if(attenuationColor != Vector3{1.0f}) - arrayAppend(attributes, InPlaceInit, - "attenuationColor"_s, - attenuationColor); + arrayAppend(attributes, InPlaceInit, + "attenuationColor"_s, + attenuationColor); } if(material.has_sheen) { From 917e11fe25393e16141f592923b4394da5648c84 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Tue, 14 Dec 2021 16:54:39 +0100 Subject: [PATCH 18/35] CgltfImporter: test custom material extensions handled by cgltf There are no wrappers for these material types/layers, so we don't have to test all these interactions that the clearcoat extension is going through. Just checking the defaults and the texture import. --- .../CgltfImporter/Test/CMakeLists.txt | 5 + .../CgltfImporter/Test/CgltfImporterTest.cpp | 628 +++++++++++++++++- .../CgltfImporter/Test/material-ior.gltf | 27 + .../CgltfImporter/Test/material-sheen.gltf | 110 +++ .../CgltfImporter/Test/material-specular.gltf | 110 +++ .../Test/material-transmission.gltf | 87 +++ .../CgltfImporter/Test/material-volume.gltf | 91 +++ 7 files changed, 1055 insertions(+), 3 deletions(-) create mode 100644 src/MagnumPlugins/CgltfImporter/Test/material-ior.gltf create mode 100644 src/MagnumPlugins/CgltfImporter/Test/material-sheen.gltf create mode 100644 src/MagnumPlugins/CgltfImporter/Test/material-specular.gltf create mode 100644 src/MagnumPlugins/CgltfImporter/Test/material-transmission.gltf create mode 100644 src/MagnumPlugins/CgltfImporter/Test/material-volume.gltf diff --git a/src/MagnumPlugins/CgltfImporter/Test/CMakeLists.txt b/src/MagnumPlugins/CgltfImporter/Test/CMakeLists.txt index b5523dfcd..dc14d394c 100644 --- a/src/MagnumPlugins/CgltfImporter/Test/CMakeLists.txt +++ b/src/MagnumPlugins/CgltfImporter/Test/CMakeLists.txt @@ -132,14 +132,19 @@ corrade_add_test(CgltfImporterTest material-invalid-pbr-diffuse-oob.gltf material-invalid-pbr-metallic-roughness-oob.gltf material-invalid-pbr-specular-oob.gltf + material-ior.gltf material-metallicroughness.gltf material-phong-fallback.gltf material-raw.gltf + material-sheen.gltf + material-specular.gltf material-specularglossiness.gltf material-texcoord-flip.bin material-texcoord-flip.gltf material-texcoord-flip-unnormalized.gltf + material-transmission.gltf material-unlit.gltf + material-volume.gltf mesh.gltf mesh.bin mesh.glb diff --git a/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp b/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp index e6b01d222..e5068d78b 100644 --- a/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp +++ b/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp @@ -151,6 +151,11 @@ struct CgltfImporterTest: TestSuite::Tester { void materialClearCoat(); void materialPhongFallback(); void materialRaw(); + void materialRawIor(); + void materialRawSpecular(); + void materialRawTransmission(); + void materialRawVolume(); + void materialRawSheen(); void materialRawOutOfBounds(); void materialOutOfBounds(); @@ -773,6 +778,11 @@ CgltfImporterTest::CgltfImporterTest() { &CgltfImporterTest::materialClearCoat, &CgltfImporterTest::materialPhongFallback, &CgltfImporterTest::materialRaw, + &CgltfImporterTest::materialRawIor, + &CgltfImporterTest::materialRawSpecular, + &CgltfImporterTest::materialRawTransmission, + &CgltfImporterTest::materialRawVolume, + &CgltfImporterTest::materialRawSheen, &CgltfImporterTest::materialRawOutOfBounds}); addInstancedTests({&CgltfImporterTest::materialOutOfBounds}, @@ -4017,7 +4027,7 @@ void CgltfImporterTest::materialRaw() { constexpr Containers::StringView name = "snakeTextureMatrix"_s; CORRADE_VERIFY(material->hasAttribute(layer, name)); CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Matrix3x3); - CORRADE_COMPARE(material->attribute(layer, name), (Matrix3{ + CORRADE_COMPARE(material->attribute(layer, name), (Matrix3{ {0.33f, 0.0f, 0.0f}, {0.0f, 0.44f, 0.0f}, {0.5f, 1.06f, 1.0f} @@ -4039,7 +4049,8 @@ void CgltfImporterTest::materialRaw() { CORRADE_COMPARE(material->attribute(layer, name), 1u); } - /* No defaultScaleTextureScale because the scale is 1.0 */ + /* No defaultScaleTextureScale because scale is 1.0 */ + CORRADE_VERIFY(!material->hasAttribute(layer, "defaultScaleTextureScale"_s)); /* Unknown extension with all other supported types */ } { @@ -4151,9 +4162,11 @@ void CgltfImporterTest::materialRaw() { } } - /* No MGNM_material_forbidden_types layer because all attributes are + /* No layer for MGNM_material_forbidden_types because all attributes are invalid types or too large and skipped */ + CORRADE_VERIFY(!material->hasLayer("#MGNM_material_forbidden_types"_s)); + /* But it produces all kinds of warnings */ CORRADE_COMPARE(out.str(), "Trade::CgltfImporter::material(): extension with an empty name, skipping\n" "Trade::CgltfImporter::material(): property null has unsupported type, skipping\n" @@ -4177,6 +4190,615 @@ void CgltfImporterTest::materialRaw() { "Trade::CgltfImporter::material(): extension name VENDOR_material_thisnameiswaytoolongforalayername! is too long with 50 characters, skipping\n"); } +void CgltfImporterTest::materialRawIor() { + Containers::Pointer importer = _manager.instantiate("CgltfImporter"); + + /* Disable Phong material fallback (enabled by default for compatibility), + testing that separately in materialPhongFallback() */ + importer->configuration().setValue("phongMaterialFallback", false); + + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(CGLTFIMPORTER_TEST_DIR, + "material-ior.gltf"))); + CORRADE_COMPARE(importer->materialCount(), 2); + + /* Extension parsed by glTF, but imported as a custom layer */ + constexpr Containers::StringView layer = "KHR_materials_ior"_s; + + { + const char* name = "defaults"; + auto material = importer->material(name); + CORRADE_ITERATION(name); + CORRADE_VERIFY(material); + CORRADE_COMPARE(material->types(), MaterialType{}); + CORRADE_COMPARE(material->layerCount(), 2); + CORRADE_VERIFY(material->hasLayer(layer)); + /* The extra attribute is the layer name */ + CORRADE_COMPARE(material->attributeCount(layer), 1 + 1); + + /* IOR is always imported, set to glTF default if missing */ + constexpr Containers::StringView attribute = "ior"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Float); + CORRADE_COMPARE(material->attribute(layer, attribute), 1.5f); + } { + const char* name = "factors"; + auto material = importer->material(name); + CORRADE_ITERATION(name); + CORRADE_VERIFY(material); + CORRADE_COMPARE(material->types(), MaterialType{}); + CORRADE_COMPARE(material->layerCount(), 2); + CORRADE_VERIFY(material->hasLayer(layer)); + CORRADE_COMPARE(material->attributeCount(layer), 1 + 1); + + constexpr Containers::StringView attribute = "ior"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Float); + CORRADE_COMPARE(material->attribute(layer, attribute), 1.25f); + } +} + +void CgltfImporterTest::materialRawSpecular() { + Containers::Pointer importer = _manager.instantiate("CgltfImporter"); + + /* Disable Phong material fallback (enabled by default for compatibility), + testing that separately in materialPhongFallback() */ + importer->configuration().setValue("phongMaterialFallback", false); + + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(CGLTFIMPORTER_TEST_DIR, + "material-specular.gltf"))); + CORRADE_COMPARE(importer->materialCount(), 5); + + constexpr Containers::StringView layer = "KHR_materials_specular"_s; + + { + const char* name = "defaults"; + auto material = importer->material(name); + CORRADE_ITERATION(name); + CORRADE_VERIFY(material); + CORRADE_COMPARE(material->types(), MaterialType{}); + CORRADE_COMPARE(material->layerCount(), 2); + CORRADE_VERIFY(material->hasLayer(layer)); + /* The extra attribute is the layer name */ + CORRADE_COMPARE(material->attributeCount(layer), 2 + 1); + + /* Factors are always imported, set to glTF defaults if missing */ + { + constexpr Containers::StringView attribute = "specularFactor"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Float); + CORRADE_COMPARE(material->attribute(layer, attribute), 1.0f); + } { + constexpr Containers::StringView attribute = "specularColorFactor"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Vector3); + CORRADE_COMPARE(material->attribute(layer, attribute), (Vector3{1.0f, 1.0f, 1.0f})); + } + } { + const char* name = "factors"; + auto material = importer->material(name); + CORRADE_ITERATION(name); + CORRADE_VERIFY(material); + CORRADE_COMPARE(material->types(), MaterialType{}); + CORRADE_COMPARE(material->layerCount(), 2); + CORRADE_VERIFY(material->hasLayer(layer)); + CORRADE_COMPARE(material->attributeCount(layer), 2 + 1); + + { + constexpr Containers::StringView attribute = "specularFactor"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Float); + CORRADE_COMPARE(material->attribute(layer, attribute), 0.67f); + } { + constexpr Containers::StringView attribute = "specularColorFactor"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Vector3); + CORRADE_COMPARE(material->attribute(layer, attribute), (Vector3{0.2f, 0.4f, 0.6f})); + } + } { + const char* name = "textures"; + auto material = importer->material(name); + CORRADE_ITERATION(name); + CORRADE_VERIFY(material); + CORRADE_COMPARE(material->types(), MaterialType{}); + CORRADE_COMPARE(material->layerCount(), 2); + CORRADE_VERIFY(material->hasLayer(layer)); + CORRADE_COMPARE(material->attributeCount(layer), 5 + 1); + + { + constexpr Containers::StringView attribute = "specularFactor"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Float); + CORRADE_COMPARE(material->attribute(layer, attribute), 0.7f); + } { + constexpr Containers::StringView attribute = "specularColorFactor"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Vector3); + CORRADE_COMPARE(material->attribute(layer, attribute), (Vector3{0.3f, 0.4f, 0.5f})); + } { + constexpr Containers::StringView attribute = "specularTexture"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::UnsignedInt); + CORRADE_COMPARE(material->attribute(layer, attribute), 2u); + } { + constexpr Containers::StringView attribute = "specularTextureSwizzle"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::TextureSwizzle); + CORRADE_COMPARE(material->attribute(layer, attribute), MaterialTextureSwizzle::A); + } { + constexpr Containers::StringView attribute = "specularColorTexture"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::UnsignedInt); + CORRADE_COMPARE(material->attribute(layer, attribute), 1u); + } + } { + const char* name = "texture identity transform"; + auto material = importer->material(name); + CORRADE_ITERATION(name); + CORRADE_VERIFY(material); + CORRADE_COMPARE(material->types(), MaterialType{}); + CORRADE_COMPARE(material->layerCount(), 2); + CORRADE_VERIFY(material->hasLayer(layer)); + CORRADE_COMPARE(material->attributeCount(layer), 7 + 1); + + { + constexpr Containers::StringView attribute = "specularTextureMatrix"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Matrix3x3); + CORRADE_COMPARE(material->attribute(layer, attribute), Matrix3{}); + } { + constexpr Containers::StringView attribute = "specularColorTextureMatrix"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Matrix3x3); + CORRADE_COMPARE(material->attribute(layer, attribute), Matrix3{}); + } + } { + const char* name = "texture transform + coordinate set"; + auto material = importer->material(name); + CORRADE_ITERATION(name); + CORRADE_VERIFY(material); + CORRADE_COMPARE(material->types(), MaterialType{}); + CORRADE_COMPARE(material->layerCount(), 2); + CORRADE_VERIFY(material->hasLayer(layer)); + CORRADE_COMPARE(material->attributeCount(layer), 9 + 1); + + { + constexpr Containers::StringView attribute = "specularTextureMatrix"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Matrix3x3); + CORRADE_COMPARE(material->attribute(layer, attribute), (Matrix3{ + {1.0f, 0.0f, 0.0f}, + {0.0f, 1.0f, 0.0f}, + {0.0f, -1.0f, 1.0f} + })); + } { + constexpr Containers::StringView attribute = "specularTextureCoordinates"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::UnsignedInt); + CORRADE_COMPARE(material->attribute(layer, attribute), 4); + } { + constexpr Containers::StringView attribute = "specularColorTextureMatrix"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Matrix3x3); + CORRADE_COMPARE(material->attribute(layer, attribute), (Matrix3{ + {0.5f, 0.0f, 0.0f}, + {0.0f, 0.5f, 0.0f}, + {0.0f, 0.5f, 1.0f} + })); + } { + constexpr Containers::StringView attribute = "specularColorTextureCoordinates"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::UnsignedInt); + CORRADE_COMPARE(material->attribute(layer, attribute), 1); + } + } +} + +void CgltfImporterTest::materialRawTransmission() { + Containers::Pointer importer = _manager.instantiate("CgltfImporter"); + + /* Disable Phong material fallback (enabled by default for compatibility), + testing that separately in materialPhongFallback() */ + importer->configuration().setValue("phongMaterialFallback", false); + + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(CGLTFIMPORTER_TEST_DIR, + "material-transmission.gltf"))); + CORRADE_COMPARE(importer->materialCount(), 5); + + constexpr Containers::StringView layer = "KHR_materials_transmission"_s; + + { + const char* name = "defaults"; + auto material = importer->material(name); + CORRADE_ITERATION(name); + CORRADE_VERIFY(material); + CORRADE_COMPARE(material->types(), MaterialType{}); + CORRADE_COMPARE(material->layerCount(), 2); + CORRADE_VERIFY(material->hasLayer(layer)); + /* The extra attribute is the layer name */ + CORRADE_COMPARE(material->attributeCount(layer), 1 + 1); + + /* transmissionFactor is always imported, set to glTF default if + missing */ + constexpr Containers::StringView attribute = "transmissionFactor"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Float); + CORRADE_COMPARE(material->attribute(layer, attribute), 0.0f); + } { + const char* name = "factors"; + auto material = importer->material(name); + CORRADE_ITERATION(name); + CORRADE_VERIFY(material); + CORRADE_COMPARE(material->types(), MaterialType{}); + CORRADE_COMPARE(material->layerCount(), 2); + CORRADE_VERIFY(material->hasLayer(layer)); + CORRADE_COMPARE(material->attributeCount(layer), 1 + 1); + + constexpr Containers::StringView attribute = "transmissionFactor"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Float); + CORRADE_COMPARE(material->attribute(layer, attribute), 0.67f); + } { + const char* name = "textures"; + auto material = importer->material(name); + CORRADE_ITERATION(name); + CORRADE_VERIFY(material); + CORRADE_COMPARE(material->types(), MaterialType{}); + CORRADE_COMPARE(material->layerCount(), 2); + CORRADE_VERIFY(material->hasLayer(layer)); + CORRADE_COMPARE(material->attributeCount(layer), 2 + 1); + + { + constexpr Containers::StringView attribute = "transmissionFactor"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Float); + CORRADE_COMPARE(material->attribute(layer, attribute), 0.7f); + } { + constexpr Containers::StringView attribute = "transmissionTexture"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::UnsignedInt); + CORRADE_COMPARE(material->attribute(layer, attribute), 1u); + } + } { + const char* name = "texture identity transform"; + auto material = importer->material(name); + CORRADE_ITERATION(name); + CORRADE_VERIFY(material); + CORRADE_COMPARE(material->types(), MaterialType{}); + CORRADE_COMPARE(material->layerCount(), 2); + CORRADE_VERIFY(material->hasLayer(layer)); + CORRADE_COMPARE(material->attributeCount(layer), 3 + 1); + + constexpr Containers::StringView attribute = "transmissionTextureMatrix"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Matrix3x3); + CORRADE_COMPARE(material->attribute(layer, attribute), Matrix3{}); + } { + const char* name = "texture transform + coordinate set"; + auto material = importer->material(name); + CORRADE_ITERATION(name); + CORRADE_VERIFY(material); + CORRADE_COMPARE(material->types(), MaterialType{}); + CORRADE_COMPARE(material->layerCount(), 2); + CORRADE_VERIFY(material->hasLayer(layer)); + CORRADE_COMPARE(material->attributeCount(layer), 4 + 1); + + { + constexpr Containers::StringView attribute = "transmissionTextureMatrix"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Matrix3x3); + CORRADE_COMPARE(material->attribute(layer, attribute), (Matrix3{ + {1.0f, 0.0f, 0.0f}, + {0.0f, 1.0f, 0.0f}, + {0.0f, -1.0f, 1.0f} + })); + } { + constexpr Containers::StringView attribute = "transmissionTextureCoordinates"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::UnsignedInt); + CORRADE_COMPARE(material->attribute(layer, attribute), 3); + } + } +} + +void CgltfImporterTest::materialRawVolume() { + Containers::Pointer importer = _manager.instantiate("CgltfImporter"); + + /* Disable Phong material fallback (enabled by default for compatibility), + testing that separately in materialPhongFallback() */ + importer->configuration().setValue("phongMaterialFallback", false); + + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(CGLTFIMPORTER_TEST_DIR, + "material-volume.gltf"))); + CORRADE_COMPARE(importer->materialCount(), 5); + + constexpr Containers::StringView layer = "KHR_materials_volume"_s; + + { + const char* name = "defaults"; + auto material = importer->material(name); + CORRADE_ITERATION(name); + CORRADE_VERIFY(material); + CORRADE_COMPARE(material->types(), MaterialType{}); + CORRADE_COMPARE(material->layerCount(), 2); + CORRADE_VERIFY(material->hasLayer(layer)); + /* The extra attribute is the layer name */ + CORRADE_COMPARE(material->attributeCount(layer), 3 + 1); + + /* Factors are always imported, set to glTF defaults if missing */ + { + constexpr Containers::StringView attribute = "thicknessFactor"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Float); + CORRADE_COMPARE(material->attribute(layer, attribute), 0.0f); + } { + constexpr Containers::StringView attribute = "attenuationDistance"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Float); + CORRADE_COMPARE(material->attribute(layer, attribute), Constants::inf()); + } { + constexpr Containers::StringView attribute = "attenuationColor"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Vector3); + CORRADE_COMPARE(material->attribute(layer, attribute), (Vector3{1.0f, 1.0f, 1.0f})); + } + } { + const char* name = "factors"; + auto material = importer->material(name); + CORRADE_ITERATION(name); + CORRADE_VERIFY(material); + CORRADE_COMPARE(material->types(), MaterialType{}); + CORRADE_COMPARE(material->layerCount(), 2); + CORRADE_VERIFY(material->hasLayer(layer)); + CORRADE_COMPARE(material->attributeCount(layer), 3 + 1); + + { + constexpr Containers::StringView attribute = "thicknessFactor"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Float); + CORRADE_COMPARE(material->attribute(layer, attribute), 0.67f); + } { + constexpr Containers::StringView attribute = "attenuationDistance"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Float); + CORRADE_COMPARE(material->attribute(layer, attribute), 15.3f); + } { + constexpr Containers::StringView attribute = "attenuationColor"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Vector3); + CORRADE_COMPARE(material->attribute(layer, attribute), (Vector3{0.7f, 0.1f, 1.0f})); + } + } { + const char* name = "textures"; + auto material = importer->material(name); + CORRADE_ITERATION(name); + CORRADE_VERIFY(material); + CORRADE_COMPARE(material->types(), MaterialType{}); + CORRADE_COMPARE(material->layerCount(), 2); + CORRADE_VERIFY(material->hasLayer(layer)); + CORRADE_COMPARE(material->attributeCount(layer), 5 + 1); + + { + constexpr Containers::StringView attribute = "thicknessFactor"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Float); + CORRADE_COMPARE(material->attribute(layer, attribute), 0.7f); + } { + constexpr Containers::StringView attribute = "attenuationDistance"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Float); + CORRADE_COMPARE(material->attribute(layer, attribute), 1.12f); + } { + constexpr Containers::StringView attribute = "attenuationColor"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Vector3); + CORRADE_COMPARE(material->attribute(layer, attribute), (Vector3{0.1f, 0.05f, 0.0f})); + } { + constexpr Containers::StringView attribute = "thicknessTexture"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::UnsignedInt); + CORRADE_COMPARE(material->attribute(layer, attribute), 1u); + } { + constexpr Containers::StringView attribute = "thicknessTextureSwizzle"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::TextureSwizzle); + CORRADE_COMPARE(material->attribute(layer, attribute), MaterialTextureSwizzle::G); + } + } { + const char* name = "texture identity transform"; + auto material = importer->material(name); + CORRADE_ITERATION(name); + CORRADE_VERIFY(material); + CORRADE_COMPARE(material->types(), MaterialType{}); + CORRADE_COMPARE(material->layerCount(), 2); + CORRADE_VERIFY(material->hasLayer(layer)); + CORRADE_COMPARE(material->attributeCount(layer), 6 + 1); + + constexpr Containers::StringView attribute = "thicknessTextureMatrix"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Matrix3x3); + CORRADE_COMPARE(material->attribute(layer, attribute), Matrix3{}); + } { + const char* name = "texture transform + coordinate set"; + auto material = importer->material(name); + CORRADE_ITERATION(name); + CORRADE_VERIFY(material); + CORRADE_COMPARE(material->types(), MaterialType{}); + CORRADE_COMPARE(material->layerCount(), 2); + CORRADE_VERIFY(material->hasLayer(layer)); + CORRADE_COMPARE(material->attributeCount(layer), 7 + 1); + + { + constexpr Containers::StringView attribute = "thicknessTextureMatrix"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Matrix3x3); + CORRADE_COMPARE(material->attribute(layer, attribute), (Matrix3{ + {1.0f, 0.0f, 0.0f}, + {0.0f, 1.0f, 0.0f}, + {0.0f, -1.0f, 1.0f} + })); + } { + constexpr Containers::StringView attribute = "thicknessTextureCoordinates"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::UnsignedInt); + CORRADE_COMPARE(material->attribute(layer, attribute), 3); + } + } +} + +void CgltfImporterTest::materialRawSheen() { + Containers::Pointer importer = _manager.instantiate("CgltfImporter"); + + /* Disable Phong material fallback (enabled by default for compatibility), + testing that separately in materialPhongFallback() */ + importer->configuration().setValue("phongMaterialFallback", false); + + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(CGLTFIMPORTER_TEST_DIR, + "material-sheen.gltf"))); + CORRADE_COMPARE(importer->materialCount(), 5); + + constexpr Containers::StringView layer = "KHR_materials_sheen"_s; + + { + const char* name = "defaults"; + auto material = importer->material(name); + CORRADE_ITERATION(name); + CORRADE_VERIFY(material); + CORRADE_COMPARE(material->types(), MaterialType{}); + CORRADE_COMPARE(material->layerCount(), 2); + CORRADE_VERIFY(material->hasLayer(layer)); + /* The extra attribute is the layer name */ + CORRADE_COMPARE(material->attributeCount(layer), 2 + 1); + + /* Factors are always imported, set to glTF defaults if missing */ + { + constexpr Containers::StringView attribute = "sheenColorFactor"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Vector3); + CORRADE_COMPARE(material->attribute(layer, attribute), (Vector3{0.0f, 0.0f, 0.0f})); + } { + constexpr Containers::StringView attribute = "sheenRoughnessFactor"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Float); + CORRADE_COMPARE(material->attribute(layer, attribute), 0.0f); + } + } { + const char* name = "factors"; + auto material = importer->material(name); + CORRADE_ITERATION(name); + CORRADE_VERIFY(material); + CORRADE_COMPARE(material->types(), MaterialType{}); + CORRADE_COMPARE(material->layerCount(), 2); + CORRADE_VERIFY(material->hasLayer(layer)); + CORRADE_COMPARE(material->attributeCount(layer), 2 + 1); + + { + constexpr Containers::StringView attribute = "sheenColorFactor"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Vector3); + CORRADE_COMPARE(material->attribute(layer, attribute), (Vector3{0.2f, 0.4f, 0.6f})); + } { + constexpr Containers::StringView attribute = "sheenRoughnessFactor"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Float); + CORRADE_COMPARE(material->attribute(layer, attribute), 0.67f); + } + } { + const char* name = "textures"; + auto material = importer->material(name); + CORRADE_ITERATION(name); + CORRADE_VERIFY(material); + CORRADE_COMPARE(material->types(), MaterialType{}); + CORRADE_COMPARE(material->layerCount(), 2); + CORRADE_VERIFY(material->hasLayer(layer)); + CORRADE_COMPARE(material->attributeCount(layer), 5 + 1); + + { + constexpr Containers::StringView attribute = "sheenColorFactor"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Vector3); + CORRADE_COMPARE(material->attribute(layer, attribute), (Vector3{0.3f, 0.4f, 0.5f})); + } { + constexpr Containers::StringView attribute = "sheenRoughnessFactor"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Float); + CORRADE_COMPARE(material->attribute(layer, attribute), 0.7f); + } { + constexpr Containers::StringView attribute = "sheenColorTexture"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::UnsignedInt); + CORRADE_COMPARE(material->attribute(layer, attribute), 1u); + } { + constexpr Containers::StringView attribute = "sheenRoughnessTexture"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::UnsignedInt); + CORRADE_COMPARE(material->attribute(layer, attribute), 2u); + } { + constexpr Containers::StringView attribute = "sheenRoughnessTextureSwizzle"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::TextureSwizzle); + CORRADE_COMPARE(material->attribute(layer, attribute), MaterialTextureSwizzle::A); + } + } { + const char* name = "texture identity transform"; + auto material = importer->material(name); + CORRADE_ITERATION(name); + CORRADE_VERIFY(material); + CORRADE_COMPARE(material->types(), MaterialType{}); + CORRADE_COMPARE(material->layerCount(), 2); + CORRADE_VERIFY(material->hasLayer(layer)); + { + CORRADE_EXPECT_FAIL("sheenRoughnessTextureMatrix attribute is skipped because it's too large."); + CORRADE_COMPARE(material->attributeCount(layer), 7 + 1); + } + CORRADE_COMPARE(material->attributeCount(layer), 6 + 1); + + { + constexpr Containers::StringView attribute = "sheenColorTextureMatrix"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Matrix3x3); + CORRADE_COMPARE(material->attribute(layer, attribute), Matrix3{}); + } { + CORRADE_VERIFY(!material->hasAttribute(layer, "sheenRoughnessTextureMatrix"_s)); + } + } { + const char* name = "texture transform + coordinate set"; + auto material = importer->material(name); + CORRADE_ITERATION(name); + CORRADE_VERIFY(material); + CORRADE_COMPARE(material->types(), MaterialType{}); + CORRADE_COMPARE(material->layerCount(), 2); + CORRADE_VERIFY(material->hasLayer(layer)); + { + CORRADE_EXPECT_FAIL("sheenRoughnessTextureMatrix attribute is skipped because it's too large."); + CORRADE_COMPARE(material->attributeCount(layer), 9 + 1); + } + CORRADE_COMPARE(material->attributeCount(layer), 8 + 1); + + { + constexpr Containers::StringView attribute = "sheenColorTextureMatrix"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Matrix3x3); + CORRADE_COMPARE(material->attribute(layer, attribute), (Matrix3{ + {1.0f, 0.0f, 0.0f}, + {0.0f, 1.0f, 0.0f}, + {0.0f, -1.0f, 1.0f} + })); + } { + constexpr Containers::StringView attribute = "sheenColorTextureCoordinates"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::UnsignedInt); + CORRADE_COMPARE(material->attribute(layer, attribute), 4); + } { + CORRADE_VERIFY(!material->hasAttribute(layer, "sheenRoughnessTextureMatrix"_s)); + } { + constexpr Containers::StringView attribute = "sheenRoughnessTextureCoordinates"_s; + CORRADE_VERIFY(material->hasAttribute(layer, attribute)); + CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::UnsignedInt); + CORRADE_COMPARE(material->attribute(layer, attribute), 1); + } + } +} + void CgltfImporterTest::materialRawOutOfBounds() { Containers::Pointer importer = _manager.instantiate("CgltfImporter"); diff --git a/src/MagnumPlugins/CgltfImporter/Test/material-ior.gltf b/src/MagnumPlugins/CgltfImporter/Test/material-ior.gltf new file mode 100644 index 000000000..2cc447b75 --- /dev/null +++ b/src/MagnumPlugins/CgltfImporter/Test/material-ior.gltf @@ -0,0 +1,27 @@ +{ + "asset": { + "version": "2.0" + }, + "materials": [ + { + "name": "defaults", + "extensions": { + "KHR_materials_ior": {} + } + }, + { + "name": "factors", + "extensions": { + "KHR_materials_ior": { + "ior": 1.25 + } + } + } + ], + "extensionsUsed": [ + "KHR_materials_ior" + ], + "extensionsRequired": [ + "KHR_materials_ior" + ] +} diff --git a/src/MagnumPlugins/CgltfImporter/Test/material-sheen.gltf b/src/MagnumPlugins/CgltfImporter/Test/material-sheen.gltf new file mode 100644 index 000000000..0baec1537 --- /dev/null +++ b/src/MagnumPlugins/CgltfImporter/Test/material-sheen.gltf @@ -0,0 +1,110 @@ +{ + "asset": { + "version": "2.0" + }, + "images": [ + { + "uri": "texture.png" + } + ], + "materials": [ + { + "name": "defaults", + "extensions": { + "KHR_materials_sheen": {} + } + }, + { + "name": "factors", + "extensions": { + "KHR_materials_sheen": { + "sheenColorFactor": [0.2, 0.4, 0.6], + "sheenRoughnessFactor": 0.67 + } + } + }, + { + "name": "textures", + "extensions": { + "KHR_materials_sheen": { + "sheenColorFactor": [0.3, 0.4, 0.5], + "sheenColorTexture": { + "index": 1 + }, + "sheenRoughnessFactor": 0.7, + "sheenRoughnessTexture": { + "index": 2 + } + } + } + }, + { + "name": "texture identity transform", + "extensions": { + "KHR_materials_sheen": { + "sheenColorTexture": { + "index": 1, + "extensions": { + "KHR_texture_transform": {} + } + }, + "sheenRoughnessTexture": { + "index": 0, + "extensions": { + "KHR_texture_transform": {} + } + } + } + } + }, + { + "name": "texture transform + coordinate set", + "extensions": { + "KHR_materials_sheen": { + "sheenColorTexture": { + "index": 2, + "extensions": { + "KHR_texture_transform": { + "offset": [0.0, 1.0], + "texCoord": 4 + } + } + }, + "sheenRoughnessTexture": { + "index": 1, + "extensions": { + "KHR_texture_transform": { + "scale": [0.5, 0.5] + } + }, + "texCoord": 1 + } + } + } + } + ], + "samplers": [ + {} + ], + "textures": [ + { + "sampler": 0, + "source": 0 + }, + { + "sampler": 0, + "source": 0 + }, + { + "sampler": 0, + "source": 0 + } + ], + "extensionsUsed": [ + "KHR_materials_sheen", + "KHR_texture_transform" + ], + "extensionsRequired": [ + "KHR_materials_sheen" + ] +} diff --git a/src/MagnumPlugins/CgltfImporter/Test/material-specular.gltf b/src/MagnumPlugins/CgltfImporter/Test/material-specular.gltf new file mode 100644 index 000000000..faef37a7c --- /dev/null +++ b/src/MagnumPlugins/CgltfImporter/Test/material-specular.gltf @@ -0,0 +1,110 @@ +{ + "asset": { + "version": "2.0" + }, + "images": [ + { + "uri": "texture.png" + } + ], + "materials": [ + { + "name": "defaults", + "extensions": { + "KHR_materials_specular": {} + } + }, + { + "name": "factors", + "extensions": { + "KHR_materials_specular": { + "specularFactor": 0.67, + "specularColorFactor": [0.2, 0.4, 0.6] + } + } + }, + { + "name": "textures", + "extensions": { + "KHR_materials_specular": { + "specularFactor": 0.7, + "specularTexture": { + "index": 2 + }, + "specularColorFactor": [0.3, 0.4, 0.5], + "specularColorTexture": { + "index": 1 + } + } + } + }, + { + "name": "texture identity transform", + "extensions": { + "KHR_materials_specular": { + "specularTexture": { + "index": 1, + "extensions": { + "KHR_texture_transform": {} + } + }, + "specularColorTexture": { + "index": 0, + "extensions": { + "KHR_texture_transform": {} + } + } + } + } + }, + { + "name": "texture transform + coordinate set", + "extensions": { + "KHR_materials_specular": { + "specularTexture": { + "index": 2, + "extensions": { + "KHR_texture_transform": { + "offset": [0.0, 1.0], + "texCoord": 4 + } + } + }, + "specularColorTexture": { + "index": 1, + "extensions": { + "KHR_texture_transform": { + "scale": [0.5, 0.5] + } + }, + "texCoord": 1 + } + } + } + } + ], + "samplers": [ + {} + ], + "textures": [ + { + "sampler": 0, + "source": 0 + }, + { + "sampler": 0, + "source": 0 + }, + { + "sampler": 0, + "source": 0 + } + ], + "extensionsUsed": [ + "KHR_materials_specular", + "KHR_texture_transform" + ], + "extensionsRequired": [ + "KHR_materials_specular" + ] +} diff --git a/src/MagnumPlugins/CgltfImporter/Test/material-transmission.gltf b/src/MagnumPlugins/CgltfImporter/Test/material-transmission.gltf new file mode 100644 index 000000000..078bbffcd --- /dev/null +++ b/src/MagnumPlugins/CgltfImporter/Test/material-transmission.gltf @@ -0,0 +1,87 @@ +{ + "asset": { + "version": "2.0" + }, + "images": [ + { + "uri": "texture.png" + } + ], + "materials": [ + { + "name": "defaults", + "extensions": { + "KHR_materials_transmission": {} + } + }, + { + "name": "factors", + "extensions": { + "KHR_materials_transmission": { + "transmissionFactor": 0.67 + } + } + }, + { + "name": "textures", + "extensions": { + "KHR_materials_transmission": { + "transmissionFactor": 0.7, + "transmissionTexture": { + "index": 1 + } + } + } + }, + { + "name": "texture identity transform", + "extensions": { + "KHR_materials_transmission": { + "transmissionTexture": { + "index": 0, + "extensions": { + "KHR_texture_transform": {} + } + } + } + } + }, + { + "name": "texture transform + coordinate set", + "extensions": { + "KHR_materials_transmission": { + "transmissionTexture": { + "index": 1, + "extensions": { + "KHR_texture_transform": { + "offset": [0.0, 1.0], + "texCoord": 3 + } + }, + "texCoord": 1 + } + } + } + } + ], + "samplers": [ + {} + ], + "textures": [ + { + "sampler": 0, + "source": 0 + }, + { + "sampler": 0, + "source": 0 + } + ], + "extensionsUsed": [ + "KHR_materials_transmission", + "KHR_texture_transform" + ], + "extensionsRequired": [ + "KHR_materials_transmission" + ] +} diff --git a/src/MagnumPlugins/CgltfImporter/Test/material-volume.gltf b/src/MagnumPlugins/CgltfImporter/Test/material-volume.gltf new file mode 100644 index 000000000..e818de0f4 --- /dev/null +++ b/src/MagnumPlugins/CgltfImporter/Test/material-volume.gltf @@ -0,0 +1,91 @@ +{ + "asset": { + "version": "2.0" + }, + "images": [ + { + "uri": "texture.png" + } + ], + "materials": [ + { + "name": "defaults", + "extensions": { + "KHR_materials_volume": {} + } + }, + { + "name": "factors", + "extensions": { + "KHR_materials_volume": { + "thicknessFactor": 0.67, + "attenuationDistance": 15.3, + "attenuationColor": [0.7, 0.1, 1] + } + } + }, + { + "name": "textures", + "extensions": { + "KHR_materials_volume": { + "thicknessFactor": 0.7, + "attenuationDistance": 1.12, + "attenuationColor": [0.1, 0.05, 0], + "thicknessTexture": { + "index": 1 + } + } + } + }, + { + "name": "texture identity transform", + "extensions": { + "KHR_materials_volume": { + "thicknessTexture": { + "index": 0, + "extensions": { + "KHR_texture_transform": {} + } + } + } + } + }, + { + "name": "texture transform + coordinate set", + "extensions": { + "KHR_materials_volume": { + "thicknessTexture": { + "index": 1, + "extensions": { + "KHR_texture_transform": { + "offset": [0.0, 1.0], + "texCoord": 3 + } + }, + "texCoord": 1 + } + } + } + } + ], + "samplers": [ + {} + ], + "textures": [ + { + "sampler": 0, + "source": 0 + }, + { + "sampler": 0, + "source": 0 + } + ], + "extensionsUsed": [ + "KHR_materials_volume", + "KHR_texture_transform" + ], + "extensionsRequired": [ + "KHR_materials_volume" + ] +} From 13f2d933b0f78c24ab1cf0d6e3a8c75247ee6cc1 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Thu, 16 Dec 2021 11:21:26 +0100 Subject: [PATCH 19/35] CgltfImporter: parse texture extensions less strictly Previously this only accepted objects with a single "source" attribute. Now it skips all unknown attributes and handles duplicate "source" attributes. This makes it behave similarly to TinyGltfImporter. --- .../CgltfImporter/CgltfImporter.cpp | 36 ++++++++++++------- .../Test/texture-extensions-invalid.gltf | 2 ++ .../Test/texture-extensions.gltf | 5 +++ 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp index 26d59fb0f..f4e19508b 100644 --- a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp +++ b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp @@ -3057,25 +3057,37 @@ Containers::Optional CgltfImporter::doTexture(const UnsignedInt id) - image importers available via manager()->aliasList()? - are there even files out there with more than one extension? */ for(std::size_t i = 0; i != tex.extensions_count && imageId == ~0u; ++i) { - for(const auto& ext: extensions) { - if(tex.extensions[i].name == ext) { + for(std::size_t j = 0; j != Containers::arraySize(extensions) && imageId == ~0u; ++j) { + if(tex.extensions[i].name == extensions[j]) { const Containers::StringView json = tex.extensions[i].data; const auto tokens = parseJson(tex.extensions[i].data); /* This is checked by cgltf */ CORRADE_INTERNAL_ASSERT(!tokens.empty() && tokens[0].type == JSMN_OBJECT); - /* @todo This assumes there is only one key in the object. - There could be unrelated things in there like "note". */ - if(tokens.size() == 3 && tokens[1].type == JSMN_STRING && - tokenString(json, tokens[1]) == "source" && tokens[2].type == JSMN_PRIMITIVE) - { - const Int source = cgltf_json_to_int(&tokens[2], reinterpret_cast(json.data())); - if(source < 0 || UnsignedInt(source) >= _d->data->images_count) { - Error{} << "Trade::CgltfImporter::texture():" << ext << "image" << source << "out of bounds for" << _d->data->images_count << "images"; + + Containers::Optional source; + std::size_t t = 1; + while(t + 1 < tokens.size()) { + /* This is checked by jsmn */ + CORRADE_INTERNAL_ASSERT(tokens[t].type == JSMN_STRING && tokens[t].size == 1); + + if(tokenString(json, tokens[t]) == "source" && tokens[t + 1].type == JSMN_PRIMITIVE) { + source = cgltf_json_to_int(&tokens[t + 1], reinterpret_cast(json.data())); + t += 2; + } else + t = skipJson(tokens, t + 1); + } + + /* Only check the index here because there can be multiple + JSON "source" keys and the last one wins. This matches + cgltf behaviour. */ + if(source) { + if(*source < 0 || std::size_t(*source) >= _d->data->images_count) { + Error{} << "Trade::CgltfImporter::texture():" << extensions[j] << "image" << + *source << "out of bounds for" << _d->data->images_count << "images"; return Containers::NullOpt; } - imageId = source; + imageId = *source; } - break; } } } diff --git a/src/MagnumPlugins/CgltfImporter/Test/texture-extensions-invalid.gltf b/src/MagnumPlugins/CgltfImporter/Test/texture-extensions-invalid.gltf index fd96dfdf5..4254b9f18 100644 --- a/src/MagnumPlugins/CgltfImporter/Test/texture-extensions-invalid.gltf +++ b/src/MagnumPlugins/CgltfImporter/Test/texture-extensions-invalid.gltf @@ -22,6 +22,8 @@ "source" : 1, "extensions": { "GOOGLE_texture_basis": { + "note": "lexically preceding keys should be overwritten, so parsing should not stop after the first source", + "source": 2, "source": 3 } } diff --git a/src/MagnumPlugins/CgltfImporter/Test/texture-extensions.gltf b/src/MagnumPlugins/CgltfImporter/Test/texture-extensions.gltf index e98dd81ac..81addd92d 100644 --- a/src/MagnumPlugins/CgltfImporter/Test/texture-extensions.gltf +++ b/src/MagnumPlugins/CgltfImporter/Test/texture-extensions.gltf @@ -30,6 +30,11 @@ "source": 0, "extensions": { "GOOGLE_texture_basis": { + "note": "other attributes should be ignored, and duplicate source attributes handled (last one wins)", + "source": -3, + "jetsam": { "ignore": "me" }, + "source": 777, + "flotsam": [1, 2, [3, 4], { "5": "6" }], "source": 1 } } From 5919d8335f70fa70b398e4ba93294ca8d0befc86 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Thu, 16 Dec 2021 11:23:02 +0100 Subject: [PATCH 20/35] CgltfImporter: test exponent notation detection and parsing --- src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp | 9 ++++++++- .../CgltfImporter/Test/CgltfImporterTest.cpp | 4 ++-- src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf | 4 ++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp index f4e19508b..57f9717fd 100644 --- a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp +++ b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp @@ -2854,7 +2854,14 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i /* Jsmn only checks the first character, which allows some invalid values like nnn. We perform some basic type detection and invalid values result in 0. This - matches cgltf behaviour. */ + matches cgltf behaviour. + Technically integers can use exponent notation, too: + https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#json-encoding + But cgltf_json_to_int() doesn't parse them correctly + and they should be incredibly rare in the wild, so + we always interpret them as float. */ + /** @todo Override CGLTF_ATOI with a parsing function + that handles integers with exponent notation */ constexpr Containers::StringView FloatIndicators = ".eE"_s; MaterialAttributeType type{}; if(value == "true"_s || value == "false"_s) { diff --git a/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp b/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp index e5068d78b..0089e2560 100644 --- a/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp +++ b/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp @@ -4103,7 +4103,7 @@ void CgltfImporterTest::materialRaw() { constexpr Containers::StringView name = "vec1"_s; CORRADE_VERIFY(material->hasAttribute(layer, name)); CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Float); - CORRADE_COMPARE(material->attribute(layer, name), 9.1f); + CORRADE_COMPARE(material->attribute(layer, name), 91.2f); } { constexpr Containers::StringView name = "vec2"_s; CORRADE_VERIFY(material->hasAttribute(layer, name)); @@ -4113,7 +4113,7 @@ void CgltfImporterTest::materialRaw() { constexpr Containers::StringView name = "vec3"_s; CORRADE_VERIFY(material->hasAttribute(layer, name)); CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Vector3); - CORRADE_COMPARE(material->attribute(layer, name), (Vector3{9.0f, 8.0f, 7.3f})); + CORRADE_COMPARE(material->attribute(layer, name), (Vector3{9.0f, 0.8f, 7.3f})); } { constexpr Containers::StringView name = "vec4"_s; CORRADE_VERIFY(material->hasAttribute(layer, name)); diff --git a/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf b/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf index 1b9db352e..c8586223e 100644 --- a/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf +++ b/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf @@ -68,11 +68,11 @@ "string": "Ribbit -- ribbit", "emptyString": "", "UppercaseName": true , - "vec1": [9.1], + "vec1": [9.12e+1], "vec2": [ -9.1 , 8.2 ], - "vec3": [9, 8, 7.3], + "vec3": [9, 8E-1, 7.3], "vec4": [-9, 8, 7.3, -6], "vec1i": [-9], "vec2i": [9, -8], From 3d2617df6322036661db79d185b1f4b7574c5e49 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Thu, 16 Dec 2021 11:23:46 +0100 Subject: [PATCH 21/35] CgltfImporter: make LCOV happier --- src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp index 57f9717fd..e9e99a841 100644 --- a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp +++ b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp @@ -2895,7 +2895,7 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i /* Only UnsignedInt left, but both types would be identical. Can't be Bool because we abort for bool arrays with size > 1. */ - CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } } } @@ -2945,7 +2945,7 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i data = cgltf_json_to_bool(&tokens[start], reinterpret_cast(json.data())); } else - CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ i += attribute.size + 1; @@ -2961,7 +2961,7 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i /* JSMN_UNDEFINED, should never happen for valid JSON files */ } else - CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ /* Ignored and warning printed, or attributes already added (texture object). Nothing left to do. */ From d3a920cca8cf40079ca5e48c0a8431ae80c2c021 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Thu, 16 Dec 2021 11:56:12 +0100 Subject: [PATCH 22/35] CgltfImporter: handle duplicate attributes Same as vertex attributes and material extensions, duplicate attributes overwrite previous ones. This requires parsing before-hand, then sorting, then importing attributes. It adds some overhead and is incredibly unlikely to matter in practice, but not handling it would lead to asserts in MaterialData, and we don't want that. --- .../CgltfImporter/CgltfImporter.cpp | 113 ++++++++++-------- .../CgltfImporter/Test/CgltfImporterTest.cpp | 35 +++--- .../CgltfImporter/Test/material-raw.gltf | 5 +- 3 files changed, 85 insertions(+), 68 deletions(-) diff --git a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp index e9e99a841..39eadac13 100644 --- a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp +++ b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp @@ -229,11 +229,11 @@ Containers::StringView tokenString(Containers::StringView json, const jsmntok_t& return json.slice(token.start, token.end); } -std::size_t skipJson(Containers::ArrayView tokens, size_t start = 0) { +std::size_t skipJson(Containers::ArrayView tokens, std::size_t start = 0) { const int skipped = cgltf_skip_json(tokens, int(start)); - /* This only happens for tokens with type JSMN_UNDEFINED, which we should - never get for valid JSON files */ - CORRADE_INTERNAL_ASSERT(skipped >= 0); + /* Negative return value only happens for tokens with type JSMN_UNDEFINED, + which we should never get for valid JSON files */ + CORRADE_INTERNAL_ASSERT(skipped >= 0 && std::size_t(skipped) > start); return skipped; } @@ -2703,7 +2703,7 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i }); /* Mark duplicates, those will be skipped later */ - for(UnsignedInt i = 0; i + 1 < extensionOrder.size(); ++i) { + for(std::size_t i = 0; i + 1 < extensionOrder.size(); ++i) { const cgltf_extension& current = material.extensions[extensionOrder[i]]; const cgltf_extension& next = material.extensions[extensionOrder[i + 1]]; if(std::strcmp(current.name, next.name) == 0) @@ -2733,29 +2733,47 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i const Containers::StringView json = extension.data; const auto tokens = parseJson(json); - - Containers::Array extensionAttributes; - Containers::arrayReserve(extensionAttributes, tokens[0].size); - /* First token is the extension object. This is checked by cgltf. If the object is empty, tokens.size() is 1. */ CORRADE_INTERNAL_ASSERT(!tokens.empty() && tokens[0].type == JSMN_OBJECT); - size_t i = 1; - while(i + 1 < tokens.size()) { - /* Keys are always strings and followed by a value. This is checked - by jsmn. */ - CORRADE_INTERNAL_ASSERT(tokens[i].type == JSMN_STRING && tokens[i].size == 1); - Containers::StringView name = tokenString(json, tokens[i]); + UnsignedInt numAttributes = tokens[0].size; + Containers::Array attributeTokens; + Containers::arrayReserve(attributeTokens, numAttributes); + for(UnsignedInt t = 1; t + 1 < tokens.size();) { + /* This is checked by jsmn */ + CORRADE_INTERNAL_ASSERT(tokens[t].type == JSMN_STRING && tokens[t].size == 1); + Containers::arrayAppend(attributeTokens, InPlaceInit, t); + t = skipJson(tokens, t + 1); + } + + /* Sort and mark duplicates, those will be skipped later */ + std::stable_sort(attributeTokens.begin(), attributeTokens.end(), [&](UnsignedInt a, UnsignedInt b) { + return tokenString(json, tokens[a]) < tokenString(json, tokens[b]); + }); + + for(std::size_t i = 0; i + 1 < attributeTokens.size(); ++i) { + if(tokenString(json, tokens[attributeTokens[i]]) == tokenString(json, tokens[attributeTokens[i + 1]])) { + --numAttributes; + /* We can use 0 as an invalid token to mark attributes to skip. + Token 0 is always the extension object itself. */ + attributeTokens[i] = 0u; + } + } + + Containers::Array extensionAttributes; + Containers::arrayReserve(extensionAttributes, numAttributes); + for(UnsignedInt tokenIndex: attributeTokens) { + if(tokenIndex == 0u) continue; + + Containers::StringView name = tokenString(json, tokens[tokenIndex]); if(name.isEmpty()) { Warning{} << "Trade::CgltfImporter::material(): property with an empty name, skipping"; - i = skipJson(tokens, i + 1); continue; } - ++i; - const jsmntok_t& attribute = tokens[i]; - const Containers::StringView value = tokenString(json, attribute); + ++tokenIndex; + const jsmntok_t& token = tokens[tokenIndex]; /* We only need temporary storage for parsing single primitives (bool, Float, Int, UnsignedInt) or Vector2/3/4 of primitives. @@ -2765,13 +2783,13 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i char attributeData[16]; Containers::StringView attributeView; MaterialAttributeType type{}; - if(attribute.type == JSMN_OBJECT) { + if(token.type == JSMN_OBJECT) { /* Parse glTF textureInfo objects. Any objects without the correct suffix and type are ignored. */ if(name.size() > 7 && name.hasSuffix("Texture")) { cgltf_texture_view textureView{}; - const int next = cgltf_parse_json_texture_view(&_d->options, tokens, i, - reinterpret_cast(json.data()), &textureView); + const bool valid = cgltf_parse_json_texture_view(&_d->options, tokens, tokenIndex, + reinterpret_cast(json.data()), &textureView) >= 0; /* Free memory allocated by cgltf. We're only interested in KHR_texture_transform and that's already parsed into textureView.transform. */ @@ -2780,10 +2798,8 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i /* cgltf_parse_json_texture_view() casts and saves the index + 1 as cgltf_texture*. 0 indicates there was no index property. It's mandatory, so we check for it. */ - if(next < 0 || !textureView.texture) { + if(!valid || !textureView.texture) { Warning{} << "Trade::CgltfImporter::material(): property" << name << "has invalid texture object type, skipping"; - i = skipJson(tokens, i); - } else { const std::size_t index = std::size_t(textureView.texture) - 1; if(index >= _d->data->images_count) { @@ -2817,22 +2833,18 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i arrayAppend(extensionAttributes, InPlaceInit, scaleName, textureView.scale); } - - i = next; } - } else { + } else Warning{} << "Trade::CgltfImporter::material(): property" << name << "has non-texture object type, skipping"; - i = skipJson(tokens, i); - } /* A primitive is anything that's not a string, object or array. We ignore non-primitive arrays, so we can handle both in one place. */ - } else if(attribute.type == JSMN_PRIMITIVE || attribute.type == JSMN_ARRAY) { - const std::size_t start = i + size_t(attribute.type == JSMN_ARRAY); + } else if(token.type == JSMN_PRIMITIVE || token.type == JSMN_ARRAY) { + const std::size_t start = tokenIndex + std::size_t(token.type == JSMN_ARRAY); /* Primitive token size is 0, but we can't use max() or we'd allow empty arrays */ - const std::size_t count = attribute.type == JSMN_PRIMITIVE ? 1 : attribute.size; + const std::size_t count = token.type == JSMN_PRIMITIVE ? 1 : token.size; /* Using a lambda here to make aborting early easier */ auto attributeType = [&]() -> MaterialAttributeType { @@ -2845,11 +2857,11 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i objects or nested arrays), because we instantly abort when we find one of those, so we never need more than four tokens */ - for(const jsmntok_t& token: tokens.slice(start, start + count)) { - if(token.type != JSMN_PRIMITIVE) + for(const jsmntok_t& element: tokens.slice(start, start + count)) { + if(element.type != JSMN_PRIMITIVE) return MaterialAttributeType{}; - const auto value = tokenString(json, token); + const Containers::StringView value = tokenString(json, element); /* Jsmn only checks the first character, which allows some invalid values like nnn. We perform some basic @@ -2915,8 +2927,8 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i type = vectorType[count - 1]; Vector4& data = *reinterpret_cast(attributeData); - for(std::size_t j = 0; j != count; ++j) - data[j] = cgltf_json_to_float(&tokens[start + j], reinterpret_cast(json.data())); + for(std::size_t i = 0; i != count; ++i) + data[i] = cgltf_json_to_float(&tokens[start + i], reinterpret_cast(json.data())); } else if(type == MaterialAttributeType::Int) { constexpr MaterialAttributeType vectorType[4] { @@ -2926,8 +2938,8 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i type = vectorType[count - 1]; Vector4i& data = *reinterpret_cast(attributeData); - for(std::size_t j = 0; j != count; ++j) - data[j] = cgltf_json_to_int(&tokens[start + j], reinterpret_cast(json.data())); + for(std::size_t i = 0; i != count; ++i) + data[i] = cgltf_json_to_int(&tokens[start + i], reinterpret_cast(json.data())); } else if(type == MaterialAttributeType::UnsignedInt) { constexpr MaterialAttributeType vectorType[4] { @@ -2937,8 +2949,8 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i type = vectorType[count - 1]; Vector4ui& data = *reinterpret_cast(attributeData); - for(std::size_t j = 0; j != count; ++j) - data[j] = cgltf_json_to_int(&tokens[start + j], reinterpret_cast(json.data())); + for(std::size_t i = 0; i != count; ++i) + data[i] = cgltf_json_to_int(&tokens[start + i], reinterpret_cast(json.data())); } else if(type == MaterialAttributeType::Bool) { bool& data = *reinterpret_cast(attributeData); @@ -2947,21 +2959,17 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ - i += attribute.size + 1; - - } else { + } else Warning{} << "Trade::CgltfImporter::material(): property" << name << "has unsupported type, skipping"; - i = skipJson(tokens, i); - } - } else if(attribute.type == JSMN_STRING) { - attributeView = value; + } else if(token.type == JSMN_STRING) { + attributeView = tokenString(json, token); type = MaterialAttributeType::String; - ++i; - /* JSMN_UNDEFINED, should never happen for valid JSON files */ - } else + } else { + /* JSMN_UNDEFINED, should never happen for valid JSON files */ CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } /* Ignored and warning printed, or attributes already added (texture object). Nothing left to do. */ @@ -2974,7 +2982,6 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i if(!checkMaterialAttributeSize(name, type, valuePointer)) continue; - /** @todo Handle duplicate attributes */ /* Uppercase attribute names are reserved. This shouldn't really happen since virtually all existing glTF extension attributes are lowercase, but better to be safe. Can't use diff --git a/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp b/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp index 0089e2560..546729138 100644 --- a/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp +++ b/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp @@ -4057,7 +4057,7 @@ void CgltfImporterTest::materialRaw() { constexpr Containers::StringView layer = "#MGNM_material_type_zoo"_s; CORRADE_ITERATION(layer); CORRADE_VERIFY(material->hasLayer(layer)); - CORRADE_COMPARE(material->attributeCount(layer), 20 + 1); + CORRADE_COMPARE(material->attributeCount(layer), 21 + 1); { constexpr Containers::StringView name = "boolTrue"_s; @@ -4159,6 +4159,11 @@ void CgltfImporterTest::materialRaw() { CORRADE_VERIFY(material->hasAttribute(layer, name)); CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Vector4ui); CORRADE_COMPARE(material->attribute(layer, name), (Vector4ui{9u, 8u, 7u, 6u})); + } { + constexpr Containers::StringView name = "duplicate"_s; + CORRADE_VERIFY(material->hasAttribute(layer, name)); + CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Bool); + CORRADE_COMPARE(material->attribute(layer, name), true); } } @@ -4166,27 +4171,29 @@ void CgltfImporterTest::materialRaw() { invalid types or too large and skipped */ CORRADE_VERIFY(!material->hasLayer("#MGNM_material_forbidden_types"_s)); - /* But it produces all kinds of warnings */ + /* But it produces all kinds of warnings. Attributes are sorted by name. */ CORRADE_COMPARE(out.str(), "Trade::CgltfImporter::material(): extension with an empty name, skipping\n" - "Trade::CgltfImporter::material(): property null has unsupported type, skipping\n" - "Trade::CgltfImporter::material(): property emptyArray has unsupported type, skipping\n" - "Trade::CgltfImporter::material(): property oversizedArray has unsupported type, skipping\n" - "Trade::CgltfImporter::material(): property stringArray has unsupported type, skipping\n" - "Trade::CgltfImporter::material(): property boolArray has unsupported type, skipping\n" - "Trade::CgltfImporter::material(): property mixedStringArray has unsupported type, skipping\n" - "Trade::CgltfImporter::material(): property mixedBoolArray has unsupported type, skipping\n" - "Trade::CgltfImporter::material(): property mixedObjectArray has unsupported type, skipping\n" - "Trade::CgltfImporter::material(): property nonTextureObject has non-texture object type, skipping\n" + "Trade::CgltfImporter::material(): property with an empty name, skipping\n" "Trade::CgltfImporter::material(): property Texture has non-texture object type, skipping\n" - "Trade::CgltfImporter::material(): property invalidTexture has invalid texture object type, skipping\n" - "Trade::CgltfImporter::material(): property anIncrediblyLongNameThatSadlyWontFitPaddingPaddingPadding!! is too large with 63 bytes, skipping\n" "Trade::CgltfImporter::material(): property aValueThatWontFit is too large with 84 bytes, skipping\n" + /* These are not sorted because they're not JSON attributes, and added + in this order by materialTexture() */ "Trade::CgltfImporter::material(): property alsoTestingThisWithAnOverlyElongatedNameButThisTimeForATextureMatrix is too large with 104 bytes, skipping\n" "Trade::CgltfImporter::material(): property alsoTestingThisWithAnOverlyElongatedNameButThisTimeForATextureCoordinates is too large with 77 bytes, skipping\n" "Trade::CgltfImporter::material(): property alsoTestingThisWithAnOverlyElongatedNameButThisTimeForATexture is too large with 66 bytes, skipping\n" "Trade::CgltfImporter::material(): property alsoTestingThisWithAnOverlyElongatedNameButThisTimeForATextureScale is too large with 71 bytes, skipping\n" - "Trade::CgltfImporter::material(): property with an empty name, skipping\n" + "Trade::CgltfImporter::material(): property anIncrediblyLongNameThatSadlyWontFitPaddingPaddingPadding!! is too large with 63 bytes, skipping\n" + "Trade::CgltfImporter::material(): property boolArray has unsupported type, skipping\n" + "Trade::CgltfImporter::material(): property emptyArray has unsupported type, skipping\n" + "Trade::CgltfImporter::material(): property invalidTexture has invalid texture object type, skipping\n" + "Trade::CgltfImporter::material(): property mixedBoolArray has unsupported type, skipping\n" + "Trade::CgltfImporter::material(): property mixedObjectArray has unsupported type, skipping\n" + "Trade::CgltfImporter::material(): property mixedStringArray has unsupported type, skipping\n" + "Trade::CgltfImporter::material(): property nonTextureObject has non-texture object type, skipping\n" + "Trade::CgltfImporter::material(): property null has unsupported type, skipping\n" + "Trade::CgltfImporter::material(): property oversizedArray has unsupported type, skipping\n" + "Trade::CgltfImporter::material(): property stringArray has unsupported type, skipping\n" "Trade::CgltfImporter::material(): extension name VENDOR_material_thisnameiswaytoolongforalayername! is too long with 50 characters, skipping\n"); } diff --git a/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf b/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf index c8586223e..db0f227e3 100644 --- a/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf +++ b/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf @@ -60,6 +60,7 @@ } }, "MGNM_material_type_zoo": { + "duplicate": "overwritten by later values", "boolTrue": true, "boolFalse": false, "int": -7992835, @@ -81,7 +82,9 @@ "vec1ui": [9], "vec2ui": [9, 8], "vec3ui": [9, 8, 7], - "vec4ui": [9, 8, 7, 6] + "vec4ui": [9, 8, 7, 6], + "duplicate": 0.2, + "duplicate": true }, "MGNM_material_forbidden_types": { "null": null, From 06016e6dcb3a417b50d32cf10796ef260e8f742f Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Fri, 17 Dec 2021 00:21:39 +0100 Subject: [PATCH 23/35] CgltfImporter: unindent some of this code We don't need all these if/else blocks anymore, we can continue; out of the attribute loop --- .../CgltfImporter/CgltfImporter.cpp | 196 +++++++++--------- 1 file changed, 99 insertions(+), 97 deletions(-) diff --git a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp index 39eadac13..f932b74d6 100644 --- a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp +++ b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp @@ -2786,65 +2786,70 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i if(token.type == JSMN_OBJECT) { /* Parse glTF textureInfo objects. Any objects without the correct suffix and type are ignored. */ - if(name.size() > 7 && name.hasSuffix("Texture")) { - cgltf_texture_view textureView{}; - const bool valid = cgltf_parse_json_texture_view(&_d->options, tokens, tokenIndex, - reinterpret_cast(json.data()), &textureView) >= 0; - /* Free memory allocated by cgltf. We're only interested in - KHR_texture_transform and that's already parsed into - textureView.transform. */ - cgltf_free_extensions(_d->data, textureView.extensions, textureView.extensions_count); - - /* cgltf_parse_json_texture_view() casts and saves the - index + 1 as cgltf_texture*. 0 indicates there was no - index property. It's mandatory, so we check for it. */ - if(!valid || !textureView.texture) { - Warning{} << "Trade::CgltfImporter::material(): property" << name << "has invalid texture object type, skipping"; - } else { - const std::size_t index = std::size_t(textureView.texture) - 1; - if(index >= _d->data->images_count) { - Error{} << "Trade::CgltfImporter::material():" << name << "index" << index << "out of bounds for" << _d->data->textures_count << "textures"; - return Containers::NullOpt; - } + if(name.size() < 8 || !name.hasSuffix("Texture")) { + Warning{} << "Trade::CgltfImporter::material(): property" << name << "has non-texture object type, skipping"; + continue; + } - /* materialTexture() expects a fixed up texture pointer - in cgltf_texture_view, normally done by cgltf */ - textureView.texture = &_d->data->textures[index]; - - Containers::String nameBuffer{NoInit, name.size()*2 + 6 + 11}; - Utility::formatInto(nameBuffer, "{}Matrix{}Coordinates", name, name); - _d->materialTexture( - textureView, - extensionAttributes, - name, - nameBuffer.prefix(name.size() + 6), - nameBuffer.suffix(name.size() + 6)); - - /** @todo If there are ever extensions that reference - texture types other than textureInfo and - normalTextureInfo, we might have to be a bit - smarter here, e.g. detect occlusionTextureInfo and - suffix with "Strength" instead. cgltf parses both - "strength" and "scale" into the same scale element. */ - if(textureView.scale != 1.0f) { - Utility::formatInto(nameBuffer, "{}Scale", name); - const Containers::StringView scaleName = nameBuffer.prefix(name.size() + 5); - if(checkMaterialAttributeSize(scaleName, MaterialAttributeType::Float)) - arrayAppend(extensionAttributes, InPlaceInit, - scaleName, textureView.scale); - } - } + cgltf_texture_view textureView{}; + const bool valid = cgltf_parse_json_texture_view(&_d->options, tokens, tokenIndex, + reinterpret_cast(json.data()), &textureView) >= 0; + /* Free memory allocated by cgltf. We're only interested in + KHR_texture_transform and that's already parsed into + textureView.transform. */ + cgltf_free_extensions(_d->data, textureView.extensions, textureView.extensions_count); + + /* cgltf_parse_json_texture_view() casts and saves index + 1 as + cgltf_texture*. 0 indicates there was no index property. + It's mandatory, so we check for it. */ + if(!valid || !textureView.texture) { + Warning{} << "Trade::CgltfImporter::material(): property" << name << "has invalid texture object type, skipping"; + continue; + } - } else - Warning{} << "Trade::CgltfImporter::material(): property" << name << "has non-texture object type, skipping"; + const std::size_t index = std::size_t(textureView.texture) - 1; + if(index >= _d->data->images_count) { + Error{} << "Trade::CgltfImporter::material():" << name << "index" << index << "out of bounds for" << _d->data->textures_count << "textures"; + return Containers::NullOpt; + } + + /* materialTexture() expects a fixed up texture pointer in + cgltf_texture_view, normally done by cgltf */ + textureView.texture = &_d->data->textures[index]; + + Containers::String nameBuffer{NoInit, name.size()*2 + 6 + 11}; + Utility::formatInto(nameBuffer, "{}Matrix{}Coordinates", name, name); + _d->materialTexture( + textureView, + extensionAttributes, + name, + nameBuffer.prefix(name.size() + 6), + nameBuffer.suffix(name.size() + 6)); + + /** @todo If there are ever extensions that reference texture + types other than textureInfo and normalTextureInfo, we + might have to be a bit smarter here, e.g. detect + occlusionTextureInfo and suffix with "Strength" instead. + cgltf parses both "strength" and "scale" into the same + variable. */ + if(textureView.scale != 1.0f) { + Utility::formatInto(nameBuffer, "{}Scale", name); + const Containers::StringView scaleName = nameBuffer.prefix(name.size() + 5); + if(checkMaterialAttributeSize(scaleName, MaterialAttributeType::Float)) + arrayAppend(extensionAttributes, InPlaceInit, + scaleName, textureView.scale); + } + + /* We added all attributes, nothing left to do */ + continue; /* A primitive is anything that's not a string, object or array. We ignore non-primitive arrays, so we can handle both in one place. */ } else if(token.type == JSMN_PRIMITIVE || token.type == JSMN_ARRAY) { - const std::size_t start = tokenIndex + std::size_t(token.type == JSMN_ARRAY); + const UnsignedInt start = tokenIndex + UnsignedInt(token.type == JSMN_ARRAY); /* Primitive token size is 0, but we can't use max() or we'd allow empty arrays */ - const std::size_t count = token.type == JSMN_PRIMITIVE ? 1 : token.size; + const UnsignedInt count = token.type == JSMN_PRIMITIVE ? 1 : token.size; /* Using a lambda here to make aborting early easier */ auto attributeType = [&]() -> MaterialAttributeType { @@ -2918,49 +2923,50 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i type = attributeType(); - if(type != MaterialAttributeType{}) { - if(type == MaterialAttributeType::Float) { - constexpr MaterialAttributeType vectorType[4] { - MaterialAttributeType::Float, MaterialAttributeType::Vector2, - MaterialAttributeType::Vector3, MaterialAttributeType::Vector4 - }; - type = vectorType[count - 1]; - - Vector4& data = *reinterpret_cast(attributeData); - for(std::size_t i = 0; i != count; ++i) - data[i] = cgltf_json_to_float(&tokens[start + i], reinterpret_cast(json.data())); - - } else if(type == MaterialAttributeType::Int) { - constexpr MaterialAttributeType vectorType[4] { - MaterialAttributeType::Int, MaterialAttributeType::Vector2i, - MaterialAttributeType::Vector3i, MaterialAttributeType::Vector4i - }; - type = vectorType[count - 1]; - - Vector4i& data = *reinterpret_cast(attributeData); - for(std::size_t i = 0; i != count; ++i) - data[i] = cgltf_json_to_int(&tokens[start + i], reinterpret_cast(json.data())); - - } else if(type == MaterialAttributeType::UnsignedInt) { - constexpr MaterialAttributeType vectorType[4] { - MaterialAttributeType::UnsignedInt, MaterialAttributeType::Vector2ui, - MaterialAttributeType::Vector3ui, MaterialAttributeType::Vector4ui - }; - type = vectorType[count - 1]; - - Vector4ui& data = *reinterpret_cast(attributeData); - for(std::size_t i = 0; i != count; ++i) - data[i] = cgltf_json_to_int(&tokens[start + i], reinterpret_cast(json.data())); - - } else if(type == MaterialAttributeType::Bool) { - bool& data = *reinterpret_cast(attributeData); - data = cgltf_json_to_bool(&tokens[start], reinterpret_cast(json.data())); - - } else - CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + if(type == MaterialAttributeType{}) { + Warning{} << "Trade::CgltfImporter::material(): property" << name << "has unsupported type, skipping"; + continue; + } + + if(type == MaterialAttributeType::Float) { + constexpr MaterialAttributeType vectorType[4] { + MaterialAttributeType::Float, MaterialAttributeType::Vector2, + MaterialAttributeType::Vector3, MaterialAttributeType::Vector4 + }; + type = vectorType[count - 1]; + + Vector4& data = *reinterpret_cast(attributeData); + for(UnsignedInt i = 0; i != count; ++i) + data[i] = cgltf_json_to_float(&tokens[start + i], reinterpret_cast(json.data())); + + } else if(type == MaterialAttributeType::Int) { + constexpr MaterialAttributeType vectorType[4] { + MaterialAttributeType::Int, MaterialAttributeType::Vector2i, + MaterialAttributeType::Vector3i, MaterialAttributeType::Vector4i + }; + type = vectorType[count - 1]; + + Vector4i& data = *reinterpret_cast(attributeData); + for(UnsignedInt i = 0; i != count; ++i) + data[i] = cgltf_json_to_int(&tokens[start + i], reinterpret_cast(json.data())); + + } else if(type == MaterialAttributeType::UnsignedInt) { + constexpr MaterialAttributeType vectorType[4] { + MaterialAttributeType::UnsignedInt, MaterialAttributeType::Vector2ui, + MaterialAttributeType::Vector3ui, MaterialAttributeType::Vector4ui + }; + type = vectorType[count - 1]; + + Vector4ui& data = *reinterpret_cast(attributeData); + for(UnsignedInt i = 0; i != count; ++i) + data[i] = cgltf_json_to_int(&tokens[start + i], reinterpret_cast(json.data())); + + } else if(type == MaterialAttributeType::Bool) { + bool& data = *reinterpret_cast(attributeData); + data = cgltf_json_to_bool(&tokens[start], reinterpret_cast(json.data())); } else - Warning{} << "Trade::CgltfImporter::material(): property" << name << "has unsupported type, skipping"; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } else if(token.type == JSMN_STRING) { attributeView = tokenString(json, token); @@ -2971,14 +2977,10 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } - /* Ignored and warning printed, or attributes already added - (texture object). Nothing left to do. */ - if(type == MaterialAttributeType{}) - continue; + CORRADE_INTERNAL_ASSERT(type != MaterialAttributeType{}); const void* valuePointer = type == MaterialAttributeType::String ? static_cast(&attributeView) : static_cast(attributeData); - if(!checkMaterialAttributeSize(name, type, valuePointer)) continue; From d846569dc3f1b1e10301e25e8f94b70b06a52cd2 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Wed, 22 Dec 2021 13:17:04 +0100 Subject: [PATCH 24/35] CgltfImporter: don't mark custom material layers as handled required extensions --- src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp | 6 ------ src/MagnumPlugins/CgltfImporter/Test/material-ior.gltf | 3 --- src/MagnumPlugins/CgltfImporter/Test/material-sheen.gltf | 3 --- src/MagnumPlugins/CgltfImporter/Test/material-specular.gltf | 3 --- .../CgltfImporter/Test/material-transmission.gltf | 3 --- src/MagnumPlugins/CgltfImporter/Test/material-volume.gltf | 3 --- 6 files changed, 21 deletions(-) diff --git a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp index f932b74d6..b66a8aa24 100644 --- a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp +++ b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp @@ -646,12 +646,6 @@ void CgltfImporter::doOpenData(Containers::Array&& data, const DataFlags d "KHR_mesh_quantization"_s, "KHR_texture_basisu"_s, "KHR_texture_transform"_s, - /* Parsed by cgltf and imported as custom material data */ - "KHR_materials_ior"_s, - "KHR_materials_sheen"_s, - "KHR_materials_specular"_s, - "KHR_materials_transmission"_s, - "KHR_materials_volume"_s, /* Manually parsed */ "GOOGLE_texture_basis"_s, "MSFT_texture_dds"_s diff --git a/src/MagnumPlugins/CgltfImporter/Test/material-ior.gltf b/src/MagnumPlugins/CgltfImporter/Test/material-ior.gltf index 2cc447b75..8309d1e04 100644 --- a/src/MagnumPlugins/CgltfImporter/Test/material-ior.gltf +++ b/src/MagnumPlugins/CgltfImporter/Test/material-ior.gltf @@ -20,8 +20,5 @@ ], "extensionsUsed": [ "KHR_materials_ior" - ], - "extensionsRequired": [ - "KHR_materials_ior" ] } diff --git a/src/MagnumPlugins/CgltfImporter/Test/material-sheen.gltf b/src/MagnumPlugins/CgltfImporter/Test/material-sheen.gltf index 0baec1537..7cc48aa7a 100644 --- a/src/MagnumPlugins/CgltfImporter/Test/material-sheen.gltf +++ b/src/MagnumPlugins/CgltfImporter/Test/material-sheen.gltf @@ -103,8 +103,5 @@ "extensionsUsed": [ "KHR_materials_sheen", "KHR_texture_transform" - ], - "extensionsRequired": [ - "KHR_materials_sheen" ] } diff --git a/src/MagnumPlugins/CgltfImporter/Test/material-specular.gltf b/src/MagnumPlugins/CgltfImporter/Test/material-specular.gltf index faef37a7c..246d7f891 100644 --- a/src/MagnumPlugins/CgltfImporter/Test/material-specular.gltf +++ b/src/MagnumPlugins/CgltfImporter/Test/material-specular.gltf @@ -103,8 +103,5 @@ "extensionsUsed": [ "KHR_materials_specular", "KHR_texture_transform" - ], - "extensionsRequired": [ - "KHR_materials_specular" ] } diff --git a/src/MagnumPlugins/CgltfImporter/Test/material-transmission.gltf b/src/MagnumPlugins/CgltfImporter/Test/material-transmission.gltf index 078bbffcd..107955afd 100644 --- a/src/MagnumPlugins/CgltfImporter/Test/material-transmission.gltf +++ b/src/MagnumPlugins/CgltfImporter/Test/material-transmission.gltf @@ -80,8 +80,5 @@ "extensionsUsed": [ "KHR_materials_transmission", "KHR_texture_transform" - ], - "extensionsRequired": [ - "KHR_materials_transmission" ] } diff --git a/src/MagnumPlugins/CgltfImporter/Test/material-volume.gltf b/src/MagnumPlugins/CgltfImporter/Test/material-volume.gltf index e818de0f4..ae182545d 100644 --- a/src/MagnumPlugins/CgltfImporter/Test/material-volume.gltf +++ b/src/MagnumPlugins/CgltfImporter/Test/material-volume.gltf @@ -84,8 +84,5 @@ "extensionsUsed": [ "KHR_materials_volume", "KHR_texture_transform" - ], - "extensionsRequired": [ - "KHR_materials_volume" ] } From 70dc96f96f26b915c089f18521b486d8382867e2 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Wed, 22 Dec 2021 13:21:17 +0100 Subject: [PATCH 25/35] CgltfImporter: use MAGNUM vendor extension prefix This is the preferred name, in case it gets added as an approved glTF vendor prefix in the future --- .../CgltfImporter/Test/CgltfImporterTest.cpp | 8 ++++---- .../CgltfImporter/Test/material-raw.gltf | 16 ++++++++-------- .../Test/texture-extensions-invalid.gltf | 4 ++-- .../CgltfImporter/Test/texture-extensions.gltf | 6 +++--- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp b/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp index 546729138..8198270f5 100644 --- a/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp +++ b/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp @@ -4008,7 +4008,7 @@ void CgltfImporterTest::materialRaw() { /* Unknown extension with a textureInfo object */ } { - constexpr Containers::StringView layer = "#MGNM_material_snake"_s; + constexpr Containers::StringView layer = "#MAGNUM_material_snake"_s; CORRADE_ITERATION(layer); CORRADE_VERIFY(material->hasLayer(layer)); CORRADE_COMPARE(material->attributeCount(layer), 6 + 1); @@ -4054,7 +4054,7 @@ void CgltfImporterTest::materialRaw() { /* Unknown extension with all other supported types */ } { - constexpr Containers::StringView layer = "#MGNM_material_type_zoo"_s; + constexpr Containers::StringView layer = "#MAGNUM_material_type_zoo"_s; CORRADE_ITERATION(layer); CORRADE_VERIFY(material->hasLayer(layer)); CORRADE_COMPARE(material->attributeCount(layer), 21 + 1); @@ -4167,9 +4167,9 @@ void CgltfImporterTest::materialRaw() { } } - /* No layer for MGNM_material_forbidden_types because all attributes are + /* No layer for MAGNUM_material_forbidden_types because all attributes are invalid types or too large and skipped */ - CORRADE_VERIFY(!material->hasLayer("#MGNM_material_forbidden_types"_s)); + CORRADE_VERIFY(!material->hasLayer("#MAGNUM_material_forbidden_types"_s)); /* But it produces all kinds of warnings. Attributes are sorted by name. */ CORRADE_COMPARE(out.str(), diff --git a/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf b/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf index db0f227e3..5602460be 100644 --- a/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf +++ b/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf @@ -14,7 +14,7 @@ { "name": "raw out-of-bounds", "extensions": { - "MGNM_material_snake": { + "MAGNUM_material_snake": { "snakeTexture": { "index": 2 } @@ -31,7 +31,7 @@ }, "doubleSided": true, "extensions": { - "MGNM_material_snake": { + "MAGNUM_material_snake": { "note": "should be overwritten by later duplicate extension", "snakeFactor": 3.3, "thisWillDisappear": true @@ -40,7 +40,7 @@ "note": "should be imported as a predefined layer", "clearcoatFactor": 0.5 }, - "MGNM_material_snake": { + "MAGNUM_material_snake": { "snakeFactor": 6.6, "snakeTexture": { "index": 1, @@ -59,7 +59,7 @@ "scale": 1.0 } }, - "MGNM_material_type_zoo": { + "MAGNUM_material_type_zoo": { "duplicate": "overwritten by later values", "boolTrue": true, "boolFalse": false, @@ -86,7 +86,7 @@ "duplicate": 0.2, "duplicate": true }, - "MGNM_material_forbidden_types": { + "MAGNUM_material_forbidden_types": { "null": null, "emptyArray": [], "oversizedArray": [9.1, 8.2, 7.3, 6.4, 5.5], @@ -147,9 +147,9 @@ ], "extensionsUsed": [ "KHR_materials_clearcoat", - "MGNM_material_snake", - "MGNM_material_type_zoo", - "MGNM_material_forbidden_types", + "MAGNUM_material_snake", + "MAGNUM_material_type_zoo", + "MAGNUM_material_forbidden_types", "VENDOR_empty_extension_object", "VENDOR_material_thisnameiswaytoolongforalayername!", "", diff --git a/src/MagnumPlugins/CgltfImporter/Test/texture-extensions-invalid.gltf b/src/MagnumPlugins/CgltfImporter/Test/texture-extensions-invalid.gltf index 4254b9f18..07fbc9723 100644 --- a/src/MagnumPlugins/CgltfImporter/Test/texture-extensions-invalid.gltf +++ b/src/MagnumPlugins/CgltfImporter/Test/texture-extensions-invalid.gltf @@ -31,14 +31,14 @@ { "name": "unknown extension, no fallback", "extensions": { - "MGNM_fake_extension": { + "MAGNUM_fake_extension": { "source": 2 } } } ], "extensionsUsed": [ - "MGNM_fake_extension", + "MAGNUM_fake_extension", "GOOGLE_texture_basis" ] } diff --git a/src/MagnumPlugins/CgltfImporter/Test/texture-extensions.gltf b/src/MagnumPlugins/CgltfImporter/Test/texture-extensions.gltf index 81addd92d..35cb80fae 100644 --- a/src/MagnumPlugins/CgltfImporter/Test/texture-extensions.gltf +++ b/src/MagnumPlugins/CgltfImporter/Test/texture-extensions.gltf @@ -85,7 +85,7 @@ "name": "unknown extension", "source": 0, "extensions": { - "MGNM_fake_extension": { + "MAGNUM_fake_extension": { "source": 4 } } @@ -94,7 +94,7 @@ "name": "GOOGLE_texture_basis and unknown", "source": 0, "extensions": { - "MGNM_fake_extension": { + "MAGNUM_fake_extension": { "source": 4 }, "GOOGLE_texture_basis": { @@ -107,6 +107,6 @@ "GOOGLE_texture_basis", "KHR_texture_basisu", "MSFT_texture_dds", - "MGNM_fake_extension" + "MAGNUM_fake_extension" ] } From f54513de2b83e9f992fb02be27196a5160b7635a Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Wed, 22 Dec 2021 13:26:21 +0100 Subject: [PATCH 26/35] CgltfImporter: parse all custom material number primitives as float Because consistently detecting if a number is int, unsigned int or float is impossible. Integer attributes may use exponent notation and decimal points followed by a 0, leaving us at the mercy of the glTF exporter used for the asset. Note that integer attributes in texture objects (texture index, coordinate set) are unaffected because we know the expected type. --- .../CgltfImporter/CgltfImporter.cpp | 127 ++++++------------ .../CgltfImporter/Test/CgltfImporterTest.cpp | 56 ++------ .../CgltfImporter/Test/material-raw.gltf | 16 +-- 3 files changed, 53 insertions(+), 146 deletions(-) diff --git a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp index b66a8aa24..67829ce14 100644 --- a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp +++ b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp @@ -84,6 +84,9 @@ namespace { #define CGLTF_FREE(ptr) freeNoop(ptr) /* If we had a good replacement for ato(i|f|ll) we could set the corresponding CGLTF_ATOI etc. here and prevent stdlib.h from being included in cgltf.h */ +/** @todo Override CGLTF_ATOI with a parsing function that handles integers + with exponent notation: + https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#json-encoding */ #define CGLTF_IMPLEMENTATION @@ -2553,7 +2556,10 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i /* Import extensions with non-standard layer/attribute types that are already parsed by cgltf and hence don't appear in the extension list anymore. We use the original attribute names as found in the extension - specifications. */ + specifications. To imitate actual unknown extension import below, + all Int and UnsignedInt attributes must be converted to Float. + Note that these custom layers will have to be duplicated for backwards + compatibility even if they're ever turned into standard layers. */ if(material.has_ior) { arrayAppend(layers, UnsignedInt(attributes.size())); arrayAppend(attributes, InPlaceInit, MaterialAttribute::LayerName, "KHR_materials_ior"_s); @@ -2769,12 +2775,12 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i ++tokenIndex; const jsmntok_t& token = tokens[tokenIndex]; - /* We only need temporary storage for parsing single primitives - (bool, Float, Int, UnsignedInt) or Vector2/3/4 of primitives. - Arrays of other types/sizes are ignored, so we know the upper - limit on the data size. If it's a string, MaterialAttributeData - expects a pointer to a StringView. */ - char attributeData[16]; + /* We only need temporary storage for parsing primitive (arrays) as + bool/Float/Vector[2/3/4]. Other types/sizes are either converted + or ignored, so we know the upper limit on the data size. The + alignas prevents unaligned reads for individual floats. For + strings, MaterialAttributeData expects a pointer to StringView. */ + char alignas(4) attributeData[16]; Containers::StringView attributeView; MaterialAttributeType type{}; if(token.type == JSMN_OBJECT) { @@ -2841,81 +2847,51 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i ignore non-primitive arrays, so we can handle both in one place. */ } else if(token.type == JSMN_PRIMITIVE || token.type == JSMN_ARRAY) { const UnsignedInt start = tokenIndex + UnsignedInt(token.type == JSMN_ARRAY); - /* Primitive token size is 0, but we can't use max() or we'd - allow empty arrays */ + /* Primitive token size is 0, but we can't use max() because + that would allow empty arrays */ const UnsignedInt count = token.type == JSMN_PRIMITIVE ? 1 : token.size; - /* Using a lambda here to make aborting early easier */ - auto attributeType = [&]() -> MaterialAttributeType { - /* No use importing arbitrarily-sized arrays of primitives, - those are currently not used in any glTF extension */ - if(count < 1 || count > 4) return MaterialAttributeType{}; - - MaterialAttributeType finalType{}; + /* No use importing arbitrarily-sized arrays of primitives, + those are currently not used in any glTF extension */ + if(count >= 1 && count <= 4) { /* This still works for non-primitive array members (like objects or nested arrays), because we instantly abort when we find one of those, so we never need more than four tokens */ for(const jsmntok_t& element: tokens.slice(start, start + count)) { - if(element.type != JSMN_PRIMITIVE) - return MaterialAttributeType{}; - - const Containers::StringView value = tokenString(json, element); + if(element.type != JSMN_PRIMITIVE) { + type = MaterialAttributeType{}; + break; + } /* Jsmn only checks the first character, which allows - some invalid values like nnn. We perform some basic - type detection and invalid values result in 0. This - matches cgltf behaviour. - Technically integers can use exponent notation, too: - https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#json-encoding - But cgltf_json_to_int() doesn't parse them correctly - and they should be incredibly rare in the wild, so - we always interpret them as float. */ - /** @todo Override CGLTF_ATOI with a parsing function - that handles integers with exponent notation */ - constexpr Containers::StringView FloatIndicators = ".eE"_s; - MaterialAttributeType type{}; + some invalid values like nnn. We perform basic type + detection and invalid values result in 0. This + matches cgltf behaviour. */ + const Containers::StringView value = tokenString(json, element); if(value == "true"_s || value == "false"_s) { /* MaterialAttributeType has no bool vectors, and converting to a number needlessly complicates parsing later. So far there is no glTF extension that uses bool vectors. */ - if(count > 1) - return MaterialAttributeType{}; + if(count > 1) { + type = MaterialAttributeType{}; + break; + } type = MaterialAttributeType::Bool; - - } else if(std::find_first_of(value.begin(), value.end(), FloatIndicators.begin(), FloatIndicators.end()) != value.end()) { - type = MaterialAttributeType::Float; - } else if(value.hasPrefix("-"_s)) { - type = MaterialAttributeType::Int; } else if(value != "null"_s) { - type = MaterialAttributeType::UnsignedInt; - } else - return MaterialAttributeType{}; - - if(finalType == MaterialAttributeType{}) - finalType = type; - - /* Upgrade mixed primitive types to a more generic type */ - if(finalType != type) { - if(type == MaterialAttributeType::Float || finalType == MaterialAttributeType::Float) { - finalType = MaterialAttributeType::Float; - } else if(type == MaterialAttributeType::Int || finalType == MaterialAttributeType::Int) { - finalType = MaterialAttributeType::Int; - } else { - /* Only UnsignedInt left, but both types would - be identical. Can't be Bool because we - abort for bool arrays with size > 1. */ - CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ - } + /* Always interpret numbers as floats because the + type can be ambiguous. E.g. integer attributes + may use exponent notation and decimal points, + making correct type detection depend on glTF + exporter behaviour. */ + type = MaterialAttributeType::Float; + } else { + type = MaterialAttributeType{}; + break; } } - - CORRADE_INTERNAL_ASSERT(finalType != MaterialAttributeType{}); - return finalType; - }; - - type = attributeType(); + } if(type == MaterialAttributeType{}) { Warning{} << "Trade::CgltfImporter::material(): property" << name << "has unsupported type, skipping"; @@ -2933,29 +2909,8 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i for(UnsignedInt i = 0; i != count; ++i) data[i] = cgltf_json_to_float(&tokens[start + i], reinterpret_cast(json.data())); - } else if(type == MaterialAttributeType::Int) { - constexpr MaterialAttributeType vectorType[4] { - MaterialAttributeType::Int, MaterialAttributeType::Vector2i, - MaterialAttributeType::Vector3i, MaterialAttributeType::Vector4i - }; - type = vectorType[count - 1]; - - Vector4i& data = *reinterpret_cast(attributeData); - for(UnsignedInt i = 0; i != count; ++i) - data[i] = cgltf_json_to_int(&tokens[start + i], reinterpret_cast(json.data())); - - } else if(type == MaterialAttributeType::UnsignedInt) { - constexpr MaterialAttributeType vectorType[4] { - MaterialAttributeType::UnsignedInt, MaterialAttributeType::Vector2ui, - MaterialAttributeType::Vector3ui, MaterialAttributeType::Vector4ui - }; - type = vectorType[count - 1]; - - Vector4ui& data = *reinterpret_cast(attributeData); - for(UnsignedInt i = 0; i != count; ++i) - data[i] = cgltf_json_to_int(&tokens[start + i], reinterpret_cast(json.data())); - } else if(type == MaterialAttributeType::Bool) { + CORRADE_INTERNAL_ASSERT(count == 1); bool& data = *reinterpret_cast(attributeData); data = cgltf_json_to_bool(&tokens[start], reinterpret_cast(json.data())); diff --git a/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp b/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp index 8198270f5..be24775dc 100644 --- a/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp +++ b/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp @@ -4057,7 +4057,7 @@ void CgltfImporterTest::materialRaw() { constexpr Containers::StringView layer = "#MAGNUM_material_type_zoo"_s; CORRADE_ITERATION(layer); CORRADE_VERIFY(material->hasLayer(layer)); - CORRADE_COMPARE(material->attributeCount(layer), 21 + 1); + CORRADE_COMPARE(material->attributeCount(layer), 13 + 1); { constexpr Containers::StringView name = "boolTrue"_s; @@ -4072,13 +4072,13 @@ void CgltfImporterTest::materialRaw() { } { constexpr Containers::StringView name = "int"_s; CORRADE_VERIFY(material->hasAttribute(layer, name)); - CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Int); - CORRADE_COMPARE(material->attribute(layer, name), -7992835); + CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Float); + CORRADE_COMPARE(material->attribute(layer, name), -7992835.0f); } { constexpr Containers::StringView name = "unsignedInt"_s; CORRADE_VERIFY(material->hasAttribute(layer, name)); - CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::UnsignedInt); - CORRADE_COMPARE(material->attribute(layer, name), 109835761u); + CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Float); + CORRADE_COMPARE(material->attribute(layer, name), 109835761.0f); } { constexpr Containers::StringView name = "float"_s; CORRADE_VERIFY(material->hasAttribute(layer, name)); @@ -4108,57 +4108,17 @@ void CgltfImporterTest::materialRaw() { constexpr Containers::StringView name = "vec2"_s; CORRADE_VERIFY(material->hasAttribute(layer, name)); CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Vector2); - CORRADE_COMPARE(material->attribute(layer, name), (Vector2{-9.1f, 8.2f})); + CORRADE_COMPARE(material->attribute(layer, name), (Vector2{9.0f, 8.0f})); } { constexpr Containers::StringView name = "vec3"_s; CORRADE_VERIFY(material->hasAttribute(layer, name)); CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Vector3); - CORRADE_COMPARE(material->attribute(layer, name), (Vector3{9.0f, 0.8f, 7.3f})); + CORRADE_COMPARE(material->attribute(layer, name), (Vector3{9.0f, 0.08f, 7.3141f})); } { constexpr Containers::StringView name = "vec4"_s; CORRADE_VERIFY(material->hasAttribute(layer, name)); CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Vector4); - CORRADE_COMPARE(material->attribute(layer, name), (Vector4{-9.0f, 8.0f, 7.3f, -6.0f})); - } { - constexpr Containers::StringView name = "vec1i"_s; - CORRADE_VERIFY(material->hasAttribute(layer, name)); - CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Int); - CORRADE_COMPARE(material->attribute(layer, name), -9); - } { - constexpr Containers::StringView name = "vec2i"_s; - CORRADE_VERIFY(material->hasAttribute(layer, name)); - CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Vector2i); - CORRADE_COMPARE(material->attribute(layer, name), (Vector2i{9, -8})); - } { - constexpr Containers::StringView name = "vec3i"_s; - CORRADE_VERIFY(material->hasAttribute(layer, name)); - CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Vector3i); - CORRADE_COMPARE(material->attribute(layer, name), (Vector3i{-9, 8, 7})); - } { - constexpr Containers::StringView name = "vec4i"_s; - CORRADE_VERIFY(material->hasAttribute(layer, name)); - CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Vector4i); - CORRADE_COMPARE(material->attribute(layer, name), (Vector4i{-9, -8, -7, -6})); - } { - constexpr Containers::StringView name = "vec1ui"_s; - CORRADE_VERIFY(material->hasAttribute(layer, name)); - CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::UnsignedInt); - CORRADE_COMPARE(material->attribute(layer, name), 9u); - } { - constexpr Containers::StringView name = "vec2ui"_s; - CORRADE_VERIFY(material->hasAttribute(layer, name)); - CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Vector2ui); - CORRADE_COMPARE(material->attribute(layer, name), (Vector2ui{9u, 8u})); - } { - constexpr Containers::StringView name = "vec3ui"_s; - CORRADE_VERIFY(material->hasAttribute(layer, name)); - CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Vector3ui); - CORRADE_COMPARE(material->attribute(layer, name), (Vector3ui{9u, 8u, 7u})); - } { - constexpr Containers::StringView name = "vec4ui"_s; - CORRADE_VERIFY(material->hasAttribute(layer, name)); - CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Vector4ui); - CORRADE_COMPARE(material->attribute(layer, name), (Vector4ui{9u, 8u, 7u, 6u})); + CORRADE_COMPARE(material->attribute(layer, name), (Vector4{-9.0f, 8.0f, 7.0f, -6.0f})); } { constexpr Containers::StringView name = "duplicate"_s; CORRADE_VERIFY(material->hasAttribute(layer, name)); diff --git a/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf b/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf index 5602460be..925139248 100644 --- a/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf +++ b/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf @@ -71,18 +71,10 @@ "UppercaseName": true , "vec1": [9.12e+1], "vec2": [ - -9.1 , - 8.2 ], - "vec3": [9, 8E-1, 7.3], - "vec4": [-9, 8, 7.3, -6], - "vec1i": [-9], - "vec2i": [9, -8], - "vec3i": [-9, 8, 7], - "vec4i": [-9, -8, -7, -6], - "vec1ui": [9], - "vec2ui": [9, 8], - "vec3ui": [9, 8, 7], - "vec4ui": [9, 8, 7, 6], + 9 , + 8 ], + "vec3": [9, 8E-2, 7.3141], + "vec4": [-9, 8, 7, -6], "duplicate": 0.2, "duplicate": true }, From eb455121cd732c9e69e9b3dfa6428a85b47e46e5 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Wed, 22 Dec 2021 16:16:17 +0100 Subject: [PATCH 27/35] CgltfImporter: handle encoded strings in custom material data --- .../CgltfImporter/CgltfImporter.cpp | 122 ++++++++++-------- .../CgltfImporter/Test/CgltfImporterTest.cpp | 7 +- .../CgltfImporter/Test/material-raw.gltf | 1 + 3 files changed, 78 insertions(+), 52 deletions(-) diff --git a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp index 67829ce14..8c4424ea7 100644 --- a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp +++ b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp @@ -204,7 +204,8 @@ bool isDataUri(Containers::StringView uri) { } /* Decode percent-encoded characters in URIs: - https://datatracker.ietf.org/doc/html/rfc3986#section-2.1 */ + https://datatracker.ietf.org/doc/html/rfc3986#section-2.1 + This returns std::string because we only use it with Directory::join(). */ std::string decodeUri(Containers::StringView uri) { std::string decoded = uri; const std::size_t decodedSize = cgltf_decode_uri(&decoded[0]); @@ -213,6 +214,26 @@ std::string decodeUri(Containers::StringView uri) { return decoded; } +/* Cgltf's JSON parser jsmn doesn't decode escaped characters so we do it after + parsing. If there's nothing to escape, returns an empty Optional. */ +Containers::Optional decodeString(Containers::StringView str) { + /* The input string can be UTF-8 encoded but we can use a byte search here + since all multi-byte UTF-8 characters have the high bit set and '\\' + doesn't, so this will only match single-byte ASCII characters. */ + const Containers::StringView escape = str.find('\\'); + if(escape.isEmpty()) + return Containers::NullOpt; + + /* Skip any processing until the first escape character */ + const std::size_t start = escape.data() - str.data(); + + Containers::String decoded{str}; + const std::size_t decodedSize = cgltf_decode_string(decoded.data() + start) + start; + CORRADE_INTERNAL_ASSERT(decodedSize < str.size()); + + return Containers::String{decoded.prefix(decodedSize)}; +} + Containers::Array parseJson(Containers::StringView str) { jsmn_parser parser{0, 0, 0}; Int numTokens = jsmn_parse(&parser, str.data(), str.size(), nullptr, 0); @@ -261,19 +282,17 @@ struct CgltfImporter::Document { so the buffer id is used as the index. */ Containers::Array> bufferData; - /* Cgltf's JSON parser jsmn doesn't decode escaped characters so we do it - after parsing. If there's nothing to escape, returns a view on the - original string. Decoded strings are cached in a map indexed by the - input view data pointer. This works because we only call this function - with views on strings from cgltf_data. + /* Decode and cache strings in a map indexed by the input view data + pointer. This works because we only call this function with views on + strings from cgltf_data. Note that parsing inside cgltf happens with unescaped strings, but we have no influence on that. In practice, this shouldn't be a problem. Old versions of the spec used to explicitly forbid non-ASCII keys/enums: https://github.com/KhronosGroup/glTF/tree/fd3ab461a1114fb0250bd76099153d2af50a7a1d/specification/2.0#json-encoding Newer spec versions changed this to "ASCII characters [...] SHOULD be - written without JSON escaping" */ - Containers::StringView decodeString(Containers::StringView str); + written without JSON escaping". */ + Containers::StringView decodeCachedString(Containers::StringView str); std::unordered_map decodedStrings; @@ -378,7 +397,7 @@ Containers::Optional> CgltfImporter::Document: storage = Containers::Array{static_cast(decoded), size}; return Containers::arrayCast(storage); } else if(importer.fileCallback()) { - const std::string fullPath = Utility::Directory::join(filePath ? *filePath : "", decodeUri(decodeString(uri))); + const std::string fullPath = Utility::Directory::join(filePath ? *filePath : "", decodeUri(decodeCachedString(uri))); Containers::Optional> view = importer.fileCallback()(fullPath, InputFileCallbackPolicy::LoadPermanent, importer.fileCallbackUserData()); if(!view) { Error{} << "Trade::CgltfImporter::" << Debug::nospace << function << Debug::nospace << "(): error opening file:" << Containers::StringView{fullPath} << ": file callback failed"; @@ -390,7 +409,7 @@ Containers::Optional> CgltfImporter::Document: Error{} << "Trade::CgltfImporter::" << Debug::nospace << function << Debug::nospace << "(): external buffers can be imported only when opening files from the filesystem or if a file callback is present"; return Containers::NullOpt; } - const std::string fullPath = Utility::Directory::join(*filePath, decodeUri(decodeString(uri))); + const std::string fullPath = Utility::Directory::join(*filePath, decodeUri(decodeCachedString(uri))); if(!Utility::Directory::exists(fullPath)) { Error{} << "Trade::CgltfImporter::" << Debug::nospace << function << Debug::nospace << "(): error opening file:" << Containers::StringView{fullPath} << ": file not found"; return Containers::NullOpt; @@ -453,32 +472,26 @@ Containers::Optional> CgltfImporter:: {std::ptrdiff_t(accessor->stride), 1}}; } -Containers::StringView CgltfImporter::Document::decodeString(Containers::StringView str) { +Containers::StringView CgltfImporter::Document::decodeCachedString(Containers::StringView str) { if(str.isEmpty()) return str; - /* String has been decoded before */ + /* StringView constructed from nullptr doesn't have this flag, but it's + caught by isEmpty() above */ + CORRADE_INTERNAL_ASSERT(str.flags() >= Containers::StringViewFlag::NullTerminated); + + /* Return cached value if the string has been decoded before */ const auto found = decodedStrings.find(str.data()); if(found != decodedStrings.end()) return found->second; - /* The input string can be UTF-8 encoded but we can use a byte search here - since all multi-byte UTF-8 characters have the high bit set and '\\' - doesn't, so this will only match single-byte ASCII characters. */ - Containers::StringView escape = str.find('\\'); - /* No escaped sequence found. If the view is null-terminated (all strings - in cgltf_data should be), this stores a non-owning String. */ - if(escape.isEmpty()) + Containers::Optional decoded = decodeString(str); + /* Nothing to escape. This creates a non-owning String with a view on the + input data. */ + if(!decoded) return decodedStrings.emplace(str.data(), Containers::String::nullTerminatedView(str)).first->second; - /* Skip any processing until the first escape character */ - const std::size_t start = escape.data() - str.data(); - - Containers::String decoded{str}; - const std::size_t decodedSize = cgltf_decode_string(decoded.data() + start) + start; - CORRADE_INTERNAL_ASSERT(decodedSize < str.size()); - - return decodedStrings.emplace(str.data(), Containers::String{decoded.prefix(decodedSize)}).first->second; + return decodedStrings.emplace(str.data(), std::move(*decoded)).first->second; } namespace { @@ -805,7 +818,7 @@ Int CgltfImporter::doAnimationForName(const std::string& name) { _d->animationsForName.emplace(); _d->animationsForName->reserve(_d->data->animations_count); for(std::size_t i = 0; i != _d->data->animations_count; ++i) - _d->animationsForName->emplace(_d->decodeString(_d->data->animations[i].name), i); + _d->animationsForName->emplace(_d->decodeCachedString(_d->data->animations[i].name), i); } const auto found = _d->animationsForName->find(name); @@ -815,7 +828,7 @@ Int CgltfImporter::doAnimationForName(const std::string& name) { std::string CgltfImporter::doAnimationName(UnsignedInt id) { /* If the animations are merged, don't report any names */ if(configuration().value("mergeAnimationClips")) return {}; - return _d->decodeString(_d->data->animations[id].name); + return _d->decodeCachedString(_d->data->animations[id].name); } namespace { @@ -1166,7 +1179,7 @@ Int CgltfImporter::doCameraForName(const std::string& name) { _d->camerasForName.emplace(); _d->camerasForName->reserve(_d->data->cameras_count); for(std::size_t i = 0; i != _d->data->cameras_count; ++i) - _d->camerasForName->emplace(_d->decodeString(_d->data->cameras[i].name), i); + _d->camerasForName->emplace(_d->decodeCachedString(_d->data->cameras[i].name), i); } const auto found = _d->camerasForName->find(name); @@ -1174,7 +1187,7 @@ Int CgltfImporter::doCameraForName(const std::string& name) { } std::string CgltfImporter::doCameraName(const UnsignedInt id) { - return _d->decodeString(_d->data->cameras[id].name); + return _d->decodeCachedString(_d->data->cameras[id].name); } Containers::Optional CgltfImporter::doCamera(UnsignedInt id) { @@ -1217,7 +1230,7 @@ Int CgltfImporter::doLightForName(const std::string& name) { _d->lightsForName.emplace(); _d->lightsForName->reserve(_d->data->lights_count); for(std::size_t i = 0; i != _d->data->lights_count; ++i) - _d->lightsForName->emplace(_d->decodeString(_d->data->lights[i].name), i); + _d->lightsForName->emplace(_d->decodeCachedString(_d->data->lights[i].name), i); } const auto found = _d->lightsForName->find(name); @@ -1225,7 +1238,7 @@ Int CgltfImporter::doLightForName(const std::string& name) { } std::string CgltfImporter::doLightName(const UnsignedInt id) { - return _d->decodeString(_d->data->lights[id].name); + return _d->decodeCachedString(_d->data->lights[id].name); } Containers::Optional CgltfImporter::doLight(UnsignedInt id) { @@ -1305,7 +1318,7 @@ Int CgltfImporter::doSceneForName(const std::string& name) { _d->scenesForName.emplace(); _d->scenesForName->reserve(_d->data->scenes_count); for(std::size_t i = 0; i != _d->data->scenes_count; ++i) - _d->scenesForName->emplace(_d->decodeString(_d->data->scenes[i].name), i); + _d->scenesForName->emplace(_d->decodeCachedString(_d->data->scenes[i].name), i); } const auto found = _d->scenesForName->find(name); @@ -1313,7 +1326,7 @@ Int CgltfImporter::doSceneForName(const std::string& name) { } std::string CgltfImporter::doSceneName(const UnsignedInt id) { - return _d->decodeString(_d->data->scenes[id].name); + return _d->decodeCachedString(_d->data->scenes[id].name); } Containers::Optional CgltfImporter::doScene(UnsignedInt id) { @@ -1611,7 +1624,7 @@ Long CgltfImporter::doObjectForName(const std::string& name) { _d->nodesForName.emplace(); _d->nodesForName->reserve(_d->data->nodes_count); for(std::size_t i = 0; i != _d->data->nodes_count; ++i) { - _d->nodesForName->emplace(_d->decodeString(_d->data->nodes[i].name), i); + _d->nodesForName->emplace(_d->decodeCachedString(_d->data->nodes[i].name), i); } } @@ -1620,7 +1633,7 @@ Long CgltfImporter::doObjectForName(const std::string& name) { } std::string CgltfImporter::doObjectName(UnsignedLong id) { - return _d->decodeString(_d->data->nodes[id].name); + return _d->decodeCachedString(_d->data->nodes[id].name); } UnsignedInt CgltfImporter::doSkin3DCount() const { @@ -1632,7 +1645,7 @@ Int CgltfImporter::doSkin3DForName(const std::string& name) { _d->skinsForName.emplace(); _d->skinsForName->reserve(_d->data->skins_count); for(std::size_t i = 0; i != _d->data->skins_count; ++i) - _d->skinsForName->emplace(_d->decodeString(_d->data->skins[i].name), i); + _d->skinsForName->emplace(_d->decodeCachedString(_d->data->skins[i].name), i); } const auto found = _d->skinsForName->find(name); @@ -1640,7 +1653,7 @@ Int CgltfImporter::doSkin3DForName(const std::string& name) { } std::string CgltfImporter::doSkin3DName(const UnsignedInt id) { - return _d->decodeString(_d->data->skins[id].name); + return _d->decodeCachedString(_d->data->skins[id].name); } Containers::Optional CgltfImporter::doSkin3D(const UnsignedInt id) { @@ -1699,7 +1712,7 @@ Int CgltfImporter::doMeshForName(const std::string& name) { for(std::size_t i = 0; i != _d->data->meshes_count; ++i) { /* The mesh can be duplicated for as many primitives as it has, point to the first mesh in the duplicate sequence */ - _d->meshesForName->emplace(_d->decodeString(_d->data->meshes[i].name), _d->meshSizeOffsets[i]); + _d->meshesForName->emplace(_d->decodeCachedString(_d->data->meshes[i].name), _d->meshSizeOffsets[i]); } } @@ -1709,7 +1722,7 @@ Int CgltfImporter::doMeshForName(const std::string& name) { std::string CgltfImporter::doMeshName(const UnsignedInt id) { /* This returns the same name for all multi-primitive mesh duplicates */ - return _d->decodeString(_d->data->meshes[_d->meshMap[id].first()].name); + return _d->decodeCachedString(_d->data->meshes[_d->meshMap[id].first()].name); } Containers::Optional CgltfImporter::doMesh(const UnsignedInt id, UnsignedInt) { @@ -2181,7 +2194,7 @@ Int CgltfImporter::doMaterialForName(const std::string& name) { _d->materialsForName.emplace(); _d->materialsForName->reserve(_d->data->materials_count); for(std::size_t i = 0; i != _d->data->materials_count; ++i) - _d->materialsForName->emplace(_d->decodeString(_d->data->materials[i].name), i); + _d->materialsForName->emplace(_d->decodeCachedString(_d->data->materials[i].name), i); } const auto found = _d->materialsForName->find(name); @@ -2189,7 +2202,7 @@ Int CgltfImporter::doMaterialForName(const std::string& name) { } std::string CgltfImporter::doMaterialName(const UnsignedInt id) { - return _d->decodeString(_d->data->materials[id].name); + return _d->decodeCachedString(_d->data->materials[id].name); } namespace { @@ -2781,7 +2794,8 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i alignas prevents unaligned reads for individual floats. For strings, MaterialAttributeData expects a pointer to StringView. */ char alignas(4) attributeData[16]; - Containers::StringView attributeView; + Containers::String attributeString; + Containers::StringView attributeStringView; MaterialAttributeType type{}; if(token.type == JSMN_OBJECT) { /* Parse glTF textureInfo objects. Any objects without the @@ -2918,7 +2932,13 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } else if(token.type == JSMN_STRING) { - attributeView = tokenString(json, token); + const Containers::StringView value = tokenString(json, token); + Containers::Optional decoded = decodeString(value); + if(decoded) { + attributeString = std::move(*decoded); + attributeStringView = attributeString; + } else + attributeStringView = value; type = MaterialAttributeType::String; } else { @@ -2929,7 +2949,7 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i CORRADE_INTERNAL_ASSERT(type != MaterialAttributeType{}); const void* valuePointer = type == MaterialAttributeType::String ? - static_cast(&attributeView) : static_cast(attributeData); + static_cast(&attributeStringView) : static_cast(attributeData); if(!checkMaterialAttributeSize(name, type, valuePointer)) continue; @@ -2981,7 +3001,7 @@ Int CgltfImporter::doTextureForName(const std::string& name) { _d->texturesForName.emplace(); _d->texturesForName->reserve(_d->data->textures_count); for(std::size_t i = 0; i != _d->data->textures_count; ++i) - _d->texturesForName->emplace(_d->decodeString(_d->data->textures[i].name), i); + _d->texturesForName->emplace(_d->decodeCachedString(_d->data->textures[i].name), i); } const auto found = _d->texturesForName->find(name); @@ -2989,7 +3009,7 @@ Int CgltfImporter::doTextureForName(const std::string& name) { } std::string CgltfImporter::doTextureName(const UnsignedInt id) { - return _d->decodeString(_d->data->textures[id].name); + return _d->decodeCachedString(_d->data->textures[id].name); } Containers::Optional CgltfImporter::doTexture(const UnsignedInt id) { @@ -3182,7 +3202,7 @@ Int CgltfImporter::doImage2DForName(const std::string& name) { _d->imagesForName.emplace(); _d->imagesForName->reserve(_d->data->images_count); for(std::size_t i = 0; i != _d->data->images_count; ++i) - _d->imagesForName->emplace(_d->decodeString(_d->data->images[i].name), i); + _d->imagesForName->emplace(_d->decodeCachedString(_d->data->images[i].name), i); } const auto found = _d->imagesForName->find(name); @@ -3190,7 +3210,7 @@ Int CgltfImporter::doImage2DForName(const std::string& name) { } std::string CgltfImporter::doImage2DName(const UnsignedInt id) { - return _d->decodeString(_d->data->images[id].name); + return _d->decodeCachedString(_d->data->images[id].name); } AbstractImporter* CgltfImporter::setupOrReuseImporterForImage(const UnsignedInt id, const char* const function) { @@ -3249,7 +3269,7 @@ AbstractImporter* CgltfImporter::setupOrReuseImporterForImage(const UnsignedInt return nullptr; } - if(!importer.openFile(Utility::Directory::join(_d->filePath ? *_d->filePath : "", decodeUri(_d->decodeString(image.uri))))) + if(!importer.openFile(Utility::Directory::join(_d->filePath ? *_d->filePath : "", decodeUri(_d->decodeCachedString(image.uri))))) return nullptr; return &_d->imageImporter.emplace(std::move(importer)); } diff --git a/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp b/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp index be24775dc..55ab4735a 100644 --- a/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp +++ b/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp @@ -4057,7 +4057,7 @@ void CgltfImporterTest::materialRaw() { constexpr Containers::StringView layer = "#MAGNUM_material_type_zoo"_s; CORRADE_ITERATION(layer); CORRADE_VERIFY(material->hasLayer(layer)); - CORRADE_COMPARE(material->attributeCount(layer), 13 + 1); + CORRADE_COMPARE(material->attributeCount(layer), 14 + 1); { constexpr Containers::StringView name = "boolTrue"_s; @@ -4089,6 +4089,11 @@ void CgltfImporterTest::materialRaw() { CORRADE_VERIFY(material->hasAttribute(layer, name)); CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::String); CORRADE_COMPARE(material->attribute(layer, name), "Ribbit -- ribbit"_s); + } { + constexpr Containers::StringView name = "encodedString"_s; + CORRADE_VERIFY(material->hasAttribute(layer, name)); + CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::String); + CORRADE_COMPARE(material->attribute(layer, name), "마이크 체크"_s); } { constexpr Containers::StringView name = "emptyString"_s; CORRADE_VERIFY(material->hasAttribute(layer, name)); diff --git a/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf b/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf index 925139248..cef609dfb 100644 --- a/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf +++ b/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf @@ -67,6 +67,7 @@ "unsignedInt": 109835761, "float": 4.321, "string": "Ribbit -- ribbit", + "encodedString": "\uB9C8\uC774\uD06C \uCCB4\uD06C", "emptyString": "", "UppercaseName": true , "vec1": [9.12e+1], From e757bd5ef545ecac8b44c849716c0d8d2c7efd77 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Wed, 22 Dec 2021 16:17:33 +0100 Subject: [PATCH 28/35] AssimpImporter: directly compare output against empty string For better diagnostics, this prints the string if it's not empty --- .../AssimpImporter/Test/AssimpImporterTest.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/MagnumPlugins/AssimpImporter/Test/AssimpImporterTest.cpp b/src/MagnumPlugins/AssimpImporter/Test/AssimpImporterTest.cpp index 62a9f7157..a20e8c0e9 100644 --- a/src/MagnumPlugins/AssimpImporter/Test/AssimpImporterTest.cpp +++ b/src/MagnumPlugins/AssimpImporter/Test/AssimpImporterTest.cpp @@ -892,7 +892,7 @@ void AssimpImporterTest::animationGltfTicksPerSecondPatching() { CORRADE_VERIFY(Containers::StringView{out.str()}.contains( " ticks per second is incorrect for glTF, patching to 1000\n")); } else - CORRADE_VERIFY(out.str().empty()); + CORRADE_COMPARE(out.str(), ""); } void AssimpImporterTest::animationFbxTicksPerSecondPatching() { @@ -918,7 +918,7 @@ void AssimpImporterTest::animationFbxTicksPerSecondPatching() { CORRADE_VERIFY(Containers::StringView{out.str()}.contains( " ticks per second is incorrect for FBX, patching to 1000\n")); } else - CORRADE_VERIFY(out.str().empty()); + CORRADE_COMPARE(out.str(), ""); } void AssimpImporterTest::animationDummyTracksRemovalEnabled() { @@ -978,7 +978,7 @@ void AssimpImporterTest::animationDummyTracksRemovalEnabled() { "Trade::AssimpImporter::animation(): ignoring dummy rotation track in animation 1, channel {}\n", targets[2].channel, targets[2].channel))); } else - CORRADE_VERIFY(out.str().empty()); + CORRADE_COMPARE(out.str(), ""); } void AssimpImporterTest::animationDummyTracksRemovalDisabled() { @@ -2011,7 +2011,7 @@ void AssimpImporterTest::materialWhiteAmbientTexture() { CORRADE_COMPARE(importer->textureCount(), 1); CORRADE_VERIFY(material->hasAttribute(MaterialAttribute::AmbientTexture)); /* It shouldn't be complaining about white ambient in this case */ - CORRADE_VERIFY(out.str().empty()); + CORRADE_COMPARE(out.str(), ""); } void AssimpImporterTest::materialMultipleTextures() { @@ -2297,7 +2297,7 @@ void AssimpImporterTest::materialRaw() { if(_assimpVersion < 500) { CORRADE_WARN("This version of Assimp doesn't import raw FBX material properties."); - CORRADE_VERIFY(out.str().empty()); + CORRADE_COMPARE(out.str(), ""); } else { /* Raw attributes taken directly from the FBX file, prefixed with "$raw.". Seems to be the only importer that supports that. */ @@ -2745,7 +2745,7 @@ void AssimpImporterTest::meshSkinningAttributes() { mesh = importer->mesh(meshName); } /* No warning about glTF dropping sets of weights */ - CORRADE_VERIFY(out.str().empty()); + CORRADE_COMPARE(out.str(), ""); CORRADE_VERIFY(mesh); CORRADE_VERIFY(mesh->hasAttribute(jointsAttribute)); From c4633de6e8d400f8f4155b5afb806e421ae45ed7 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Wed, 22 Dec 2021 19:18:06 +0100 Subject: [PATCH 29/35] CgltfImporter: test materials more concisely Ideally, this would become DebugTools::CompareMaterial in the future. Only doing this for custom materials because base material tests aren't as verbose thanks to convenenience accessors. --- .../CgltfImporter/Test/CgltfImporterTest.cpp | 1015 +++++------------ 1 file changed, 313 insertions(+), 702 deletions(-) diff --git a/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp b/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp index 55ab4735a..aa2e38dc3 100644 --- a/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp +++ b/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -3491,7 +3492,8 @@ void CgltfImporterTest::materialPbrSpecularGlossiness() { CORRADE_COMPARE(pbr.diffuseTexture(), 0); CORRADE_COMPARE(pbr.specularColor(), (Color4{0.4f, 0.5f, 0.6f, 0.0f})); CORRADE_VERIFY(pbr.hasSpecularGlossinessTexture()); - CORRADE_COMPARE(pbr.specularTexture(), 1); CORRADE_COMPARE(pbr.glossiness(), 0.9f); + CORRADE_COMPARE(pbr.specularTexture(), 1); + CORRADE_COMPARE(pbr.glossiness(), 0.9f); } { const char* name = "identity texture transform"; auto material = importer->material(name); @@ -3945,6 +3947,43 @@ void CgltfImporterTest::materialPhongFallback() { } } +void compareMaterials(const MaterialData& actual, const MaterialData& expected) { + CORRADE_COMPARE(actual.types(), expected.types()); + CORRADE_COMPARE(actual.layerCount(), expected.layerCount()); + + for(UnsignedInt layer = 0; layer != expected.layerCount(); ++layer) { + CORRADE_ITERATION(expected.layerName(layer)); + CORRADE_COMPARE(actual.layerName(layer), expected.layerName(layer)); + CORRADE_COMPARE(actual.attributeCount(layer), expected.attributeCount(layer)); + for(UnsignedInt i = 0; i != expected.attributeCount(layer); ++i) { + const Containers::StringView name = expected.attributeName(layer, i); + CORRADE_ITERATION(name); + CORRADE_VERIFY(actual.hasAttribute(layer, name)); + const MaterialAttributeType type = expected.attributeType(layer, name); + CORRADE_COMPARE(actual.attributeType(layer, name), type); + switch(type) { + #define _v(type, valueType) case MaterialAttributeType::type: \ + CORRADE_COMPARE(actual.attribute(layer, name), expected.attribute(layer, name)); \ + break; + #define _c(type) _v(type, type) + _c(UnsignedInt) + _c(Float) + _c(Vector2) + _c(Vector3) + _c(Vector4) + _c(Matrix3x3) + _v(Bool, bool) + _v(String, Containers::StringView) + _v(TextureSwizzle, MaterialTextureSwizzle) + #undef _c + #undef _v + default: + CORRADE_FAIL_IF(true, "Unexpected attribute type" << type); + } + } + } +} + void CgltfImporterTest::materialRaw() { Containers::Pointer importer = _manager.instantiate("CgltfImporter"); importer->configuration().setValue("phongMaterialFallback", false); @@ -3957,186 +3996,59 @@ void CgltfImporterTest::materialRaw() { { Warning redirectWarning{&out}; material = importer->material("raw"); + CORRADE_VERIFY(material); } - CORRADE_VERIFY(material); - /* Standard layer + clear coat + 2 unknown extensions */ - CORRADE_COMPARE(material->types(), MaterialType::PbrMetallicRoughness|MaterialType::PbrClearCoat); - CORRADE_COMPARE(material->layerCount(), 2 + 2); - - /* Standard layer import still works */ - { - CORRADE_ITERATION("base layer"); - CORRADE_COMPARE(material->attributeCount(0), 3); - - { - constexpr MaterialAttribute name = MaterialAttribute::BaseColor; - CORRADE_VERIFY(material->hasAttribute(0, name)); - CORRADE_COMPARE(material->attributeType(0, name), MaterialAttributeType::Vector4); - CORRADE_COMPARE(material->attribute(0, name), (Color4{0.8f, 0.2f, 0.4f, 0.3f})); - } { - constexpr MaterialAttribute name = MaterialAttribute::BaseColorTexture; - CORRADE_VERIFY(material->hasAttribute(0, name)); - CORRADE_COMPARE(material->attributeType(0, name), MaterialAttributeType::UnsignedInt); - CORRADE_COMPARE(material->attribute(0, name), 0u); - } { - constexpr MaterialAttribute name = MaterialAttribute::DoubleSided; - CORRADE_VERIFY(material->hasAttribute(0, name)); - CORRADE_COMPARE(material->attributeType(0, name), MaterialAttributeType::Bool); - CORRADE_COMPARE(material->attribute(0, name), true); - } - - /* Known extension layer import still works */ - } { - constexpr MaterialLayer layer = MaterialLayer::ClearCoat; - CORRADE_ITERATION(layer); - CORRADE_VERIFY(material->hasLayer(layer)); - /* +1 for the layer name */ - CORRADE_COMPARE(material->attributeCount(layer), 2 + 1); - - { - constexpr MaterialAttribute name = MaterialAttribute::LayerFactor; - CORRADE_VERIFY(material->hasAttribute(layer, name)); - CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Float); - CORRADE_COMPARE(material->attribute(layer, name), 0.5f); - } { - constexpr MaterialAttribute name = MaterialAttribute::Roughness; - CORRADE_VERIFY(material->hasAttribute(layer, name)); - CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Float); - CORRADE_COMPARE(material->attribute(layer, name), 0.0f); - } - - /* Unknown extension with a textureInfo object */ - } { - constexpr Containers::StringView layer = "#MAGNUM_material_snake"_s; - CORRADE_ITERATION(layer); - CORRADE_VERIFY(material->hasLayer(layer)); - CORRADE_COMPARE(material->attributeCount(layer), 6 + 1); - - { - constexpr Containers::StringView name = "snakeFactor"_s; - CORRADE_VERIFY(material->hasAttribute(layer, name)); - CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Float); - CORRADE_COMPARE(material->attribute(layer, name), 6.6f); - } { - constexpr Containers::StringView name = "snakeTexture"_s; - CORRADE_VERIFY(material->hasAttribute(layer, name)); - CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::UnsignedInt); - CORRADE_COMPARE(material->attribute(layer, name), 1u); - } { - constexpr Containers::StringView name = "snakeTextureMatrix"_s; - CORRADE_VERIFY(material->hasAttribute(layer, name)); - CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Matrix3x3); - CORRADE_COMPARE(material->attribute(layer, name), (Matrix3{ - {0.33f, 0.0f, 0.0f}, - {0.0f, 0.44f, 0.0f}, - {0.5f, 1.06f, 1.0f} - })); - } { - constexpr Containers::StringView name = "snakeTextureCoordinates"_s; - CORRADE_VERIFY(material->hasAttribute(layer, name)); - CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::UnsignedInt); - CORRADE_COMPARE(material->attribute(layer, name), 3u); - } { - constexpr Containers::StringView name = "snakeTextureScale"_s; - CORRADE_VERIFY(material->hasAttribute(layer, name)); - CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Float); - CORRADE_COMPARE(material->attribute(layer, name), 0.2f); - } { - constexpr Containers::StringView name = "defaultScaleTexture"_s; - CORRADE_VERIFY(material->hasAttribute(layer, name)); - CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::UnsignedInt); - CORRADE_COMPARE(material->attribute(layer, name), 1u); - } - + const MaterialData expected{MaterialType::PbrMetallicRoughness|MaterialType::PbrClearCoat, { + /* Standard layer import still works */ + {MaterialAttribute::BaseColor, Color4{0.8f, 0.2f, 0.4f, 0.3f}}, + {MaterialAttribute::BaseColorTexture, 0u}, + {MaterialAttribute::DoubleSided, true}, + + /* Known extension layer import still works */ + {Trade::MaterialAttribute::LayerName, "ClearCoat"_s}, + {MaterialAttribute::LayerFactor, 0.5f}, + {MaterialAttribute::Roughness, 0.0f}, + + /* Unknown extension with a textureInfo object */ + {Trade::MaterialAttribute::LayerName, "#MAGNUM_material_snake"_s}, + {"snakeFactor"_s, 6.6f}, + {"snakeTexture"_s, 1u}, + {"snakeTextureMatrix"_s, Matrix3{ + {0.33f, 0.0f, 0.0f}, + {0.0f, 0.44f, 0.0f}, + {0.5f, 1.06f, 1.0f} + }}, + {"snakeTextureCoordinates"_s, 3u}, + {"snakeTextureScale"_s, 0.2f}, + {"defaultScaleTexture"_s, 1u}, /* No defaultScaleTextureScale because scale is 1.0 */ - CORRADE_VERIFY(!material->hasAttribute(layer, "defaultScaleTextureScale"_s)); - - /* Unknown extension with all other supported types */ - } { - constexpr Containers::StringView layer = "#MAGNUM_material_type_zoo"_s; - CORRADE_ITERATION(layer); - CORRADE_VERIFY(material->hasLayer(layer)); - CORRADE_COMPARE(material->attributeCount(layer), 14 + 1); - - { - constexpr Containers::StringView name = "boolTrue"_s; - CORRADE_VERIFY(material->hasAttribute(layer, name)); - CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Bool); - CORRADE_COMPARE(material->attribute(layer, name), true); - } { - constexpr Containers::StringView name = "boolFalse"_s; - CORRADE_VERIFY(material->hasAttribute(layer, name)); - CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Bool); - CORRADE_COMPARE(material->attribute(layer, name), false); - } { - constexpr Containers::StringView name = "int"_s; - CORRADE_VERIFY(material->hasAttribute(layer, name)); - CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Float); - CORRADE_COMPARE(material->attribute(layer, name), -7992835.0f); - } { - constexpr Containers::StringView name = "unsignedInt"_s; - CORRADE_VERIFY(material->hasAttribute(layer, name)); - CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Float); - CORRADE_COMPARE(material->attribute(layer, name), 109835761.0f); - } { - constexpr Containers::StringView name = "float"_s; - CORRADE_VERIFY(material->hasAttribute(layer, name)); - CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Float); - CORRADE_COMPARE(material->attribute(layer, name), 4.321f); - } { - constexpr Containers::StringView name = "string"_s; - CORRADE_VERIFY(material->hasAttribute(layer, name)); - CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::String); - CORRADE_COMPARE(material->attribute(layer, name), "Ribbit -- ribbit"_s); - } { - constexpr Containers::StringView name = "encodedString"_s; - CORRADE_VERIFY(material->hasAttribute(layer, name)); - CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::String); - CORRADE_COMPARE(material->attribute(layer, name), "마이크 체크"_s); - } { - constexpr Containers::StringView name = "emptyString"_s; - CORRADE_VERIFY(material->hasAttribute(layer, name)); - CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::String); - CORRADE_COMPARE(material->attribute(layer, name), ""_s); - } { - constexpr Containers::StringView name = "uppercaseName"_s; - CORRADE_VERIFY(material->hasAttribute(layer, name)); - CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Bool); - CORRADE_COMPARE(material->attribute(layer, name), true); - } { - constexpr Containers::StringView name = "vec1"_s; - CORRADE_VERIFY(material->hasAttribute(layer, name)); - CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Float); - CORRADE_COMPARE(material->attribute(layer, name), 91.2f); - } { - constexpr Containers::StringView name = "vec2"_s; - CORRADE_VERIFY(material->hasAttribute(layer, name)); - CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Vector2); - CORRADE_COMPARE(material->attribute(layer, name), (Vector2{9.0f, 8.0f})); - } { - constexpr Containers::StringView name = "vec3"_s; - CORRADE_VERIFY(material->hasAttribute(layer, name)); - CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Vector3); - CORRADE_COMPARE(material->attribute(layer, name), (Vector3{9.0f, 0.08f, 7.3141f})); - } { - constexpr Containers::StringView name = "vec4"_s; - CORRADE_VERIFY(material->hasAttribute(layer, name)); - CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Vector4); - CORRADE_COMPARE(material->attribute(layer, name), (Vector4{-9.0f, 8.0f, 7.0f, -6.0f})); - } { - constexpr Containers::StringView name = "duplicate"_s; - CORRADE_VERIFY(material->hasAttribute(layer, name)); - CORRADE_COMPARE(material->attributeType(layer, name), MaterialAttributeType::Bool); - CORRADE_COMPARE(material->attribute(layer, name), true); - } - } - /* No layer for MAGNUM_material_forbidden_types because all attributes are - invalid types or too large and skipped */ - CORRADE_VERIFY(!material->hasLayer("#MAGNUM_material_forbidden_types"_s)); - - /* But it produces all kinds of warnings. Attributes are sorted by name. */ + /* Unknown extension with all other supported types */ + {Trade::MaterialAttribute::LayerName, "#MAGNUM_material_type_zoo"_s}, + {"boolTrue"_s, true}, + {"boolFalse"_s, false}, + {"int"_s, -7992835.0f}, + {"unsignedInt"_s, 109835761.0f}, + {"float"_s, 4.321f}, + {"string"_s, "Ribbit -- ribbit"_s}, + {"encodedString"_s, "마이크 체크"_s}, + {"emptyString"_s, ""_s}, + {"uppercaseName"_s, true}, + {"vec1"_s, 91.2f}, + {"vec2"_s, Vector2{9.0f, 8.0f}}, + {"vec3"_s, Vector3{9.0f, 0.08f, 7.3141f}}, + {"vec4"_s, Vector4{-9.0f, 8.0f, 7.0f, -6.0f}}, + {"duplicate"_s, true} + + /* No layer for MAGNUM_material_forbidden_types because all attributes + have invalid types or are too large, and hence are skipped */ + }, {3, 6, 13, 28}}; + + compareMaterials(*material, expected); + + /* MAGNUM_material_forbidden_types produces all kinds of warnings. + Attributes are sorted by name. */ CORRADE_COMPARE(out.str(), "Trade::CgltfImporter::material(): extension with an empty name, skipping\n" "Trade::CgltfImporter::material(): property with an empty name, skipping\n" @@ -4171,41 +4083,28 @@ void CgltfImporterTest::materialRawIor() { CORRADE_VERIFY(importer->openFile(Utility::Directory::join(CGLTFIMPORTER_TEST_DIR, "material-ior.gltf"))); - CORRADE_COMPARE(importer->materialCount(), 2); - /* Extension parsed by glTF, but imported as a custom layer */ constexpr Containers::StringView layer = "KHR_materials_ior"_s; - { - const char* name = "defaults"; - auto material = importer->material(name); - CORRADE_ITERATION(name); - CORRADE_VERIFY(material); - CORRADE_COMPARE(material->types(), MaterialType{}); - CORRADE_COMPARE(material->layerCount(), 2); - CORRADE_VERIFY(material->hasLayer(layer)); - /* The extra attribute is the layer name */ - CORRADE_COMPARE(material->attributeCount(layer), 1 + 1); - - /* IOR is always imported, set to glTF default if missing */ - constexpr Containers::StringView attribute = "ior"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Float); - CORRADE_COMPARE(material->attribute(layer, attribute), 1.5f); - } { - const char* name = "factors"; - auto material = importer->material(name); - CORRADE_ITERATION(name); - CORRADE_VERIFY(material); - CORRADE_COMPARE(material->types(), MaterialType{}); - CORRADE_COMPARE(material->layerCount(), 2); - CORRADE_VERIFY(material->hasLayer(layer)); - CORRADE_COMPARE(material->attributeCount(layer), 1 + 1); + const Containers::Pair materials[]{ + {"defaults"_s, MaterialData{MaterialType{}, { + {Trade::MaterialAttribute::LayerName, layer}, + /* IOR is always imported, set to glTF default if missing */ + {"ior"_s, 1.5f} + }, {0, 2}}}, + {"factors"_s, MaterialData{MaterialType{}, { + {Trade::MaterialAttribute::LayerName, layer}, + {"ior"_s, 1.25f} + }, {0, 2}}} + }; - constexpr Containers::StringView attribute = "ior"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Float); - CORRADE_COMPARE(material->attribute(layer, attribute), 1.25f); + CORRADE_COMPARE(importer->materialCount(), Containers::arraySize(materials)); + + for(const auto& expected: materials) { + auto material = importer->material(expected.first()); + CORRADE_ITERATION(expected.first()); + CORRADE_VERIFY(material); + compareMaterials(*material, expected.second()); } } @@ -4218,150 +4117,68 @@ void CgltfImporterTest::materialRawSpecular() { CORRADE_VERIFY(importer->openFile(Utility::Directory::join(CGLTFIMPORTER_TEST_DIR, "material-specular.gltf"))); - CORRADE_COMPARE(importer->materialCount(), 5); constexpr Containers::StringView layer = "KHR_materials_specular"_s; - { - const char* name = "defaults"; - auto material = importer->material(name); - CORRADE_ITERATION(name); - CORRADE_VERIFY(material); - CORRADE_COMPARE(material->types(), MaterialType{}); - CORRADE_COMPARE(material->layerCount(), 2); - CORRADE_VERIFY(material->hasLayer(layer)); - /* The extra attribute is the layer name */ - CORRADE_COMPARE(material->attributeCount(layer), 2 + 1); - - /* Factors are always imported, set to glTF defaults if missing */ - { - constexpr Containers::StringView attribute = "specularFactor"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Float); - CORRADE_COMPARE(material->attribute(layer, attribute), 1.0f); - } { - constexpr Containers::StringView attribute = "specularColorFactor"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Vector3); - CORRADE_COMPARE(material->attribute(layer, attribute), (Vector3{1.0f, 1.0f, 1.0f})); - } - } { - const char* name = "factors"; - auto material = importer->material(name); - CORRADE_ITERATION(name); - CORRADE_VERIFY(material); - CORRADE_COMPARE(material->types(), MaterialType{}); - CORRADE_COMPARE(material->layerCount(), 2); - CORRADE_VERIFY(material->hasLayer(layer)); - CORRADE_COMPARE(material->attributeCount(layer), 2 + 1); - - { - constexpr Containers::StringView attribute = "specularFactor"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Float); - CORRADE_COMPARE(material->attribute(layer, attribute), 0.67f); - } { - constexpr Containers::StringView attribute = "specularColorFactor"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Vector3); - CORRADE_COMPARE(material->attribute(layer, attribute), (Vector3{0.2f, 0.4f, 0.6f})); - } - } { - const char* name = "textures"; - auto material = importer->material(name); - CORRADE_ITERATION(name); - CORRADE_VERIFY(material); - CORRADE_COMPARE(material->types(), MaterialType{}); - CORRADE_COMPARE(material->layerCount(), 2); - CORRADE_VERIFY(material->hasLayer(layer)); - CORRADE_COMPARE(material->attributeCount(layer), 5 + 1); + const Containers::Pair materials[]{ + {"defaults"_s, MaterialData{MaterialType{}, { + {Trade::MaterialAttribute::LayerName, layer}, + /* Factors are always imported, set to glTF defaults if missing */ + {"specularFactor"_s, 1.0f}, + {"specularColorFactor"_s, Vector3{1.0f, 1.0f, 1.0f}}, + }, {0, 3}}}, + {"factors"_s, MaterialData{MaterialType{}, { + {Trade::MaterialAttribute::LayerName, layer}, + {"specularFactor"_s, 0.67f}, + {"specularColorFactor"_s, Vector3{0.2f, 0.4f, 0.6f}}, + }, {0, 3}}}, + {"textures"_s, MaterialData{MaterialType{}, { + {Trade::MaterialAttribute::LayerName, layer}, + {"specularFactor"_s, 0.7f}, + {"specularColorFactor"_s, Vector3{0.3f, 0.4f, 0.5f}}, + {"specularTexture"_s, 2u}, + {"specularTextureSwizzle"_s, MaterialTextureSwizzle::A}, + {"specularColorTexture"_s, 1u} + }, {0, 6}}}, + {"texture identity transform"_s, MaterialData{MaterialType{}, { + {Trade::MaterialAttribute::LayerName, layer}, + {"specularFactor"_s, 1.0f}, + {"specularColorFactor"_s, Vector3{1.0f, 1.0f, 1.0f}}, + {"specularTexture"_s, 1u}, + {"specularTextureMatrix"_s, Matrix3{}}, + {"specularTextureSwizzle"_s, MaterialTextureSwizzle::A}, + {"specularColorTexture"_s, 0u}, + {"specularColorTextureMatrix"_s, Matrix3{}} + }, {0, 8}}}, + {"texture transform + coordinate set"_s, MaterialData{MaterialType{}, { + {Trade::MaterialAttribute::LayerName, layer}, + {"specularFactor"_s, 1.0f}, + {"specularColorFactor"_s, Vector3{1.0f, 1.0f, 1.0f}}, + {"specularTexture"_s, 2u}, + {"specularTextureCoordinates"_s, 4u}, + {"specularTextureMatrix"_s, Matrix3{ + {1.0f, 0.0f, 0.0f}, + {0.0f, 1.0f, 0.0f}, + {0.0f, -1.0f, 1.0f} + }}, + {"specularTextureSwizzle"_s, MaterialTextureSwizzle::A}, + {"specularColorTexture"_s, 1u}, + {"specularColorTextureCoordinates"_s, 1u}, + {"specularColorTextureMatrix"_s, Matrix3{ + {0.5f, 0.0f, 0.0f}, + {0.0f, 0.5f, 0.0f}, + {0.0f, 0.5f, 1.0f} + }} + }, {0, 10}}} + }; - { - constexpr Containers::StringView attribute = "specularFactor"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Float); - CORRADE_COMPARE(material->attribute(layer, attribute), 0.7f); - } { - constexpr Containers::StringView attribute = "specularColorFactor"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Vector3); - CORRADE_COMPARE(material->attribute(layer, attribute), (Vector3{0.3f, 0.4f, 0.5f})); - } { - constexpr Containers::StringView attribute = "specularTexture"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::UnsignedInt); - CORRADE_COMPARE(material->attribute(layer, attribute), 2u); - } { - constexpr Containers::StringView attribute = "specularTextureSwizzle"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::TextureSwizzle); - CORRADE_COMPARE(material->attribute(layer, attribute), MaterialTextureSwizzle::A); - } { - constexpr Containers::StringView attribute = "specularColorTexture"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::UnsignedInt); - CORRADE_COMPARE(material->attribute(layer, attribute), 1u); - } - } { - const char* name = "texture identity transform"; - auto material = importer->material(name); - CORRADE_ITERATION(name); - CORRADE_VERIFY(material); - CORRADE_COMPARE(material->types(), MaterialType{}); - CORRADE_COMPARE(material->layerCount(), 2); - CORRADE_VERIFY(material->hasLayer(layer)); - CORRADE_COMPARE(material->attributeCount(layer), 7 + 1); + CORRADE_COMPARE(importer->materialCount(), Containers::arraySize(materials)); - { - constexpr Containers::StringView attribute = "specularTextureMatrix"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Matrix3x3); - CORRADE_COMPARE(material->attribute(layer, attribute), Matrix3{}); - } { - constexpr Containers::StringView attribute = "specularColorTextureMatrix"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Matrix3x3); - CORRADE_COMPARE(material->attribute(layer, attribute), Matrix3{}); - } - } { - const char* name = "texture transform + coordinate set"; - auto material = importer->material(name); - CORRADE_ITERATION(name); + for(const auto& expected: materials) { + auto material = importer->material(expected.first()); + CORRADE_ITERATION(expected.first()); CORRADE_VERIFY(material); - CORRADE_COMPARE(material->types(), MaterialType{}); - CORRADE_COMPARE(material->layerCount(), 2); - CORRADE_VERIFY(material->hasLayer(layer)); - CORRADE_COMPARE(material->attributeCount(layer), 9 + 1); - - { - constexpr Containers::StringView attribute = "specularTextureMatrix"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Matrix3x3); - CORRADE_COMPARE(material->attribute(layer, attribute), (Matrix3{ - {1.0f, 0.0f, 0.0f}, - {0.0f, 1.0f, 0.0f}, - {0.0f, -1.0f, 1.0f} - })); - } { - constexpr Containers::StringView attribute = "specularTextureCoordinates"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::UnsignedInt); - CORRADE_COMPARE(material->attribute(layer, attribute), 4); - } { - constexpr Containers::StringView attribute = "specularColorTextureMatrix"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Matrix3x3); - CORRADE_COMPARE(material->attribute(layer, attribute), (Matrix3{ - {0.5f, 0.0f, 0.0f}, - {0.0f, 0.5f, 0.0f}, - {0.0f, 0.5f, 1.0f} - })); - } { - constexpr Containers::StringView attribute = "specularColorTextureCoordinates"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::UnsignedInt); - CORRADE_COMPARE(material->attribute(layer, attribute), 1); - } + compareMaterials(*material, expected.second()); } } @@ -4374,101 +4191,51 @@ void CgltfImporterTest::materialRawTransmission() { CORRADE_VERIFY(importer->openFile(Utility::Directory::join(CGLTFIMPORTER_TEST_DIR, "material-transmission.gltf"))); - CORRADE_COMPARE(importer->materialCount(), 5); constexpr Containers::StringView layer = "KHR_materials_transmission"_s; - { - const char* name = "defaults"; - auto material = importer->material(name); - CORRADE_ITERATION(name); - CORRADE_VERIFY(material); - CORRADE_COMPARE(material->types(), MaterialType{}); - CORRADE_COMPARE(material->layerCount(), 2); - CORRADE_VERIFY(material->hasLayer(layer)); - /* The extra attribute is the layer name */ - CORRADE_COMPARE(material->attributeCount(layer), 1 + 1); - - /* transmissionFactor is always imported, set to glTF default if - missing */ - constexpr Containers::StringView attribute = "transmissionFactor"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Float); - CORRADE_COMPARE(material->attribute(layer, attribute), 0.0f); - } { - const char* name = "factors"; - auto material = importer->material(name); - CORRADE_ITERATION(name); - CORRADE_VERIFY(material); - CORRADE_COMPARE(material->types(), MaterialType{}); - CORRADE_COMPARE(material->layerCount(), 2); - CORRADE_VERIFY(material->hasLayer(layer)); - CORRADE_COMPARE(material->attributeCount(layer), 1 + 1); + const Containers::Pair materials[]{ + {"defaults"_s, MaterialData{MaterialType{}, { + {Trade::MaterialAttribute::LayerName, layer}, + /* transmissionFactor is always imported, set to glTF default + if missing */ + {"transmissionFactor"_s, 0.0f} + }, {0, 2}}}, + {"factors"_s, MaterialData{MaterialType{}, { + {Trade::MaterialAttribute::LayerName, layer}, + {"transmissionFactor"_s, 0.67f} + }, {0, 2}}}, + {"textures"_s, MaterialData{MaterialType{}, { + {Trade::MaterialAttribute::LayerName, layer}, + {"transmissionFactor"_s, 0.7f}, + {"transmissionTexture"_s, 1u} + }, {0, 3}}}, + {"texture identity transform"_s, MaterialData{MaterialType{}, { + {Trade::MaterialAttribute::LayerName, layer}, + {"transmissionFactor"_s, 0.0f}, + {"transmissionTexture"_s, 0u}, + {"transmissionTextureMatrix"_s, Matrix3{}} + }, {0, 4}}}, + {"texture transform + coordinate set"_s, MaterialData{MaterialType{}, { + {Trade::MaterialAttribute::LayerName, layer}, + {"transmissionFactor"_s, 0.0f}, + {"transmissionTexture"_s, 1u}, + {"transmissionTextureCoordinates"_s, 3u}, + {"transmissionTextureMatrix"_s, Matrix3{ + {1.0f, 0.0f, 0.0f}, + {0.0f, 1.0f, 0.0f}, + {0.0f, -1.0f, 1.0f} + }} + }, {0, 5}}} + }; - constexpr Containers::StringView attribute = "transmissionFactor"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Float); - CORRADE_COMPARE(material->attribute(layer, attribute), 0.67f); - } { - const char* name = "textures"; - auto material = importer->material(name); - CORRADE_ITERATION(name); - CORRADE_VERIFY(material); - CORRADE_COMPARE(material->types(), MaterialType{}); - CORRADE_COMPARE(material->layerCount(), 2); - CORRADE_VERIFY(material->hasLayer(layer)); - CORRADE_COMPARE(material->attributeCount(layer), 2 + 1); + CORRADE_COMPARE(importer->materialCount(), Containers::arraySize(materials)); - { - constexpr Containers::StringView attribute = "transmissionFactor"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Float); - CORRADE_COMPARE(material->attribute(layer, attribute), 0.7f); - } { - constexpr Containers::StringView attribute = "transmissionTexture"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::UnsignedInt); - CORRADE_COMPARE(material->attribute(layer, attribute), 1u); - } - } { - const char* name = "texture identity transform"; - auto material = importer->material(name); - CORRADE_ITERATION(name); - CORRADE_VERIFY(material); - CORRADE_COMPARE(material->types(), MaterialType{}); - CORRADE_COMPARE(material->layerCount(), 2); - CORRADE_VERIFY(material->hasLayer(layer)); - CORRADE_COMPARE(material->attributeCount(layer), 3 + 1); - - constexpr Containers::StringView attribute = "transmissionTextureMatrix"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Matrix3x3); - CORRADE_COMPARE(material->attribute(layer, attribute), Matrix3{}); - } { - const char* name = "texture transform + coordinate set"; - auto material = importer->material(name); - CORRADE_ITERATION(name); + for(const auto& expected: materials) { + auto material = importer->material(expected.first()); + CORRADE_ITERATION(expected.first()); CORRADE_VERIFY(material); - CORRADE_COMPARE(material->types(), MaterialType{}); - CORRADE_COMPARE(material->layerCount(), 2); - CORRADE_VERIFY(material->hasLayer(layer)); - CORRADE_COMPARE(material->attributeCount(layer), 4 + 1); - - { - constexpr Containers::StringView attribute = "transmissionTextureMatrix"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Matrix3x3); - CORRADE_COMPARE(material->attribute(layer, attribute), (Matrix3{ - {1.0f, 0.0f, 0.0f}, - {0.0f, 1.0f, 0.0f}, - {0.0f, -1.0f, 1.0f} - })); - } { - constexpr Containers::StringView attribute = "transmissionTextureCoordinates"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::UnsignedInt); - CORRADE_COMPARE(material->attribute(layer, attribute), 3); - } + compareMaterials(*material, expected.second()); } } @@ -4481,139 +4248,63 @@ void CgltfImporterTest::materialRawVolume() { CORRADE_VERIFY(importer->openFile(Utility::Directory::join(CGLTFIMPORTER_TEST_DIR, "material-volume.gltf"))); - CORRADE_COMPARE(importer->materialCount(), 5); constexpr Containers::StringView layer = "KHR_materials_volume"_s; - { - const char* name = "defaults"; - auto material = importer->material(name); - CORRADE_ITERATION(name); - CORRADE_VERIFY(material); - CORRADE_COMPARE(material->types(), MaterialType{}); - CORRADE_COMPARE(material->layerCount(), 2); - CORRADE_VERIFY(material->hasLayer(layer)); - /* The extra attribute is the layer name */ - CORRADE_COMPARE(material->attributeCount(layer), 3 + 1); - - /* Factors are always imported, set to glTF defaults if missing */ - { - constexpr Containers::StringView attribute = "thicknessFactor"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Float); - CORRADE_COMPARE(material->attribute(layer, attribute), 0.0f); - } { - constexpr Containers::StringView attribute = "attenuationDistance"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Float); - CORRADE_COMPARE(material->attribute(layer, attribute), Constants::inf()); - } { - constexpr Containers::StringView attribute = "attenuationColor"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Vector3); - CORRADE_COMPARE(material->attribute(layer, attribute), (Vector3{1.0f, 1.0f, 1.0f})); - } - } { - const char* name = "factors"; - auto material = importer->material(name); - CORRADE_ITERATION(name); - CORRADE_VERIFY(material); - CORRADE_COMPARE(material->types(), MaterialType{}); - CORRADE_COMPARE(material->layerCount(), 2); - CORRADE_VERIFY(material->hasLayer(layer)); - CORRADE_COMPARE(material->attributeCount(layer), 3 + 1); - - { - constexpr Containers::StringView attribute = "thicknessFactor"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Float); - CORRADE_COMPARE(material->attribute(layer, attribute), 0.67f); - } { - constexpr Containers::StringView attribute = "attenuationDistance"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Float); - CORRADE_COMPARE(material->attribute(layer, attribute), 15.3f); - } { - constexpr Containers::StringView attribute = "attenuationColor"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Vector3); - CORRADE_COMPARE(material->attribute(layer, attribute), (Vector3{0.7f, 0.1f, 1.0f})); - } - } { - const char* name = "textures"; - auto material = importer->material(name); - CORRADE_ITERATION(name); - CORRADE_VERIFY(material); - CORRADE_COMPARE(material->types(), MaterialType{}); - CORRADE_COMPARE(material->layerCount(), 2); - CORRADE_VERIFY(material->hasLayer(layer)); - CORRADE_COMPARE(material->attributeCount(layer), 5 + 1); + const Containers::Pair materials[]{ + {"defaults"_s, MaterialData{MaterialType{}, { + {Trade::MaterialAttribute::LayerName, layer}, + /* Factors are always imported, set to glTF defaults if missing */ + {"thicknessFactor"_s, 0.0f}, + {"attenuationDistance"_s, Constants::inf()}, + {"attenuationColor"_s, Vector3{1.0f, 1.0f, 1.0f}} + }, {0, 4}}}, + {"factors"_s, MaterialData{MaterialType{}, { + {Trade::MaterialAttribute::LayerName, layer}, + {"thicknessFactor"_s, 0.67f}, + {"attenuationDistance"_s, 15.3f}, + {"attenuationColor"_s, Vector3{0.7f, 0.1f, 1.0f}} + }, {0, 4}}}, + {"textures"_s, MaterialData{MaterialType{}, { + {Trade::MaterialAttribute::LayerName, layer}, + {"thicknessFactor"_s, 0.7f}, + {"attenuationDistance"_s, 1.12f}, + {"attenuationColor"_s, Vector3{0.1f, 0.05f, 0.0f}}, + {"thicknessTexture"_s, 1u}, + {"thicknessTextureSwizzle"_s, MaterialTextureSwizzle::G} + }, {0, 6}}}, + {"texture identity transform"_s, MaterialData{MaterialType{}, { + {Trade::MaterialAttribute::LayerName, layer}, + {"thicknessFactor"_s, 0.0f}, + {"attenuationDistance"_s, Constants::inf()}, + {"attenuationColor"_s, Vector3{1.0f, 1.0f, 1.0f}}, + {"thicknessTexture"_s, 0u}, + {"thicknessTextureMatrix"_s, Matrix3{}}, + {"thicknessTextureSwizzle"_s, MaterialTextureSwizzle::G} + }, {0, 7}}}, + {"texture transform + coordinate set"_s, MaterialData{MaterialType{}, { + {Trade::MaterialAttribute::LayerName, layer}, + {"thicknessFactor"_s, 0.0f}, + {"attenuationDistance"_s, Constants::inf()}, + {"attenuationColor"_s, Vector3{1.0f, 1.0f, 1.0f}}, + {"thicknessTexture"_s, 1u}, + {"thicknessTextureCoordinates"_s, 3u}, + {"thicknessTextureMatrix"_s, Matrix3{ + {1.0f, 0.0f, 0.0f}, + {0.0f, 1.0f, 0.0f}, + {0.0f, -1.0f, 1.0f} + }}, + {"thicknessTextureSwizzle"_s, MaterialTextureSwizzle::G} + }, {0, 8}}} + }; - { - constexpr Containers::StringView attribute = "thicknessFactor"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Float); - CORRADE_COMPARE(material->attribute(layer, attribute), 0.7f); - } { - constexpr Containers::StringView attribute = "attenuationDistance"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Float); - CORRADE_COMPARE(material->attribute(layer, attribute), 1.12f); - } { - constexpr Containers::StringView attribute = "attenuationColor"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Vector3); - CORRADE_COMPARE(material->attribute(layer, attribute), (Vector3{0.1f, 0.05f, 0.0f})); - } { - constexpr Containers::StringView attribute = "thicknessTexture"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::UnsignedInt); - CORRADE_COMPARE(material->attribute(layer, attribute), 1u); - } { - constexpr Containers::StringView attribute = "thicknessTextureSwizzle"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::TextureSwizzle); - CORRADE_COMPARE(material->attribute(layer, attribute), MaterialTextureSwizzle::G); - } - } { - const char* name = "texture identity transform"; - auto material = importer->material(name); - CORRADE_ITERATION(name); - CORRADE_VERIFY(material); - CORRADE_COMPARE(material->types(), MaterialType{}); - CORRADE_COMPARE(material->layerCount(), 2); - CORRADE_VERIFY(material->hasLayer(layer)); - CORRADE_COMPARE(material->attributeCount(layer), 6 + 1); + CORRADE_COMPARE(importer->materialCount(), Containers::arraySize(materials)); - constexpr Containers::StringView attribute = "thicknessTextureMatrix"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Matrix3x3); - CORRADE_COMPARE(material->attribute(layer, attribute), Matrix3{}); - } { - const char* name = "texture transform + coordinate set"; - auto material = importer->material(name); - CORRADE_ITERATION(name); + for(const auto& expected: materials) { + auto material = importer->material(expected.first()); + CORRADE_ITERATION(expected.first()); CORRADE_VERIFY(material); - CORRADE_COMPARE(material->types(), MaterialType{}); - CORRADE_COMPARE(material->layerCount(), 2); - CORRADE_VERIFY(material->hasLayer(layer)); - CORRADE_COMPARE(material->attributeCount(layer), 7 + 1); - - { - constexpr Containers::StringView attribute = "thicknessTextureMatrix"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Matrix3x3); - CORRADE_COMPARE(material->attribute(layer, attribute), (Matrix3{ - {1.0f, 0.0f, 0.0f}, - {0.0f, 1.0f, 0.0f}, - {0.0f, -1.0f, 1.0f} - })); - } { - constexpr Containers::StringView attribute = "thicknessTextureCoordinates"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::UnsignedInt); - CORRADE_COMPARE(material->attribute(layer, attribute), 3); - } + compareMaterials(*material, expected.second()); } } @@ -4626,149 +4317,72 @@ void CgltfImporterTest::materialRawSheen() { CORRADE_VERIFY(importer->openFile(Utility::Directory::join(CGLTFIMPORTER_TEST_DIR, "material-sheen.gltf"))); - CORRADE_COMPARE(importer->materialCount(), 5); constexpr Containers::StringView layer = "KHR_materials_sheen"_s; - { - const char* name = "defaults"; - auto material = importer->material(name); - CORRADE_ITERATION(name); - CORRADE_VERIFY(material); - CORRADE_COMPARE(material->types(), MaterialType{}); - CORRADE_COMPARE(material->layerCount(), 2); - CORRADE_VERIFY(material->hasLayer(layer)); - /* The extra attribute is the layer name */ - CORRADE_COMPARE(material->attributeCount(layer), 2 + 1); - - /* Factors are always imported, set to glTF defaults if missing */ - { - constexpr Containers::StringView attribute = "sheenColorFactor"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Vector3); - CORRADE_COMPARE(material->attribute(layer, attribute), (Vector3{0.0f, 0.0f, 0.0f})); - } { - constexpr Containers::StringView attribute = "sheenRoughnessFactor"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Float); - CORRADE_COMPARE(material->attribute(layer, attribute), 0.0f); - } - } { - const char* name = "factors"; - auto material = importer->material(name); - CORRADE_ITERATION(name); - CORRADE_VERIFY(material); - CORRADE_COMPARE(material->types(), MaterialType{}); - CORRADE_COMPARE(material->layerCount(), 2); - CORRADE_VERIFY(material->hasLayer(layer)); - CORRADE_COMPARE(material->attributeCount(layer), 2 + 1); + const Containers::Pair materials[]{ + {"defaults"_s, MaterialData{MaterialType{}, { + {Trade::MaterialAttribute::LayerName, layer}, + /* Factors are always imported, set to glTF defaults if missing */ + {"sheenColorFactor"_s, Vector3{0.0f, 0.0f, 0.0f}}, + {"sheenRoughnessFactor"_s, 0.0f} + }, {0, 3}}}, + {"factors"_s, MaterialData{MaterialType{}, { + {Trade::MaterialAttribute::LayerName, layer}, + {"sheenColorFactor"_s, Vector3{0.2f, 0.4f, 0.6f}}, + {"sheenRoughnessFactor"_s, 0.67f} + }, {0, 3}}}, + {"textures"_s, MaterialData{MaterialType{}, { + {Trade::MaterialAttribute::LayerName, layer}, + {"sheenColorFactor"_s, Vector3{0.3f, 0.4f, 0.5f}}, + {"sheenRoughnessFactor"_s, 0.7f}, + {"sheenColorTexture"_s, 1u}, + {"sheenRoughnessTexture"_s, 2u}, + {"sheenRoughnessTextureSwizzle"_s, MaterialTextureSwizzle::A} + }, {0, 6}}}, + {"texture identity transform"_s, MaterialData{MaterialType{}, { + {Trade::MaterialAttribute::LayerName, layer}, + {"sheenColorFactor"_s, Vector3{0.0f, 0.0f, 0.0f}}, + {"sheenRoughnessFactor"_s, 0.0f}, + {"sheenColorTexture"_s, 1u}, + {"sheenColorTextureMatrix"_s, Matrix3x3{}}, + {"sheenRoughnessTexture"_s, 0u}, + /* sheenRoughnessTextureMatrix is too large and skipped */ + {"sheenRoughnessTextureSwizzle"_s, MaterialTextureSwizzle::A} + }, {0, 7}}}, + {"texture transform + coordinate set"_s, MaterialData{MaterialType{}, { + {Trade::MaterialAttribute::LayerName, layer}, + {"sheenColorFactor"_s, Vector3{0.0f, 0.0f, 0.0f}}, + {"sheenRoughnessFactor"_s, 0.0f}, + {"sheenColorTexture"_s, 2u}, + {"sheenColorTextureCoordinates"_s, 4u}, + {"sheenColorTextureMatrix"_s, Matrix3{ + {1.0f, 0.0f, 0.0f}, + {0.0f, 1.0f, 0.0f}, + {0.0f, -1.0f, 1.0f} + }}, + {"sheenRoughnessTexture"_s, 1u}, + {"sheenRoughnessTextureCoordinates"_s, 1u}, + /* sheenRoughnessTextureMatrix is too large and skipped */ + {"sheenRoughnessTextureSwizzle"_s, MaterialTextureSwizzle::A} + }, {0, 9}}} + }; - { - constexpr Containers::StringView attribute = "sheenColorFactor"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Vector3); - CORRADE_COMPARE(material->attribute(layer, attribute), (Vector3{0.2f, 0.4f, 0.6f})); - } { - constexpr Containers::StringView attribute = "sheenRoughnessFactor"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Float); - CORRADE_COMPARE(material->attribute(layer, attribute), 0.67f); - } - } { - const char* name = "textures"; - auto material = importer->material(name); - CORRADE_ITERATION(name); - CORRADE_VERIFY(material); - CORRADE_COMPARE(material->types(), MaterialType{}); - CORRADE_COMPARE(material->layerCount(), 2); - CORRADE_VERIFY(material->hasLayer(layer)); - CORRADE_COMPARE(material->attributeCount(layer), 5 + 1); + CORRADE_COMPARE(importer->materialCount(), Containers::arraySize(materials)); - { - constexpr Containers::StringView attribute = "sheenColorFactor"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Vector3); - CORRADE_COMPARE(material->attribute(layer, attribute), (Vector3{0.3f, 0.4f, 0.5f})); - } { - constexpr Containers::StringView attribute = "sheenRoughnessFactor"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Float); - CORRADE_COMPARE(material->attribute(layer, attribute), 0.7f); - } { - constexpr Containers::StringView attribute = "sheenColorTexture"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::UnsignedInt); - CORRADE_COMPARE(material->attribute(layer, attribute), 1u); - } { - constexpr Containers::StringView attribute = "sheenRoughnessTexture"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::UnsignedInt); - CORRADE_COMPARE(material->attribute(layer, attribute), 2u); - } { - constexpr Containers::StringView attribute = "sheenRoughnessTextureSwizzle"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::TextureSwizzle); - CORRADE_COMPARE(material->attribute(layer, attribute), MaterialTextureSwizzle::A); - } - } { - const char* name = "texture identity transform"; - auto material = importer->material(name); - CORRADE_ITERATION(name); - CORRADE_VERIFY(material); - CORRADE_COMPARE(material->types(), MaterialType{}); - CORRADE_COMPARE(material->layerCount(), 2); - CORRADE_VERIFY(material->hasLayer(layer)); - { - CORRADE_EXPECT_FAIL("sheenRoughnessTextureMatrix attribute is skipped because it's too large."); - CORRADE_COMPARE(material->attributeCount(layer), 7 + 1); - } - CORRADE_COMPARE(material->attributeCount(layer), 6 + 1); + std::ostringstream out; + Warning redirectWarning{&out}; - { - constexpr Containers::StringView attribute = "sheenColorTextureMatrix"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Matrix3x3); - CORRADE_COMPARE(material->attribute(layer, attribute), Matrix3{}); - } { - CORRADE_VERIFY(!material->hasAttribute(layer, "sheenRoughnessTextureMatrix"_s)); - } - } { - const char* name = "texture transform + coordinate set"; - auto material = importer->material(name); - CORRADE_ITERATION(name); + for(const auto& expected: materials) { + auto material = importer->material(expected.first()); + CORRADE_ITERATION(expected.first()); CORRADE_VERIFY(material); - CORRADE_COMPARE(material->types(), MaterialType{}); - CORRADE_COMPARE(material->layerCount(), 2); - CORRADE_VERIFY(material->hasLayer(layer)); - { - CORRADE_EXPECT_FAIL("sheenRoughnessTextureMatrix attribute is skipped because it's too large."); - CORRADE_COMPARE(material->attributeCount(layer), 9 + 1); - } - CORRADE_COMPARE(material->attributeCount(layer), 8 + 1); - - { - constexpr Containers::StringView attribute = "sheenColorTextureMatrix"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::Matrix3x3); - CORRADE_COMPARE(material->attribute(layer, attribute), (Matrix3{ - {1.0f, 0.0f, 0.0f}, - {0.0f, 1.0f, 0.0f}, - {0.0f, -1.0f, 1.0f} - })); - } { - constexpr Containers::StringView attribute = "sheenColorTextureCoordinates"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::UnsignedInt); - CORRADE_COMPARE(material->attribute(layer, attribute), 4); - } { - CORRADE_VERIFY(!material->hasAttribute(layer, "sheenRoughnessTextureMatrix"_s)); - } { - constexpr Containers::StringView attribute = "sheenRoughnessTextureCoordinates"_s; - CORRADE_VERIFY(material->hasAttribute(layer, attribute)); - CORRADE_COMPARE(material->attributeType(layer, attribute), MaterialAttributeType::UnsignedInt); - CORRADE_COMPARE(material->attribute(layer, attribute), 1); - } + compareMaterials(*material, expected.second()); } + + CORRADE_COMPARE(out.str(), + "Trade::CgltfImporter::material(): property sheenRoughnessTextureMatrix is too large with 63 bytes, skipping\n" + "Trade::CgltfImporter::material(): property sheenRoughnessTextureMatrix is too large with 63 bytes, skipping\n"); } void CgltfImporterTest::materialRawOutOfBounds() { @@ -4778,11 +4392,8 @@ void CgltfImporterTest::materialRawOutOfBounds() { "material-raw.gltf"))); std::ostringstream out; - { - Error redirectError{&out}; - CORRADE_VERIFY(!importer->material("raw out-of-bounds")); - } - + Error redirectError{&out}; + CORRADE_VERIFY(!importer->material("raw out-of-bounds")); CORRADE_COMPARE(out.str(), "Trade::CgltfImporter::material(): snakeTexture index 2 out of bounds for 2 textures\n"); } From da05efedaf21321205e2d9e19f41dddb5dd33b56 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Wed, 22 Dec 2021 19:18:57 +0100 Subject: [PATCH 30/35] CgltfImporter: also set custom layer prefix for cgltf-handled materials --- src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp | 10 +++++----- .../CgltfImporter/Test/CgltfImporterTest.cpp | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp index 8c4424ea7..ebd480a2c 100644 --- a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp +++ b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp @@ -2575,14 +2575,14 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i compatibility even if they're ever turned into standard layers. */ if(material.has_ior) { arrayAppend(layers, UnsignedInt(attributes.size())); - arrayAppend(attributes, InPlaceInit, MaterialAttribute::LayerName, "KHR_materials_ior"_s); + arrayAppend(attributes, InPlaceInit, MaterialAttribute::LayerName, "#KHR_materials_ior"_s); arrayAppend(attributes, InPlaceInit, "ior", material.ior.ior); } if(material.has_specular) { arrayAppend(layers, UnsignedInt(attributes.size())); - arrayAppend(attributes, InPlaceInit, MaterialAttribute::LayerName, "KHR_materials_specular"_s); + arrayAppend(attributes, InPlaceInit, MaterialAttribute::LayerName, "#KHR_materials_specular"_s); arrayAppend(attributes, InPlaceInit, "specularFactor"_s, @@ -2618,7 +2618,7 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i if(material.has_transmission) { arrayAppend(layers, UnsignedInt(attributes.size())); - arrayAppend(attributes, InPlaceInit, MaterialAttribute::LayerName, "KHR_materials_transmission"_s); + arrayAppend(attributes, InPlaceInit, MaterialAttribute::LayerName, "#KHR_materials_transmission"_s); arrayAppend(attributes, InPlaceInit, "transmissionFactor"_s, @@ -2635,7 +2635,7 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i if(material.has_volume) { arrayAppend(layers, UnsignedInt(attributes.size())); - arrayAppend(attributes, InPlaceInit, MaterialAttribute::LayerName, "KHR_materials_volume"_s); + arrayAppend(attributes, InPlaceInit, MaterialAttribute::LayerName, "#KHR_materials_volume"_s); arrayAppend(attributes, InPlaceInit, "thicknessFactor"_s, @@ -2670,7 +2670,7 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i if(material.has_sheen) { arrayAppend(layers, UnsignedInt(attributes.size())); - arrayAppend(attributes, InPlaceInit, MaterialAttribute::LayerName, "KHR_materials_sheen"_s); + arrayAppend(attributes, InPlaceInit, MaterialAttribute::LayerName, "#KHR_materials_sheen"_s); const Vector3 sheenColorFactor = Vector3::from(material.sheen.sheen_color_factor); arrayAppend(attributes, InPlaceInit, diff --git a/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp b/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp index aa2e38dc3..0ff80f79e 100644 --- a/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp +++ b/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp @@ -4084,7 +4084,7 @@ void CgltfImporterTest::materialRawIor() { CORRADE_VERIFY(importer->openFile(Utility::Directory::join(CGLTFIMPORTER_TEST_DIR, "material-ior.gltf"))); - constexpr Containers::StringView layer = "KHR_materials_ior"_s; + constexpr Containers::StringView layer = "#KHR_materials_ior"_s; const Containers::Pair materials[]{ {"defaults"_s, MaterialData{MaterialType{}, { @@ -4118,7 +4118,7 @@ void CgltfImporterTest::materialRawSpecular() { CORRADE_VERIFY(importer->openFile(Utility::Directory::join(CGLTFIMPORTER_TEST_DIR, "material-specular.gltf"))); - constexpr Containers::StringView layer = "KHR_materials_specular"_s; + constexpr Containers::StringView layer = "#KHR_materials_specular"_s; const Containers::Pair materials[]{ {"defaults"_s, MaterialData{MaterialType{}, { @@ -4192,7 +4192,7 @@ void CgltfImporterTest::materialRawTransmission() { CORRADE_VERIFY(importer->openFile(Utility::Directory::join(CGLTFIMPORTER_TEST_DIR, "material-transmission.gltf"))); - constexpr Containers::StringView layer = "KHR_materials_transmission"_s; + constexpr Containers::StringView layer = "#KHR_materials_transmission"_s; const Containers::Pair materials[]{ {"defaults"_s, MaterialData{MaterialType{}, { @@ -4249,7 +4249,7 @@ void CgltfImporterTest::materialRawVolume() { CORRADE_VERIFY(importer->openFile(Utility::Directory::join(CGLTFIMPORTER_TEST_DIR, "material-volume.gltf"))); - constexpr Containers::StringView layer = "KHR_materials_volume"_s; + constexpr Containers::StringView layer = "#KHR_materials_volume"_s; const Containers::Pair materials[]{ {"defaults"_s, MaterialData{MaterialType{}, { @@ -4318,7 +4318,7 @@ void CgltfImporterTest::materialRawSheen() { CORRADE_VERIFY(importer->openFile(Utility::Directory::join(CGLTFIMPORTER_TEST_DIR, "material-sheen.gltf"))); - constexpr Containers::StringView layer = "KHR_materials_sheen"_s; + constexpr Containers::StringView layer = "#KHR_materials_sheen"_s; const Containers::Pair materials[]{ {"defaults"_s, MaterialData{MaterialType{}, { From 33dc8d7fa40242f9445cd56baa2daa86dd9bafb9 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Wed, 22 Dec 2021 20:19:35 +0100 Subject: [PATCH 31/35] CgltfImporter: fix alignas position Should've just copied straight from the review snippet. --- src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp index ebd480a2c..84c5a7e15 100644 --- a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp +++ b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp @@ -2793,7 +2793,7 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i or ignored, so we know the upper limit on the data size. The alignas prevents unaligned reads for individual floats. For strings, MaterialAttributeData expects a pointer to StringView. */ - char alignas(4) attributeData[16]; + alignas(4) char attributeData[16]; Containers::String attributeString; Containers::StringView attributeStringView; MaterialAttributeType type{}; From e73b85e7cec6ccb47e556bb7e379a4b699fa3a80 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Fri, 7 Jan 2022 21:11:51 +0100 Subject: [PATCH 32/35] CgltfImporter: update docs about parsing of unknown extension number properties --- .../CgltfImporter/CgltfImporter.h | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/MagnumPlugins/CgltfImporter/CgltfImporter.h b/src/MagnumPlugins/CgltfImporter/CgltfImporter.h index e30a8b346..dde04eb14 100644 --- a/src/MagnumPlugins/CgltfImporter/CgltfImporter.h +++ b/src/MagnumPlugins/CgltfImporter/CgltfImporter.h @@ -317,21 +317,20 @@ fail. - Unrecognized material extensions are imported as custom layers with a `#` prefix. Extension properties are imported with their raw names and types, the following of which are supported: - - @ref MaterialAttributeType::String, @ref MaterialAttributeType::Bool, - @ref MaterialAttributeType::Float, @ref MaterialAttributeType::Int, - @ref MaterialAttributeType::UnsignedInt - - arrays as @ref MaterialAttributeType::Vector2/ + - @ref MaterialAttributeType::String + - @ref MaterialAttributeType::Bool + - All numbers as @ref MaterialAttributeType::Float to avoid inconsistency + with different glTF exporters. The only exception are texture indices + and coordinate sets inside [textureInfo](https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#reference-textureinfo) + objects. + - Number arrays as @ref MaterialAttributeType::Vector2/ @ref MaterialAttributeType::Vector3/ - @ref MaterialAttributeType::Vector4, - MaterialAttributeType::Vector2i/@ref MaterialAttributeType::Vector3i/ - @ref MaterialAttributeType::Vector4i, MaterialAttributeType::Vector2ui/ - @ref MaterialAttributeType::Vector3ui/ - @ref MaterialAttributeType::Vector4ui. + @ref MaterialAttributeType::Vector4. Empty arrays, arrays of size 5 or higher as well as arrays containing anything that isn't a number are ignored. - [textureInfo](https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#reference-textureinfo) objects, including all attributes handled for regular textures. Texture - attributes are suffixed to the name of the object: e.g. if an extension + attributes are prefixed by the name of the object: e.g. if an extension has a `someTexture` property, the texture index, matrix, coordinate set and scale would be imported as `someTexture`, `someTextureMatrix`, `someTextureCoordinates` and `someTextureScale`. Non-texture object From 9dbd66a46feb548f76a389e982c7a74edc46301f Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Sun, 9 Jan 2022 14:39:39 +0100 Subject: [PATCH 33/35] CgltfImporter: add some TODOs --- src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp index 84c5a7e15..3ccddc729 100644 --- a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp +++ b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp @@ -651,6 +651,8 @@ void CgltfImporter::doOpenData(Containers::Array&& data, const DataFlags d /* Check required extensions. Every extension in extensionsRequired is required to "load and/or render an asset". */ + /** @todo Allow ignoring specific extensions through a config option, e.g. + ignoreRequiredExtension=KHR_materials_volume */ const bool ignoreRequiredExtensions = configuration().value("ignoreRequiredExtensions"); constexpr Containers::StringView supportedExtensions[]{ @@ -2569,10 +2571,11 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i /* Import extensions with non-standard layer/attribute types that are already parsed by cgltf and hence don't appear in the extension list anymore. We use the original attribute names as found in the extension - specifications. To imitate actual unknown extension import below, - all Int and UnsignedInt attributes must be converted to Float. - Note that these custom layers will have to be duplicated for backwards - compatibility even if they're ever turned into standard layers. */ + specifications. To imitate actual unknown extension import below, all + Int and UnsignedInt attributes must be converted to Float. */ + /** @todo If these are turned into standard layer types they will have to + be duplicated for backwards compatibility, with the old names and type + conversion. */ if(material.has_ior) { arrayAppend(layers, UnsignedInt(attributes.size())); arrayAppend(attributes, InPlaceInit, MaterialAttribute::LayerName, "#KHR_materials_ior"_s); From 6b17a3031ba9d04fa5c1b7da3169f92b6a755105 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Mon, 10 Jan 2022 15:23:18 +0100 Subject: [PATCH 34/35] CgltfImporter: test warnings in between valid material attributes The existing tests only tested one extensions that had ALL the warnings, but not that parsing still works --- src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp | 6 ++++-- src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp b/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp index 0ff80f79e..fb137dae7 100644 --- a/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp +++ b/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp @@ -4047,9 +4047,9 @@ void CgltfImporterTest::materialRaw() { compareMaterials(*material, expected); - /* MAGNUM_material_forbidden_types produces all kinds of warnings. - Attributes are sorted by name. */ + CORRADE_COMPARE(out.str(), + /* MAGNUM_material_forbidden_types. Attributes are sorted by name. */ "Trade::CgltfImporter::material(): extension with an empty name, skipping\n" "Trade::CgltfImporter::material(): property with an empty name, skipping\n" "Trade::CgltfImporter::material(): property Texture has non-texture object type, skipping\n" @@ -4071,6 +4071,8 @@ void CgltfImporterTest::materialRaw() { "Trade::CgltfImporter::material(): property null has unsupported type, skipping\n" "Trade::CgltfImporter::material(): property oversizedArray has unsupported type, skipping\n" "Trade::CgltfImporter::material(): property stringArray has unsupported type, skipping\n" + /* MAGNUM_material_type_zoo */ + "Trade::CgltfImporter::material(): property invalid has unsupported type, skipping\n" "Trade::CgltfImporter::material(): extension name VENDOR_material_thisnameiswaytoolongforalayername! is too long with 50 characters, skipping\n"); } diff --git a/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf b/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf index cef609dfb..94b8275a4 100644 --- a/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf +++ b/src/MagnumPlugins/CgltfImporter/Test/material-raw.gltf @@ -70,6 +70,7 @@ "encodedString": "\uB9C8\uC774\uD06C \uCCB4\uD06C", "emptyString": "", "UppercaseName": true , + "invalid": ["just checking if it keeps parsing after a warning"], "vec1": [9.12e+1], "vec2": [ 9 , From b85ea4064f85bdee82bcbe2d9f9b7eb2251d1af8 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Mon, 10 Jan 2022 15:27:47 +0100 Subject: [PATCH 35/35] CgltfImporter: import material extras The extras type can be any valid JSON token, but we ignore anything that's not an object. Type support and conversion is the same as unrecognized material extensions, except we ignore ALL nested objects, including textureInfo. Attributes are written directly into the material base layer. --- .../CgltfImporter/CgltfImporter.cpp | 330 +++++++++++------- .../CgltfImporter/CgltfImporter.h | 5 + .../CgltfImporter/Test/CMakeLists.txt | 1 + .../CgltfImporter/Test/CgltfImporterTest.cpp | 176 ++++++++-- .../CgltfImporter/Test/material-extras.gltf | 99 ++++++ 5 files changed, 448 insertions(+), 163 deletions(-) create mode 100644 src/MagnumPlugins/CgltfImporter/Test/material-extras.gltf diff --git a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp index 3ccddc729..775d2c4b3 100644 --- a/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp +++ b/src/MagnumPlugins/CgltfImporter/CgltfImporter.cpp @@ -2228,6 +2228,144 @@ bool checkMaterialAttributeSize(Containers::StringView name, MaterialAttributeTy return true; } +Containers::Optional parseMaterialAttribute(Containers::StringView json, Containers::ArrayView tokens) { + std::size_t tokenIndex = 0; + + CORRADE_INTERNAL_ASSERT(tokens[tokenIndex].type == JSMN_STRING); + + Containers::StringView name = tokenString(json, tokens[tokenIndex]); + if(name.isEmpty()) { + Warning{} << "Trade::CgltfImporter::material(): property with an empty name, skipping"; + return Containers::NullOpt; + } + + ++tokenIndex; + const jsmntok_t& token = tokens[tokenIndex]; + + /* We only need temporary storage for parsing primitive (arrays) as bool/ + Float/Vector[2/3/4]. Other types/sizes are either converted or ignored, + so we know the upper limit on the data size. The alignas prevents + unaligned reads for individual floats. For strings, + MaterialAttributeData expects a pointer to StringView. */ + alignas(4) char attributeData[16]; + Containers::String attributeString; + Containers::StringView attributeStringView; + MaterialAttributeType type{}; + if(token.type == JSMN_OBJECT) { + /* Not parsing textureInfo objects here because they're only needed by + extensions but not by extras. They may also append more than one + attribute, so this is handled directly in the extension parsing + loop. */ + Warning{} << "Trade::CgltfImporter::material(): property" << name << "is an object, skipping"; + return Containers::NullOpt; + + /* A primitive is anything that's not a string, object or array. We ignore + non-primitive arrays, so we can handle both in one place. */ + } else if(token.type == JSMN_PRIMITIVE || token.type == JSMN_ARRAY) { + const UnsignedInt start = tokenIndex + UnsignedInt(token.type == JSMN_ARRAY); + /* Primitive token size is 0, but we can't use max() because + that would allow empty arrays */ + const UnsignedInt count = token.type == JSMN_PRIMITIVE ? 1 : token.size; + + /* No use importing arbitrarily-sized arrays of primitives, those are + currently not used in any glTF extension */ + if(count >= 1 && count <= 4) { + /* This still works for non-primitive array members (like objects + or nested arrays), because we instantly abort when we find one + of those, so we never need more than four tokens */ + for(const jsmntok_t& element: tokens.slice(start, start + count)) { + if(element.type != JSMN_PRIMITIVE) { + type = MaterialAttributeType{}; + break; + } + + /* Jsmn only checks the first character, which allows some + invalid values like nnn. We perform basic type detection and + invalid values result in 0. This matches cgltf behaviour. */ + const Containers::StringView value = tokenString(json, element); + if(value == "true"_s || value == "false"_s) { + /* MaterialAttributeType has no bool vectors, and + converting to a number needlessly complicates parsing + later. So far there is no glTF extension that uses bool + vectors. */ + if(count > 1) { + type = MaterialAttributeType{}; + break; + } + type = MaterialAttributeType::Bool; + } else if(value != "null"_s) { + /* Always interpret numbers as floats because the type can + be ambiguous. E.g. integer attributes may use exponent + notation and decimal points, making correct type + detection depend on glTF exporter behaviour. */ + type = MaterialAttributeType::Float; + } else { + type = MaterialAttributeType{}; + break; + } + } + } + + if(type == MaterialAttributeType{}) { + Warning{} << "Trade::CgltfImporter::material(): property" << name << "has unsupported type, skipping"; + return Containers::NullOpt; + } + + if(type == MaterialAttributeType::Float) { + constexpr MaterialAttributeType vectorType[4] { + MaterialAttributeType::Float, MaterialAttributeType::Vector2, + MaterialAttributeType::Vector3, MaterialAttributeType::Vector4 + }; + type = vectorType[count - 1]; + + Vector4& data = *reinterpret_cast(attributeData); + for(UnsignedInt i = 0; i != count; ++i) + data[i] = cgltf_json_to_float(&tokens[start + i], reinterpret_cast(json.data())); + + } else if(type == MaterialAttributeType::Bool) { + CORRADE_INTERNAL_ASSERT(count == 1); + bool& data = *reinterpret_cast(attributeData); + data = cgltf_json_to_bool(&tokens[start], reinterpret_cast(json.data())); + + } else + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + + } else if(token.type == JSMN_STRING) { + const Containers::StringView value = tokenString(json, token); + Containers::Optional decoded = decodeString(value); + if(decoded) { + attributeString = std::move(*decoded); + attributeStringView = attributeString; + } else + attributeStringView = value; + type = MaterialAttributeType::String; + + } else { + /* JSMN_UNDEFINED, should never happen for valid JSON files */ + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + + CORRADE_INTERNAL_ASSERT(type != MaterialAttributeType{}); + + const void* valuePointer = type == MaterialAttributeType::String ? + static_cast(&attributeStringView) : static_cast(attributeData); + if(!checkMaterialAttributeSize(name, type, valuePointer)) + return Containers::NullOpt; + + /* Uppercase attribute names are reserved. Standard glTF (extension) + attributes should all be lowercase but we don't have this guarantee for + extras attributes. Can't use String::nullTerminatedView() here because + JSON tokens are not null-terminated. */ + Containers::String nameLowercase; + if(!name.isEmpty() && std::isupper(static_cast(name.front()))) { + nameLowercase = name; + nameLowercase[0] = std::tolower(static_cast(name.front())); + name = nameLowercase; + } + + return MaterialAttributeData{name, type, valuePointer}; +} + } void CgltfImporter::Document::materialTexture(const cgltf_texture_view& texture, Containers::Array& attributes, Containers::StringView attribute, Containers::StringView matrixAttribute, Containers::StringView coordinateAttribute) const { @@ -2511,6 +2649,63 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i arrayAppend(attributes, InPlaceInit, MaterialAttribute::DiffuseTextureCoordinates, *diffuseTextureCoordinates); } + /* Extras -- application-specific data, added to the base layer */ + if(material.extras.start_offset) { + CORRADE_INTERNAL_ASSERT(material.extras.end_offset > material.extras.start_offset); + const Containers::StringView json{ + _d->data->json + material.extras.start_offset, + material.extras.end_offset - material.extras.start_offset}; + /* Theoretically extras can be any token type but the glTF spec + recommends objects for interoperability, makes our life easier, too: + https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#reference-extras + Cgltf directly gives us the JSON value string instead of a token so + we can only check the type like this. */ + if(json[0] == '{') { + const auto tokens = parseJson(json); + /* This is checked by jsmn */ + CORRADE_INTERNAL_ASSERT(!tokens.empty() && tokens[0].type == JSMN_OBJECT); + + UnsignedInt numAttributes = tokens[0].size; + Containers::Array attributeTokens; + Containers::arrayReserve(attributeTokens, numAttributes); + for(UnsignedInt t = 1; t + 1 < tokens.size();) { + /* This is checked by jsmn */ + CORRADE_INTERNAL_ASSERT(tokens[t].type == JSMN_STRING && tokens[t].size == 1); + Containers::arrayAppend(attributeTokens, InPlaceInit, t); + t = skipJson(tokens, t + 1); + } + + /* Sort and mark duplicates, those will be skipped later. We don't + need to cross-check for duplicates in the base layer because + those are all internal uppercase names and we make all names + lowercase. */ + std::stable_sort(attributeTokens.begin(), attributeTokens.end(), [&](UnsignedInt a, UnsignedInt b) { + return tokenString(json, tokens[a]) < tokenString(json, tokens[b]); + }); + + for(std::size_t i = 0; i + 1 < attributeTokens.size(); ++i) { + if(tokenString(json, tokens[attributeTokens[i]]) == tokenString(json, tokens[attributeTokens[i + 1]])) { + --numAttributes; + /* We can use 0 as an invalid token to mark attributes to skip. + Token 0 is always the extras object itself. */ + attributeTokens[i] = 0u; + } + } + + Containers::arrayReserve(attributes, attributes.size() + numAttributes); + for(UnsignedInt tokenIndex: attributeTokens) { + if(tokenIndex == 0u) continue; + + const Containers::Optional parsed = parseMaterialAttribute( + json, tokens.suffix(tokenIndex)); + if(parsed) + Containers::arrayAppend(attributes, *parsed); + } + + } else + Warning{} << "Trade::CgltfImporter::material(): extras property is not an object, skipping"; + } + /* Clear coat layer -- needs to be after all base material attributes */ if(material.has_clearcoat) { types |= MaterialType::PbrClearCoat; @@ -2782,25 +2977,13 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i for(UnsignedInt tokenIndex: attributeTokens) { if(tokenIndex == 0u) continue; - Containers::StringView name = tokenString(json, tokens[tokenIndex]); + const Containers::StringView name = tokenString(json, tokens[tokenIndex]); if(name.isEmpty()) { Warning{} << "Trade::CgltfImporter::material(): property with an empty name, skipping"; continue; } - ++tokenIndex; - const jsmntok_t& token = tokens[tokenIndex]; - - /* We only need temporary storage for parsing primitive (arrays) as - bool/Float/Vector[2/3/4]. Other types/sizes are either converted - or ignored, so we know the upper limit on the data size. The - alignas prevents unaligned reads for individual floats. For - strings, MaterialAttributeData expects a pointer to StringView. */ - alignas(4) char attributeData[16]; - Containers::String attributeString; - Containers::StringView attributeStringView; - MaterialAttributeType type{}; - if(token.type == JSMN_OBJECT) { + if(tokens[tokenIndex + 1].type == JSMN_OBJECT) { /* Parse glTF textureInfo objects. Any objects without the correct suffix and type are ignored. */ if(name.size() < 8 || !name.hasSuffix("Texture")) { @@ -2809,7 +2992,7 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i } cgltf_texture_view textureView{}; - const bool valid = cgltf_parse_json_texture_view(&_d->options, tokens, tokenIndex, + const bool valid = cgltf_parse_json_texture_view(&_d->options, tokens, tokenIndex + 1, reinterpret_cast(json.data()), &textureView) >= 0; /* Free memory allocated by cgltf. We're only interested in KHR_texture_transform and that's already parsed into @@ -2857,125 +3040,20 @@ Containers::Optional CgltfImporter::doMaterial(const UnsignedInt i scaleName, textureView.scale); } - /* We added all attributes, nothing left to do */ - continue; - - /* A primitive is anything that's not a string, object or array. We - ignore non-primitive arrays, so we can handle both in one place. */ - } else if(token.type == JSMN_PRIMITIVE || token.type == JSMN_ARRAY) { - const UnsignedInt start = tokenIndex + UnsignedInt(token.type == JSMN_ARRAY); - /* Primitive token size is 0, but we can't use max() because - that would allow empty arrays */ - const UnsignedInt count = token.type == JSMN_PRIMITIVE ? 1 : token.size; - - /* No use importing arbitrarily-sized arrays of primitives, - those are currently not used in any glTF extension */ - if(count >= 1 && count <= 4) { - /* This still works for non-primitive array members (like - objects or nested arrays), because we instantly abort - when we find one of those, so we never need more than - four tokens */ - for(const jsmntok_t& element: tokens.slice(start, start + count)) { - if(element.type != JSMN_PRIMITIVE) { - type = MaterialAttributeType{}; - break; - } - - /* Jsmn only checks the first character, which allows - some invalid values like nnn. We perform basic type - detection and invalid values result in 0. This - matches cgltf behaviour. */ - const Containers::StringView value = tokenString(json, element); - if(value == "true"_s || value == "false"_s) { - /* MaterialAttributeType has no bool vectors, and - converting to a number needlessly complicates - parsing later. So far there is no glTF extension - that uses bool vectors. */ - if(count > 1) { - type = MaterialAttributeType{}; - break; - } - type = MaterialAttributeType::Bool; - } else if(value != "null"_s) { - /* Always interpret numbers as floats because the - type can be ambiguous. E.g. integer attributes - may use exponent notation and decimal points, - making correct type detection depend on glTF - exporter behaviour. */ - type = MaterialAttributeType::Float; - } else { - type = MaterialAttributeType{}; - break; - } - } - } - - if(type == MaterialAttributeType{}) { - Warning{} << "Trade::CgltfImporter::material(): property" << name << "has unsupported type, skipping"; - continue; - } - - if(type == MaterialAttributeType::Float) { - constexpr MaterialAttributeType vectorType[4] { - MaterialAttributeType::Float, MaterialAttributeType::Vector2, - MaterialAttributeType::Vector3, MaterialAttributeType::Vector4 - }; - type = vectorType[count - 1]; - - Vector4& data = *reinterpret_cast(attributeData); - for(UnsignedInt i = 0; i != count; ++i) - data[i] = cgltf_json_to_float(&tokens[start + i], reinterpret_cast(json.data())); - - } else if(type == MaterialAttributeType::Bool) { - CORRADE_INTERNAL_ASSERT(count == 1); - bool& data = *reinterpret_cast(attributeData); - data = cgltf_json_to_bool(&tokens[start], reinterpret_cast(json.data())); - - } else - CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ - - } else if(token.type == JSMN_STRING) { - const Containers::StringView value = tokenString(json, token); - Containers::Optional decoded = decodeString(value); - if(decoded) { - attributeString = std::move(*decoded); - attributeStringView = attributeString; - } else - attributeStringView = value; - type = MaterialAttributeType::String; - } else { - /* JSMN_UNDEFINED, should never happen for valid JSON files */ - CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ - } - - CORRADE_INTERNAL_ASSERT(type != MaterialAttributeType{}); - - const void* valuePointer = type == MaterialAttributeType::String ? - static_cast(&attributeStringView) : static_cast(attributeData); - if(!checkMaterialAttributeSize(name, type, valuePointer)) - continue; - - /* Uppercase attribute names are reserved. This shouldn't really - happen since virtually all existing glTF extension attributes - are lowercase, but better to be safe. Can't use - String::nullTerminatedView() here because JSON tokens are not - null-terminated. */ - Containers::String nameLowercase; - if(!name.isEmpty() && std::isupper(static_cast(name.front()))) { - nameLowercase = name; - nameLowercase[0] = std::tolower(static_cast(name.front())); - name = nameLowercase; + /* All other attribute types: bool, numbers, strings */ + const Containers::Optional parsed = parseMaterialAttribute( + json, tokens.suffix(tokenIndex)); + if(parsed) + Containers::arrayAppend(extensionAttributes, *parsed); } - - arrayAppend(extensionAttributes, InPlaceInit, name, type, valuePointer); } /* Only add a layer if the extension contains any attributes */ if(!extensionAttributes.empty()) { /* Uppercase layer names are reserved. Since all extension names start with an uppercase vendor identifier, making the first - character lower-case seems silly, so we use a unique prefix. */ + character lowercase seems silly, so we use a unique prefix. */ Containers::String layerName{NoInit, extensionName.size() + 1}; Utility::formatInto(layerName, "#{}", extensionName); diff --git a/src/MagnumPlugins/CgltfImporter/CgltfImporter.h b/src/MagnumPlugins/CgltfImporter/CgltfImporter.h index dde04eb14..fe647de3f 100644 --- a/src/MagnumPlugins/CgltfImporter/CgltfImporter.h +++ b/src/MagnumPlugins/CgltfImporter/CgltfImporter.h @@ -338,6 +338,11 @@ fail. If you handle any of these custom material extensions, it may make sense to enable the @cb{.ini} ignoreRequiredExtensions @ce @ref Trade-CgltfImporter-configuration "configuration option". +- [Extras](https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#reference-extras) + metadata is imported into the base material layer. The `extras` attribute + must be an object. Type support is the same as for unrecognized material + extensions, except for [textureInfo](https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#reference-textureinfo) + objects which are ignored. - If the on-by-default @cb{.ini} phongMaterialFallback @ce @ref Trade-CgltfImporter-configuration "configuration option" is enabled, the importer provides a Phong fallback for backwards diff --git a/src/MagnumPlugins/CgltfImporter/Test/CMakeLists.txt b/src/MagnumPlugins/CgltfImporter/Test/CMakeLists.txt index dc14d394c..850a50e6b 100644 --- a/src/MagnumPlugins/CgltfImporter/Test/CMakeLists.txt +++ b/src/MagnumPlugins/CgltfImporter/Test/CMakeLists.txt @@ -121,6 +121,7 @@ corrade_add_test(CgltfImporterTest light-missing-spot.gltf material-common.gltf material-clearcoat.gltf + material-extras.gltf material-invalid-alpha-mode.gltf material-invalid-clearcoat-factor-oob.gltf material-invalid-clearcoat-normal-oob.gltf diff --git a/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp b/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp index fb137dae7..05b2be9e7 100644 --- a/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp +++ b/src/MagnumPlugins/CgltfImporter/Test/CgltfImporterTest.cpp @@ -149,6 +149,7 @@ struct CgltfImporterTest: TestSuite::Tester { void materialPbrSpecularGlossiness(); void materialCommon(); void materialUnlit(); + void materialExtras(); void materialClearCoat(); void materialPhongFallback(); void materialRaw(); @@ -776,6 +777,7 @@ CgltfImporterTest::CgltfImporterTest() { &CgltfImporterTest::materialPbrSpecularGlossiness, &CgltfImporterTest::materialCommon, &CgltfImporterTest::materialUnlit, + &CgltfImporterTest::materialExtras, &CgltfImporterTest::materialClearCoat, &CgltfImporterTest::materialPhongFallback, &CgltfImporterTest::materialRaw, @@ -3700,6 +3702,143 @@ void CgltfImporterTest::materialUnlit() { CORRADE_COMPARE(flat.texture(), 1); } +void compareMaterials(const MaterialData& actual, const MaterialData& expected) { + CORRADE_COMPARE(actual.types(), expected.types()); + CORRADE_COMPARE(actual.layerCount(), expected.layerCount()); + + for(UnsignedInt layer = 0; layer != expected.layerCount(); ++layer) { + CORRADE_ITERATION(expected.layerName(layer)); + CORRADE_COMPARE(actual.layerName(layer), expected.layerName(layer)); + CORRADE_COMPARE(actual.attributeCount(layer), expected.attributeCount(layer)); + for(UnsignedInt i = 0; i != expected.attributeCount(layer); ++i) { + const Containers::StringView name = expected.attributeName(layer, i); + CORRADE_ITERATION(name); + CORRADE_VERIFY(actual.hasAttribute(layer, name)); + const MaterialAttributeType type = expected.attributeType(layer, name); + CORRADE_COMPARE(actual.attributeType(layer, name), type); + switch(type) { + #define _v(type, valueType) case MaterialAttributeType::type: \ + CORRADE_COMPARE(actual.attribute(layer, name), expected.attribute(layer, name)); \ + break; + #define _c(type) _v(type, type) + _c(UnsignedInt) + _c(Float) + _c(Vector2) + _c(Vector3) + _c(Vector4) + _c(Matrix3x3) + _v(Bool, bool) + _v(String, Containers::StringView) + _v(TextureSwizzle, MaterialTextureSwizzle) + #undef _c + #undef _v + default: + CORRADE_FAIL_IF(true, "Unexpected attribute type" << type); + } + } + } +} + +void CgltfImporterTest::materialExtras() { + Containers::Pointer importer = _manager.instantiate("CgltfImporter"); + importer->configuration().setValue("phongMaterialFallback", false); + + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(CGLTFIMPORTER_TEST_DIR, + "material-extras.gltf"))); + + { + for(const char* name: {"primitive", "string", "array"}) { + CORRADE_ITERATION(name); + Containers::Optional material; + std::ostringstream out; + { + Warning redirectWarning{&out}; + material = importer->material(name); + } + CORRADE_VERIFY(material); + CORRADE_COMPARE(material->layerCount(), 1); + CORRADE_COMPARE(material->attributeCount(), 0); + + CORRADE_COMPARE(out.str(), "Trade::CgltfImporter::material(): extras property is not an object, skipping\n"); + } + } { + const char* name = "empty"; + CORRADE_ITERATION(name); + const auto material = importer->material(name); + CORRADE_VERIFY(material); + CORRADE_COMPARE(material->layerCount(), 1); + CORRADE_COMPARE(material->attributeCount(), 0); + } { + const char* name = "invalid"; + CORRADE_ITERATION(name); + Containers::Optional material; + std::ostringstream out; + { + Warning redirectWarning{&out}; + material = importer->material(name); + } + CORRADE_VERIFY(material); + /* All attributes are invalid and ignored */ + CORRADE_COMPARE(material->layerCount(), 1); + CORRADE_COMPARE(material->attributeCount(), 0); + + CORRADE_COMPARE(out.str(), + "Trade::CgltfImporter::material(): property with an empty name, skipping\n" + "Trade::CgltfImporter::material(): property aValueThatWontFit is too large with 84 bytes, skipping\n" + "Trade::CgltfImporter::material(): property anIncrediblyLongNameThatSadlyWontFitPaddingPaddingPadding!! is too large with 63 bytes, skipping\n" + "Trade::CgltfImporter::material(): property boolArray has unsupported type, skipping\n" + "Trade::CgltfImporter::material(): property emptyArray has unsupported type, skipping\n" + "Trade::CgltfImporter::material(): property mixedBoolArray has unsupported type, skipping\n" + "Trade::CgltfImporter::material(): property mixedObjectArray has unsupported type, skipping\n" + "Trade::CgltfImporter::material(): property mixedStringArray has unsupported type, skipping\n" + "Trade::CgltfImporter::material(): property nestedObject is an object, skipping\n" + "Trade::CgltfImporter::material(): property nestedObjectTexture is an object, skipping\n" + "Trade::CgltfImporter::material(): property null has unsupported type, skipping\n" + "Trade::CgltfImporter::material(): property oversizedArray has unsupported type, skipping\n" + "Trade::CgltfImporter::material(): property stringArray has unsupported type, skipping\n"); + } { + const char* name = "extras"; + CORRADE_ITERATION(name); + Containers::Optional material; + std::ostringstream out; + { + Warning redirectWarning{&out}; + material = importer->material(name); + } + CORRADE_VERIFY(material); + + const MaterialData expected{MaterialType::PbrMetallicRoughness|MaterialType::PbrClearCoat, { + {MaterialAttribute::BaseColor, Color4{0.8f, 0.2f, 0.4f, 0.3f}}, + {MaterialAttribute::BaseColorTexture, 0u}, + {MaterialAttribute::DoubleSided, true}, + + /* Extras are in the base layer */ + {"boolTrue"_s, true}, + {"boolFalse"_s, false}, + {"int"_s, -7992835.0f}, + {"unsignedInt"_s, 109835761.0f}, + {"float"_s, 4.321f}, + {"string"_s, "Ribbit -- ribbit"_s}, + {"encodedString"_s, "마이크 체크"_s}, + {"emptyString"_s, ""_s}, + {"doubleSided"_s, false}, + {"vec1"_s, 91.2f}, + {"vec2"_s, Vector2{9.0f, 8.0f}}, + {"vec3"_s, Vector3{9.0f, 0.08f, 7.3141f}}, + {"vec4"_s, Vector4{-9.0f, 8.0f, 7.0f, -6.0f}}, + {"duplicate"_s, true}, + + {Trade::MaterialAttribute::LayerName, "ClearCoat"_s}, + {MaterialAttribute::LayerFactor, 0.5f}, + {MaterialAttribute::Roughness, 0.0f} + }, {17, 20}}; + + compareMaterials(*material, expected); + + CORRADE_COMPARE(out.str(), "Trade::CgltfImporter::material(): property invalid has unsupported type, skipping\n"); + } +} + void CgltfImporterTest::materialClearCoat() { Containers::Pointer importer = _manager.instantiate("CgltfImporter"); @@ -3947,43 +4086,6 @@ void CgltfImporterTest::materialPhongFallback() { } } -void compareMaterials(const MaterialData& actual, const MaterialData& expected) { - CORRADE_COMPARE(actual.types(), expected.types()); - CORRADE_COMPARE(actual.layerCount(), expected.layerCount()); - - for(UnsignedInt layer = 0; layer != expected.layerCount(); ++layer) { - CORRADE_ITERATION(expected.layerName(layer)); - CORRADE_COMPARE(actual.layerName(layer), expected.layerName(layer)); - CORRADE_COMPARE(actual.attributeCount(layer), expected.attributeCount(layer)); - for(UnsignedInt i = 0; i != expected.attributeCount(layer); ++i) { - const Containers::StringView name = expected.attributeName(layer, i); - CORRADE_ITERATION(name); - CORRADE_VERIFY(actual.hasAttribute(layer, name)); - const MaterialAttributeType type = expected.attributeType(layer, name); - CORRADE_COMPARE(actual.attributeType(layer, name), type); - switch(type) { - #define _v(type, valueType) case MaterialAttributeType::type: \ - CORRADE_COMPARE(actual.attribute(layer, name), expected.attribute(layer, name)); \ - break; - #define _c(type) _v(type, type) - _c(UnsignedInt) - _c(Float) - _c(Vector2) - _c(Vector3) - _c(Vector4) - _c(Matrix3x3) - _v(Bool, bool) - _v(String, Containers::StringView) - _v(TextureSwizzle, MaterialTextureSwizzle) - #undef _c - #undef _v - default: - CORRADE_FAIL_IF(true, "Unexpected attribute type" << type); - } - } - } -} - void CgltfImporterTest::materialRaw() { Containers::Pointer importer = _manager.instantiate("CgltfImporter"); importer->configuration().setValue("phongMaterialFallback", false); diff --git a/src/MagnumPlugins/CgltfImporter/Test/material-extras.gltf b/src/MagnumPlugins/CgltfImporter/Test/material-extras.gltf new file mode 100644 index 000000000..3d88a9ec5 --- /dev/null +++ b/src/MagnumPlugins/CgltfImporter/Test/material-extras.gltf @@ -0,0 +1,99 @@ +{ + "asset": { + "version": "2.0" + }, + "materials": [ + { + "name": "primitive", + "extras": 0.5 + }, + { + "name": "string", + "extras": "Ignored" + }, + { + "name": "array", + "extras": [ 1.2 ] + }, + { + "name": "empty", + "extras": {} + }, + { + "name": "invalid", + "extras": { + "nestedObject": { + "this": "should be ignored" + }, + "nestedObjectTexture": { + "this": "should also be ignored", + "index": 0 + }, + "null": null, + "emptyArray": [], + "oversizedArray": [9.1, 8.2, 7.3, 6.4, 5.5], + "stringArray": ["one", "two", "", "four"], + "boolArray": [true, false], + "mixedStringArray": ["1", "2", 3, "4"], + "mixedBoolArray": [1, 2, 3, false], + "mixedObjectArray": [1, 2, 3, {"4": "5", "6": "7"}], + "anIncrediblyLongNameThatSadlyWontFitPaddingPaddingPadding!!": 0, + "aValueThatWontFit": "1 pound ground turkey, 1 medium-sized onion, 1 cup celery (diced)", + "": "Empty attributes are ignored" + } + }, + { + "name": "extras", + "pbrMetallicRoughness": { + "baseColorFactor": [0.8, 0.2, 0.4, 0.3], + "baseColorTexture": { + "index": 0 + } + }, + "doubleSided": true, + "extensions": { + "KHR_materials_clearcoat": { + "clearcoatFactor": 0.5 + } + }, + "extras": { + "duplicate": "overwritten by later values", + "boolTrue": true, + "boolFalse": false, + "int": -7992835, + "unsignedInt": 109835761, + "float": 4.321, + "string": "Ribbit -- ribbit", + "encodedString": "\uB9C8\uC774\uD06C \uCCB4\uD06C", + "emptyString": "", + "DoubleSided": false, + "invalid": ["just checking if it keeps parsing after a warning"], + "vec1": [9.12e+1], + "vec2": [ + 9 , + 8 ], + "vec3": [9, 8E-2, 7.3141], + "vec4": [-9, 8, 7, -6], + "duplicate": 0.2, + "duplicate": true + } + } + ], + "samplers": [ + {} + ], + "textures": [ + { + "sampler": 0, + "source": 0 + } + ], + "images": [ + { + "uri": "nonexistent.png" + } + ], + "extensionsUsed": [ + "KHR_materials_clearcoat" + ] +}