Browse Source

MaterialTools: removeDuplicates() and variants.

Usage pattern is very similar to the duplicate removal utilities in
MeshTools, except that here the process is a simple O(n^2 m) operation,
without any hashing. Turned out to be good enough, in the initial tests
at least.
pull/168/head
Vladimír Vondruš 2 years ago
parent
commit
c659c47525
  1. 4
      src/Magnum/MaterialTools/CMakeLists.txt
  2. 165
      src/Magnum/MaterialTools/RemoveDuplicates.cpp
  3. 124
      src/Magnum/MaterialTools/RemoveDuplicates.h
  4. 1
      src/Magnum/MaterialTools/Test/CMakeLists.txt
  5. 661
      src/Magnum/MaterialTools/Test/RemoveDuplicatesTest.cpp

4
src/Magnum/MaterialTools/CMakeLists.txt

@ -35,13 +35,15 @@ set(MagnumMaterialTools_SRCS
# Files compiled with different flags for main library and unit test library
set(MagnumMaterialTools_GracefulAssert_SRCS
Filter.cpp
Merge.cpp)
Merge.cpp
RemoveDuplicates.cpp)
set(MagnumMaterialTools_HEADERS
Copy.h
Filter.h
Merge.h
PhongToPbrMetallicRoughness.h
RemoveDuplicates.h
visibility.h)

165
src/Magnum/MaterialTools/RemoveDuplicates.cpp

@ -0,0 +1,165 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021, 2022, 2023 Vladimír Vondruš <mosra@centrum.cz>
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 "RemoveDuplicates.h"
#include <Corrade/Containers/Iterable.h>
#include <Corrade/Containers/Pair.h>
#include <Corrade/Containers/StridedArrayView.h>
#include "Magnum/MaterialTools/Implementation/attributesEqual.h"
#include "Magnum/Trade/MaterialData.h"
namespace Magnum { namespace MaterialTools {
namespace {
bool materialEqual(const Trade::MaterialData& a, const Trade::MaterialData& b) {
/* Check if types match */
if(a.types() != b.types())
return false;
/* If one has layer data implicit and the other has just one layer, they're
equivalent */
if(!(a.layerData().isEmpty() && b.layerData().size() == 1 && a.attributeData().size() == b.layerData()[0]) &&
!(b.layerData().isEmpty() && a.layerData().size() == 1 && b.attributeData().size() == a.layerData()[0]))
{
/* Otherwise, check if layer count matches */
if(a.layerData().size() != b.layerData().size())
return false;
/* And if layer data match */
if(const Containers::ArrayView<const UnsignedInt> layerData = a.layerData()) {
for(UnsignedInt layer = 0; layer != layerData.size(); ++layer) {
if(b.layerData()[layer] != layerData[layer])
return false;
}
}
}
/* Check if attribute count matches */
if(a.attributeData().size() != b.attributeData().size())
return false;
/* Check if attribute data match */
for(UnsignedInt attribute = 0; attribute != a.attributeData().size(); ++attribute) {
if(a.attributeData()[attribute].name() != b.attributeData()[attribute].name() ||
a.attributeData()[attribute].type() != b.attributeData()[attribute].type() ||
!Implementation::attributesEqual(a.attributeData()[attribute], b.attributeData()[attribute]))
return false;
}
return true;
}
}
std::size_t removeDuplicatesInPlaceInto(const Containers::Iterable<Trade::MaterialData>& materials, const Containers::StridedArrayView1D<UnsignedInt>& mapping) {
CORRADE_ASSERT(mapping.size() == materials.size(),
"MaterialTools::removeDuplicatesInPlaceInto(): bad output size, expected" << materials.size() << "but got" << mapping.size(), {});
/* O(n^2). As there's a lot of early returns, should be fine for a moderate
count of materials that differ in a significant way. Won't work well for
materials that are all the same except one attribute value. */
std::size_t uniqueCount = 0;
for(std::size_t i = 0; i != materials.size(); ++i) {
/* Find a material that's already in the unique set */
Containers::Optional<UnsignedInt> found;
for(std::size_t j = 0; j != uniqueCount; ++j) {
if(materialEqual(materials[i], materials[j])) {
found = j;
break;
}
}
/* Material found, reference its ID */
if(found) {
mapping[i] = *found;
/* Move the material into its new location, unless it's the same
index, and increase the number of unique materials */
} else {
if(uniqueCount != i)
materials[uniqueCount] = Utility::move(materials[i]);
mapping[i] = uniqueCount++;
}
}
return uniqueCount;
}
Containers::Pair<Containers::Array<UnsignedInt>, std::size_t> removeDuplicatesInPlace(const Containers::Iterable<Trade::MaterialData>& materials) {
Containers::Array<UnsignedInt> out{NoInit, materials.size()};
const std::size_t uniqueCount = removeDuplicatesInPlaceInto(materials, out);
return {Utility::move(out), uniqueCount};
}
std::size_t removeDuplicatesInto(const Containers::Iterable<const Trade::MaterialData>& materials, const Containers::StridedArrayView1D<UnsignedInt>& mapping) {
CORRADE_ASSERT(mapping.size() == materials.size(),
"MaterialTools::removeDuplicatesInto(): bad output size, expected" << materials.size() << "but got" << mapping.size(), {});
/* O(n^2). Like removeDuplicatesInPlaceInto(), but as the input material
list is immutable, it has to go through the already-processed prefix
and compare only against materials that are unique, which may add some
extra overhead. Another option would be to allocate a temporary array
with (contiguous) references to the material data, but so far I think
the prefix iteration is efficient enough to not need that. */
std::size_t uniqueCount = 0;
for(std::size_t i = 0; i != materials.size(); ++i) {
/* Find a material that's already in the unique set by going through
the already-processed prefix and comparing only against materials
that are unique, i.e. for which the output index is the same as the
input index. */
Containers::Optional<UnsignedInt> found;
for(std::size_t j = 0; j != i; ++j) {
if(mapping[j] == j && materialEqual(materials[i], materials[j])) {
found = j;
break;
}
}
/* Material found, reference its ID */
if(found) {
mapping[i] = *found;
/* Otherwise the output index the same as the input index. Also
increase the number of unique materials which isn't used for
anything here except the return value. */
} else {
mapping[i] = i;
uniqueCount++;
}
}
return uniqueCount;
}
Containers::Pair<Containers::Array<UnsignedInt>, std::size_t> removeDuplicates(const Containers::Iterable<const Trade::MaterialData>& materials) {
Containers::Array<UnsignedInt> out{NoInit, materials.size()};
const std::size_t uniqueCount = removeDuplicatesInto(materials, out);
return {Utility::move(out), uniqueCount};
}
}}

