diff --git a/doc/snippets/CMakeLists.txt b/doc/snippets/CMakeLists.txt index 41321f9d2..ba1a42ea0 100644 --- a/doc/snippets/CMakeLists.txt +++ b/doc/snippets/CMakeLists.txt @@ -99,6 +99,15 @@ if(MAGNUM_WITH_GL) endif() endif() +if(MAGNUM_WITH_MATERIALTOOLS) + add_library(snippets-MagnumMaterialTools STATIC ${EXCLUDE_FROM_ALL_IF_TEST_TARGET} + MagnumMaterialTools.cpp) + target_link_libraries(snippets-MagnumMaterialTools PRIVATE MagnumMaterialTools) + if(CORRADE_TESTSUITE_TEST_TARGET) + add_dependencies(${CORRADE_TESTSUITE_TEST_TARGET} snippets-MagnumMaterialTools) + endif() +endif() + if(MAGNUM_WITH_MESHTOOLS) add_library(snippets-MagnumMeshTools STATIC ${EXCLUDE_FROM_ALL_IF_TEST_TARGET} MagnumMeshTools.cpp) diff --git a/doc/snippets/MagnumMaterialTools.cpp b/doc/snippets/MagnumMaterialTools.cpp new file mode 100644 index 000000000..fd77ede59 --- /dev/null +++ b/doc/snippets/MagnumMaterialTools.cpp @@ -0,0 +1,66 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022, 2023 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/MaterialTools/RemoveDuplicates.h" +#include "Magnum/SceneTools/Map.h" +#include "Magnum/Trade/AbstractImporter.h" +#include "Magnum/Trade/MaterialData.h" +#include "Magnum/Trade/SceneData.h" + +#define DOXYGEN_ELLIPSIS(...) __VA_ARGS__ + +using namespace Magnum; + +int main() { +{ +/* -Wnonnull in GCC 11+ "helpfully" says "this is null" if I don't initialize + the font pointer. I don't care, I just want you to check compilation errors, + not more! */ +PluginManager::Manager manager; +/* [removeDuplicatesInPlace] */ +Containers::Pointer importer = DOXYGEN_ELLIPSIS(manager.loadAndInstantiate("SomethingWhatever")); + +/* Import all materials */ +Containers::Array materials; +for(UnsignedInt i = 0; i != importer->materialCount(); ++i) + arrayAppend(materials, *importer->material(i)); + +/* Remove duplicates, putting the unique materials to the prefix and removing + the rest */ +Containers::Pair, std::size_t> mapping = + MaterialTools::removeDuplicatesInPlace(materials); +arrayRemoveSuffix(materials, materials.size() - mapping.second()); + +/* Apply the mapping to unique materials to the scene */ +Trade::SceneData scene = DOXYGEN_ELLIPSIS(Trade::SceneData{Trade::SceneMappingType::UnsignedInt, 0, nullptr, {}}); +SceneTools::mapIndexFieldInPlace(scene, + Trade::SceneField::MeshMaterial, mapping.first()); +/* [removeDuplicatesInPlace] */ +} +} diff --git a/src/Magnum/MaterialTools/RemoveDuplicates.h b/src/Magnum/MaterialTools/RemoveDuplicates.h index c001823a0..4f2ad4b83 100644 --- a/src/Magnum/MaterialTools/RemoveDuplicates.h +++ b/src/Magnum/MaterialTools/RemoveDuplicates.h @@ -61,6 +61,13 @@ 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. + +The output index array can be passed to @ref SceneTools::mapIndexField() to +update a @ref Trade::SceneField::MeshMaterial field to reference only the +unique materials. For example: + +@snippet MagnumMaterialTools.cpp removeDuplicatesInPlace + @see @ref removeDuplicatesInPlaceInto() */ MAGNUM_MATERIALTOOLS_EXPORT Containers::Pair, std::size_t> removeDuplicatesInPlace(const Containers::Iterable& materials); @@ -92,7 +99,8 @@ 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. +for a variant that also shifts the unique materials to the front of the list +and for a practical usage example. 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 diff --git a/src/Magnum/SceneTools/CMakeLists.txt b/src/Magnum/SceneTools/CMakeLists.txt index b12bf8146..28d70602a 100644 --- a/src/Magnum/SceneTools/CMakeLists.txt +++ b/src/Magnum/SceneTools/CMakeLists.txt @@ -46,12 +46,14 @@ set(MagnumSceneTools_SRCS set(MagnumSceneTools_GracefulAssert_SRCS Combine.cpp Filter.cpp - Hierarchy.cpp) + Hierarchy.cpp + Map.cpp) set(MagnumSceneTools_HEADERS Combine.h Filter.h Hierarchy.h + Map.h visibility.h) diff --git a/src/Magnum/SceneTools/Map.cpp b/src/Magnum/SceneTools/Map.cpp new file mode 100644 index 000000000..cbe24efee --- /dev/null +++ b/src/Magnum/SceneTools/Map.cpp @@ -0,0 +1,201 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022, 2023 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 "Map.h" + +#include + +#include "Magnum/Math/PackingBatch.h" +#include "Magnum/SceneTools/Combine.h" +#include "Magnum/Trade/SceneData.h" + +namespace Magnum { namespace SceneTools { + +Trade::SceneData mapIndexField(const Trade::SceneData& scene, const UnsignedInt fieldId, const Containers::StridedArrayView1D& mapping) { + CORRADE_ASSERT(fieldId < scene.fieldCount(), + "SceneTools::mapIndexField(): index" << fieldId << "out of range for" << scene.fieldCount() << "fields", + (Trade::SceneData{Trade::SceneMappingType::UnsignedInt, 0, nullptr, {}})); + + const Trade::SceneFieldType fieldType = scene.fieldType(fieldId); + Containers::Array fields{NoInit, scene.fieldCount()}; + for(std::size_t i = 0; i != scene.fieldCount(); ++i) { + if(i == fieldId) { + CORRADE_ASSERT(scene.fieldArraySize(i) == 0, + "SceneTools::mapIndexField(): array field mapping isn't supported", + (Trade::SceneData{Trade::SceneMappingType::UnsignedInt, 0, nullptr, {}})); + + Trade::SceneFieldType outputFieldType; + if(fieldType == Trade::SceneFieldType::UnsignedInt || + fieldType == Trade::SceneFieldType::UnsignedShort || + fieldType == Trade::SceneFieldType::UnsignedByte) + outputFieldType = Trade::SceneFieldType::UnsignedInt; + else if(fieldType == Trade::SceneFieldType::Int || + fieldType == Trade::SceneFieldType::Short || + fieldType == Trade::SceneFieldType::Byte) + outputFieldType = Trade::SceneFieldType::Int; + else CORRADE_ASSERT_UNREACHABLE("SceneTools::mapIndexField(): unsupported field type" << fieldType, + (Trade::SceneData{Trade::SceneMappingType::UnsignedInt, 0, nullptr, {}})); + + fields[i] = Trade::SceneFieldData{scene.fieldName(i), + scene.mapping(i), outputFieldType, + Containers::StridedArrayView2D{{nullptr, ~std::size_t{}}, {scene.fieldSize(i), 4}}, + /* We aren't removing any field entries from the scene nor + modifying the mapping in any way, so the flags can be passed + through in full */ + scene.fieldFlags(i)}; // TODO test flags + + /* Otherwise grab the field in full. This will also convert offset-only + fields to absolute. */ + } else fields[i] = scene.fieldData(i); + } + + /* Create a new SceneData out of the unpacked index field and all others, + unpack its data into the placeholder location */ + Trade::SceneData unpacked = combineFields(scene.mappingType(), scene.mappingBound(), fields); + if(fieldType == Trade::SceneFieldType::UnsignedInt) + Math::castInto(scene.field(fieldId), + unpacked.mutableField(fieldId)); + else if(fieldType == Trade::SceneFieldType::UnsignedShort) + Math::castInto(scene.field(fieldId), + unpacked.mutableField(fieldId)); + else if(fieldType == Trade::SceneFieldType::UnsignedByte) + Math::castInto(scene.field(fieldId), + unpacked.mutableField(fieldId)); + else if(fieldType == Trade::SceneFieldType::Int) + Math::castInto(scene.field(fieldId), + unpacked.mutableField(fieldId)); + else if(fieldType == Trade::SceneFieldType::Short) + Math::castInto(scene.field(fieldId), + unpacked.mutableField(fieldId)); + else if(fieldType == Trade::SceneFieldType::Byte) + Math::castInto(scene.field(fieldId), + unpacked.mutableField(fieldId)); + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + + mapIndexFieldInPlace(unpacked, fieldId, mapping); + return unpacked; +} + +Trade::SceneData mapIndexField(const Trade::SceneData& scene, const Trade::SceneField field, const Containers::StridedArrayView1D& mapping) { + const Containers::Optional fieldId = scene.findFieldId(field); + CORRADE_ASSERT(fieldId, + "SceneTools::mapIndexField(): field" << field << "not found", + (Trade::SceneData{Trade::SceneMappingType::UnsignedInt, 0, nullptr, {}})); + + return mapIndexField(scene, *fieldId, mapping); +} + +Trade::SceneData mapIndexField(Trade::SceneData&& scene, const UnsignedInt fieldId, const Containers::StridedArrayView1D& mapping) { + CORRADE_ASSERT(fieldId < scene.fieldCount(), + "SceneTools::mapIndexField(): index" << fieldId << "out of range for" << scene.fieldCount() << "fields", + (Trade::SceneData{Trade::SceneMappingType::UnsignedInt, 0, nullptr, {}})); + + /* Perform the operation in-place, if we can transfewr the ownership and + have the field in the target format already. There's currently no way to + create a SceneData that's Owned but not Mutable so check for Owned is + enough. */ + if(scene.dataFlags() & Trade::DataFlag::Owned && + (scene.fieldType(fieldId) == Trade::SceneFieldType::UnsignedInt || + scene.fieldType(fieldId) == Trade::SceneFieldType::Int)) + { + mapIndexFieldInPlace(scene, fieldId, mapping); + return Utility::move(scene); + } + + /* Otherwise delegate to the function that does all the copying and format + expansion */ + return mapIndexField(scene, fieldId, mapping); +} + +Trade::SceneData mapIndexField(Trade::SceneData&& scene, const Trade::SceneField field, const Containers::StridedArrayView1D& mapping) { + const Containers::Optional fieldId = scene.findFieldId(field); + CORRADE_ASSERT(fieldId, + "SceneTools::mapIndexField(): field" << field << "not found", + (Trade::SceneData{Trade::SceneMappingType::UnsignedInt, 0, nullptr, {}})); + + // TODO test that it gets actually moved + return mapIndexField(Utility::move(scene), *fieldId, mapping); +} + +namespace { + +template void mapImplementation(const Containers::StridedArrayView1D& field, const Containers::StridedArrayView1D& mapping) { + for(T& i: field) { + CORRADE_ASSERT(i < mapping.size(), + "SceneTools::mapIndexFieldInPlace(): index" << i << "out of range for" << mapping.size() << "mapping values", ); + CORRADE_ASSERT(mapping[i] < (1ull << sizeof(T)*8), + "SceneTools::mapIndexFieldInPlace(): mapping value" << mapping[i] << "not representable in" << Trade::Implementation::SceneFieldTypeFor::type(), ); + i = mapping[i]; + } +} + +template void mapSignedImplementation(const Containers::StridedArrayView1D& field, const Containers::StridedArrayView1D& mapping) { + for(T& i: field) { + if(i == T(-1)) + continue; + + CORRADE_ASSERT(UnsignedInt(i) < mapping.size(), + "SceneTools::mapIndexFieldInPlace(): index" << i << "out of range for" << mapping.size() << "mapping values", ); + CORRADE_ASSERT(mapping[i] >= 0 && mapping[i] < (1u << (sizeof(T)*8 - 1)), + "SceneTools::mapIndexFieldInPlace(): mapping value" << mapping[i] << "not representable in" << Trade::Implementation::SceneFieldTypeFor::type(), ); + i = T(mapping[i]); + } +} + +} + +void mapIndexFieldInPlace(Trade::SceneData& scene, const UnsignedInt fieldId, const Containers::StridedArrayView1D& mapping) { + CORRADE_ASSERT(fieldId < scene.fieldCount(), + "SceneTools::mapIndexFieldInPlace(): index" << fieldId << "out of range for" << scene.fieldCount() << "fields", ); + CORRADE_ASSERT(scene.dataFlags() & Trade::DataFlag::Mutable, + "SceneTools::mapIndexFieldInPlace(): data not mutable", ); + CORRADE_ASSERT(scene.fieldArraySize(fieldId) == 0, + "SceneTools::mapIndexFieldInPlace(): array field mapping isn't supported", ); + + const Trade::SceneFieldType fieldType = scene.fieldType(fieldId); + if(fieldType == Trade::SceneFieldType::UnsignedInt) + mapImplementation(scene.mutableField(fieldId), mapping); + else if(fieldType == Trade::SceneFieldType::UnsignedShort) + mapImplementation(scene.mutableField(fieldId), mapping); + else if(fieldType == Trade::SceneFieldType::UnsignedByte) + mapImplementation(scene.mutableField(fieldId), mapping); + else if(fieldType == Trade::SceneFieldType::Int) + mapSignedImplementation(scene.mutableField(fieldId), mapping); + else if(fieldType == Trade::SceneFieldType::Short) + mapSignedImplementation(scene.mutableField(fieldId), mapping); + else if(fieldType == Trade::SceneFieldType::Byte) + mapSignedImplementation(scene.mutableField(fieldId), mapping); + else CORRADE_ASSERT_UNREACHABLE("SceneTools::mapIndexFieldInPlace(): unsupported field type" << fieldType, ); +} + +void mapIndexFieldInPlace(Trade::SceneData& scene, const Trade::SceneField field, const Containers::StridedArrayView1D& mapping) { + const Containers::Optional fieldId = scene.findFieldId(field); + CORRADE_ASSERT(fieldId, + "SceneTools::mapIndexFieldInPlace(): field" << field << "not found", ); + + return mapIndexFieldInPlace(scene, *fieldId, mapping); +} + +}} diff --git a/src/Magnum/SceneTools/Map.h b/src/Magnum/SceneTools/Map.h new file mode 100644 index 000000000..4812ce17b --- /dev/null +++ b/src/Magnum/SceneTools/Map.h @@ -0,0 +1,145 @@ +#ifndef Magnum_SceneTools_Map_h +#define Magnum_SceneTools_Map_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š + + 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::mapIndexField(), @ref Magnum::SceneTools::mapIndexFieldInPlace() + * @m_since_latest + */ + +#include "Magnum/SceneTools/visibility.h" +#include "Magnum/Trade/Trade.h" + +namespace Magnum { namespace SceneTools { + +/** +@brief Map an index field in a scene +@m_since_latest + +Maps a field containing data indices, such as @ref Trade::SceneField::Mesh or +@ref Trade::SceneField::Camera, to different indices by iterating through the +field at index @p fieldId and replacing a particular @cpp value @ce with +@cpp mapping[value] @ce. If the field has a signed type (such as is the case +with @ref Trade::SceneField::MeshMaterial), @cpp -1 @ce is treated as an +"unset" value and preserved verbatim. + +Expects that @p fieldId is less than @ref Trade::SceneData::fieldCount(), the +field is @ref Trade::SceneFieldType::UnsignedInt, +@relativeref{Trade::SceneFieldType,Int}, @relativeref{Trade::SceneFieldType,UnsignedShort}, +@relativeref{Trade::SceneFieldType,Short}, +@relativeref{Trade::SceneFieldType,UnsignedByte} or +@relativeref{Trade::SceneFieldType,Byte} and is not an array, and that the +@p mapping array is large enough to cover all field values. + +The output field is always a @ref Trade::SceneFieldType::UnsignedInt if the +input type is unsigned and @ref Trade::SceneFieldType::Int if it's signed. See +also @ref mapIndexField(Trade::SceneData&&, UnsignedInt, const Containers::StridedArrayView1D&) +for a potentially more efficient operation instead of always performing a full +copy, you can also do an in-place mapping using +@ref mapIndexFieldInPlace(Trade::SceneData&, UnsignedInt, const Containers::StridedArrayView1D&) +which doesn't change the field type but additionally expects that the +@p mapping values don't overflow given type. +*/ +MAGNUM_SCENETOOLS_EXPORT Trade::SceneData mapIndexField(const Trade::SceneData& scene, UnsignedInt fieldId, const Containers::StridedArrayView1D& mapping); + +/** +@brief Map a named index field in a scene +@m_since_latest + +Translates @p field to a field ID using @ref Trade::SceneData::fieldId() and +delegates to @ref mapIndexField(const Trade::SceneData&, UnsignedInt, const Containers::StridedArrayView1D&). +The @p field is expected to exist in @p scene. +*/ +MAGNUM_SCENETOOLS_EXPORT Trade::SceneData mapIndexField(const Trade::SceneData& scene, Trade::SceneField field, const Containers::StridedArrayView1D& mapping); + +/** +@brief Map an index field in a scene +@m_since_latest + +Compared to @ref mapIndexField(const Trade::SceneData&, UnsignedInt, const Containers::StridedArrayView1D&) +this function can perform the mapping in-place, transferring the data ownership +to the returned instance if the data is owned and mutable and the field at +index @p fieldId is @ref Trade::SceneFieldType::UnsignedInt or +@ref Trade::SceneFieldType::Int. +*/ +MAGNUM_SCENETOOLS_EXPORT Trade::SceneData mapIndexField(Trade::SceneData&& scene, UnsignedInt fieldId, const Containers::StridedArrayView1D& mapping); + +/** +@brief Map a named index field in a scene +@m_since_latest + +Translates @p field to a field ID using @ref Trade::SceneData::fieldId() and +delegates to @ref mapIndexField(Trade::SceneData&&, UnsignedInt, const Containers::StridedArrayView1D&). +The @p field is expected to exist in @p scene. +*/ +MAGNUM_SCENETOOLS_EXPORT Trade::SceneData mapIndexField(Trade::SceneData&& scene, Trade::SceneField field, const Containers::StridedArrayView1D& mapping); + +/** +@brief Map an index field in a scene in-place +@param[in,out] scene Scene +@param[in] fieldId Field to map +@param[in] mapping Index value mapping +@m_since_latest + +Maps a field containing data indices, such as @ref Trade::SceneField::Mesh or +@ref Trade::SceneField::Camera, to different indices by iterating through the +field at index @p fieldId and replacing a particular @cpp value @ce with +@cpp mapping[value] @ce. If the field has a signed type (such as is the case +with @ref Trade::SceneField::MeshMaterial), @cpp -1 @ce is treated as an +"unset" value and preserved verbatim. + +Expects that the @p scene has mutable data, @p fieldId is less than +@ref Trade::SceneData::fieldCount(), the field is +@ref Trade::SceneFieldType::UnsignedInt, +@relativeref{Trade::SceneFieldType,Int}, @relativeref{Trade::SceneFieldType,UnsignedShort}, +@relativeref{Trade::SceneFieldType,Short}, +@relativeref{Trade::SceneFieldType,UnsignedByte} or +@relativeref{Trade::SceneFieldType,Byte} and is not an array, the @p mapping +array is large enough to cover all field values and the @p mapping values don't +overflow given type. + +If you need to map to a larger index range that doesn't fit into the original +field type, use @ref mapIndexField(const Trade::SceneData&, UnsignedInt, const Containers::StridedArrayView1D&) instead. +@see @ref Trade::SceneData::dataFlags() +*/ +MAGNUM_SCENETOOLS_EXPORT void mapIndexFieldInPlace(Trade::SceneData& scene, UnsignedInt fieldId, const Containers::StridedArrayView1D& mapping); + +/** +@brief Map a named index field in a scene in-place +@param[in,out] scene Scene +@param[in] field Field to map +@param[in] mapping Index value mapping +@m_since_latest + +Translates @p field to a field ID using @ref Trade::SceneData::fieldId() and +delegates to @ref mapIndexFieldInPlace(Trade::SceneData&, UnsignedInt, const Containers::StridedArrayView1D&). +The @p field is expected to exist in @p scene. +*/ +MAGNUM_SCENETOOLS_EXPORT void mapIndexFieldInPlace(Trade::SceneData& scene, Trade::SceneField field, const Containers::StridedArrayView1D& mapping); + +}} + +#endif diff --git a/src/Magnum/SceneTools/Test/CMakeLists.txt b/src/Magnum/SceneTools/Test/CMakeLists.txt index 2da47f075..dae7aa355 100644 --- a/src/Magnum/SceneTools/Test/CMakeLists.txt +++ b/src/Magnum/SceneTools/Test/CMakeLists.txt @@ -55,6 +55,7 @@ corrade_add_test(SceneToolsCopyTest CopyTest.cpp LIBRARIES MagnumSceneTools) corrade_add_test(SceneToolsConvertToSingleFunc___Test ConvertToSingleFunctionObjectsTest.cpp LIBRARIES MagnumSceneToolsTestLib) corrade_add_test(SceneToolsFilterTest FilterTest.cpp LIBRARIES MagnumSceneToolsTestLib) corrade_add_test(SceneToolsHierarchyTest HierarchyTest.cpp LIBRARIES MagnumSceneToolsTestLib) +corrade_add_test(SceneToolsMapTest MapTest.cpp LIBRARIES MagnumSceneToolsTestLib) corrade_add_test(SceneToolsSceneConverterImple___Test SceneConverterImplementationTest.cpp LIBRARIES MagnumSceneTools diff --git a/src/Magnum/SceneTools/Test/MapTest.cpp b/src/Magnum/SceneTools/Test/MapTest.cpp new file mode 100644 index 000000000..945d5a1a8 --- /dev/null +++ b/src/Magnum/SceneTools/Test/MapTest.cpp @@ -0,0 +1,761 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022, 2023 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 /** @todo remove once Debug is stream-free */ +#include +#include +#include +#include /** @todo remove once Debug is stream-free */ +#include +#include +#include +#include +#include /** @todo remove once Debug is stream-free */ + +#include "Magnum/Math/TypeTraits.h" +#include "Magnum/SceneTools/Map.h" +#include "Magnum/Trade/SceneData.h" + +namespace Magnum { namespace SceneTools { namespace Test { namespace { + +struct MapTest: TestSuite::Tester { + explicit MapTest(); + + template void indexField(); + template void indexFieldSigned(); + void indexFieldOffsetOnly(); + void indexFieldFieldNotFound(); + void indexFieldInvalidType(); + void indexFieldArrayField(); + void indexFieldIndexOutOfBounds(); + void indexFieldMappingNotRepresentable(); + + void indexFieldRvalue(); + void indexFieldRvalueSigned(); + void indexFieldRvalueNamed(); + void indexFieldRvalueNotOwned(); + void indexFieldRvalueNotFullType(); +}; + +const struct { + const char* name; + bool inPlace; + bool byName; +} IndexFieldData[]{ + {"in place, by ID", true, false}, + {"in place, by name", true, true}, + {"by ID", false, false}, + {"by name", false, true} +}; + +const struct { + const char* name; + bool byName; +} IndexFieldRvalueData[]{ + {"by ID", false}, + {"by name", true} +}; + +MapTest::MapTest() { + addInstancedTests({ + &MapTest::indexField, + &MapTest::indexField, + &MapTest::indexField, + &MapTest::indexFieldSigned, + &MapTest::indexFieldSigned, + &MapTest::indexFieldSigned}, + Containers::arraySize(IndexFieldData)); + + addTests({&MapTest::indexFieldOffsetOnly, + &MapTest::indexFieldFieldNotFound, + &MapTest::indexFieldInvalidType, + &MapTest::indexFieldArrayField, + &MapTest::indexFieldIndexOutOfBounds, + &MapTest::indexFieldMappingNotRepresentable}); + + addInstancedTests({&MapTest::indexFieldRvalue, + &MapTest::indexFieldRvalueSigned}, + Containers::arraySize(IndexFieldRvalueData)); + + addTests({&MapTest::indexFieldRvalueNotOwned, + &MapTest::indexFieldRvalueNotFullType}); +} + +template struct IndexFieldTraits; + +template<> struct IndexFieldTraits { + enum: UnsignedInt { Max = 0xffffffffu }; +}; +template<> struct IndexFieldTraits { + enum: Int { Max = 0x7fffffffu }; +}; +template<> struct IndexFieldTraits { + enum: UnsignedShort { Max = 0xffff }; +}; +template<> struct IndexFieldTraits { + enum: Short { Max = 0x7fff }; +}; +template<> struct IndexFieldTraits { + enum: UnsignedByte { Max = 0xff }; +}; +template<> struct IndexFieldTraits { + enum: Byte { Max = 0x7f }; +}; + +template void MapTest::indexField() { + auto&& data = IndexFieldData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + setTestCaseTemplateName(Math::TypeTraits::name()); + + struct { + UnsignedLong parentMapping[5]; + Int parent[5]; + UnsignedLong meshMaterialMapping[4]; + T mesh[4]; + Short custom[4][2]; + } sceneData[]{{ + {0, 11, 22, 33, 44}, + {-1, -1, 1, 4, 0}, + {0, 33, 2, 2}, + {5, 9, 1, 0}, /* this one gets mapped */ + {{9, 2}, {-1, 3}, {5, 6}, {0, 1}}, + }}; + + Trade::SceneData scene{Trade::SceneMappingType::UnsignedLong, 5, Trade::DataFlag::Mutable, sceneData, { + Trade::SceneFieldData{Trade::SceneField::Parent, + Containers::arrayView(sceneData->parentMapping), + Containers::arrayView(sceneData->parent)}, + Trade::SceneFieldData{Trade::SceneField::Mesh, + Containers::arrayView(sceneData->meshMaterialMapping), + Containers::arrayView(sceneData->mesh), + /* Verify that the flags get preserved */ + Trade::SceneFieldFlag::MultiEntry}, + /* Verify that array fields are supported in non-mapped fields */ + Trade::SceneFieldData{Trade::sceneFieldCustom(1), + Trade::SceneMappingType::UnsignedLong, + Containers::arrayView(sceneData->meshMaterialMapping), + Trade::SceneFieldType::Short, + Containers::arrayView(sceneData->custom), 2}, + }}; + + /* The 0xffffffffu values shouldn't be used for anything */ + const UnsignedInt mapping[]{ + 12, 0, 0xffffffffu, 0xffffffffu, 0xffffffffu, + /* If not doing an in-place mapping, the output doesn't have to fit + into the original type */ + data.inPlace ? UnsignedInt(IndexFieldTraits::Max) : 1000000u, + 0xffffffffu, 0xffffffffu, 0xffffffffu, 3 + }; + + Trade::SceneData output{Trade::SceneMappingType::UnsignedInt, 0, nullptr, {}}; + if(data.inPlace) { + if(data.byName) + mapIndexFieldInPlace(scene, Trade::SceneField::Mesh, mapping); + else + mapIndexFieldInPlace(scene, 1, mapping); + } else { + if(data.byName) + output = mapIndexField(scene, Trade::SceneField::Mesh, mapping); + else + output = mapIndexField(scene, 1, mapping); + } + const Trade::SceneData& result = data.inPlace ? scene : output; + + /* Mapping should stay untouched */ + CORRADE_COMPARE(result.mappingBound(), 5); + CORRADE_COMPARE(result.mappingType(), Trade::SceneMappingType::UnsignedLong); + CORRADE_COMPARE_AS(result.mapping(0), Containers::arrayView({ + 0, 11, 22, 33, 44 + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(result.mapping(1), Containers::arrayView({ + 0, 33, 2, 2 + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(result.mapping(2), Containers::arrayView({ + 0, 33, 2, 2 + }), TestSuite::Compare::Container); + + /* All fields except the mesh should stay the same as before. With the + in-place variant the type should stay the same, otherwise expanded to + 32-bit. */ + CORRADE_COMPARE_AS(result.field(0), Containers::arrayView({ + -1, -1, 1, 4, 0 + }), TestSuite::Compare::Container); + if(data.inPlace) { + CORRADE_COMPARE(result.fieldType(1), Trade::Implementation::SceneFieldTypeFor::type()); + CORRADE_COMPARE_AS(result.field(1), Containers::arrayView({ + IndexFieldTraits::Max, 3, 0, 12 + }), TestSuite::Compare::Container); + } else { + CORRADE_COMPARE(result.fieldType(1), Trade::SceneFieldType::UnsignedInt); + CORRADE_COMPARE_AS(result.field(1), Containers::arrayView({ + 1000000, 3, 0, 12 + }), TestSuite::Compare::Container); + } + /* The flags should be preserved */ + CORRADE_COMPARE(result.fieldFlags(1), Trade::SceneFieldFlag::MultiEntry); + /* Non-mapped array field should be copied as-is without any assert */ + CORRADE_COMPARE_AS((result.field(2).transposed<0, 1>()[0]), Containers::arrayView({ + 9, -1, 5, 0 + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS((result.field(2).transposed<0, 1>()[1]), Containers::arrayView({ + 2, 3, 6, 1 + }), TestSuite::Compare::Container); +} + +template void MapTest::indexFieldSigned() { + auto&& data = IndexFieldData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + setTestCaseTemplateName(Math::TypeTraits::name()); + + /* Similar to indexField(), except that here the meshMaterial gets mapped + instead */ + + struct { + UnsignedByte parentMapping[5]; + Int parent[5]; + UnsignedByte meshMaterialMapping[4]; + T meshMaterial[4]; + UnsignedShort mesh[4]; + } sceneData[]{{ + {0, 11, 22, 33, 44}, + {-1, -1, 1, 4, 0}, + {0, 33, 2, 2}, + {9, -1, 5, 1}, /* this one gets mapped */ + {5, 9, 1, 0}, + }}; + + Trade::SceneData scene{Trade::SceneMappingType::UnsignedByte, 55, Trade::DataFlag::Mutable, sceneData, { + Trade::SceneFieldData{Trade::SceneField::Parent, + Containers::arrayView(sceneData->parentMapping), + Containers::arrayView(sceneData->parent)}, + Trade::SceneFieldData{Trade::SceneField::MeshMaterial, + Containers::arrayView(sceneData->meshMaterialMapping), + Containers::arrayView(sceneData->meshMaterial)}, + Trade::SceneFieldData{Trade::SceneField::Mesh, + Containers::arrayView(sceneData->meshMaterialMapping), + Containers::arrayView(sceneData->mesh)}, + }}; + + /* The 0xffffffffu values shouldn't be used for anything */ + const UnsignedInt mapping[]{ + 0xffffffffu, 12, 0xffffffffu, 0xffffffffu, 0xffffffffu, + IndexFieldTraits::Max, 0xffffffffu, 0xffffffffu, 0xffffffffu, 3 + }; + + Trade::SceneData output{Trade::SceneMappingType::UnsignedInt, 0, nullptr, {}}; + if(data.inPlace) { + if(data.byName) + mapIndexFieldInPlace(scene, Trade::SceneField::MeshMaterial, mapping); + else + mapIndexFieldInPlace(scene, 1, mapping); + } else { + if(data.byName) + output = mapIndexField(scene, Trade::SceneField::MeshMaterial, mapping); + else + output = mapIndexField(scene, 1, mapping); + } + const Trade::SceneData& result = data.inPlace ? scene : output; + + /* Mapping should stay untouched */ + CORRADE_COMPARE(result.mappingBound(), 55); + CORRADE_COMPARE(result.mappingType(), Trade::SceneMappingType::UnsignedByte); + CORRADE_COMPARE_AS(result.mapping(0), Containers::arrayView({ + 0, 11, 22, 33, 44 + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(result.mapping(1), Containers::arrayView({ + 0, 33, 2, 2 + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(result.mapping(2), Containers::arrayView({ + 0, 33, 2, 2 + }), TestSuite::Compare::Container); + + /* All fields except the mesh material should stay the same as before. With + the in-place variant the type should stay the same, otherwise expanded + to 32-bit. */ + CORRADE_COMPARE_AS(result.field(0), Containers::arrayView({ + -1, -1, 1, 4, 0 + }), TestSuite::Compare::Container); + if(data.inPlace) { + CORRADE_COMPARE(result.fieldType(1), Trade::Implementation::SceneFieldTypeFor::type()); + CORRADE_COMPARE_AS(result.field(1), Containers::arrayView({ + 3, -1, IndexFieldTraits::Max, 12 + }), TestSuite::Compare::Container); + } else { + CORRADE_COMPARE(result.fieldType(1), Trade::SceneFieldType::Int); + CORRADE_COMPARE_AS(result.field(1), Containers::arrayView({ + 3, -1, IndexFieldTraits::Max, 12 + }), TestSuite::Compare::Container); + } + CORRADE_COMPARE_AS(result.field(2), Containers::arrayView({ + 5, 9, 1, 0 + }), TestSuite::Compare::Container); +} + +void MapTest::indexFieldOffsetOnly() { + /* Subset of indexField() with the mapped field being specified as + offset-only. Should "just work" without any special treatment needed in + the implementation. */ + + struct SceneData { + UnsignedShort meshMaterialMapping[4]; + Byte meshMaterial[4]; + UnsignedShort mesh[4]; + } sceneData[]{{ + {0, 33, 2, 2}, + {9, -1, 5, 1}, + {5, 9, 1, 0}, /* this one gets mapped */ + }}; + + Trade::SceneData scene{Trade::SceneMappingType::UnsignedShort, 5, Trade::DataFlag::Mutable, sceneData, { + Trade::SceneFieldData{Trade::SceneField::MeshMaterial, + Containers::arrayView(sceneData->meshMaterialMapping), + Containers::arrayView(sceneData->meshMaterial)}, + Trade::SceneFieldData{Trade::SceneField::Mesh, 4, + Trade::SceneMappingType::UnsignedShort, offsetof(SceneData, meshMaterialMapping), sizeof(UnsignedShort), + Trade::SceneFieldType::UnsignedShort, offsetof(SceneData, mesh), sizeof(UnsignedShort)}, + }}; + + /* The 0xffffffffu values shouldn't be used for anything */ + const UnsignedInt mapping[]{ + 12, 0, 0xffffffffu, 0xffffffffu, 0xffffffffu, + 0xffff, 0xffffffffu, 0xffffffffu, 0xffffffffu, 3 + }; + mapIndexFieldInPlace(scene, Trade::SceneField::Mesh, mapping); + CORRADE_COMPARE_AS(scene.field(1), Containers::arrayView({ + 0xffff, 3, 0, 12 + }), TestSuite::Compare::Container); +} + +void MapTest::indexFieldFieldNotFound() { + CORRADE_SKIP_IF_NO_ASSERT(); + + Trade::SceneData scene{Trade::SceneMappingType::UnsignedInt, 0, nullptr, { + Trade::SceneFieldData{Trade::SceneField::Parent, + Trade::SceneMappingType::UnsignedInt, nullptr, + Trade::SceneFieldType::Int, nullptr}, + Trade::SceneFieldData{Trade::SceneField::Mesh, + Trade::SceneMappingType::UnsignedInt, nullptr, + Trade::SceneFieldType::UnsignedInt, nullptr}, + }}; + + UnsignedInt mapping[5]{}; + + std::ostringstream out; + Error redirectError{&out}; + mapIndexField(scene, 2, mapping); + mapIndexFieldInPlace(scene, 2, mapping); + mapIndexField(scene, Trade::SceneField::MeshMaterial, mapping); + mapIndexFieldInPlace(scene, Trade::SceneField::MeshMaterial, mapping); + CORRADE_COMPARE_AS(out.str(), + "SceneTools::mapIndexField(): index 2 out of range for 2 fields\n" + "SceneTools::mapIndexFieldInPlace(): index 2 out of range for 2 fields\n" + "SceneTools::mapIndexField(): field Trade::SceneField::MeshMaterial not found\n" + "SceneTools::mapIndexFieldInPlace(): field Trade::SceneField::MeshMaterial not found\n", + TestSuite::Compare::String); +} + +void MapTest::indexFieldInvalidType() { + CORRADE_SKIP_IF_NO_ASSERT(); + + Trade::SceneData scene{Trade::SceneMappingType::UnsignedInt, 0, nullptr, { + Trade::SceneFieldData{Trade::SceneField::Mesh, + Trade::SceneMappingType::UnsignedInt, nullptr, + Trade::SceneFieldType::UnsignedInt, nullptr}, + Trade::SceneFieldData{Trade::SceneField::Parent, + Trade::SceneMappingType::UnsignedInt, nullptr, + Trade::SceneFieldType::Long, nullptr}, + }}; + + UnsignedInt mapping[5]{}; + + std::ostringstream out; + Error redirectError{&out}; + mapIndexField(scene, 1, mapping); + mapIndexFieldInPlace(scene, 1, mapping); + CORRADE_COMPARE_AS(out.str(), + "SceneTools::mapIndexField(): unsupported field type Trade::SceneFieldType::Long\n" + "SceneTools::mapIndexFieldInPlace(): unsupported field type Trade::SceneFieldType::Long\n", + TestSuite::Compare::String); +} + +void MapTest::indexFieldArrayField() { + CORRADE_SKIP_IF_NO_ASSERT(); + + Trade::SceneData scene{Trade::SceneMappingType::UnsignedInt, 0, nullptr, { + Trade::SceneFieldData{Trade::SceneField::Mesh, + Trade::SceneMappingType::UnsignedInt, nullptr, + Trade::SceneFieldType::UnsignedInt, nullptr}, + Trade::SceneFieldData{Trade::sceneFieldCustom(0x1337), + Trade::SceneMappingType::UnsignedInt, nullptr, + Trade::SceneFieldType::Byte, nullptr, 3}, + }}; + + UnsignedInt mapping[5]{}; + + std::ostringstream out; + Error redirectError{&out}; + mapIndexField(scene, 1, mapping); + mapIndexFieldInPlace(scene, 1, mapping); + CORRADE_COMPARE_AS(out.str(), + "SceneTools::mapIndexField(): array field mapping isn't supported\n" + "SceneTools::mapIndexFieldInPlace(): array field mapping isn't supported\n", + TestSuite::Compare::String); +} + +void MapTest::indexFieldIndexOutOfBounds() { + CORRADE_SKIP_IF_NO_ASSERT(); + + const struct { + UnsignedShort meshMaterialMapping[4]; + Byte meshMaterial[4]; + UnsignedShort mesh[4]; + } sceneData[]{{ + {}, + {5, -1, 9, -2}, + {5, 10, 1, 0}, + }}; + Trade::SceneData scene{Trade::SceneMappingType::UnsignedShort, 5, {}, sceneData, { + Trade::SceneFieldData{Trade::SceneField::Mesh, + Containers::arrayView(sceneData->meshMaterialMapping), + Containers::arrayView(sceneData->mesh)}, + Trade::SceneFieldData{Trade::SceneField::MeshMaterial, + Containers::arrayView(sceneData->meshMaterialMapping), + Containers::arrayView(sceneData->meshMaterial)}, + }}; + + const UnsignedInt mapping9[9]{}; + const UnsignedInt mapping10[10]{}; + + std::ostringstream out; + Error redirectError{&out}; + mapIndexField(scene, Trade::SceneField::MeshMaterial, mapping10); + mapIndexField(scene, Trade::SceneField::MeshMaterial, mapping9); + mapIndexField(scene, Trade::SceneField::Mesh, mapping10); + CORRADE_COMPARE_AS(out.str(), + "SceneTools::mapIndexFieldInPlace(): index -2 out of range for 10 mapping values\n" + "SceneTools::mapIndexFieldInPlace(): index 9 out of range for 9 mapping values\n" + "SceneTools::mapIndexFieldInPlace(): index 10 out of range for 10 mapping values\n", + TestSuite::Compare::String); +} + +void MapTest::indexFieldMappingNotRepresentable() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct { + UnsignedShort mapping[4]; + UnsignedShort mesh[4]; + UnsignedByte light[4]; + Int custom1[4]; + Short custom2[4]; + Byte meshMaterial[4]; + } sceneData[]{{ + {}, + {0, 4, 3, 1}, + {0, 4, 3, 1}, + {0, -1, 3, 1}, + {0, -1, 2, 1}, + {0, -1, 0, 1}, + }}; + Trade::SceneData scene{Trade::SceneMappingType::UnsignedShort, 1, Trade::DataFlag::Mutable, sceneData, { + Trade::SceneFieldData{Trade::SceneField::Mesh, + Containers::arrayView(sceneData->mapping), + Containers::arrayView(sceneData->mesh)}, + Trade::SceneFieldData{Trade::SceneField::Light, + Containers::arrayView(sceneData->mapping), + Containers::arrayView(sceneData->light)}, + Trade::SceneFieldData{Trade::sceneFieldCustom(1), + Containers::arrayView(sceneData->mapping), + Containers::arrayView(sceneData->custom1)}, + Trade::SceneFieldData{Trade::sceneFieldCustom(2), + Containers::arrayView(sceneData->mapping), + Containers::arrayView(sceneData->custom2)}, + Trade::SceneFieldData{Trade::SceneField::MeshMaterial, + Containers::arrayView(sceneData->mapping), + Containers::arrayView(sceneData->meshMaterial)}, + }}; + + UnsignedInt mappingUnsigned[]{ + /* Index 1 is too large for UnsignedByte, 2 isn't used, 3 is + too large for UnsignedShort */ + 1, 0x100, 0xffffffffu, 0x10000, 3 + }; + UnsignedInt mappingSigned[]{ + /* Index 1 is too large for a Byte, 3 too large for Int, 2 too large + for Short */ + 1, 0x80, 0x8000, 0x80000000u + }; + + /* These should all be okay as they expand to 32 bits */ + mapIndexField(scene, Trade::SceneField::Mesh, mappingUnsigned); + mapIndexField(scene, Trade::SceneField::Light, mappingUnsigned); + mapIndexField(scene, Trade::sceneFieldCustom(2), mappingSigned); + mapIndexField(scene, Trade::SceneField::MeshMaterial, mappingSigned); + + std::ostringstream out; + Error redirectError{&out}; + mapIndexFieldInPlace(scene, Trade::SceneField::Mesh, mappingUnsigned); + mapIndexFieldInPlace(scene, Trade::SceneField::Light, mappingUnsigned); + /* This one expands to 32 bits but is still signed which isn't enough */ + mapIndexField(scene, Trade::sceneFieldCustom(1), mappingSigned); + mapIndexFieldInPlace(scene, Trade::sceneFieldCustom(1), mappingSigned); + mapIndexFieldInPlace(scene, Trade::sceneFieldCustom(2), mappingSigned); + mapIndexFieldInPlace(scene, Trade::SceneField::MeshMaterial, mappingSigned); + CORRADE_COMPARE_AS(out.str(), + "SceneTools::mapIndexFieldInPlace(): mapping value 65536 not representable in Trade::SceneFieldType::UnsignedShort\n" + "SceneTools::mapIndexFieldInPlace(): mapping value 65536 not representable in Trade::SceneFieldType::UnsignedByte\n" + "SceneTools::mapIndexFieldInPlace(): mapping value 2147483648 not representable in Trade::SceneFieldType::Int\n" + "SceneTools::mapIndexFieldInPlace(): mapping value 2147483648 not representable in Trade::SceneFieldType::Int\n" + "SceneTools::mapIndexFieldInPlace(): mapping value 32768 not representable in Trade::SceneFieldType::Short\n" + "SceneTools::mapIndexFieldInPlace(): mapping value 128 not representable in Trade::SceneFieldType::Byte\n", + TestSuite::Compare::String); +} + +void MapTest::indexFieldRvalue() { + auto&& data = IndexFieldRvalueData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct Data { + UnsignedByte mapping[4]; + Short meshMaterial[4]; + UnsignedInt mesh[4]; + }; + Containers::Array sceneData{NoInit, sizeof(Data)}; + Containers::StridedArrayView1D view = Containers::arrayCast(sceneData); + Utility::copy({{ + {77, 33, 44, 66}, + {2, -1, 0, 1}, + {3, 4, 1, 0}, + }}, view); + + Trade::SceneData scene{Trade::SceneMappingType::UnsignedByte, 88, Utility::move(sceneData), { + Trade::SceneFieldData{Trade::SceneField::MeshMaterial, + Containers::arrayView(view[0].mapping), + Containers::arrayView(view[0].meshMaterial)}, + Trade::SceneFieldData{Trade::SceneField::Mesh, + Containers::arrayView(view[0].mapping), + Containers::arrayView(view[0].mesh)}, + }}; + const Trade::SceneFieldData* originalFields = scene.fieldData(); + + const UnsignedInt mapping[]{ + 15, 16, 0xffffffffu, 7, 9 + }; + Trade::SceneData mapped = data.byName ? + mapIndexField(Utility::move(scene), Trade::SceneField::Mesh, mapping) : + mapIndexField(Utility::move(scene), 1, mapping); + + /* Mapping should stay untouched */ + CORRADE_COMPARE(mapped.mappingBound(), 88); + CORRADE_COMPARE(mapped.mappingType(), Trade::SceneMappingType::UnsignedByte); + CORRADE_COMPARE_AS(mapped.mapping(0), Containers::arrayView({ + 77, 33, 44, 66 + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(mapped.mapping(1), Containers::arrayView({ + 77, 33, 44, 66 + }), TestSuite::Compare::Container); + + /* Mesh should be mapped, materials should stay the same as before */ + CORRADE_COMPARE_AS(mapped.field(0), Containers::arrayView({ + 2, -1, 0, 1 + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(mapped.field(1), Containers::arrayView({ + 7, 9, 16, 15 + }), TestSuite::Compare::Container); + + /* Both data should be transferred without any copy */ + CORRADE_COMPARE(mapped.data().data(), view.data()); + CORRADE_COMPARE(mapped.fieldData().data(), originalFields); +} + +void MapTest::indexFieldRvalueSigned() { + auto&& data = IndexFieldRvalueData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct Data { + UnsignedByte mapping[4]; + UnsignedShort mesh[4]; + Int meshMaterial[4]; + }; + Containers::Array sceneData{NoInit, sizeof(Data)}; + Containers::StridedArrayView1D view = Containers::arrayCast(sceneData); + Utility::copy({{ + {77, 33, 44, 66}, + {3, 4, 1, 0}, + {2, -1, 0, 3}, + }}, view); + + Trade::SceneData scene{Trade::SceneMappingType::UnsignedByte, 88, Utility::move(sceneData), { + Trade::SceneFieldData{Trade::SceneField::Mesh, + Containers::arrayView(view[0].mapping), + Containers::arrayView(view[0].mesh)}, + Trade::SceneFieldData{Trade::SceneField::MeshMaterial, + Containers::arrayView(view[0].mapping), + Containers::arrayView(view[0].meshMaterial)}, + }}; + const Trade::SceneFieldData* originalFields = scene.fieldData(); + + const UnsignedInt mapping[]{ + 15, 0xffffffffu, 16, 7 + }; + Trade::SceneData mapped = data.byName ? + mapIndexField(Utility::move(scene), Trade::SceneField::MeshMaterial, mapping) : + mapIndexField(Utility::move(scene), 1, mapping); + + /* Mapping should stay untouched */ + CORRADE_COMPARE(mapped.mappingBound(), 88); + CORRADE_COMPARE(mapped.mappingType(), Trade::SceneMappingType::UnsignedByte); + CORRADE_COMPARE_AS(mapped.mapping(0), Containers::arrayView({ + 77, 33, 44, 66 + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(mapped.mapping(1), Containers::arrayView({ + 77, 33, 44, 66 + }), TestSuite::Compare::Container); + + /* Mesh should stay the same as before, materials should be mapped */ + CORRADE_COMPARE_AS(mapped.field(0), Containers::arrayView({ + 3, 4, 1, 0 + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(mapped.field(1), Containers::arrayView({ + 16, -1, 15, 7 + }), TestSuite::Compare::Container); + + /* Both data should be transferred without any copy */ + CORRADE_COMPARE(mapped.data().data(), view.data()); + CORRADE_COMPARE(mapped.fieldData().data(), originalFields); +} + +void MapTest::indexFieldRvalueNotOwned() { + /* Like indexFieldRvalue(), but the data is not owned so it should perform + a copy */ + + struct { + UnsignedByte mapping[4]; + Short meshMaterial[4]; + UnsignedInt mesh[4]; + } sceneData[]{{ + {77, 33, 44, 66}, + {2, -1, 0, 1}, + {3, 4, 1, 0}, + }}; + + /* Mark the data as Mutable to test it isn't accidentally treated the same + as Owned */ + Trade::SceneData scene{Trade::SceneMappingType::UnsignedByte, 88, Trade::DataFlag::Mutable, sceneData, { + Trade::SceneFieldData{Trade::SceneField::MeshMaterial, + Containers::arrayView(sceneData->mapping), + Containers::arrayView(sceneData->meshMaterial)}, + Trade::SceneFieldData{Trade::SceneField::Mesh, + Containers::arrayView(sceneData->mapping), + Containers::arrayView(sceneData->mesh)}, + }}; + const Trade::SceneFieldData* originalFields = scene.fieldData(); + + const UnsignedInt mapping[]{ + 15, 16, 0xffffffffu, 7, 9 + }; + Trade::SceneData mapped = mapIndexField(Utility::move(scene), 1, mapping); + + /* Mapping should stay untouched */ + CORRADE_COMPARE(mapped.mappingBound(), 88); + CORRADE_COMPARE(mapped.mappingType(), Trade::SceneMappingType::UnsignedByte); + CORRADE_COMPARE_AS(mapped.mapping(0), Containers::arrayView({ + 77, 33, 44, 66 + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(mapped.mapping(1), Containers::arrayView({ + 77, 33, 44, 66 + }), TestSuite::Compare::Container); + + /* Mesh should be mapped, materials should stay the same as before */ + CORRADE_COMPARE_AS(mapped.field(0), Containers::arrayView({ + 2, -1, 0, 1 + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(mapped.field(1), Containers::arrayView({ + 7, 9, 16, 15 + }), TestSuite::Compare::Container); + + /* Data should be copied */ + CORRADE_VERIFY(mapped.data().data() != static_cast(sceneData)); + CORRADE_VERIFY(mapped.fieldData().data() != originalFields); +} + +void MapTest::indexFieldRvalueNotFullType() { + /* Like indexFieldRvalue(), but the field is not a 32-bit type */ + + struct Data { + UnsignedByte mapping[4]; + Int meshMaterial[4]; + UnsignedShort mesh[4]; + }; + Containers::Array sceneData{NoInit, sizeof(Data)}; + Containers::StridedArrayView1D view = Containers::arrayCast(sceneData); + Utility::copy({{ + {77, 33, 44, 66}, + {2, -1, 0, 1}, + {3, 4, 1, 0}, + }}, view); + + Trade::SceneData scene{Trade::SceneMappingType::UnsignedByte, 88, Utility::move(sceneData), { + Trade::SceneFieldData{Trade::SceneField::MeshMaterial, + Containers::arrayView(view[0].mapping), + Containers::arrayView(view[0].meshMaterial)}, + Trade::SceneFieldData{Trade::SceneField::Mesh, + Containers::arrayView(view[0].mapping), + Containers::arrayView(view[0].mesh)}, + }}; + const Trade::SceneFieldData* originalFields = scene.fieldData(); + + const UnsignedInt mapping[]{ + 15, 16, 0xffffffffu, 7, 9 + }; + Trade::SceneData mapped = mapIndexField(Utility::move(scene), 1, mapping); + + /* Mapping should stay untouched */ + CORRADE_COMPARE(mapped.mappingBound(), 88); + CORRADE_COMPARE(mapped.mappingType(), Trade::SceneMappingType::UnsignedByte); + CORRADE_COMPARE_AS(mapped.mapping(0), Containers::arrayView({ + 77, 33, 44, 66 + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(mapped.mapping(1), Containers::arrayView({ + 77, 33, 44, 66 + }), TestSuite::Compare::Container); + + /* Mesh should be mapped, materials should stay the same as before. As a + copy is performed, the type is expanded to 32 bits. */ + CORRADE_COMPARE_AS(mapped.field(0), Containers::arrayView({ + 2, -1, 0, 1 + }), TestSuite::Compare::Container); + CORRADE_COMPARE(mapped.fieldType(1), Trade::SceneFieldType::UnsignedInt); + CORRADE_COMPARE_AS(mapped.field(1), Containers::arrayView({ + 7, 9, 16, 15 + }), TestSuite::Compare::Container); + + /* Data should be copied */ + CORRADE_VERIFY(mapped.data().data() != static_cast(sceneData)); + CORRADE_VERIFY(mapped.fieldData().data() != originalFields); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::SceneTools::Test::MapTest)