diff --git a/src/Magnum/MaterialTools/CMakeLists.txt b/src/Magnum/MaterialTools/CMakeLists.txt index 77d3bbe07..1767b90ad 100644 --- a/src/Magnum/MaterialTools/CMakeLists.txt +++ b/src/Magnum/MaterialTools/CMakeLists.txt @@ -33,10 +33,12 @@ set(MagnumMaterialTools_SRCS # Files compiled with different flags for main library and unit test library set(MagnumMaterialTools_GracefulAssert_SRCS - Filter.cpp) + Filter.cpp + Merge.cpp) set(MagnumMaterialTools_HEADERS Filter.h + Merge.h PhongToPbrMetallicRoughness.h visibility.h) diff --git a/src/Magnum/MaterialTools/Merge.cpp b/src/Magnum/MaterialTools/Merge.cpp new file mode 100644 index 000000000..bb5d71d6e --- /dev/null +++ b/src/Magnum/MaterialTools/Merge.cpp @@ -0,0 +1,120 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include "Merge.h" + +#include +#include + +#include "Magnum/Trade/MaterialData.h" + +namespace Magnum { namespace MaterialTools { + +Containers::Optional merge(const Trade::MaterialData& first, const Trade::MaterialData& second, MergeConflicts conflicts) { + Containers::Array attributes; + arrayReserve(attributes, first.attributeData().size() + second.attributeData().size()); + + Containers::Array layers{NoInit, Math::max(first.layerCount(), second.layerCount())}; + + /* Go over all layers that are in both materials */ + std::size_t layer = 0; + for(const std::size_t layerMin = Math::min(first.layerCount(), second.layerCount()); layer != layerMin; ++layer) { + UnsignedInt attributeFirst = 0; + UnsignedInt attributeSecond = 0; + + /* Take the earliest-sorted attribute from either material */ + while(attributeFirst != first.attributeCount(layer) && attributeSecond != second.attributeCount(layer)) { + /* Attributes have the same name */ + /** @todo add StringView::compare() returning -1, 0, 1 and do the + comparison just once instead of three times */ + if(first.attributeName(layer, attributeFirst) == second.attributeName(layer, attributeSecond)) { + /* Fail if we are told to not merge attributes of the same + name */ + if(conflicts == MergeConflicts::Fail) { + Error{} << "MaterialTools::merge(): conflicting attribute" << first.attributeName(layer, attributeFirst) << "in layer" << layer; + return {}; + } + + /* Fail if we are told to not merge attributes of the same name + but different type */ + if(first.attributeType(layer, attributeFirst) != second.attributeType(layer, attributeSecond) && conflicts == MergeConflicts::KeepFirstIfSameType) { + Error{} << "MaterialTools::merge(): conflicting type" << first.attributeType(layer, attributeFirst) << "vs" << Debug::packed << second.attributeType(layer, attributeSecond) << "of attribute" << first.attributeName(layer, attributeFirst) << "in layer" << layer; + return {}; + } + + /* Add the first attribute, ignore the second */ + arrayAppend(attributes, first.attributeData(layer, attributeFirst)); + ++attributeFirst; + ++attributeSecond; + + /* The attribute from first material should go first */ + } else if(first.attributeName(layer, attributeFirst) < second.attributeName(layer, attributeSecond)) { + arrayAppend(attributes, first.attributeData(layer, attributeFirst)); + ++attributeFirst; + + /* The attribute from second material should go first */ + } else if(first.attributeName(layer, attributeFirst) > second.attributeName(layer, attributeSecond)) { + arrayAppend(attributes, second.attributeData(layer, attributeSecond)); + ++attributeSecond; + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + + /* Consume remaining leftover attributes in either. Only one of these + loops get entered. */ + while(attributeFirst < first.attributeCount(layer)) { + arrayAppend(attributes, first.attributeData(layer, attributeFirst)); + ++attributeFirst; + } + while(attributeSecond < second.attributeCount(layer)) { + arrayAppend(attributes, second.attributeData(layer, attributeSecond)); + ++attributeSecond; + } + + layers[layer] = attributes.size(); + } + + /* Go over remaining layers which weren't in the other attribute and + add them as a whole. Only one of these loops get entered. */ + for(; layer < first.layerCount(); ++layer) { + arrayAppend(attributes, first.attributeData().slice( + first.attributeDataOffset(layer), + first.attributeDataOffset(layer + 1))); + + layers[layer] = attributes.size(); + } + for(; layer < second.layerCount(); ++layer) { + arrayAppend(attributes, second.attributeData().slice( + second.attributeDataOffset(layer), + second.attributeDataOffset(layer + 1))); + + layers[layer] = attributes.size(); + } + + CORRADE_INTERNAL_ASSERT(layer == layers.size()); + + return Trade::MaterialData{first.types()|second.types(), std::move(attributes), std::move(layers)}; +} + +}} diff --git a/src/Magnum/MaterialTools/Merge.h b/src/Magnum/MaterialTools/Merge.h new file mode 100644 index 000000000..00a58d076 --- /dev/null +++ b/src/Magnum/MaterialTools/Merge.h @@ -0,0 +1,96 @@ +#ifndef Magnum_MaterialTools_Merge_h +#define Magnum_MaterialTools_Merge_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +/** @file + * @brief Function @ref Magnum::MaterialTools::merge(), enum @ref Magnum::MaterialTools::MergeConflicts + * @m_since_latest + */ + +#include "Magnum/MaterialTools/visibility.h" +#include "Magnum/Trade/Trade.h" + +namespace Magnum { namespace MaterialTools { + +/** +@brief Material merge conflict resolution +@m_since_latest + +@see @ref merge() +*/ +enum MergeConflicts: UnsignedInt { + /** + * Print a message to @relativeref{Magnum,Error} and return + * @ref Containers::NullOpt in case both materials contain an attribute of + * the same name in the same layer index. Neither its type nor its value is + * checked, so this fails also in case the values are the same. + */ + Fail, + + /** + * Keep the value from the first material in case both materials contain an + * attribute of the same name in the same layer index and both attributes + * have the same type. Print a message to @relativeref{Magnum,Error} and + * return @ref Containers::NullOpt if they have a different type, for + * example in case of custom attributes. + * + * If you want to keep the value from the second material instead, call + * @ref merge() with this option and the materials swapped. + */ + KeepFirstIfSameType, + + /** + * Keep the value from the first material in case both materials contain an + * attribute of the same name in the same layer index, regardless of their + * type. With this option the operation always succeeds. + * + * If you want to keep the value from the second material instead, call + * @ref merge() with this option and the materials swapped. + */ + KeepFirstIgnoreType +}; + +/** +@brief Merge two materials +@m_since_latest + +Takes attributes from @p second and inserts them to layers of the same index in +@p first. If @p second has more layers than @p first, the additional layers are +added at the end of @p first. @ref Trade::MaterialTypes from @p first and +@p second are merged together. If both materials contain an attribute of the +same name in the same layer index, conflict resolution is performed according +to the @p conflicts option. + +As the input materials have the attributes sorted already, the operation is +done in an @f$ \mathcal{O}(m + n) @f$ execution time and memory complexity, +with @f$ m @f$ and @f$ n @f$ being count of all attributes and layers in +@p first and @p second, respectively. +*/ +Containers::Optional MAGNUM_MATERIALTOOLS_EXPORT merge(const Trade::MaterialData& first, const Trade::MaterialData& second, MergeConflicts conflicts = MergeConflicts::Fail); + +}} + +#endif diff --git a/src/Magnum/MaterialTools/Test/CMakeLists.txt b/src/Magnum/MaterialTools/Test/CMakeLists.txt index 6b6a12c73..8bffb1d70 100644 --- a/src/Magnum/MaterialTools/Test/CMakeLists.txt +++ b/src/Magnum/MaterialTools/Test/CMakeLists.txt @@ -28,4 +28,5 @@ set(CMAKE_FOLDER "Magnum/MaterialTools/Test") corrade_add_test(MaterialToolsFilterTest FilterTest.cpp LIBRARIES MagnumDebugTools MagnumMaterialToolsTestLib) +corrade_add_test(MaterialToolsMergeTest MergeTest.cpp LIBRARIES MagnumDebugTools MagnumMaterialToolsTestLib) corrade_add_test(MaterialToolsPhongToPbrMetall___Test PhongToPbrMetallicRoughnessTest.cpp LIBRARIES MagnumDebugTools MagnumMaterialTools) diff --git a/src/Magnum/MaterialTools/Test/MergeTest.cpp b/src/Magnum/MaterialTools/Test/MergeTest.cpp new file mode 100644 index 000000000..a98cfc67b --- /dev/null +++ b/src/Magnum/MaterialTools/Test/MergeTest.cpp @@ -0,0 +1,348 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include +#include + +#include "Magnum/DebugTools/CompareMaterial.h" +#include "Magnum/Math/Color.h" +#include "Magnum/MaterialTools/Merge.h" +#include "Magnum/Trade/MaterialData.h" + +namespace Magnum { namespace MaterialTools { namespace Test { namespace { + +struct MergeTest: TestSuite::Tester { + explicit MergeTest(); + + void singleLayer(); + + void multipleLayersIntoSingleLayer(); + void multipleLayers(); + + void conflictsSameType(); + void conflictsDifferentType(); + void conflictsFail(); + + void emptyInput(); +}; + +using namespace Math::Literals; + +MergeTest::MergeTest() { + addTests({&MergeTest::singleLayer, + + &MergeTest::multipleLayersIntoSingleLayer, + &MergeTest::multipleLayers, + + &MergeTest::conflictsSameType, + &MergeTest::conflictsDifferentType, + &MergeTest::conflictsFail, + + &MergeTest::emptyInput}); +} + +void MergeTest::singleLayer() { + Trade::MaterialData a{Trade::MaterialType::PbrMetallicRoughness, { + {Trade::MaterialAttribute::BaseColor, 0xffcc66ff_rgbaf}, + /* These two go at the end */ + {Trade::MaterialAttribute::RoughnessTexture, 7u}, + {Trade::MaterialAttribute::Roughness, 0.3f} + }}; + + Trade::MaterialData b{Trade::MaterialType::Phong|Trade::MaterialType::Flat, { + /* This attribute goes first */ + {Trade::MaterialAttribute::AlphaBlend, true}, + {Trade::MaterialAttribute::DiffuseColor, 0x808080ff_rgbaf}, + {Trade::MaterialAttribute::BaseColorTexture, 3u}, + {Trade::MaterialAttribute::BaseColorTextureCoordinates, 2u}, + }}; + + Trade::MaterialData expected{Trade::MaterialType::PbrMetallicRoughness|Trade::MaterialType::Phong|Trade::MaterialType::Flat, { + {Trade::MaterialAttribute::AlphaBlend, true}, + {Trade::MaterialAttribute::BaseColor, 0xffcc66ff_rgbaf}, + {Trade::MaterialAttribute::BaseColorTexture, 3u}, + {Trade::MaterialAttribute::BaseColorTextureCoordinates, 2u}, + {Trade::MaterialAttribute::DiffuseColor, 0x808080ff_rgbaf}, + {Trade::MaterialAttribute::Roughness, 0.3f}, + {Trade::MaterialAttribute::RoughnessTexture, 7u}, + }}; + + /* It should give the same result both ways */ + { + Containers::Optional actual = merge(a, b); + CORRADE_VERIFY(actual); + CORRADE_COMPARE_AS(*actual, expected, DebugTools::CompareMaterial); + } { + Containers::Optional actual = merge(b, a); + CORRADE_VERIFY(actual); + CORRADE_COMPARE_AS(*actual, expected, DebugTools::CompareMaterial); + } +} + +void MergeTest::multipleLayersIntoSingleLayer() { + Trade::MaterialData a{Trade::MaterialType::PbrMetallicRoughness, { + {Trade::MaterialAttribute::BaseColor, 0xffcc66ff_rgbaf}, + {Trade::MaterialAttribute::RoughnessTexture, 7u}, + {Trade::MaterialAttribute::Roughness, 0.3f}, + }}; + + Trade::MaterialData b{Trade::MaterialType::PbrClearCoat|Trade::MaterialType::PbrMetallicRoughness, { + {Trade::MaterialAttribute::AlphaBlend, true}, + /* These two layers are only in this material */ + {Trade::MaterialLayer::ClearCoat}, + {Trade::MaterialAttribute::LayerFactor, 0.1f}, + {Trade::MaterialAttribute::Roughness, 0.5f}, + {Trade::MaterialAttribute::LayerFactor, 1.0f}, + {"layerBlendApproach", "irreversibly"} + }, {1, 4, 6}}; + + Trade::MaterialData expected{Trade::MaterialType::PbrMetallicRoughness|Trade::MaterialType::PbrClearCoat, { + {Trade::MaterialAttribute::AlphaBlend, true}, + {Trade::MaterialAttribute::BaseColor, 0xffcc66ff_rgbaf}, + {Trade::MaterialAttribute::Roughness, 0.3f}, + {Trade::MaterialAttribute::RoughnessTexture, 7u}, + {Trade::MaterialLayer::ClearCoat}, + {Trade::MaterialAttribute::LayerFactor, 0.1f}, + {Trade::MaterialAttribute::Roughness, 0.5f}, + {Trade::MaterialAttribute::LayerFactor, 1.0f}, + {"layerBlendApproach", "irreversibly"} + }, {4, 7, 9}}; + + /* It should give the same result both ways */ + { + Containers::Optional actual = merge(a, b); + CORRADE_VERIFY(actual); + CORRADE_COMPARE_AS(*actual, expected, DebugTools::CompareMaterial); + } { + Containers::Optional actual = merge(b, a); + CORRADE_VERIFY(actual); + CORRADE_COMPARE_AS(*actual, expected, DebugTools::CompareMaterial); + } +} + +void MergeTest::multipleLayers() { + Trade::MaterialData a{Trade::MaterialType::PbrMetallicRoughness, { + {Trade::MaterialAttribute::BaseColor, 0xffcc66ff_rgbaf}, + {Trade::MaterialAttribute::RoughnessTexture, 7u}, + {Trade::MaterialAttribute::Roughness, 0.3f}, + /* This layer has no name but it'll get it from the other material + (and that's fine) */ + {Trade::MaterialAttribute::LayerFactor, 0.1f}, + {Trade::MaterialAttribute::Roughness, 0.5f}, + }, {3, 5}}; + + Trade::MaterialData b{Trade::MaterialType::PbrClearCoat|Trade::MaterialType::PbrMetallicRoughness, { + {Trade::MaterialAttribute::AlphaBlend, true}, + /* These two layers are only in this material */ + {Trade::MaterialLayer::ClearCoat}, + /* Here's an empty layer that ends up being empty as well */ + {Trade::MaterialAttribute::LayerFactor, 1.0f}, + {"layerBlendApproach", "irreversibly"} + }, {1, 2, 2, 4}}; + + Trade::MaterialData expected{Trade::MaterialType::PbrMetallicRoughness|Trade::MaterialType::PbrClearCoat, { + {Trade::MaterialAttribute::AlphaBlend, true}, + {Trade::MaterialAttribute::BaseColor, 0xffcc66ff_rgbaf}, + {Trade::MaterialAttribute::Roughness, 0.3f}, + {Trade::MaterialAttribute::RoughnessTexture, 7u}, + {Trade::MaterialLayer::ClearCoat}, + {Trade::MaterialAttribute::LayerFactor, 0.1f}, + {Trade::MaterialAttribute::Roughness, 0.5f}, + /* Empty layer here */ + {Trade::MaterialAttribute::LayerFactor, 1.0f}, + {"layerBlendApproach", "irreversibly"} + }, {4, 7, 7, 9}}; + + /* It should give the same result both ways */ + { + Containers::Optional actual = merge(a, b); + CORRADE_VERIFY(actual); + CORRADE_COMPARE_AS(*actual, expected, DebugTools::CompareMaterial); + } { + Containers::Optional actual = merge(b, a); + CORRADE_VERIFY(actual); + CORRADE_COMPARE_AS(*actual, expected, DebugTools::CompareMaterial); + } +} + +void MergeTest::conflictsSameType() { + Trade::MaterialData a{Trade::MaterialType::PbrMetallicRoughness, { + {Trade::MaterialAttribute::BaseColor, 0xffcc66ff_rgbaf}, + {Trade::MaterialAttribute::RoughnessTexture, 7u}, + /* Second layer */ + {Trade::MaterialAttribute::Roughness, 0.3f}, + {"customAttribute", 15.0f}, + }, {2, 4}}; + + Trade::MaterialData b{Trade::MaterialType::PbrClearCoat, { + {Trade::MaterialAttribute::Roughness, 0.5f}, + {Trade::MaterialAttribute::RoughnessTexture, 777u}, + {Trade::MaterialAttribute::LayerFactor, 1.0f}, + {"customAttribute", 223.0f}, + }, {2, 4}}; + + /* If called swapped it'll use the other values */ + { + Containers::Optional actual = merge(a, b, MergeConflicts::KeepFirstIfSameType); + CORRADE_VERIFY(actual); + CORRADE_COMPARE_AS(*actual, (Trade::MaterialData {Trade::MaterialType::PbrMetallicRoughness|Trade::MaterialType::PbrClearCoat, { + {Trade::MaterialAttribute::BaseColor, 0xffcc66ff_rgbaf}, + {Trade::MaterialAttribute::Roughness, 0.5f}, + {Trade::MaterialAttribute::RoughnessTexture, 7u}, + {Trade::MaterialAttribute::LayerFactor, 1.0f}, + {Trade::MaterialAttribute::Roughness, 0.3f}, + {"customAttribute", 15.0f}, + }, {3, 6}}), DebugTools::CompareMaterial); + } { + Containers::Optional actual = merge(b, a, MergeConflicts::KeepFirstIfSameType); + CORRADE_VERIFY(actual); + CORRADE_COMPARE_AS(*actual, (Trade::MaterialData {Trade::MaterialType::PbrMetallicRoughness|Trade::MaterialType::PbrClearCoat, { + {Trade::MaterialAttribute::BaseColor, 0xffcc66ff_rgbaf}, + {Trade::MaterialAttribute::Roughness, 0.5f}, + {Trade::MaterialAttribute::RoughnessTexture, 777u}, /* different */ + {Trade::MaterialAttribute::LayerFactor, 1.0f}, + {Trade::MaterialAttribute::Roughness, 0.3f}, + {"customAttribute", 223.0f}, /* different */ + }, {3, 6}}), DebugTools::CompareMaterial); + } +} + +void MergeTest::conflictsDifferentType() { + Trade::MaterialData a{Trade::MaterialType::PbrMetallicRoughness, { + {Trade::MaterialAttribute::BaseColor, 0xffcc66ff_rgbaf}, + {Trade::MaterialAttribute::RoughnessTexture, 7u}, + /* Second layer */ + {Trade::MaterialAttribute::Roughness, 0.3f}, + {"customAttribute", 15.0f}, + }, {2, 4}}; + + /* Builtin attributes have an enforced type so this can only happen with + custom ones. It should however handle (builtin) attributes of the same + type as well */ + Trade::MaterialData b{Trade::MaterialType::PbrClearCoat, { + {Trade::MaterialAttribute::Roughness, 0.5f}, + {Trade::MaterialAttribute::RoughnessTexture, 777u}, + {Trade::MaterialAttribute::LayerFactor, 1.0f}, + {"customAttribute", "hello!"}, + }, {2, 4}}; + + /* If called swapped it'll use the other values */ + { + Containers::Optional actual = merge(a, b, MergeConflicts::KeepFirstIgnoreType); + CORRADE_VERIFY(actual); + CORRADE_COMPARE_AS(*actual, (Trade::MaterialData {Trade::MaterialType::PbrMetallicRoughness|Trade::MaterialType::PbrClearCoat, { + {Trade::MaterialAttribute::BaseColor, 0xffcc66ff_rgbaf}, + {Trade::MaterialAttribute::Roughness, 0.5f}, + {Trade::MaterialAttribute::RoughnessTexture, 7u}, + {Trade::MaterialAttribute::LayerFactor, 1.0f}, + {Trade::MaterialAttribute::Roughness, 0.3f}, + {"customAttribute", 15.0f}, + }, {3, 6}}), DebugTools::CompareMaterial); + } { + Containers::Optional actual = merge(b, a, MergeConflicts::KeepFirstIgnoreType); + CORRADE_VERIFY(actual); + CORRADE_COMPARE_AS(*actual, (Trade::MaterialData {Trade::MaterialType::PbrMetallicRoughness|Trade::MaterialType::PbrClearCoat, { + {Trade::MaterialAttribute::BaseColor, 0xffcc66ff_rgbaf}, + {Trade::MaterialAttribute::Roughness, 0.5f}, + {Trade::MaterialAttribute::RoughnessTexture, 777u}, /* different */ + {Trade::MaterialAttribute::LayerFactor, 1.0f}, + {Trade::MaterialAttribute::Roughness, 0.3f}, + {"customAttribute", "hello!"}, /* different */ + }, {3, 6}}), DebugTools::CompareMaterial); + } +} + +void MergeTest::conflictsFail() { + CORRADE_SKIP_IF_NO_ASSERT(); + + Trade::MaterialData a{{}, { + {Trade::MaterialAttribute::BaseColor, 0xffcc66ff_rgbaf}, + {Trade::MaterialAttribute::RoughnessTexture, 7u}, + /* Second layer */ + {Trade::MaterialAttribute::Roughness, 0.3f}, + {"customAttribute", 15.0f}, + }, {2, 4}}; + + /* Contains Roughness but in another layer (which should be fine), + but has a conflicitng BaseColorTexture even though it's the same + value */ + Trade::MaterialData b{{}, { + {Trade::MaterialAttribute::Roughness, 0.5f}, + {Trade::MaterialAttribute::RoughnessTexture, 7u}, + {Trade::MaterialAttribute::LayerFactor, 1.0f}, + }, {2, 3}}; + + /* Contains customAttribute in second layer which is of a different type. + The RoughnessTexture is also conflicting but that shouldn't produce a + message since it's the same type. */ + Trade::MaterialData c{{}, { + {Trade::MaterialAttribute::Roughness, 0.5f}, + {Trade::MaterialAttribute::RoughnessTexture, 1u}, + {"customAttribute", "hello"}, + }, {2, 3}}; + + /* Verify that it fails in all variants */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!merge(a, b)); + CORRADE_VERIFY(!merge(b, a)); + CORRADE_VERIFY(!merge(a, c, MergeConflicts::KeepFirstIfSameType)); + CORRADE_VERIFY(!merge(c, a, MergeConflicts::KeepFirstIfSameType)); + CORRADE_COMPARE(out.str(), + "MaterialTools::merge(): conflicting attribute RoughnessTexture in layer 0\n" + "MaterialTools::merge(): conflicting attribute RoughnessTexture in layer 0\n" + "MaterialTools::merge(): conflicting type Trade::MaterialAttributeType::Float vs String of attribute customAttribute in layer 1\n" + "MaterialTools::merge(): conflicting type Trade::MaterialAttributeType::String vs Float of attribute customAttribute in layer 1\n"); +} + +void MergeTest::emptyInput() { + Trade::MaterialData a{Trade::MaterialType::PbrMetallicRoughness, { + {Trade::MaterialAttribute::BaseColor, 0xffcc66ff_rgbaf}, + {Trade::MaterialAttribute::Roughness, 0.3f}, + {Trade::MaterialLayer::ClearCoat}, + {Trade::MaterialAttribute::LayerFactor, 0.1f}, + }, {2, 4}}; + + Trade::MaterialData empty{Trade::MaterialType::PbrClearCoat, {}}; + + /* The result has just the types changed, nothing else */ + Trade::MaterialData expected{Trade::MaterialType::PbrMetallicRoughness|Trade::MaterialType::PbrClearCoat, {}, a.attributeData(), {}, a.layerData()}; + + /* It should give the same result both ways */ + { + Containers::Optional actual = merge(a, empty); + CORRADE_VERIFY(actual); + CORRADE_COMPARE_AS(*actual, expected, DebugTools::CompareMaterial); + } { + Containers::Optional actual = merge(empty, a); + CORRADE_VERIFY(actual); + CORRADE_COMPARE_AS(*actual, expected, DebugTools::CompareMaterial); + } +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::MaterialTools::Test::MergeTest)