124
src/Magnum/MaterialTools/RemoveDuplicates.h

@ -0,0 +1,124 @@
#ifndef Magnum_MaterialTools_RemoveDuplicates_h
#define Magnum_MaterialTools_RemoveDuplicates_h
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021, 2022, 2023 Vladimír Vondruš <mosra@centrum.cz>
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::removeDuplicatesInPlace(), @ref Magnum::MaterialTools::removeDuplicatesInPlaceInto(), @ref Magnum::MaterialTools::removeDuplicates(), @ref Magnum::MaterialTools::removeDuplicatesInto()
* @m_since_latest
*/
#include <Corrade/Containers/Containers.h>
#include "Magnum/MaterialTools/visibility.h"
#include "Magnum/Trade/Trade.h"
namespace Magnum { namespace MaterialTools {
/**
@brief Remove duplicate materials from a list in-place
@param[in,out] materials List of materials
@return Index array to map the original material indices to the output indices
and size of the unique prefix in the cleaned up @p materials array
@m_since_latest
Removes duplicate materials from the input by comparing material types,
attribute names, types and values and layer offsets. Floating-point attribute
values are compared using fuzzy comparison. Importer state and data flags
aren't considered when comparing the materials. Unique materials are shifted to
the front with order preserved, the returned mapping array has the same size as
the @p materials list and maps from the original indices to prefix of the
output. See @ref removeDuplicates() for a variant that doesn't modify the input
list in any way but instead returns a mapping array pointing to original data
locations.
The operation is done in an @f$ \mathcal{O}(n^2 m) @f$ complexity with
@f$ n @f$ being the material list size and @f$ m @f$ the per-material attribute
count --- every material in the list is compared to all unique materials
collected so far. As attributes are sorted in @ref Trade::MaterialData,
material comparison is just a linear operation. The function doesn't allocate
any temporary memory.
@see @ref removeDuplicatesInPlaceInto()
*/
MAGNUM_MATERIALTOOLS_EXPORT Containers::Pair<Containers::Array<UnsignedInt>, std::size_t> removeDuplicatesInPlace(const Containers::Iterable<Trade::MaterialData>& materials);
/**
@brief Remove duplicate materials from a list in-place and put mapping into given output array
@param[in,out] materials List of materials
@param[out] mapping Where to put the resulting mapping array
@return Size of the unique prefix in the cleaned up @p materials array
@m_since_latest
Like @ref removeDuplicatesInPlace() but puts the mapping indices into
@p mapping instead of allocating a new array. Expects that @p mapping has the
same size as @p materials.
@see @ref removeDuplicatesInto()
*/
MAGNUM_MATERIALTOOLS_EXPORT std::size_t removeDuplicatesInPlaceInto(const Containers::Iterable<Trade::MaterialData>& materials, const Containers::StridedArrayView1D<UnsignedInt>& mapping);
/**
@brief Remove duplicate materials from a list
@param[in] materials List of materials
@return Array to map the original material indices to unique materials and size
of the unique prefix in the cleaned up @p materials array
@m_since_latest
Removes duplicate materials from the input by comparing material types,
attribute names, types and values and layer offsets. Floating-point attribute
values are compared using fuzzy comparison. Importer state and data flags
aren't considered when comparing the materials. The returned mapping array has
the same size as the @p materials list and maps from the original indices to
only unique materials in the input array. See @ref removeDuplicatesInPlace()
for a variant that also shifts the unique materials to the front of the list.
The operation is done in an @f$ \mathcal{O}(n^2 m) @f$ complexity with
@f$ n @f$ being the material list size and @f$ m @f$ the per-material attribute
count --- every material in the list is compared to all unique materials
collected so far, by iterating the filled prefix of the output index list and
considering only index for which the index value is the same as the index. As
attributes are sorted in @ref Trade::MaterialData, material comparison is just
a linear operation. The function doesn't allocate any temporary memory.
@see @ref removeDuplicatesInto()
*/
MAGNUM_MATERIALTOOLS_EXPORT Containers::Pair<Containers::Array<UnsignedInt>, std::size_t> removeDuplicates(const Containers::Iterable<const Trade::MaterialData>& materials);
/**
@brief Remove duplicate materials from a list in-place and put mapping into given output array
@param[in,out] materials List of materials
@param[out] mapping Where to put the resulting mapping array
@return Size of the unique prefix in the cleaned up @p materials array
@m_since_latest
Like @ref removeDuplicates() but puts the mapping indices into @p mapping
instead of allocating a new array. Expects that @p mapping has the same size as
@p materials.
@see @ref removeDuplicatesInPlaceInto()
*/
MAGNUM_MATERIALTOOLS_EXPORT std::size_t removeDuplicatesInto(const Containers::Iterable<const Trade::MaterialData>& materials, const Containers::StridedArrayView1D<UnsignedInt>& mapping);
}}
#endif

