diff --git a/src/Magnum/SceneTools/CMakeLists.txt b/src/Magnum/SceneTools/CMakeLists.txt index 3ef8edfa1..c517d8db5 100644 --- a/src/Magnum/SceneTools/CMakeLists.txt +++ b/src/Magnum/SceneTools/CMakeLists.txt @@ -44,11 +44,13 @@ set(MagnumSceneTools_SRCS ) # Files compiled with different flags for main library and unit test library set(MagnumSceneTools_GracefulAssert_SRCS Combine.cpp + Filter.cpp FlattenTransformationHierarchy.cpp OrderClusterParents.cpp) set(MagnumSceneTools_HEADERS Combine.h + Filter.h FlattenTransformationHierarchy.h OrderClusterParents.h diff --git a/src/Magnum/SceneTools/Combine.h b/src/Magnum/SceneTools/Combine.h index 24a0bf1f3..7261ee65d 100644 --- a/src/Magnum/SceneTools/Combine.h +++ b/src/Magnum/SceneTools/Combine.h @@ -78,6 +78,7 @@ Calls @ref combineFields(Trade::SceneMappingType, UnsignedLong, Containers::Arra with mapping type, bound and fields coming from @p scene. Useful for conveniently repacking an existing scene and throwing away data not referenced by any field. +@see @ref filterFields(), @ref filterOnlyFields(), @ref filterExceptFields() */ MAGNUM_SCENETOOLS_EXPORT Trade::SceneData combineFields(const Trade::SceneData& scene); diff --git a/src/Magnum/SceneTools/Filter.cpp b/src/Magnum/SceneTools/Filter.cpp new file mode 100644 index 000000000..1aa795c26 --- /dev/null +++ b/src/Magnum/SceneTools/Filter.cpp @@ -0,0 +1,79 @@ +/* + 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 "Filter.h" + +#include +#include +#include + +#include "Magnum/Trade/SceneData.h" + +namespace Magnum { namespace SceneTools { + +Trade::SceneData filterFields(const Trade::SceneData& scene, const Containers::BitArrayView fieldsToKeep) { + CORRADE_ASSERT(fieldsToKeep.size() == scene.fieldCount(), + "SceneTools::filterFields(): expected" << scene.fieldCount() << "bits but got" << fieldsToKeep.size(), (Trade::SceneData{Trade::SceneMappingType::UnsignedInt, 0, nullptr, {}})); + + Containers::Array filtered{ValueInit, fieldsToKeep.count()}; + std::size_t offset = 0; + /** @todo some "iterate set bits" API */ + for(std::size_t i = 0; i != fieldsToKeep.size(); ++i) { + if(!fieldsToKeep[i]) continue; + filtered[offset++] = scene.fieldData(i); + } + CORRADE_INTERNAL_ASSERT(offset == filtered.size()); + + return Trade::SceneData{scene.mappingType(), scene.mappingBound(), + {}, scene.data(), std::move(filtered)}; +} + +Trade::SceneData filterOnlyFields(const Trade::SceneData& scene, const Containers::ArrayView fields) { + Containers::BitArray fieldsToKeep{DirectInit, scene.fieldCount(), false}; + for(const Trade::SceneField field: fields) { + if(const Containers::Optional fieldId = scene.findFieldId(field)) + fieldsToKeep.set(*fieldId); + } + return filterFields(scene, fieldsToKeep); +} + +Trade::SceneData filterOnlyFields(const Trade::SceneData& scene, std::initializer_list fields) { + return filterOnlyFields(scene, Containers::arrayView(fields)); +} + +Trade::SceneData filterExceptFields(const Trade::SceneData& scene, const Containers::ArrayView fields) { + Containers::BitArray fieldsToKeep{DirectInit, scene.fieldCount(), true}; + for(const Trade::SceneField field: fields) { + if(const Containers::Optional fieldId = scene.findFieldId(field)) + fieldsToKeep.reset(*fieldId); + } + return filterFields(scene, fieldsToKeep); +} + +Trade::SceneData filterExceptFields(const Trade::SceneData& scene, std::initializer_list fields) { + return filterExceptFields(scene, Containers::arrayView(fields)); +} + +}} diff --git a/src/Magnum/SceneTools/Filter.h b/src/Magnum/SceneTools/Filter.h new file mode 100644 index 000000000..6ae8f50b6 --- /dev/null +++ b/src/Magnum/SceneTools/Filter.h @@ -0,0 +1,98 @@ +#ifndef Magnum_SceneTools_Filter_h +#define Magnum_SceneTools_Filter_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::SceneTools::filterFields(), @ref Magnum::SceneTools::filterOnlyFields(), @ref Magnum::SceneTools::filterExceptFields() + * @m_since_latest + */ + +#include + +#include "Magnum/SceneTools/visibility.h" +#include "Magnum/Trade/Trade.h" + +namespace Magnum { namespace SceneTools { + +/** +@brief Filter a scene to contain only the selected subset of fields +@m_since_latest + +Returns a non-owning reference to the data from @p scene with only the fields +for which the corresponding bit in @p fieldsToKeep was set. The size of +@p fieldsToKeep is expected to be equal to @ref Trade::SceneData::fieldCount(). + +This function only operates on the field metadata --- if you'd like to have +the data repacked to contain just the remaining fields as well, pass +the output to @ref combineFields(const Trade::SceneData&). +*/ +MAGNUM_SCENETOOLS_EXPORT Trade::SceneData filterFields(const Trade::SceneData& scene, Containers::BitArrayView fieldsToKeep); + +/** +@brief Filter a scene to contain only the selected subset of named fields +@m_since_latest + +Returns a non-owning reference to the data from @p scene with only the fields +that are listed in @p fields. Fields from the list that are not present in +@p scene are skipped, duplicates in the list are treated the same as if given +field was listed just once. + +This function only operates on the field metadata --- if you'd like to have +the data repacked to contain just the remaining fields as well, pass +the output to @ref combineFields(const Trade::SceneData&). +*/ +MAGNUM_SCENETOOLS_EXPORT Trade::SceneData filterOnlyFields(const Trade::SceneData& scene, Containers::ArrayView fields); + +/** +@overload +@m_since_latest +*/ +MAGNUM_SCENETOOLS_EXPORT Trade::SceneData filterOnlyFields(const Trade::SceneData& scene, std::initializer_list fields); + +/** +@brief Filter a scene to contain everything except the selected subset of named fields +@m_since_latest + +Returns a non-owning reference to the data from @p scene with only the fields +that are not listed in @p fields. Fields from the list that are not present in +@p scene are skipped, duplicates in the list are treated the same as if given +fields was listed just once. + +This function only operates on the field metadata --- if you'd like to have +the data repacked to contain just the remaining fields as well, pass +the output to @ref combineFields(const Trade::SceneData&). +*/ +MAGNUM_SCENETOOLS_EXPORT Trade::SceneData filterExceptFields(const Trade::SceneData& scene, Containers::ArrayView fields); + +/** +@overload +@m_since_latest +*/ +MAGNUM_SCENETOOLS_EXPORT Trade::SceneData filterExceptFields(const Trade::SceneData& scene, std::initializer_list fields); + +}} + +#endif diff --git a/src/Magnum/SceneTools/Test/CMakeLists.txt b/src/Magnum/SceneTools/Test/CMakeLists.txt index 328ae256d..5b8f5112d 100644 --- a/src/Magnum/SceneTools/Test/CMakeLists.txt +++ b/src/Magnum/SceneTools/Test/CMakeLists.txt @@ -52,6 +52,7 @@ file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/$/configure.h corrade_add_test(SceneToolsCombineTest CombineTest.cpp LIBRARIES MagnumSceneToolsTestLib) corrade_add_test(SceneToolsConvertToSingleFunc___Test ConvertToSingleFunctionObjectsTest.cpp LIBRARIES MagnumSceneToolsTestLib) +corrade_add_test(SceneToolsFilterTest FilterTest.cpp LIBRARIES MagnumSceneToolsTestLib) corrade_add_test(SceneToolsFlattenTra___HierarchyTest FlattenTransformationHierarchyTest.cpp LIBRARIES MagnumSceneToolsTestLib) corrade_add_test(SceneToolsOrderClusterParentsTest OrderClusterParentsTest.cpp LIBRARIES MagnumSceneToolsTestLib) diff --git a/src/Magnum/SceneTools/Test/FilterTest.cpp b/src/Magnum/SceneTools/Test/FilterTest.cpp new file mode 100644 index 000000000..ca53921ad --- /dev/null +++ b/src/Magnum/SceneTools/Test/FilterTest.cpp @@ -0,0 +1,264 @@ +/* + 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 +#include + +#include "Magnum/SceneTools/Filter.h" +#include "Magnum/Trade/SceneData.h" + +namespace Magnum { namespace SceneTools { namespace Test { namespace { + +struct FilterTest: TestSuite::Tester { + explicit FilterTest(); + + void fields(); + void fieldsWrongBitCount(); + + void onlyFields(); + void onlyFieldsNoFieldData(); + + void exceptFields(); + void exceptFieldsNoFieldData(); +}; + +FilterTest::FilterTest() { + addTests({&FilterTest::fields, + &FilterTest::fieldsWrongBitCount, + + &FilterTest::onlyFields, + &FilterTest::onlyFieldsNoFieldData, + + &FilterTest::exceptFields, + &FilterTest::exceptFieldsNoFieldData}); +} + +void FilterTest::fields() { + const struct Data { + UnsignedShort meshMaterialMapping[5]; + UnsignedByte mesh[5]; + Byte meshMaterial[5]; + UnsignedShort lightMapping[3]; + UnsignedInt light[3]; + UnsignedShort visibilityMapping[2]; + bool visible[2]; + } data[1]{}; + + Trade::SceneData scene{Trade::SceneMappingType::UnsignedShort, 76, Trade::DataFlag::Mutable, data, { + Trade::SceneFieldData{Trade::SceneField::Mesh, + Containers::arrayView(data->meshMaterialMapping), + Containers::arrayView(data->mesh)}, + /* Offset-only */ + Trade::SceneFieldData{Trade::SceneField::MeshMaterial, 5, + Trade::SceneMappingType::UnsignedShort, offsetof(Data, meshMaterialMapping), sizeof(UnsignedShort), + Trade::SceneFieldType::Byte, offsetof(Data, meshMaterial), sizeof(UnsignedByte)}, + Trade::SceneFieldData{Trade::SceneField::Light, + Containers::arrayView(data->lightMapping), + Containers::arrayView(data->light)}, + /* Bit */ + Trade::SceneFieldData{Trade::sceneFieldCustom(15), + Containers::arrayView(data->visibilityMapping), + Containers::stridedArrayView(data->visible).sliceBit(0)}, + }}; + + Containers::BitArray attributesToKeep{ValueInit, scene.fieldCount()}; + attributesToKeep.set(0); + attributesToKeep.set(1); + attributesToKeep.set(3); + + Trade::SceneData filtered = filterFields(scene, attributesToKeep); + CORRADE_COMPARE(filtered.mappingType(), Trade::SceneMappingType::UnsignedShort); + CORRADE_COMPARE(filtered.mappingBound(), 76); + CORRADE_COMPARE(filtered.data().data(), static_cast(data)); + CORRADE_COMPARE(filtered.dataFlags(), Trade::DataFlags{}); + + /* Testing just the pointers if they match expectations, the SceneFieldData + get copied directly so no metadata should get lost */ + CORRADE_COMPARE(filtered.fieldCount(), 3); + CORRADE_COMPARE(filtered.fieldName(0), Trade::SceneField::Mesh); + CORRADE_COMPARE(filtered.field(0).data(), data->mesh); + CORRADE_COMPARE(filtered.fieldName(1), Trade::SceneField::MeshMaterial); + CORRADE_COMPARE(filtered.mapping(1).data(), data->meshMaterialMapping); + CORRADE_COMPARE(filtered.fieldName(2), Trade::sceneFieldCustom(15)); + CORRADE_COMPARE(filtered.fieldBits(2).data(), data->visible); + + /* The attribute data should not be a growable array to make this usable in + plugins */ + Containers::Array fieldData = filtered.releaseFieldData(); + CORRADE_VERIFY(!fieldData.deleter()); +} + +void FilterTest::fieldsWrongBitCount() { + CORRADE_SKIP_IF_NO_ASSERT(); + + UnsignedInt data[3]; + + Trade::SceneData scene{Trade::SceneMappingType::UnsignedInt, 15, {}, data, { + Trade::SceneFieldData{Trade::SceneField::Camera, + Containers::arrayView(data), + Containers::arrayView(data)}, + Trade::SceneFieldData{Trade::SceneField::Light, + Containers::arrayView(data), + Containers::arrayView(data)}, + }}; + + std::ostringstream out; + Error redirectError{&out}; + filterFields(scene, Containers::BitArray{ValueInit, 3}); + CORRADE_COMPARE(out.str(), "SceneTools::filterFields(): expected 2 bits but got 3\n"); +} + +void FilterTest::onlyFields() { + const struct { + UnsignedByte meshMaterialMapping[5]; + UnsignedByte mesh[5]; + Byte meshMaterial[5]; + UnsignedByte lightMapping[3]; + UnsignedInt light[3]; + } data[1]{}; + + Trade::SceneData scene{Trade::SceneMappingType::UnsignedByte, 133, Trade::DataFlag::Mutable, data, { + Trade::SceneFieldData{Trade::SceneField::Mesh, + Containers::arrayView(data->meshMaterialMapping), + Containers::arrayView(data->mesh)}, + Trade::SceneFieldData{Trade::SceneField::MeshMaterial, + Containers::arrayView(data->meshMaterialMapping), + Containers::arrayView(data->meshMaterial)}, + Trade::SceneFieldData{Trade::SceneField::Light, + Containers::arrayView(data->lightMapping), + Containers::arrayView(data->light)}, + }}; + + Trade::SceneData filtered = filterOnlyFields(scene, { + Trade::SceneField::Light, + Trade::SceneField::Camera, /* not present in the scene, ignored */ + Trade::SceneField::MeshMaterial, + Trade::SceneField::Light, /* listed twice, ignored */ + }); + CORRADE_COMPARE(filtered.mappingType(), Trade::SceneMappingType::UnsignedByte); + CORRADE_COMPARE(filtered.mappingBound(), 133); + CORRADE_COMPARE(filtered.data().data(), static_cast(data)); + CORRADE_COMPARE(filtered.dataFlags(), Trade::DataFlags{}); + + /* Testing just the pointers if they match expectations, the SceneFieldData + get copied directly so no metadata should get lost */ + CORRADE_COMPARE(filtered.fieldCount(), 2); + /* The original order stays even though MeshMaterial was specified after + Light in the list */ + CORRADE_COMPARE(filtered.fieldName(0), Trade::SceneField::MeshMaterial); + CORRADE_COMPARE(filtered.field(0).data(), data->meshMaterial); + CORRADE_COMPARE(filtered.fieldName(1), Trade::SceneField::Light); + CORRADE_COMPARE(filtered.mapping(1).data(), data->lightMapping); + + /* The attribute data should not be a growable array to make this usable in + plugins */ + Containers::Array fieldData = filtered.releaseFieldData(); + CORRADE_VERIFY(!fieldData.deleter()); +} + +void FilterTest::onlyFieldsNoFieldData() { + /* Just to verify it doesn't crash */ + + Trade::SceneData filtered = filterOnlyFields(Trade::SceneData{Trade::SceneMappingType::UnsignedShort, 331, nullptr, {}}, { + Trade::SceneField::MeshMaterial, + }); + CORRADE_COMPARE(filtered.mappingType(), Trade::SceneMappingType::UnsignedShort); + CORRADE_COMPARE(filtered.mappingBound(), 331); + CORRADE_COMPARE(filtered.data().data(), nullptr); + CORRADE_COMPARE(filtered.dataFlags(), Trade::DataFlags{}); +} + +void FilterTest::exceptFields() { + const struct { + UnsignedLong meshMaterialMapping[5]; + UnsignedByte mesh[5]; + Byte meshMaterial[5]; + UnsignedLong lightMapping[3]; + UnsignedInt light[3]; + UnsignedLong visibilityMapping[2]; + bool visible[2]; + } data[1]{}; + + Trade::SceneData scene{Trade::SceneMappingType::UnsignedLong, 1, Trade::DataFlag::Mutable, data, { + Trade::SceneFieldData{Trade::SceneField::Mesh, + Containers::arrayView(data->meshMaterialMapping), + Containers::arrayView(data->mesh)}, + Trade::SceneFieldData{Trade::SceneField::MeshMaterial, + Containers::arrayView(data->meshMaterialMapping), + Containers::arrayView(data->meshMaterial)}, + Trade::SceneFieldData{Trade::SceneField::Light, + Containers::arrayView(data->lightMapping), + Containers::arrayView(data->light)}, + Trade::SceneFieldData{Trade::sceneFieldCustom(15), + Containers::arrayView(data->visibilityMapping), + Containers::stridedArrayView(data->visible).sliceBit(0)}, + }}; + + Trade::SceneData filtered = filterExceptFields(scene, { + Trade::SceneField::Light, + Trade::SceneField::Camera, /* not present in the scene, ignored */ + Trade::SceneField::MeshMaterial, + Trade::SceneField::Light, /* listed twice, ignored */ + }); + CORRADE_COMPARE(filtered.mappingType(), Trade::SceneMappingType::UnsignedLong); + CORRADE_COMPARE(filtered.mappingBound(), 1); + CORRADE_COMPARE(filtered.data().data(), static_cast(data)); + CORRADE_COMPARE(filtered.dataFlags(), Trade::DataFlags{}); + + /* Testing just the pointers if they match expectations, the SceneFieldData + get copied directly so no metadata should get lost */ + CORRADE_COMPARE(filtered.fieldCount(), 2); + /* The original order stays even though MeshMaterial was specified after + Light in the list */ + CORRADE_COMPARE(filtered.fieldName(0), Trade::SceneField::Mesh); + CORRADE_COMPARE(filtered.field(0).data(), data->mesh); + CORRADE_COMPARE(filtered.mapping(0).data(), data->meshMaterialMapping); + CORRADE_COMPARE(filtered.fieldName(1), Trade::sceneFieldCustom(15)); + CORRADE_COMPARE(filtered.fieldBits(1).data(), data->visible); + + /* The attribute data should not be a growable array to make this usable in + plugins */ + Containers::Array fieldData = filtered.releaseFieldData(); + CORRADE_VERIFY(!fieldData.deleter()); +} + +void FilterTest::exceptFieldsNoFieldData() { + /* Just to verify it doesn't crash */ + + Trade::SceneData filtered = filterOnlyFields(Trade::SceneData{Trade::SceneMappingType::UnsignedShort, 331, nullptr, {}}, { + Trade::SceneField::MeshMaterial, + }); + CORRADE_COMPARE(filtered.mappingType(), Trade::SceneMappingType::UnsignedShort); + CORRADE_COMPARE(filtered.mappingBound(), 331); + CORRADE_COMPARE(filtered.data().data(), nullptr); + CORRADE_COMPARE(filtered.dataFlags(), Trade::DataFlags{}); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::SceneTools::Test::FilterTest)