1
src/Magnum/MaterialTools/Test/CMakeLists.txt

@ -30,4 +30,5 @@ set(CMAKE_FOLDER "Magnum/MaterialTools/Test")
corrade_add_test(MaterialToolsCopyTest CopyTest.cpp LIBRARIES MagnumMaterialTools)
corrade_add_test(MaterialToolsFilterTest FilterTest.cpp LIBRARIES MagnumDebugTools MagnumMaterialToolsTestLib)
corrade_add_test(MaterialToolsMergeTest MergeTest.cpp LIBRARIES MagnumDebugTools MagnumMaterialToolsTestLib)
corrade_add_test(MaterialToolsRemoveDuplicatesTest RemoveDuplicatesTest.cpp LIBRARIES MagnumDebugTools MagnumMaterialToolsTestLib)
corrade_add_test(MaterialToolsPhongToPbrMetall___Test PhongToPbrMetallicRoughnessTest.cpp LIBRARIES MagnumDebugTools MagnumMaterialTools)

661
src/Magnum/MaterialTools/Test/RemoveDuplicatesTest.cpp

@ -0,0 +1,661 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021, 2022, 2023 Vladimír Vondruš <mosra@centrum.cz>
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 <sstream> /** @todo remove once Debug is stream-free */
#include <Corrade/Containers/Iterable.h>
#include <Corrade/Containers/StridedArrayView.h>
#include <Corrade/TestSuite/Tester.h>
#include <Corrade/TestSuite/Compare/Container.h>
#include <Corrade/Utility/DebugStl.h> /** @todo remove once Debug is stream-free */
#include "Magnum/DebugTools/CompareMaterial.h"
#include "Magnum/Math/Color.h"
#include "Magnum/Math/Matrix3.h"
#include "Magnum/MaterialTools/RemoveDuplicates.h"
#include "Magnum/Trade/MaterialData.h"
namespace Magnum { namespace MaterialTools { namespace Test { namespace {
struct RemoveDuplicatesTest: TestSuite::Tester {
explicit RemoveDuplicatesTest();
void empty();
void emptyMaterial();
void emptyMaterialLayers();
void differentAttributeName();
void differentAttributeType();
void differentAttributeValue();
void differentAttributeValueFuzzy();
void extraAttributes();
void implicitBaseLayerSize();
void multipleLayersSameContents();
void multipleLayersDifferentContents();
/* All cases above test the removeDuplicatesInto() variant, these the
remaining 3 */
void asArray();
void inPlace();
void inPlaceAsArray();
void invalidSize();
};
using namespace Math::Literals;
constexpr Int A = 3, B = 4;
constexpr const Int *PointerA = &A, *PointerB = &B;
const struct {
const char* name;
Trade::MaterialAttributeData attribute, different;
} DifferentAttributeValueData[]{
{"bool",
{Trade::MaterialAttribute::AlphaBlend, true},
{Trade::MaterialAttribute::AlphaBlend, false}},
{"scalar",
{Trade::MaterialAttribute::BaseColorTexture, 2u},
{Trade::MaterialAttribute::BaseColorTexture, 1u}},
{"vector",
{"objectIds", Vector3ui{3, 7, 9}},
{"objectIds", Vector3ui{3, 6, 9}}},
/* Matrices are only floating-point, tested in
DifferentAttributeValueFuzzyData instead */
{"pointer",
/* Takes a pointer to a pointer, not the pointer itself */
{"objectPtr", &PointerA},
{"objectPtr", &PointerB}},
{"mutable pointer",
/* The pointed-to-locations aren't actually mutable, but as the value
isn't used anywhere it should be okay */
{"objectPtr", Trade::MaterialAttributeType::MutablePointer, &PointerA},
{"objectPtr", Trade::MaterialAttributeType::MutablePointer, &PointerB}},
{"string",
{"name", "hellO"},
{"name", "hell0"}},
{"buffer",
{"data", Containers::ArrayView<const void>{"\x0a\x0b\x0c"}},
{"data", Containers::ArrayView<const void>{"\x0a\x0c\x0b"}}},
};
const struct {
const char* name;
Trade::MaterialAttributeData attribute, same, different;
} DifferentAttributeValueFuzzyData[]{
{"scalar",
{Trade::MaterialAttribute::Roughness, 0.7f},
{Trade::MaterialAttribute::Roughness, 0.7f + Math::TypeTraits<Float>::epsilon()*0.5f},
{Trade::MaterialAttribute::Roughness, 0.7f + Math::TypeTraits<Float>::epsilon()*2.0f}},
{"vector",
{Trade::MaterialAttribute::BaseColor, Vector4{0.5f, 0.9f, 0.7f, 0.9f}},
{Trade::MaterialAttribute::BaseColor, Vector4{0.5f, 0.9f, 0.7f + Math::TypeTraits<Float>::epsilon()*0.5f, 0.9f}},
{Trade::MaterialAttribute::BaseColor, Vector4{0.5f, 0.9f, 0.7f + Math::TypeTraits<Float>::epsilon()*2.0f, 0.9f}}},
{"matrix",
{Trade::MaterialAttribute::TextureMatrix,
Matrix3::translation({5.0f, 9.0f})},
{Trade::MaterialAttribute::TextureMatrix,
Matrix3::translation({5.0f, 9.0f + Math::TypeTraits<Float>::epsilon()*5.0f})},
{Trade::MaterialAttribute::TextureMatrix,
Matrix3::translation({5.0f, 9.0f + Math::TypeTraits<Float>::epsilon()*20.0f})}},
};
RemoveDuplicatesTest::RemoveDuplicatesTest() {
addTests({&RemoveDuplicatesTest::empty,
&RemoveDuplicatesTest::emptyMaterial,
&RemoveDuplicatesTest::emptyMaterialLayers,
&RemoveDuplicatesTest::differentAttributeName,
&RemoveDuplicatesTest::differentAttributeType});
addInstancedTests({&RemoveDuplicatesTest::differentAttributeValue},
Containers::arraySize(DifferentAttributeValueData));
addInstancedTests({&RemoveDuplicatesTest::differentAttributeValueFuzzy},
Containers::arraySize(DifferentAttributeValueFuzzyData));
addTests({&RemoveDuplicatesTest::extraAttributes,
&RemoveDuplicatesTest::implicitBaseLayerSize,
&RemoveDuplicatesTest::multipleLayersSameContents,
&RemoveDuplicatesTest::multipleLayersDifferentContents,
&RemoveDuplicatesTest::asArray,
&RemoveDuplicatesTest::inPlace,
&RemoveDuplicatesTest::inPlaceAsArray,
&RemoveDuplicatesTest::invalidSize});
}
void RemoveDuplicatesTest::empty() {
CORRADE_COMPARE(removeDuplicatesInPlaceInto({}, {}), 0);
}
void RemoveDuplicatesTest::emptyMaterial() {
const Trade::MaterialData materials[]{
Trade::MaterialData{Trade::MaterialType::PbrClearCoat, {}},
Trade::MaterialData{Trade::MaterialType::PbrMetallicRoughness|Trade::MaterialType::PbrClearCoat, {}},
Trade::MaterialData{Trade::MaterialType::PbrClearCoat, {}},
Trade::MaterialData{{}, {}},
Trade::MaterialData{Trade::MaterialType::PbrMetallicRoughness|Trade::MaterialType::PbrClearCoat, {}},
/* This one has an importer state compared to the first. It's ignored
so it should also be treated as the same. */
Trade::MaterialData{Trade::MaterialType::PbrClearCoat, {}, &A},
};
UnsignedInt mapping[6];
CORRADE_COMPARE(removeDuplicatesInto(materials, mapping), 3);
CORRADE_COMPARE_AS(Containers::arrayView(mapping), Containers::arrayView({
0u, 1u, 0u, 3u, 1u, 0u
}), TestSuite::Compare::Container);
}
void RemoveDuplicatesTest::emptyMaterialLayers() {
const Trade::MaterialData materials[]{
Trade::MaterialData{Trade::MaterialType::PbrClearCoat, {}, {0, 0}},
Trade::MaterialData{Trade::MaterialType::PbrClearCoat, {}, {0}},
Trade::MaterialData{Trade::MaterialType::PbrClearCoat, {}, {0, 0}},
/* This one has the same prefix as the first but different count */
Trade::MaterialData{Trade::MaterialType::PbrClearCoat, {}, {0, 0, 0}},
/* This one is the same as second but has different type so it
shouldn't match */
Trade::MaterialData{{}, {}, {0}},
/* This one is the same as the second, it just has the base layer size
implicit */
Trade::MaterialData{Trade::MaterialType::PbrClearCoat, {}},
};
UnsignedInt mapping[6];
CORRADE_COMPARE(removeDuplicatesInto(materials, mapping), 4);
CORRADE_COMPARE_AS(Containers::arrayView(mapping), Containers::arrayView({
0u, 1u, 0u, 3u, 4u, 1u
}), TestSuite::Compare::Container);
}
void RemoveDuplicatesTest::differentAttributeName() {
const Trade::MaterialData materials[]{
Trade::MaterialData{Trade::MaterialType::Flat, {
{Trade::MaterialAttribute::BaseColor, 0xff3366aa_rgbaf},
{Trade::MaterialAttribute::Metalness, 0.3f},
{Trade::MaterialAttribute::SpecularColor, 0x66779900_rgbaf},
}},
/* This one has the same attribute value and type at the same position
but different attribute name, should be treated as different. Both
instances of it are the same tho, so they should be treated as
same. */
Trade::MaterialData{Trade::MaterialType::Flat, {
{Trade::MaterialAttribute::BaseColor, 0xff3366aa_rgbaf},
{Trade::MaterialAttribute::Roughness, 0.3f},
{Trade::MaterialAttribute::SpecularColor, 0x66779900_rgbaf},
}},
Trade::MaterialData{Trade::MaterialType::Flat, {
{Trade::MaterialAttribute::BaseColor, 0xff3366aa_rgbaf},
{Trade::MaterialAttribute::Roughness, 0.3f},
{Trade::MaterialAttribute::SpecularColor, 0x66779900_rgbaf},
}},
/* This one should be treated as equivalent to the first */
Trade::MaterialData{Trade::MaterialType::Flat, {
{Trade::MaterialAttribute::BaseColor, 0xff3366aa_rgbaf},
{Trade::MaterialAttribute::Metalness, 0.3f},
{Trade::MaterialAttribute::SpecularColor, 0x66779900_rgbaf},
}},
/* This one has everything the same as the first but has a different
type, should be treated different also */
Trade::MaterialData{Trade::MaterialType::PbrMetallicRoughness, {
{Trade::MaterialAttribute::BaseColor, 0xff3366aa_rgbaf},
{Trade::MaterialAttribute::Metalness, 0.3f},
{Trade::MaterialAttribute::SpecularColor, 0x66779900_rgbaf},
}},
};
UnsignedInt mapping[5];
CORRADE_COMPARE(removeDuplicatesInto(materials, mapping), 3);
CORRADE_COMPARE_AS(Containers::arrayView(mapping), Containers::arrayView({
0u, 1u, 1u, 0u, 4u
}), TestSuite::Compare::Container);
}
void RemoveDuplicatesTest::differentAttributeType() {
const Trade::MaterialData materials[]{
Trade::MaterialData{Trade::MaterialType::Flat, {
{Trade::MaterialAttribute::BaseColor, 0xff3366aa_rgbaf},
{Trade::MaterialAttribute::Metalness, 0.0f},
{Trade::MaterialAttribute::SpecularColor, 0x66779900_rgbaf},
}},
/* This one has the same attribute name and bit-exact value at the same
position but different attribute name, should be treated as
different */
Trade::MaterialData{Trade::MaterialType::Flat, {
{Trade::MaterialAttribute::BaseColor, 0xff3366aa_rgbaf},
{Trade::MaterialAttribute::MetalnessTexture, 0u},
{Trade::MaterialAttribute::SpecularColor, 0x66779900_rgbaf},
}},
/* This one should be treated as equivalent to the first */
Trade::MaterialData{Trade::MaterialType::Flat, {
{Trade::MaterialAttribute::BaseColor, 0xff3366aa_rgbaf},
{Trade::MaterialAttribute::Metalness, 0.0f},
{Trade::MaterialAttribute::SpecularColor, 0x66779900_rgbaf},
}},
/* This one has everything the same as the third first but has a
different type for the last attribute, should be treated different
also */
Trade::MaterialData{Trade::MaterialType::PbrMetallicRoughness, {
{Trade::MaterialAttribute::BaseColor, 0xff3366aa_rgbaf},
{Trade::MaterialAttribute::Metalness, 0.0f},
/* Different type allowed only with a string name, not with enum */
{"SpecularColor", "brown"},
}},
};
UnsignedInt mapping[4];
CORRADE_COMPARE(removeDuplicatesInto(materials, mapping), 3);
CORRADE_COMPARE_AS(Containers::arrayView(mapping), Containers::arrayView({
0u, 1u, 0u, 3u
}), TestSuite::Compare::Container);
}
void RemoveDuplicatesTest::differentAttributeValue() {
auto&& data = DifferentAttributeValueData[testCaseInstanceId()];
setTestCaseDescription(data.name);
const Trade::MaterialData materials[]{
Trade::MaterialData{{}, {
{Trade::MaterialAttribute::BaseColorTextureCoordinates, 3u},
{Trade::MaterialAttribute::Glossiness, 3.7f},
data.attribute
}},
Trade::MaterialData{{}, {
{Trade::MaterialAttribute::BaseColorTextureCoordinates, 3u},
{Trade::MaterialAttribute::Glossiness, 3.7f},
data.different
}},
/* It's sorted on construction, so this should compare equal */
Trade::MaterialData{{}, {
{Trade::MaterialAttribute::BaseColorTextureCoordinates, 3u},
data.attribute,
{Trade::MaterialAttribute::Glossiness, 3.7f},
}},
};
UnsignedInt mapping[3];
CORRADE_COMPARE(removeDuplicatesInto(materials, mapping), 2);
CORRADE_COMPARE_AS(Containers::arrayView(mapping), Containers::arrayView({
0u, 1u, 0u
}), TestSuite::Compare::Container);
}
void RemoveDuplicatesTest::differentAttributeValueFuzzy() {
auto&& data = DifferentAttributeValueFuzzyData[testCaseInstanceId()];
setTestCaseDescription(data.name);
const Trade::MaterialData materials[]{
Trade::MaterialData{{}, {
{Trade::MaterialAttribute::TextureCoordinates, 3u},
{Trade::MaterialAttribute::AlphaMask, 0.7f},
data.attribute
}},
Trade::MaterialData{{}, {
{Trade::MaterialAttribute::TextureCoordinates, 3u},
{Trade::MaterialAttribute::AlphaMask, 0.7f},
data.different
}},
/* Not bit-exact but should be treated as the same */
Trade::MaterialData{{}, {
{Trade::MaterialAttribute::TextureCoordinates, 3u},
{Trade::MaterialAttribute::AlphaMask, 0.7f},
data.same
}},
};
UnsignedInt mapping[3];
CORRADE_COMPARE(removeDuplicatesInto(materials, mapping), 2);
CORRADE_COMPARE_AS(Containers::arrayView(mapping), Containers::arrayView({
0u, 1u, 0u
}), TestSuite::Compare::Container);
}
void RemoveDuplicatesTest::extraAttributes() {
const Trade::MaterialData materials[]{
Trade::MaterialData{Trade::MaterialType::Flat, {
{Trade::MaterialAttribute::BaseColor, 0xff3366aa_rgbaf},
{Trade::MaterialAttribute::Metalness, 0.3f},
{Trade::MaterialAttribute::SpecularColor, 0x66779900_rgbaf},
}},
/* This one has the same attribute prefix as the first but one more
attribute after, should be treated as different */
Trade::MaterialData{Trade::MaterialType::Flat, {
{Trade::MaterialAttribute::BaseColor, 0xff3366aa_rgbaf},
{Trade::MaterialAttribute::Metalness, 0.3f},
{Trade::MaterialAttribute::SpecularColor, 0x66779900_rgbaf},
{Trade::MaterialAttribute::TextureLayer, 0u},
}},
/* This one has the same attribute prefix as the second, but one
attribute less, should be treated as different */
Trade::MaterialData{Trade::MaterialType::Flat, {
{Trade::MaterialAttribute::BaseColor, 0xff3366aa_rgbaf},
{Trade::MaterialAttribute::Roughness, 0.3f}
}},
/* This one is the same again, just with (ignored) importer state */
Trade::MaterialData{Trade::MaterialType::Flat, {
{Trade::MaterialAttribute::BaseColor, 0xff3366aa_rgbaf},
{Trade::MaterialAttribute::Metalness, 0.3f},
{Trade::MaterialAttribute::SpecularColor, 0x66779900_rgbaf},
}, {}, &B},
};
UnsignedInt mapping[4];
CORRADE_COMPARE(removeDuplicatesInto(materials, mapping), 3);
CORRADE_COMPARE_AS(Containers::arrayView(mapping), Containers::arrayView({
0u, 1u, 2u, 0u
}), TestSuite::Compare::Container);
}
void RemoveDuplicatesTest::implicitBaseLayerSize() {
const Trade::MaterialData materials[]{
/* Implicit layer size after explicit, should be treated the same. Not
the one at the end though, which has a different attribute value. */
Trade::MaterialData{Trade::MaterialType::Flat, {
{Trade::MaterialAttribute::BaseColor, 0xff3366aa_rgbaf},
{Trade::MaterialAttribute::Metalness, 0.0f},
{Trade::MaterialAttribute::SpecularColor, 0x66779900_rgbaf},
}, {3}},
Trade::MaterialData{Trade::MaterialType::Flat, {
{Trade::MaterialAttribute::BaseColor, 0xff3366aa_rgbaf},
{Trade::MaterialAttribute::Metalness, 0.0f},
{Trade::MaterialAttribute::SpecularColor, 0x66779900_rgbaf},
}},
Trade::MaterialData{Trade::MaterialType::Flat, {
{Trade::MaterialAttribute::BaseColor, 0xff3366aa_rgbaf},
{Trade::MaterialAttribute::Metalness, 0.3f},
{Trade::MaterialAttribute::SpecularColor, 0x66779900_rgbaf},
}},
/* Explicit layer size after implicit, should be treated the same. Not
the one in the middle though, which has a different attribute
value. */
Trade::MaterialData{{}, {
{Trade::MaterialAttribute::TextureCoordinates, 3u},
{Trade::MaterialAttribute::AlphaMask, 0.7f},
}},
Trade::MaterialData{{}, {
{Trade::MaterialAttribute::TextureCoordinates, 4u},
{Trade::MaterialAttribute::AlphaMask, 0.7f},
}, {2}},
Trade::MaterialData{{}, {
{Trade::MaterialAttribute::TextureCoordinates, 3u},
{Trade::MaterialAttribute::AlphaMask, 0.7f},
}, {2}},
};
UnsignedInt mapping[6];
CORRADE_COMPARE(removeDuplicatesInto(materials, mapping), 4);
CORRADE_COMPARE_AS(Containers::arrayView(mapping), Containers::arrayView({
0u, 0u, 2u, 3u, 4u, 3u
}), TestSuite::Compare::Container);
}
void RemoveDuplicatesTest::multipleLayersSameContents() {
const Trade::MaterialAttributeData attributes[]{
{Trade::MaterialAttribute::AlphaBlend, false},
{Trade::MaterialAttribute::AlphaMask, 0.7f},
{Trade::MaterialAttribute::BaseColor, 0xff3366aa_rgbaf},
{Trade::MaterialAttribute::Metalness, 0.3f},
{Trade::MaterialAttribute::SpecularColor, 0x66779900_rgbaf},
{Trade::MaterialAttribute::TextureCoordinates, 3u},
};
const UnsignedInt layers[]{3, 6};
const Trade::MaterialData materials[]{
/* The attributes are deliberately ordered alphabetically to ensure
they retain the same order even if different layers */
Trade::MaterialData{{}, {
{Trade::MaterialAttribute::AlphaBlend, false},
{Trade::MaterialAttribute::AlphaMask, 0.7f},
{Trade::MaterialAttribute::BaseColor, 0xff3366aa_rgbaf},
{Trade::MaterialAttribute::Metalness, 0.3f},
{Trade::MaterialAttribute::SpecularColor, 0x66779900_rgbaf},
{Trade::MaterialAttribute::TextureCoordinates, 3u},
}, {2, 6}},
/* The first layer has 3 elements instead of 2, should be different */
Trade::MaterialData{{}, {
{Trade::MaterialAttribute::AlphaBlend, false},
{Trade::MaterialAttribute::AlphaMask, 0.7f},
{Trade::MaterialAttribute::BaseColor, 0xff3366aa_rgbaf},
{Trade::MaterialAttribute::Metalness, 0.3f},
{Trade::MaterialAttribute::SpecularColor, 0x66779900_rgbaf},
{Trade::MaterialAttribute::TextureCoordinates, 3u},
}, {3, 6}},
/* There's an empty base layer before, should be different */
Trade::MaterialData{{}, {
{Trade::MaterialAttribute::AlphaBlend, false},
{Trade::MaterialAttribute::AlphaMask, 0.7f},
{Trade::MaterialAttribute::BaseColor, 0xff3366aa_rgbaf},
{Trade::MaterialAttribute::Metalness, 0.3f},
{Trade::MaterialAttribute::SpecularColor, 0x66779900_rgbaf},
{Trade::MaterialAttribute::TextureCoordinates, 3u},
}, {0, 2, 6}},
/* There's an empty layer at the end, should be different */
Trade::MaterialData{{}, {
{Trade::MaterialAttribute::AlphaBlend, false},
{Trade::MaterialAttribute::AlphaMask, 0.7f},
{Trade::MaterialAttribute::BaseColor, 0xff3366aa_rgbaf},
{Trade::MaterialAttribute::Metalness, 0.3f},
{Trade::MaterialAttribute::SpecularColor, 0x66779900_rgbaf},
{Trade::MaterialAttribute::TextureCoordinates, 3u},
}, {2, 6, 6}},
/* Same as the second, just with externally owned data */
Trade::MaterialData{{}, {}, attributes, {}, layers},
/* Everything in one layer, should be different */
Trade::MaterialData{{}, {}, attributes},
};
UnsignedInt mapping[6];
CORRADE_COMPARE(removeDuplicatesInto(materials, mapping), 5);
CORRADE_COMPARE_AS(Containers::arrayView(mapping), Containers::arrayView({
0u, 1u, 2u, 3u, 1u, 5u
}), TestSuite::Compare::Container);
}
void RemoveDuplicatesTest::multipleLayersDifferentContents() {
const Trade::MaterialData materials[]{
/* Same thing, twice */
Trade::MaterialData{{}, {
{Trade::MaterialAttribute::AlphaBlend, false},
{Trade::MaterialAttribute::AlphaMask, 0.7f},
{Trade::MaterialAttribute::BaseColor, 0xff3366aa_rgbaf},
{Trade::MaterialAttribute::Metalness, 0.3f},
{Trade::MaterialAttribute::SpecularColor, 0x66779900_rgbaf},
{Trade::MaterialAttribute::TextureCoordinates, 3u},
}, {3, 4, 6}},
Trade::MaterialData{{}, {
{Trade::MaterialAttribute::AlphaBlend, false},
{Trade::MaterialAttribute::AlphaMask, 0.7f},
{Trade::MaterialAttribute::BaseColor, 0xff3366aa_rgbaf},
{Trade::MaterialAttribute::Metalness, 0.3f},
{Trade::MaterialAttribute::SpecularColor, 0x66779900_rgbaf},
{Trade::MaterialAttribute::TextureCoordinates, 3u},
}, {3, 4, 6}},
/* Same layer order, different value in one layer. Should be treated
as different. */
Trade::MaterialData{{}, {
{Trade::MaterialAttribute::AlphaBlend, false},
{Trade::MaterialAttribute::AlphaMask, 0.7f},
{Trade::MaterialAttribute::BaseColor, 0xff3366aa_rgbaf},
{Trade::MaterialAttribute::Metalness, 0.4f},
{Trade::MaterialAttribute::SpecularColor, 0x66779900_rgbaf},
{Trade::MaterialAttribute::TextureCoordinates, 3u},
}, {3, 4, 6}},
};
UnsignedInt mapping[3];
CORRADE_COMPARE(removeDuplicatesInto(materials, mapping), 2);
CORRADE_COMPARE_AS(Containers::arrayView(mapping), Containers::arrayView({
0u, 0u, 2u
}), TestSuite::Compare::Container);
}
void RemoveDuplicatesTest::asArray() {
const Trade::MaterialData materials[]{
Trade::MaterialData{Trade::MaterialType::PbrMetallicRoughness, {
{Trade::MaterialAttribute::BaseColorTexture, 2u},
}},
Trade::MaterialData{Trade::MaterialType::PbrMetallicRoughness, {
{Trade::MaterialAttribute::Roughness, 0.3f},
{Trade::MaterialAttribute::SpecularColor, 0x66779900_rgbaf},
}},
Trade::MaterialData{Trade::MaterialType::PbrMetallicRoughness, {
{Trade::MaterialAttribute::BaseColorTexture, 2u},
}},
Trade::MaterialData{Trade::MaterialType::Flat, {}},
Trade::MaterialData{Trade::MaterialType::PbrMetallicRoughness, {
{Trade::MaterialAttribute::Roughness, 0.3f},
{Trade::MaterialAttribute::SpecularColor, 0x66779900_rgbaf},
}},
Trade::MaterialData{Trade::MaterialType::Flat, {}},
Trade::MaterialData{{}, {
{Trade::MaterialAttribute::AlphaBlend, false},
{Trade::MaterialAttribute::AlphaMask, 0.7f},
}}
};
Containers::Pair<Containers::Array<UnsignedInt>, std::size_t> out = removeDuplicates(materials);
CORRADE_COMPARE(out.second(), 4);
CORRADE_COMPARE_AS(out.first(), Containers::arrayView({
0u, 1u, 0u, 3u, 1u, 3u, 6u
}), TestSuite::Compare::Container);
}
void RemoveDuplicatesTest::inPlace() {
Trade::MaterialData materials[]{
Trade::MaterialData{Trade::MaterialType::PbrMetallicRoughness, {
{Trade::MaterialAttribute::BaseColorTexture, 2u},
}},
Trade::MaterialData{Trade::MaterialType::PbrMetallicRoughness, {
{Trade::MaterialAttribute::Roughness, 0.3f},
{Trade::MaterialAttribute::SpecularColor, 0x66779900_rgbaf},
}},
Trade::MaterialData{Trade::MaterialType::PbrMetallicRoughness, {
{Trade::MaterialAttribute::BaseColorTexture, 2u},
}},
Trade::MaterialData{Trade::MaterialType::Flat, {}},
Trade::MaterialData{Trade::MaterialType::PbrMetallicRoughness, {
{Trade::MaterialAttribute::Roughness, 0.3f},
{Trade::MaterialAttribute::SpecularColor, 0x66779900_rgbaf},
}},
Trade::MaterialData{Trade::MaterialType::Flat, {}},
Trade::MaterialData{{}, {
{Trade::MaterialAttribute::AlphaBlend, false},
{Trade::MaterialAttribute::AlphaMask, 0.7f},
}}
};
UnsignedInt mapping[7];
CORRADE_COMPARE(removeDuplicatesInPlaceInto(materials, mapping), 4);
CORRADE_COMPARE_AS(Containers::arrayView(mapping), Containers::arrayView({
0u, 1u, 0u, 2u, 1u, 2u, 3u
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(Containers::arrayView(materials)[0], (Trade::MaterialData{Trade::MaterialType::PbrMetallicRoughness, {
{Trade::MaterialAttribute::BaseColorTexture, 2u},
}}), DebugTools::CompareMaterial);
CORRADE_COMPARE_AS(Containers::arrayView(materials)[1], (Trade::MaterialData{Trade::MaterialType::PbrMetallicRoughness, {
{Trade::MaterialAttribute::Roughness, 0.3f},
{Trade::MaterialAttribute::SpecularColor, 0x66779900_rgbaf},
}}), DebugTools::CompareMaterial);
CORRADE_COMPARE_AS(Containers::arrayView(materials)[2],
(Trade::MaterialData{Trade::MaterialType::Flat, {}}),
DebugTools::CompareMaterial);
CORRADE_COMPARE_AS(Containers::arrayView(materials)[3], (Trade::MaterialData{{}, {
{Trade::MaterialAttribute::AlphaBlend, false},
{Trade::MaterialAttribute::AlphaMask, 0.7f},
}}), DebugTools::CompareMaterial);
}
void RemoveDuplicatesTest::inPlaceAsArray() {
Trade::MaterialData materials[]{
Trade::MaterialData{Trade::MaterialType::PbrMetallicRoughness, {
{Trade::MaterialAttribute::BaseColorTexture, 2u},
}},
Trade::MaterialData{Trade::MaterialType::PbrMetallicRoughness, {
{Trade::MaterialAttribute::Roughness, 0.3f},
{Trade::MaterialAttribute::SpecularColor, 0x66779900_rgbaf},
}},
Trade::MaterialData{Trade::MaterialType::PbrMetallicRoughness, {
{Trade::MaterialAttribute::BaseColorTexture, 2u},
}},
Trade::MaterialData{Trade::MaterialType::Flat, {}},
Trade::MaterialData{Trade::MaterialType::PbrMetallicRoughness, {
{Trade::MaterialAttribute::Roughness, 0.3f},
{Trade::MaterialAttribute::SpecularColor, 0x66779900_rgbaf},
}},
Trade::MaterialData{Trade::MaterialType::Flat, {}},
Trade::MaterialData{{}, {
{Trade::MaterialAttribute::AlphaBlend, false},
{Trade::MaterialAttribute::AlphaMask, 0.7f},
}}
};
Containers::Pair<Containers::Array<UnsignedInt>, std::size_t> out = removeDuplicatesInPlace(materials);
CORRADE_COMPARE(out.second(), 4);
CORRADE_COMPARE_AS(out.first(), Containers::arrayView({
0u, 1u, 0u, 2u, 1u, 2u, 3u
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(Containers::arrayView(materials)[0], (Trade::MaterialData{Trade::MaterialType::PbrMetallicRoughness, {
{Trade::MaterialAttribute::BaseColorTexture, 2u},
}}), DebugTools::CompareMaterial);
CORRADE_COMPARE_AS(Containers::arrayView(materials)[1], (Trade::MaterialData{Trade::MaterialType::PbrMetallicRoughness, {
{Trade::MaterialAttribute::Roughness, 0.3f},
{Trade::MaterialAttribute::SpecularColor, 0x66779900_rgbaf},
}}), DebugTools::CompareMaterial);
CORRADE_COMPARE_AS(Containers::arrayView(materials)[2],
(Trade::MaterialData{Trade::MaterialType::Flat, {}}),
DebugTools::CompareMaterial);
CORRADE_COMPARE_AS(Containers::arrayView(materials)[3], (Trade::MaterialData{{}, {
{Trade::MaterialAttribute::AlphaBlend, false},
{Trade::MaterialAttribute::AlphaMask, 0.7f},
}}), DebugTools::CompareMaterial);
}
void RemoveDuplicatesTest::invalidSize() {
CORRADE_SKIP_IF_NO_ASSERT();
Trade::MaterialData data[]{
Trade::MaterialData{{}, {}},
Trade::MaterialData{{}, {}},
};
UnsignedInt mapping[3];
std::ostringstream out;
Error redirectError{&out};
removeDuplicatesInto(data, mapping);
removeDuplicatesInPlaceInto(data, mapping);
CORRADE_COMPARE(out.str(),
"MaterialTools::removeDuplicatesInto(): bad output size, expected 2 but got 3\n"
"MaterialTools::removeDuplicatesInPlaceInto(): bad output size, expected 2 but got 3\n");
}
}}}}
CORRADE_TEST_MAIN(Magnum::MaterialTools::Test::RemoveDuplicatesTest)
Loading…
Cancel
Save