From aff4a8144d4e90ff90d6a4c8fc2c167407505518 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 14 Dec 2021 14:10:42 +0100 Subject: [PATCH] Trade: move the internal scene conversion tools to SceneTools. No functional change, just splitting them to two separate headers and two separate tests. These will eventually become public SceneTools APIs... once I figure out better naming. --- src/Magnum/SceneTools/CMakeLists.txt | 7 +- .../SceneTools/Implementation/combine.h | 196 ++++++ .../convertToSingleFunctionObjects.h} | 197 +----- src/Magnum/SceneTools/Test/CMakeLists.txt | 8 + src/Magnum/SceneTools/Test/CombineTest.cpp | 329 +++++++++ .../ConvertToSingleFunctionObjectsTest.cpp | 383 ++++++++++ src/Magnum/Trade/AbstractImporter.cpp | 6 +- src/Magnum/Trade/CMakeLists.txt | 3 +- src/Magnum/Trade/Test/CMakeLists.txt | 2 - src/Magnum/Trade/Test/SceneToolsTest.cpp | 666 ------------------ 10 files changed, 950 insertions(+), 847 deletions(-) create mode 100644 src/Magnum/SceneTools/Implementation/combine.h rename src/Magnum/{Trade/Implementation/sceneTools.h => SceneTools/Implementation/convertToSingleFunctionObjects.h} (52%) create mode 100644 src/Magnum/SceneTools/Test/CombineTest.cpp create mode 100644 src/Magnum/SceneTools/Test/ConvertToSingleFunctionObjectsTest.cpp delete mode 100644 src/Magnum/Trade/Test/SceneToolsTest.cpp diff --git a/src/Magnum/SceneTools/CMakeLists.txt b/src/Magnum/SceneTools/CMakeLists.txt index 4ab58cd75..622156ad1 100644 --- a/src/Magnum/SceneTools/CMakeLists.txt +++ b/src/Magnum/SceneTools/CMakeLists.txt @@ -28,9 +28,14 @@ set(MagnumSceneTools_HEADERS visibility.h) +set(MagnumSceneTools_PRIVATE_HEADERS + Implementation/combine.h + Implementation/convertToSingleFunctionObjects.h) + # Main SceneTools library add_library(MagnumSceneTools INTERFACE - ${MagnumSceneTools_HEADERS}) + ${MagnumSceneTools_HEADERS} + ${MagnumSceneTools_PRIVATE_HEADERS}) set_target_properties(MagnumSceneTools PROPERTIES DEBUG_POSTFIX "-d" FOLDER "Magnum/SceneTools") diff --git a/src/Magnum/SceneTools/Implementation/combine.h b/src/Magnum/SceneTools/Implementation/combine.h new file mode 100644 index 000000000..c90bfd05e --- /dev/null +++ b/src/Magnum/SceneTools/Implementation/combine.h @@ -0,0 +1,196 @@ +#ifndef Magnum_SceneTools_Implementation_combine_h +#define Magnum_SceneTools_Implementation_combine_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021 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 + +#include "Magnum/Math/Functions.h" +#include "Magnum/Math/PackingBatch.h" +#include "Magnum/Trade/SceneData.h" + +namespace Magnum { namespace SceneTools { namespace Implementation { + +/* These two are needed because there (obviously) isn't any overload of + castInto with the same input and output type */ +template void copyOrCastInto(const Containers::StridedArrayView1D& src, const Containers::StridedArrayView1D& dst) { + Math::castInto(Containers::arrayCast<2, const T>(src), Containers::arrayCast<2, U>(dst)); +} +template void copyOrCastInto(const Containers::StridedArrayView1D& src, const Containers::StridedArrayView1D& dst) { + Utility::copy(src, dst); +} + +template void combineCopyObjects(const Containers::ArrayView fields, const Containers::ArrayView> itemViews, const Containers::ArrayView> itemViewMappings) { + std::size_t latestMapping = 0; + for(std::size_t i = 0; i != fields.size(); ++i) { + /* If there are no aliased object mappings, itemViewMappings should be + monotonically increasing. If it's not, it means the mapping is + shared with something earlier and it got already copied -- skip. */ + const std::size_t mapping = itemViewMappings[i].first(); + if(i && mapping <= latestMapping) continue; + latestMapping = mapping; + + /* If the field has null object data, no need to copy anything. This + covers reserved fields but also fields of zero size. */ + if(!fields[i].mappingData()) continue; + + const Containers::StridedArrayView1D src = fields[i].mappingData(); + const Containers::StridedArrayView1D dst = Containers::arrayCast<1, T>(itemViews[mapping]); + if(fields[i].mappingType() == Trade::SceneMappingType::UnsignedByte) + copyOrCastInto(Containers::arrayCast(src), dst); + else if(fields[i].mappingType() == Trade::SceneMappingType::UnsignedShort) + copyOrCastInto(Containers::arrayCast(src), dst); + else if(fields[i].mappingType() == Trade::SceneMappingType::UnsignedInt) + copyOrCastInto(Containers::arrayCast(src), dst); + else if(fields[i].mappingType() == Trade::SceneMappingType::UnsignedLong) + copyOrCastInto(Containers::arrayCast(src), dst); + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } +} + +/* Combine fields of varying mapping type together into a SceneData of a single + given mappingType. The fields are expected to point to existing + mapping/field memory, which will be then copied to the resulting scene. If + you supply a field with null mapping or field data, the mapping or field + data will not get copied, only a placeholder for copying the data later will + be allocated. If you however need to have placeholder mapping data shared + among multiple fields you have to allocate them upfront. Offset-only fields + are not allowed. + + The resulting fields are always tightly packed (not interleaved). + + If multiple fields share the same object mapping views, those are preserved, + however they have to have the exact same length. Sharing object mappings + with different lengths will assert. */ +/** @todo when published, add an initializer_list overload and turn all + internal asserts into (tested!) message asserts */ +inline Trade::SceneData combine(const Trade::SceneMappingType mappingType, const UnsignedLong mappingBound, const Containers::ArrayView fields) { + const std::size_t mappingTypeSize = sceneMappingTypeSize(mappingType); + const std::size_t mappingTypeAlignment = sceneMappingTypeAlignment(mappingType); + + /* Go through all fields and collect ArrayTuple allocations for these */ + std::unordered_map objectMappings; + Containers::Array items; + Containers::Array> itemViewMappings{NoInit, fields.size()}; + + /* The item views are referenced from ArrayTuple::Item, not using a + growable array in order to avoid an accidental reallocation */ + /** @todo once never-reallocating allocators are present, use them instead + of the manual offset */ + Containers::Array> itemViews{fields.size()*2}; + std::size_t itemViewOffset = 0; + + for(std::size_t i = 0; i != fields.size(); ++i) { + const Trade::SceneFieldData& field = fields[i]; + CORRADE_INTERNAL_ASSERT(!(field.flags() & Trade::SceneFieldFlag::OffsetOnly)); + + /* Mapping data. Allocate if the view is a placeholder of if it wasn't + used by other fields yet. */ + std::pair::iterator, bool> inserted; + if(field.mappingData().data()) + inserted = objectMappings.emplace(field.mappingData().data(), itemViewOffset); + if(field.mappingData().data() && !inserted.second) { + itemViewMappings[i].first() = inserted.first->second; + /* Expect that fields sharing the same object mapping view have the + exact same length (the length gets stored in the output view + during the ArrayTuple::Item construction). + + We could just ignore the sharing in that case, but that'd only + lead to misery down the line -- imagine a field that shares the + first two items with a mesh and a material object mapping. If it + would be the last, it gets duplicated and everything is great, + however if it's the first then both mesh and the material get + duplicated, and that then asserts inside the SceneData + constructor, as those are always expected to share. + + One option that would solve this would be to store pointer+size + in the objectMappings map (and then only mappings that share + also the same size would be shared), another would be to use the + longest used view (and then the shorter prefixes would share + with it). The ultimate option would be to have some range map + where it'd be possible to locate also arbitrary subranges, not + just prefixes. A whole other topic altogether is checking for + the same stride, which is not done at all. + + This might theoretically lead to assertions also when two + compile-time arrays share a common prefix and get deduplicated + by the compiler. But that's unlikely, at least for the internal + use case we have right now. */ + CORRADE_INTERNAL_ASSERT(itemViews[inserted.first->second].size()[0] == field.size()); + } else { + itemViewMappings[i].first() = itemViewOffset; + arrayAppend(items, InPlaceInit, NoInit, std::size_t(field.size()), mappingTypeSize, mappingTypeAlignment, itemViews[itemViewOffset]); + ++itemViewOffset; + } + + /* Field data. No aliasing here right now, no sharing between object + and field data either. */ + /** @todo field aliasing might be useful at some point */ + itemViewMappings[i].second() = itemViewOffset; + arrayAppend(items, InPlaceInit, NoInit, std::size_t(field.size()), sceneFieldTypeSize(field.fieldType())*(field.fieldArraySize() ? field.fieldArraySize() : 1), sceneFieldTypeAlignment(field.fieldType()), itemViews[itemViewOffset]); + ++itemViewOffset; + } + + /* Allocate the data */ + Containers::Array outData = Containers::ArrayTuple{items}; + CORRADE_INTERNAL_ASSERT(!outData.deleter()); + + /* Copy the object data over and cast them as necessary */ + if(mappingType == Trade::SceneMappingType::UnsignedByte) + combineCopyObjects(fields, itemViews, itemViewMappings); + else if(mappingType == Trade::SceneMappingType::UnsignedShort) + combineCopyObjects(fields, itemViews, itemViewMappings); + else if(mappingType == Trade::SceneMappingType::UnsignedInt) + combineCopyObjects(fields, itemViews, itemViewMappings); + else if(mappingType == Trade::SceneMappingType::UnsignedLong) + combineCopyObjects(fields, itemViews, itemViewMappings); + + /* Copy the field data over. No special handling needed here. */ + for(std::size_t i = 0; i != fields.size(); ++i) { + /* If the field has null field data, no need to copy anything. This + covers reserved fields but also fields of zero size. */ + if(!fields[i].fieldData()) continue; + + /** @todo isn't there some less awful way to create a 2D view, sigh */ + Utility::copy(Containers::arrayCast<2, const char>(fields[i].fieldData(), sceneFieldTypeSize(fields[i].fieldType())*(fields[i].fieldArraySize() ? fields[i].fieldArraySize() : 1)), itemViews[itemViewMappings[i].second()]); + } + + /* Map the fields to the new data */ + Containers::Array outFields{fields.size()}; + for(std::size_t i = 0; i != fields.size(); ++i) { + outFields[i] = Trade::SceneFieldData{fields[i].name(), itemViews[itemViewMappings[i].first()], fields[i].fieldType(), itemViews[itemViewMappings[i].second()], fields[i].fieldArraySize(), fields[i].flags()}; + } + + return Trade::SceneData{mappingType, mappingBound, std::move(outData), std::move(outFields)}; +} + +}}} + +#endif diff --git a/src/Magnum/Trade/Implementation/sceneTools.h b/src/Magnum/SceneTools/Implementation/convertToSingleFunctionObjects.h similarity index 52% rename from src/Magnum/Trade/Implementation/sceneTools.h rename to src/Magnum/SceneTools/Implementation/convertToSingleFunctionObjects.h index a7a67e67b..854152f2f 100644 --- a/src/Magnum/Trade/Implementation/sceneTools.h +++ b/src/Magnum/SceneTools/Implementation/convertToSingleFunctionObjects.h @@ -1,5 +1,5 @@ -#ifndef Magnum_Trade_Implementation_sceneTools_h -#define Magnum_Trade_Implementation_sceneTools_h +#ifndef Magnum_SceneTools_Implementation_convertToSingleFunctionObjects_h +#define Magnum_SceneTools_Implementation_convertToSingleFunctionObjects_h /* This file is part of Magnum. @@ -34,164 +34,12 @@ #include "Magnum/Math/Functions.h" #include "Magnum/Math/PackingBatch.h" +#include "Magnum/SceneTools/Implementation/combine.h" #include "Magnum/Trade/SceneData.h" -namespace Magnum { namespace Trade { namespace Implementation { +namespace Magnum { namespace SceneTools { namespace Implementation { -/* These two are needed because there (obviously) isn't any overload of - castInto with the same input and output type */ -template void copyOrCastInto(const Containers::StridedArrayView1D& src, const Containers::StridedArrayView1D& dst) { - Math::castInto(Containers::arrayCast<2, const T>(src), Containers::arrayCast<2, U>(dst)); -} -template void copyOrCastInto(const Containers::StridedArrayView1D& src, const Containers::StridedArrayView1D& dst) { - Utility::copy(src, dst); -} - -template void sceneCombineCopyObjects(const Containers::ArrayView fields, const Containers::ArrayView> itemViews, const Containers::ArrayView> itemViewMappings) { - std::size_t latestMapping = 0; - for(std::size_t i = 0; i != fields.size(); ++i) { - /* If there are no aliased object mappings, itemViewMappings should be - monotonically increasing. If it's not, it means the mapping is - shared with something earlier and it got already copied -- skip. */ - const std::size_t mapping = itemViewMappings[i].first(); - if(i && mapping <= latestMapping) continue; - latestMapping = mapping; - - /* If the field has null object data, no need to copy anything. This - covers reserved fields but also fields of zero size. */ - if(!fields[i].mappingData()) continue; - - const Containers::StridedArrayView1D src = fields[i].mappingData(); - const Containers::StridedArrayView1D dst = Containers::arrayCast<1, T>(itemViews[mapping]); - if(fields[i].mappingType() == SceneMappingType::UnsignedByte) - copyOrCastInto(Containers::arrayCast(src), dst); - else if(fields[i].mappingType() == SceneMappingType::UnsignedShort) - copyOrCastInto(Containers::arrayCast(src), dst); - else if(fields[i].mappingType() == SceneMappingType::UnsignedInt) - copyOrCastInto(Containers::arrayCast(src), dst); - else if(fields[i].mappingType() == SceneMappingType::UnsignedLong) - copyOrCastInto(Containers::arrayCast(src), dst); - else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ - } -} - -/* Combine fields of varying mapping type together into a SceneData of a single - given mappingType. The fields are expected to point to existing - mapping/field memory, which will be then copied to the resulting scene. If - you supply a field with null mapping or field data, the mapping or field - data will not get copied, only a placeholder for copying the data later will - be allocated. If you however need to have placeholder mapping data shared - among multiple fields you have to allocate them upfront. Offset-only fields - are not allowed. - - The resulting fields are always tightly packed (not interleaved). - - If multiple fields share the same object mapping views, those are preserved, - however they have to have the exact same length. Sharing object mappings - with different lengths will assert. */ -/** @todo when published, add an initializer_list overload and turn all - internal asserts into (tested!) message asserts */ -inline SceneData sceneCombine(const SceneMappingType mappingType, const UnsignedLong mappingBound, const Containers::ArrayView fields) { - const std::size_t mappingTypeSize = sceneMappingTypeSize(mappingType); - const std::size_t mappingTypeAlignment = sceneMappingTypeAlignment(mappingType); - - /* Go through all fields and collect ArrayTuple allocations for these */ - std::unordered_map objectMappings; - Containers::Array items; - Containers::Array> itemViewMappings{NoInit, fields.size()}; - - /* The item views are referenced from ArrayTuple::Item, not using a - growable array in order to avoid an accidental reallocation */ - /** @todo once never-reallocating allocators are present, use them instead - of the manual offset */ - Containers::Array> itemViews{fields.size()*2}; - std::size_t itemViewOffset = 0; - - for(std::size_t i = 0; i != fields.size(); ++i) { - const SceneFieldData& field = fields[i]; - CORRADE_INTERNAL_ASSERT(!(field.flags() & SceneFieldFlag::OffsetOnly)); - - /* Mapping data. Allocate if the view is a placeholder of if it wasn't - used by other fields yet. */ - std::pair::iterator, bool> inserted; - if(field.mappingData().data()) - inserted = objectMappings.emplace(field.mappingData().data(), itemViewOffset); - if(field.mappingData().data() && !inserted.second) { - itemViewMappings[i].first() = inserted.first->second; - /* Expect that fields sharing the same object mapping view have the - exact same length (the length gets stored in the output view - during the ArrayTuple::Item construction). - - We could just ignore the sharing in that case, but that'd only - lead to misery down the line -- imagine a field that shares the - first two items with a mesh and a material object mapping. If it - would be the last, it gets duplicated and everything is great, - however if it's the first then both mesh and the material get - duplicated, and that then asserts inside the SceneData - constructor, as those are always expected to share. - - One option that would solve this would be to store pointer+size - in the objectMappings map (and then only mappings that share - also the same size would be shared), another would be to use the - longest used view (and then the shorter prefixes would share - with it). The ultimate option would be to have some range map - where it'd be possible to locate also arbitrary subranges, not - just prefixes. A whole other topic altogether is checking for - the same stride, which is not done at all. - - This might theoretically lead to assertions also when two - compile-time arrays share a common prefix and get deduplicated - by the compiler. But that's unlikely, at least for the internal - use case we have right now. */ - CORRADE_INTERNAL_ASSERT(itemViews[inserted.first->second].size()[0] == field.size()); - } else { - itemViewMappings[i].first() = itemViewOffset; - arrayAppend(items, InPlaceInit, NoInit, std::size_t(field.size()), mappingTypeSize, mappingTypeAlignment, itemViews[itemViewOffset]); - ++itemViewOffset; - } - - /* Field data. No aliasing here right now, no sharing between object - and field data either. */ - /** @todo field aliasing might be useful at some point */ - itemViewMappings[i].second() = itemViewOffset; - arrayAppend(items, InPlaceInit, NoInit, std::size_t(field.size()), sceneFieldTypeSize(field.fieldType())*(field.fieldArraySize() ? field.fieldArraySize() : 1), sceneFieldTypeAlignment(field.fieldType()), itemViews[itemViewOffset]); - ++itemViewOffset; - } - - /* Allocate the data */ - Containers::Array outData = Containers::ArrayTuple{items}; - CORRADE_INTERNAL_ASSERT(!outData.deleter()); - - /* Copy the object data over and cast them as necessary */ - if(mappingType == SceneMappingType::UnsignedByte) - sceneCombineCopyObjects(fields, itemViews, itemViewMappings); - else if(mappingType == SceneMappingType::UnsignedShort) - sceneCombineCopyObjects(fields, itemViews, itemViewMappings); - else if(mappingType == SceneMappingType::UnsignedInt) - sceneCombineCopyObjects(fields, itemViews, itemViewMappings); - else if(mappingType == SceneMappingType::UnsignedLong) - sceneCombineCopyObjects(fields, itemViews, itemViewMappings); - - /* Copy the field data over. No special handling needed here. */ - for(std::size_t i = 0; i != fields.size(); ++i) { - /* If the field has null field data, no need to copy anything. This - covers reserved fields but also fields of zero size. */ - if(!fields[i].fieldData()) continue; - - /** @todo isn't there some less awful way to create a 2D view, sigh */ - Utility::copy(Containers::arrayCast<2, const char>(fields[i].fieldData(), sceneFieldTypeSize(fields[i].fieldType())*(fields[i].fieldArraySize() ? fields[i].fieldArraySize() : 1)), itemViews[itemViewMappings[i].second()]); - } - - /* Map the fields to the new data */ - Containers::Array outFields{fields.size()}; - for(std::size_t i = 0; i != fields.size(); ++i) { - outFields[i] = SceneFieldData{fields[i].name(), itemViews[itemViewMappings[i].first()], fields[i].fieldType(), itemViews[itemViewMappings[i].second()], fields[i].fieldArraySize(), fields[i].flags()}; - } - - return SceneData{mappingType, mappingBound, std::move(outData), std::move(outFields)}; -} - -inline Containers::Optional findField(Containers::ArrayView fields, SceneField field) { +inline Containers::Optional findField(Containers::ArrayView fields, Trade::SceneField field) { for(std::size_t i = 0; i != fields.size(); ++i) if(fields[i] == field) return i; return {}; @@ -212,11 +60,12 @@ inline Containers::Optional findField(Containers::ArrayView fieldsToConvert, Containers::ArrayView fieldsToCopy, const UnsignedInt newObjectOffset) { +/** @todo it also needs a better name ("make fields unique"?) */ +inline Trade::SceneData convertToSingleFunctionObjects(const Trade::SceneData& scene, Containers::ArrayView fieldsToConvert, Containers::ArrayView fieldsToCopy, const UnsignedInt newObjectOffset) { /** @todo assert for really high object counts (where this cast would fail) */ Containers::Array objectAttachmentCount{ValueInit, std::size_t(scene.mappingBound())}; - for(const SceneField field: fieldsToConvert) { - CORRADE_INTERNAL_ASSERT(field != SceneField::Parent); + for(const Trade::SceneField field: fieldsToConvert) { + CORRADE_INTERNAL_ASSERT(field != Trade::SceneField::Parent); /* Skip fields that are not present -- is it's not present, then it definitely won't be responsible for multi-function objects */ @@ -236,8 +85,8 @@ inline SceneData sceneConvertToSingleFunctionObjects(const SceneData& scene, Con fieldsToCopy[i] field */ Containers::Array fieldsToCopyAdditionCount{ValueInit, fieldsToCopy.size()}; for(std::size_t i = 0; i != fieldsToCopy.size(); ++i) { - const SceneField field = fieldsToCopy[i]; - CORRADE_INTERNAL_ASSERT(field != SceneField::Parent); + const Trade::SceneField field = fieldsToCopy[i]; + CORRADE_INTERNAL_ASSERT(field != Trade::SceneField::Parent); CORRADE_INTERNAL_ASSERT(!findField(fieldsToConvert, field)); /* Skip fields that are not present */ @@ -264,16 +113,16 @@ inline SceneData sceneConvertToSingleFunctionObjects(const SceneData& scene, Con CORRADE_INTERNAL_ASSERT(newObjectOffset + objectsToAdd >= newObjectOffset); /* Copy the fields over, enlarging them as necessary */ - const UnsignedInt parentFieldId = scene.fieldId(SceneField::Parent); - Containers::Array fields{scene.fieldCount()}; + const UnsignedInt parentFieldId = scene.fieldId(Trade::SceneField::Parent); + Containers::Array fields{scene.fieldCount()}; for(std::size_t i = 0; i != scene.fieldCount(); ++i) { - const SceneFieldData& field = scene.fieldData(i); + const Trade::SceneFieldData& field = scene.fieldData(i); /* If this field is among the fields we want to copy, enlarge it for the new entries */ if(Containers::Optional fieldToCopy = findField(fieldsToCopy, field.name())) { /** @todo wow this placeholder construction is HIDEOUS */ - fields[i] = SceneFieldData{field.name(), + fields[i] = Trade::SceneFieldData{field.name(), field.mappingType(), Containers::ArrayView{nullptr, std::size_t(field.size() + fieldsToCopyAdditionCount[*fieldToCopy])}, field.fieldType(), @@ -283,18 +132,18 @@ inline SceneData sceneConvertToSingleFunctionObjects(const SceneData& scene, Con std::ptrdiff_t((field.fieldArraySize() ? field.fieldArraySize() : 1)*sceneFieldTypeSize(field.fieldType())) }, field.fieldArraySize(), - field.flags() & ~SceneFieldFlag::ImplicitMapping + field.flags() & ~Trade::SceneFieldFlag::ImplicitMapping }; /* If this is a parent, enlarge it for the newly added objects, and if it was implicit make it ordered */ - } else if(field.name() == SceneField::Parent) { + } else if(field.name() == Trade::SceneField::Parent) { /** @todo some nicer constructor for placeholders once this is in public interest */ - fields[i] = SceneFieldData{SceneField::Parent, Containers::ArrayView{nullptr, std::size_t(field.size() + objectsToAdd)}, Containers::ArrayView{nullptr, std::size_t(field.size() + objectsToAdd)}, + fields[i] = Trade::SceneFieldData{Trade::SceneField::Parent, Containers::ArrayView{nullptr, std::size_t(field.size() + objectsToAdd)}, Containers::ArrayView{nullptr, std::size_t(field.size() + objectsToAdd)}, /* If the field is ordered, we preserve that. But if it's implicit, we can't. */ - field.flags() & ~(SceneFieldFlag::ImplicitMapping & ~SceneFieldFlag::OrderedMapping) + field.flags() & ~(Trade::SceneFieldFlag::ImplicitMapping & ~Trade::SceneFieldFlag::OrderedMapping) }; /* All other fields are copied as-is, but lose the implicit/ordered @@ -305,11 +154,11 @@ inline SceneData sceneConvertToSingleFunctionObjects(const SceneData& scene, Con - fields that don't actually get their object mapping touched during the process (and then all fields that share object mapping with them) */ - } else fields[i] = SceneFieldData{field.name(), field.mappingType(), field.mappingData(), field.fieldType(), field.fieldData(), field.fieldArraySize(), field.flags() & ~SceneFieldFlag::ImplicitMapping}; + } else fields[i] = Trade::SceneFieldData{field.name(), field.mappingType(), field.mappingData(), field.fieldType(), field.fieldData(), field.fieldArraySize(), field.flags() & ~Trade::SceneFieldFlag::ImplicitMapping}; } /* Combine the fields into a new SceneData */ - SceneData out = sceneCombine(SceneMappingType::UnsignedInt, Math::max(scene.mappingBound(), UnsignedLong(newObjectOffset) + objectsToAdd), fields); + Trade::SceneData out = combine(Trade::SceneMappingType::UnsignedInt, Math::max(scene.mappingBound(), UnsignedLong(newObjectOffset) + objectsToAdd), fields); /* Copy existing parent object/field data to a prefix of the output */ const Containers::StridedArrayView1D outParentMapping = out.mutableMapping(parentFieldId); @@ -318,7 +167,7 @@ inline SceneData sceneConvertToSingleFunctionObjects(const SceneData& scene, Con /* Copy existing field-to-copy data to a prefix of the output */ for(std::size_t i = 0; i != fieldsToCopy.size(); ++i) { - const SceneField field = fieldsToCopy[i]; + const Trade::SceneField field = fieldsToCopy[i]; const Containers::Optional fieldId = scene.findFieldId(field); if(!fieldId) continue; @@ -349,7 +198,7 @@ inline SceneData sceneConvertToSingleFunctionObjects(const SceneData& scene, Con children */ { std::size_t newParentIndex = 0; - for(const SceneField field: fieldsToConvert) { + for(const Trade::SceneField field: fieldsToConvert) { const Containers::Optional fieldId = scene.findFieldId(field); if(!fieldId) continue; diff --git a/src/Magnum/SceneTools/Test/CMakeLists.txt b/src/Magnum/SceneTools/Test/CMakeLists.txt index 69069041f..718344b8c 100644 --- a/src/Magnum/SceneTools/Test/CMakeLists.txt +++ b/src/Magnum/SceneTools/Test/CMakeLists.txt @@ -22,3 +22,11 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. # + +corrade_add_test(SceneToolsCombineTest CombineTest.cpp LIBRARIES MagnumTrade) +corrade_add_test(SceneToolsConvertToSingleFunctionObjectsTest ConvertToSingleFunctionObjectsTest.cpp LIBRARIES MagnumTrade) + +set_target_properties( + SceneToolsCombineTest + SceneToolsConvertToSingleFunctionObjectsTest + PROPERTIES FOLDER "Magnum/SceneTools/Test") diff --git a/src/Magnum/SceneTools/Test/CombineTest.cpp b/src/Magnum/SceneTools/Test/CombineTest.cpp new file mode 100644 index 000000000..544cc5b45 --- /dev/null +++ b/src/Magnum/SceneTools/Test/CombineTest.cpp @@ -0,0 +1,329 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021 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/Math/Complex.h" +#include "Magnum/Math/Vector2.h" +#include "Magnum/SceneTools/Implementation/combine.h" + +namespace Magnum { namespace SceneTools { namespace Test { namespace { + +struct CombineTest: TestSuite::Tester { + explicit CombineTest(); + + void test(); + void alignment(); + void objectsShared(); + void objectsPlaceholderFieldPlaceholder(); + void objectSharedFieldPlaceholder(); +}; + +struct { + const char* name; + Trade::SceneMappingType objectType; +} TestData[]{ + {"UnsignedByte output", Trade::SceneMappingType::UnsignedByte}, + {"UnsignedShort output", Trade::SceneMappingType::UnsignedShort}, + {"UnsignedInt output", Trade::SceneMappingType::UnsignedInt}, + {"UnsignedLong output", Trade::SceneMappingType::UnsignedLong}, +}; + +CombineTest::CombineTest() { + addInstancedTests({&CombineTest::test}, + Containers::arraySize(TestData)); + + addTests({&CombineTest::alignment, + &CombineTest::objectsShared, + &CombineTest::objectsPlaceholderFieldPlaceholder, + &CombineTest::objectSharedFieldPlaceholder}); +} + +using namespace Math::Literals; + +void CombineTest::test() { + auto&& data = TestData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Testing the four possible object types, it should be possible to combine + them */ + + const UnsignedInt meshMappingData[]{45, 78, 23}; + const UnsignedByte meshFieldData[]{3, 5, 17}; + + const UnsignedShort parentMappingData[]{0, 1}; + const Short parentData[]{-1, 0}; + + const UnsignedByte translationMappingData[]{16}; + const Vector2d translationFieldData[]{{1.5, -0.5}}; + + const UnsignedLong fooMappingData[]{15, 23}; + const Int fooFieldData[]{0, 1, 2, 3}; + + Trade::SceneData scene = Implementation::combine(data.objectType, 167, Containers::arrayView({ + Trade::SceneFieldData{Trade::SceneField::Mesh, Containers::arrayView(meshMappingData), Containers::arrayView(meshFieldData)}, + Trade::SceneFieldData{Trade::SceneField::Parent, Containers::arrayView(parentMappingData), Containers::arrayView(parentData), Trade::SceneFieldFlag::ImplicitMapping}, + Trade::SceneFieldData{Trade::SceneField::Translation, Containers::arrayView(translationMappingData), Containers::arrayView(translationFieldData)}, + /* Array field */ + Trade::SceneFieldData{Trade::sceneFieldCustom(15), Containers::arrayView(fooMappingData), Containers::StridedArrayView2D{fooFieldData, {2, 2}}, Trade::SceneFieldFlag::OrderedMapping}, + /* Empty field */ + Trade::SceneFieldData{Trade::SceneField::Camera, Containers::ArrayView{}, Containers::ArrayView{}} + })); + + CORRADE_COMPARE(scene.dataFlags(), Trade::DataFlag::Owned|Trade::DataFlag::Mutable); + CORRADE_COMPARE(scene.mappingType(), data.objectType); + CORRADE_COMPARE(scene.mappingBound(), 167); + CORRADE_COMPARE(scene.fieldCount(), 5); + + CORRADE_COMPARE(scene.fieldName(0), Trade::SceneField::Mesh); + CORRADE_COMPARE(scene.fieldFlags(0), Trade::SceneFieldFlags{}); + CORRADE_COMPARE(scene.fieldType(0), Trade::SceneFieldType::UnsignedByte); + CORRADE_COMPARE(scene.fieldArraySize(0), 0); + CORRADE_COMPARE_AS(scene.mappingAsArray(0), Containers::arrayView({ + 45, 78, 23 + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.field(0), + Containers::arrayView(meshFieldData), + TestSuite::Compare::Container); + + CORRADE_COMPARE(scene.fieldName(1), Trade::SceneField::Parent); + CORRADE_COMPARE(scene.fieldFlags(1), Trade::SceneFieldFlag::ImplicitMapping); + CORRADE_COMPARE(scene.fieldType(1), Trade::SceneFieldType::Short); + CORRADE_COMPARE(scene.fieldArraySize(1), 0); + CORRADE_COMPARE_AS(scene.mappingAsArray(1), Containers::arrayView({ + 0, 1 + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.field(1), + Containers::arrayView(parentData), + TestSuite::Compare::Container); + + CORRADE_COMPARE(scene.fieldName(2), Trade::SceneField::Translation); + CORRADE_COMPARE(scene.fieldFlags(2), Trade::SceneFieldFlags{}); + CORRADE_COMPARE(scene.fieldType(2), Trade::SceneFieldType::Vector2d); + CORRADE_COMPARE(scene.fieldArraySize(2), 0); + CORRADE_COMPARE_AS(scene.mappingAsArray(2), + Containers::arrayView({16}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.field(2), + Containers::arrayView(translationFieldData), + TestSuite::Compare::Container); + + CORRADE_COMPARE(scene.fieldName(3), Trade::sceneFieldCustom(15)); + CORRADE_COMPARE(scene.fieldFlags(3), Trade::SceneFieldFlag::OrderedMapping); + CORRADE_COMPARE(scene.fieldType(3), Trade::SceneFieldType::Int); + CORRADE_COMPARE(scene.fieldArraySize(3), 2); + CORRADE_COMPARE_AS(scene.mappingAsArray(3), + Containers::arrayView({15, 23}), + TestSuite::Compare::Container); + /** @todo clean up once it's possible to compare multidimensional + containers */ + CORRADE_COMPARE_AS(scene.field(3)[0], + (Containers::StridedArrayView2D{fooFieldData, {2, 2}})[0], + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.field(3)[1], + (Containers::StridedArrayView2D{fooFieldData, {2, 2}})[1], + TestSuite::Compare::Container); + + CORRADE_COMPARE(scene.fieldName(4), Trade::SceneField::Camera); + CORRADE_COMPARE(scene.fieldFlags(4), Trade::SceneFieldFlags{}); + CORRADE_COMPARE(scene.fieldType(4), Trade::SceneFieldType::UnsignedShort); + CORRADE_COMPARE(scene.fieldSize(4), 0); + CORRADE_COMPARE(scene.fieldArraySize(4), 0); +} + +void CombineTest::alignment() { + const UnsignedShort meshMappingData[]{15, 23, 47}; + const UnsignedByte meshFieldData[]{0, 1, 2}; + const UnsignedShort translationMappingData[]{5}; /* 1 byte padding before */ + const Vector2d translationFieldData[]{{1.5, 3.0}}; /* 4 byte padding before */ + + Trade::SceneData scene = Implementation::combine(Trade::SceneMappingType::UnsignedShort, 167, Containers::arrayView({ + Trade::SceneFieldData{Trade::SceneField::Mesh, Containers::arrayView(meshMappingData), Containers::arrayView(meshFieldData)}, + Trade::SceneFieldData{Trade::SceneField::Translation, Containers::arrayView(translationMappingData), Containers::arrayView(translationFieldData)} + })); + + CORRADE_COMPARE(scene.dataFlags(), Trade::DataFlag::Owned|Trade::DataFlag::Mutable); + CORRADE_COMPARE(scene.mappingType(), Trade::SceneMappingType::UnsignedShort); + CORRADE_COMPARE(scene.mappingBound(), 167); + CORRADE_COMPARE(scene.fieldCount(), 2); + + CORRADE_COMPARE(scene.fieldName(0), Trade::SceneField::Mesh); + CORRADE_COMPARE(scene.fieldType(0), Trade::SceneFieldType::UnsignedByte); + CORRADE_COMPARE(scene.fieldArraySize(0), 0); + CORRADE_COMPARE_AS(scene.mapping(0), + Containers::arrayView(meshMappingData), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.field(0), + Containers::arrayView(meshFieldData), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(reinterpret_cast(scene.mapping(0).data()), 2, TestSuite::Compare::Divisible); + CORRADE_COMPARE(scene.mapping(0).data(), scene.data()); + CORRADE_COMPARE(scene.mapping(0).stride()[0], 2); + CORRADE_COMPARE_AS(reinterpret_cast(scene.field(0).data()), 1, TestSuite::Compare::Divisible); + CORRADE_COMPARE(scene.field(0).data(), scene.data() + 3*2); + CORRADE_COMPARE(scene.field(0).stride()[0], 1); + + CORRADE_COMPARE(scene.fieldName(1), Trade::SceneField::Translation); + CORRADE_COMPARE(scene.fieldType(1), Trade::SceneFieldType::Vector2d); + CORRADE_COMPARE(scene.fieldArraySize(1), 0); + CORRADE_COMPARE_AS(scene.mapping(1), + Containers::arrayView(translationMappingData), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.field(1), + Containers::arrayView(translationFieldData), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(reinterpret_cast(scene.mapping(1).data()), 2, TestSuite::Compare::Divisible); + CORRADE_COMPARE(scene.mapping(1).data(), scene.data() + 3*2 + 3 + 1); + CORRADE_COMPARE(scene.mapping(1).stride()[0], 2); + CORRADE_COMPARE_AS(reinterpret_cast(scene.field(1).data()), 8, TestSuite::Compare::Divisible); + CORRADE_COMPARE(scene.field(1).data(), scene.data() + 3*2 + 3 + 1 + 2 + 4); + CORRADE_COMPARE(scene.field(1).stride()[0], 16); +} + +void CombineTest::objectsShared() { + const UnsignedShort meshMappingData[]{15, 23, 47}; + const UnsignedByte meshFieldData[]{0, 1, 2}; + const Int meshMaterialFieldData[]{72, -1, 23}; + + const UnsignedShort translationRotationMappingData[]{14, 22}; + const Vector2 translationFieldData[]{{-1.0f, 25.3f}, {2.2f, 2.1f}}; + const Complex rotationFieldData[]{Complex::rotation(35.0_degf), Complex::rotation(22.5_degf)}; + + Trade::SceneData scene = Implementation::combine(Trade::SceneMappingType::UnsignedInt, 173, Containers::arrayView({ + /* Deliberately in an arbitrary order to avoid false assumptions like + fields sharing the same object mapping always being after each + other */ + Trade::SceneFieldData{Trade::SceneField::Mesh, Containers::arrayView(meshMappingData), Containers::arrayView(meshFieldData)}, + Trade::SceneFieldData{Trade::SceneField::Translation, Containers::arrayView(translationRotationMappingData), Containers::arrayView(translationFieldData)}, + Trade::SceneFieldData{Trade::SceneField::MeshMaterial, Containers::arrayView(meshMappingData), Containers::arrayView(meshMaterialFieldData)}, + Trade::SceneFieldData{Trade::SceneField::Rotation, Containers::arrayView(translationRotationMappingData), Containers::arrayView(rotationFieldData)} + })); + + CORRADE_COMPARE(scene.dataFlags(), Trade::DataFlag::Owned|Trade::DataFlag::Mutable); + CORRADE_COMPARE(scene.mappingType(), Trade::SceneMappingType::UnsignedInt); + CORRADE_COMPARE(scene.mappingBound(), 173); + CORRADE_COMPARE(scene.fieldCount(), 4); + + CORRADE_COMPARE(scene.fieldSize(Trade::SceneField::Mesh), 3); + CORRADE_COMPARE(scene.fieldSize(Trade::SceneField::MeshMaterial), 3); + CORRADE_COMPARE(scene.mapping(Trade::SceneField::Mesh).data(), scene.mapping(Trade::SceneField::MeshMaterial).data()); + + CORRADE_COMPARE(scene.fieldSize(Trade::SceneField::Translation), 2); + CORRADE_COMPARE(scene.fieldSize(Trade::SceneField::Rotation), 2); + CORRADE_COMPARE(scene.mapping(Trade::SceneField::Translation).data(), scene.mapping(Trade::SceneField::Rotation).data()); +} + +void CombineTest::objectsPlaceholderFieldPlaceholder() { + const UnsignedShort meshMappingData[]{15, 23, 47}; + const UnsignedByte meshFieldData[]{0, 1, 2}; + + Trade::SceneData scene = Implementation::combine(Trade::SceneMappingType::UnsignedShort, 173, Containers::arrayView({ + Trade::SceneFieldData{Trade::SceneField::Camera, Containers::ArrayView{nullptr, 1}, Containers::ArrayView{nullptr, 1}}, + Trade::SceneFieldData{Trade::SceneField::Mesh, Containers::arrayView(meshMappingData), Containers::arrayView(meshFieldData)}, + /* Looks like sharing object mapping with the Camera field, but + actually both are placeholders */ + Trade::SceneFieldData{Trade::SceneField::Light, Containers::ArrayView{nullptr, 2}, Containers::ArrayView{nullptr, 2}}, + /* Array field */ + Trade::SceneFieldData{Trade::sceneFieldCustom(15), Containers::ArrayView{nullptr, 2}, Containers::StridedArrayView2D{{nullptr, 16}, {2, 4}}}, + })); + + CORRADE_COMPARE(scene.dataFlags(), Trade::DataFlag::Owned|Trade::DataFlag::Mutable); + CORRADE_COMPARE(scene.mappingType(), Trade::SceneMappingType::UnsignedShort); + CORRADE_COMPARE(scene.mappingBound(), 173); + CORRADE_COMPARE(scene.fieldCount(), 4); + + CORRADE_COMPARE(scene.fieldType(Trade::SceneField::Camera), Trade::SceneFieldType::UnsignedShort); + CORRADE_COMPARE(scene.fieldSize(Trade::SceneField::Camera), 1); + CORRADE_COMPARE(scene.fieldArraySize(Trade::SceneField::Camera), 0); + CORRADE_COMPARE(scene.mapping(Trade::SceneField::Camera).data(), scene.data()); + CORRADE_COMPARE(scene.mapping(Trade::SceneField::Camera).stride()[0], 2); + CORRADE_COMPARE(scene.field(Trade::SceneField::Camera).data(), scene.data() + 2); + CORRADE_COMPARE(scene.field(Trade::SceneField::Camera).stride()[0], 2); + + CORRADE_COMPARE(scene.fieldType(Trade::SceneField::Mesh), Trade::SceneFieldType::UnsignedByte); + CORRADE_COMPARE(scene.fieldArraySize(Trade::SceneField::Mesh), 0); + CORRADE_COMPARE_AS(scene.mapping(Trade::SceneField::Mesh), + Containers::arrayView(meshMappingData), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.field(Trade::SceneField::Mesh), + Containers::arrayView(meshFieldData), + TestSuite::Compare::Container); + + CORRADE_COMPARE(scene.fieldType(Trade::SceneField::Light), Trade::SceneFieldType::UnsignedInt); + CORRADE_COMPARE(scene.fieldSize(Trade::SceneField::Light), 2); + CORRADE_COMPARE(scene.fieldArraySize(Trade::SceneField::Light), 0); + CORRADE_COMPARE(scene.mapping(Trade::SceneField::Light).data(), scene.data() + 2 + 2 + 3*2 + 3 + 1); + CORRADE_COMPARE(scene.mapping(Trade::SceneField::Light).stride()[0], 2); + CORRADE_COMPARE(scene.field(Trade::SceneField::Light).data(), scene.data() + 2 + 2 + 3*2 + 3 + 1 + 2*2 + 2); + CORRADE_COMPARE(scene.field(Trade::SceneField::Light).stride()[0], 4); + + CORRADE_COMPARE(scene.fieldType(Trade::sceneFieldCustom(15)), Trade::SceneFieldType::Short); + CORRADE_COMPARE(scene.fieldSize(Trade::sceneFieldCustom(15)), 2); + CORRADE_COMPARE(scene.fieldArraySize(Trade::sceneFieldCustom(15)), 4); + CORRADE_COMPARE(scene.mapping(Trade::sceneFieldCustom(15)).data(), scene.data() + 2 + 2 + 3*2 + 3 + 1 + 2*2 + 2 + 2*4); + CORRADE_COMPARE(scene.mapping(Trade::sceneFieldCustom(15)).stride()[0], 2); + CORRADE_COMPARE(scene.field(Trade::sceneFieldCustom(15)).data(), scene.data() + 2 + 2 + 3*2 + 3 + 1 + 2*2 + 2 + 2*4 + 2*2); + CORRADE_COMPARE(scene.field(Trade::sceneFieldCustom(15)).stride()[0], 4*2); +} + +void CombineTest::objectSharedFieldPlaceholder() { + const UnsignedInt meshMappingData[]{15, 23, 47}; + const UnsignedByte meshFieldData[]{0, 1, 2}; + + Trade::SceneData scene = Implementation::combine(Trade::SceneMappingType::UnsignedInt, 173, Containers::arrayView({ + Trade::SceneFieldData{Trade::SceneField::Mesh, Containers::arrayView(meshMappingData), Containers::arrayView(meshFieldData)}, + Trade::SceneFieldData{Trade::SceneField::MeshMaterial, Containers::arrayView(meshMappingData), Containers::ArrayView{nullptr, 3}}, + })); + + CORRADE_COMPARE(scene.dataFlags(), Trade::DataFlag::Owned|Trade::DataFlag::Mutable); + CORRADE_COMPARE(scene.mappingType(), Trade::SceneMappingType::UnsignedInt); + CORRADE_COMPARE(scene.mappingBound(), 173); + CORRADE_COMPARE(scene.fieldCount(), 2); + + CORRADE_COMPARE(scene.fieldType(Trade::SceneField::Mesh), Trade::SceneFieldType::UnsignedByte); + CORRADE_COMPARE(scene.fieldArraySize(Trade::SceneField::Mesh), 0); + CORRADE_COMPARE_AS(scene.mapping(0), + Containers::arrayView(meshMappingData), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.field(0), + Containers::arrayView(meshFieldData), + TestSuite::Compare::Container); + + CORRADE_COMPARE(scene.fieldType(Trade::SceneField::MeshMaterial), Trade::SceneFieldType::Int); + CORRADE_COMPARE(scene.fieldSize(Trade::SceneField::MeshMaterial), 3); + CORRADE_COMPARE(scene.fieldArraySize(Trade::SceneField::MeshMaterial), 0); + CORRADE_COMPARE(scene.mapping(Trade::SceneField::MeshMaterial).data(), scene.mapping(Trade::SceneField::Mesh).data()); + CORRADE_COMPARE_AS(scene.mapping(Trade::SceneField::MeshMaterial), + Containers::arrayView(meshMappingData), + TestSuite::Compare::Container); + CORRADE_COMPARE(scene.field(Trade::SceneField::MeshMaterial).data(), scene.data() + 3*4 + 3 + 1); + CORRADE_COMPARE(scene.field(Trade::SceneField::MeshMaterial).stride()[0], 4); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::SceneTools::Test::CombineTest) diff --git a/src/Magnum/SceneTools/Test/ConvertToSingleFunctionObjectsTest.cpp b/src/Magnum/SceneTools/Test/ConvertToSingleFunctionObjectsTest.cpp new file mode 100644 index 000000000..c6dd2fe49 --- /dev/null +++ b/src/Magnum/SceneTools/Test/ConvertToSingleFunctionObjectsTest.cpp @@ -0,0 +1,383 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021 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/Math/Complex.h" +#include "Magnum/Math/Vector2.h" +#include "Magnum/SceneTools/Implementation/convertToSingleFunctionObjects.h" + +namespace Magnum { namespace SceneTools { namespace Test { namespace { + +struct ConvertToSingleFunctionObjectsTest: TestSuite::Tester { + explicit ConvertToSingleFunctionObjectsTest(); + + void test(); + void fieldsToCopy(); +}; + +struct { + const char* name; + UnsignedLong originalObjectCount; + UnsignedLong expectedObjectCount; + Trade::SceneFieldFlags parentFieldFlagsInput; + Trade::SceneFieldFlags parentFieldFlagsExpected; +} TestData[]{ + {"original object count smaller than new", 64, 70, {}, {}}, + {"original object count larger than new", 96, 96, {}, {}}, + {"parent field with ordered mapping", 64, 70, + Trade::SceneFieldFlag::OrderedMapping, Trade::SceneFieldFlag::OrderedMapping}, + {"parent field with implicit mapping", 64, 70, + /* The mapping is *not* implicit but we're not using the flag for + anything so this should work */ + Trade::SceneFieldFlag::ImplicitMapping, Trade::SceneFieldFlag::OrderedMapping} +}; + +ConvertToSingleFunctionObjectsTest::ConvertToSingleFunctionObjectsTest() { + addInstancedTests({&ConvertToSingleFunctionObjectsTest::test}, + Containers::arraySize(TestData)); + + addTests({&ConvertToSingleFunctionObjectsTest::fieldsToCopy}); +} + +using namespace Math::Literals; + +void ConvertToSingleFunctionObjectsTest::test() { + auto&& data = TestData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Haha now I can use sceneCombine() to conveniently prepare the initial + state here, without having to mess with an ArrayTuple */ + + const UnsignedShort parentMappingData[]{2, 15, 21, 22, 23}; + const Byte parentFieldData[]{-1, -1, -1, 21, 22}; + + /* Two objects have two and three mesh assignments respectively, meaning we + need three extra */ + const UnsignedShort meshMappingData[]{15, 23, 23, 23, 2, 15, 21}; + const Containers::Pair meshMaterialFieldData[]{ + {6, 4}, + {1, 0}, + {2, 3}, + {4, 2}, + {7, 2}, + {3, 1}, + {5, -1} + }; + + /* One camera is attached to an object that already has a mesh, meaning we + need a fourth extra object */ + const UnsignedShort cameraMappingData[]{22, 2}; + const UnsignedInt cameraFieldData[]{1, 5}; + + /* Lights don't conflict with anything so they *could* retain the + ImplicitMapping flag */ + const UnsignedShort lightMappingData[]{0, 1}; + const UnsignedByte lightFieldData[]{15, 23}; + + /* Object 0 and 1 has a light, 2 a mesh already, meaning we need a fifth, + sixth and seventh extra object and we lose the ImplicitMapping flag. */ + const UnsignedShort fooMappingData[]{0, 1, 2, 3}; + const Float fooFieldData[]{1.0f, 2.0f, 3.0f, 4.0f}; + + /* This field is not among the fields to convert so it should preserve the + ImplicitMapping flag */ + const UnsignedShort foo2MappingData[]{0, 1}; + const Byte foo2FieldData[]{-5, -7}; + + /* This field shares mapping with foo (and thus has the ImplicitMapping + flag), but it's not among the fields to convert. Since the mapping gets + changed, it should not retain the ImplicitMapping flag. */ + const Byte foo3FieldData[]{-1, -2, 7, 2}; + + Trade::SceneData original = Implementation::combine(Trade::SceneMappingType::UnsignedShort, data.originalObjectCount, Containers::arrayView({ + Trade::SceneFieldData{Trade::SceneField::Parent, Containers::arrayView(parentMappingData), Containers::arrayView(parentFieldData), data.parentFieldFlagsInput}, + Trade::SceneFieldData{Trade::SceneField::Mesh, Containers::arrayView(meshMappingData), Containers::StridedArrayView1D{meshMaterialFieldData, &meshMaterialFieldData[0].first(), Containers::arraySize(meshMaterialFieldData), sizeof(meshMaterialFieldData[0])}}, + Trade::SceneFieldData{Trade::SceneField::MeshMaterial, Containers::arrayView(meshMappingData), Containers::StridedArrayView1D{meshMaterialFieldData, &meshMaterialFieldData[0].second(), Containers::arraySize(meshMaterialFieldData), sizeof(meshMaterialFieldData[0])}}, + Trade::SceneFieldData{Trade::SceneField::Camera, Containers::arrayView(cameraMappingData), Containers::arrayView(cameraFieldData)}, + Trade::SceneFieldData{Trade::SceneField::Light, Containers::arrayView(lightMappingData), Containers::arrayView(lightFieldData), Trade::SceneFieldFlag::ImplicitMapping}, + Trade::SceneFieldData{Trade::sceneFieldCustom(15), Containers::arrayView(fooMappingData), Containers::arrayView(fooFieldData), Trade::SceneFieldFlag::ImplicitMapping}, + Trade::SceneFieldData{Trade::sceneFieldCustom(16), Containers::arrayView(foo2MappingData), Containers::arrayView(foo2FieldData), Trade::SceneFieldFlag::ImplicitMapping}, + Trade::SceneFieldData{Trade::sceneFieldCustom(17), Containers::arrayView(fooMappingData), Containers::arrayView(foo3FieldData), Trade::SceneFieldFlag::ImplicitMapping} + })); + + Trade::SceneData scene = Implementation::convertToSingleFunctionObjects(original, Containers::arrayView({ + Trade::SceneField::Mesh, + /* Deliberately not including MeshMaterial in the list -- these should + get automatically updated as they share the same object mapping. + OTOH including them would break the output. */ + Trade::SceneField::Camera, + /* A field with implicit mapping that doesn't conflict with anything so + it *could* retain the flag */ + Trade::SceneField::Light, + /* A field with implicit mapping, which loses the flag because entries + get reassigned */ + Trade::sceneFieldCustom(15), + /* Include also a field that's not present -- it should get skipped */ + Trade::SceneField::ImporterState + }), {}, 63); + + /* There should be three more objects, or the original count preserved if + it's large enough */ + CORRADE_COMPARE(scene.mappingBound(), data.expectedObjectCount); + + /* Object 0 should have new children with "foo", as it has a light */ + CORRADE_COMPARE_AS(scene.childrenFor(0), + Containers::arrayView({67}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.lightsFor(0), + Containers::arrayView({15}), + TestSuite::Compare::Container); + + /* Object 1 should have a new child with "foo", as it has a light */ + CORRADE_COMPARE_AS(scene.childrenFor(1), + Containers::arrayView({68}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.lightsFor(1), + Containers::arrayView({23}), + TestSuite::Compare::Container); + + /* Object 2 should have a new child with the camera and "foo", as it has a + mesh */ + CORRADE_COMPARE_AS(scene.childrenFor(2), + Containers::arrayView({66, 69}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.meshesMaterialsFor(2), + (Containers::arrayView>({{7, 2}})), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.camerasFor(2), + Containers::arrayView({}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.camerasFor(66), + Containers::arrayView({5}), + TestSuite::Compare::Container); + + /* Object 15 should have a new child that has the second mesh */ + CORRADE_COMPARE_AS(scene.childrenFor(15), + Containers::arrayView({65}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.meshesMaterialsFor(15), + (Containers::arrayView>({{6, 4}})), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.meshesMaterialsFor(65), + (Containers::arrayView>({{3, 1}})), + TestSuite::Compare::Container); + + /* Object 23 should have two new children that have the second and third + mesh */ + CORRADE_COMPARE_AS(scene.childrenFor(23), + Containers::arrayView({63, 64}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.meshesMaterialsFor(23), + (Containers::arrayView>({{1, 0}})), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.meshesMaterialsFor(63), + (Containers::arrayView>({{2, 3}})), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.meshesMaterialsFor(64), + (Containers::arrayView>({{4, 2}})), + TestSuite::Compare::Container); + + /* To be extra sure, verify the actual data. Parents have a few objects + added, the rest is the same. Because new objects are added at the end, + the ordered flag is preserved if present. */ + CORRADE_COMPARE_AS(scene.parentsAsArray(), (Containers::arrayView>({ + {2, -1}, + {15, -1}, + {21, -1}, + {22, 21}, + {23, 22}, + {63, 23}, + {64, 23}, + {65, 15}, + {66, 2}, + {67, 0}, + {68, 1}, + {69, 2}, + })), TestSuite::Compare::Container); + CORRADE_COMPARE(scene.fieldFlags(Trade::SceneField::Parent), data.parentFieldFlagsExpected); + + /* Meshes / materials have certain objects reassigned, field data stay the + same. There was no flag before so neither is after. */ + CORRADE_COMPARE_AS(scene.meshesMaterialsAsArray(), (Containers::arrayView>>({ + {15, {6, 4}}, + {23, {1, 0}}, + {63, {2, 3}}, + {64, {4, 2}}, + {2, {7, 2}}, + {65, {3, 1}}, + {21, {5, -1}} + })), TestSuite::Compare::Container); + CORRADE_COMPARE(scene.fieldFlags(Trade::SceneField::Mesh), Trade::SceneFieldFlags{}); + CORRADE_COMPARE(scene.fieldFlags(Trade::SceneField::MeshMaterial), Trade::SceneFieldFlags{}); + + /* Cameras have certain objects reassigned, field data stay the same. There + was no flag before so neither is after. */ + CORRADE_COMPARE_AS(scene.camerasAsArray(), (Containers::arrayView>({ + {22, 1}, + {66, 5} + })), TestSuite::Compare::Container); + CORRADE_COMPARE(scene.fieldFlags(Trade::SceneField::Camera), Trade::SceneFieldFlags{}); + + /* Lights stay the same, thus the implicit flag could be preserved. It's + not currently, though. */ + CORRADE_COMPARE_AS(scene.lightsAsArray(), (Containers::arrayView>({ + {0, 15}, + {1, 23} + })), TestSuite::Compare::Container); + { + CORRADE_EXPECT_FAIL("Logic for preserving flags of untouched fields is rather complex and thus not implemented yet."); + CORRADE_COMPARE(scene.fieldFlags(Trade::SceneField::Light), Trade::SceneFieldFlag::ImplicitMapping); + } { + CORRADE_COMPARE(scene.fieldFlags(Trade::SceneField::Light), Trade::SceneFieldFlags{}); + } + + /* A custom field gets the last object reassigned, field data stay the + same. The implicit flag gets turned to nothing after that. */ + CORRADE_COMPARE_AS(scene.mappingAsArray(Trade::sceneFieldCustom(15)), (Containers::arrayView({ + 67, 68, 69, 3 + })), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.field(Trade::sceneFieldCustom(15)), + Containers::arrayView(fooFieldData), + TestSuite::Compare::Container); + CORRADE_COMPARE(scene.fieldFlags(Trade::sceneFieldCustom(15)), Trade::SceneFieldFlags{}); + + /* A custom field that is not among fields to convert so it preserves the + flag */ + CORRADE_COMPARE_AS(scene.mappingAsArray(Trade::sceneFieldCustom(16)), (Containers::arrayView({ + 0, 1 + })), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.field(Trade::sceneFieldCustom(16)), + Containers::arrayView(foo2FieldData), + TestSuite::Compare::Container); + { + CORRADE_EXPECT_FAIL("Logic for preserving flags of untouched fields is rather complex and thus not implemented yet."); + CORRADE_COMPARE(scene.fieldFlags(Trade::sceneFieldCustom(16)), Trade::SceneFieldFlag::ImplicitMapping); + } { + CORRADE_COMPARE(scene.fieldFlags(Trade::sceneFieldCustom(16)), Trade::SceneFieldFlags{}); + } + + /* A custom field that is not among fields to convert but it shares the + mapping with a field that is and that gets changed. The implicit flag + should thus get removed here as well. */ + CORRADE_COMPARE_AS(scene.mappingAsArray(Trade::sceneFieldCustom(17)), (Containers::arrayView({ + 67, 68, 69, 3 + })), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.field(Trade::sceneFieldCustom(17)), + Containers::arrayView(foo3FieldData), + TestSuite::Compare::Container); + CORRADE_COMPARE(scene.fieldFlags(Trade::sceneFieldCustom(17)), Trade::SceneFieldFlags{}); +} + +void ConvertToSingleFunctionObjectsTest::fieldsToCopy() { + const UnsignedShort parentMappingData[]{2, 15, 21, 22}; + const Byte parentFieldData[]{-1, -1, -1, 21}; + + const UnsignedShort meshMappingData[]{15, 21, 21, 21, 22, 15}; + const UnsignedInt meshFieldData[]{6, 1, 2, 4, 7, 3}; + + const UnsignedShort skinMappingData[]{22, 21}; + const UnsignedInt skinFieldData[]{5, 13}; + + const UnsignedLong fooMappingData[]{15, 23, 15, 21}; + const Int fooFieldData[]{0, 1, 2, 3, 4, 5, 6, 7}; + + Trade::SceneData original = Implementation::combine(Trade::SceneMappingType::UnsignedShort, 50, Containers::arrayView({ + Trade::SceneFieldData{Trade::SceneField::Parent, Containers::arrayView(parentMappingData), Containers::arrayView(parentFieldData)}, + Trade::SceneFieldData{Trade::SceneField::Mesh, Containers::arrayView(meshMappingData), Containers::arrayView(meshFieldData)}, + Trade::SceneFieldData{Trade::SceneField::Skin, Containers::arrayView(skinMappingData), Containers::arrayView(skinFieldData)}, + /* Array field */ + Trade::SceneFieldData{Trade::sceneFieldCustom(15), Containers::arrayView(fooMappingData), Containers::StridedArrayView2D{fooFieldData, {4, 2}}}, + /* Just to disambiguate between 2D and 3D */ + Trade::SceneFieldData{Trade::SceneField::Transformation, Trade::SceneMappingType::UnsignedShort, nullptr, Trade::SceneFieldType::Matrix4x4, nullptr} + })); + + Trade::SceneData scene = Implementation::convertToSingleFunctionObjects(original, + Containers::arrayView({ + /* Include also a field that's not present -- it should get skipped */ + Trade::SceneField::ImporterState, + /* Three additional mesh assignments that go to new objects */ + Trade::SceneField::Mesh + }), + Containers::arrayView({ + /* One assignment is to an object that has just one mesh, it should + not be copied anywhere, the other should be duplicated two + times */ + Trade::SceneField::Skin, + /* Array field with multiple assignments per object -- all should + be copied */ + Trade::sceneFieldCustom(15), + /* Include also a field that's not present -- it should get skipped */ + Trade::SceneField::Camera + }), 60); + + CORRADE_COMPARE_AS(scene.parentsAsArray(), (Containers::arrayView>({ + {2, -1}, + {15, -1}, + {21, -1}, + {22, 21}, + {60, 21}, /* duplicated mesh assignment to object 21 */ + {61, 21}, /* duplicated mesh assignment to object 21 */ + {62, 15} /* duplicated mesh assignment to object 15 */ + })), TestSuite::Compare::Container); + + CORRADE_COMPARE_AS(scene.meshesMaterialsAsArray(), (Containers::arrayView>>({ + {15, {6, -1}}, + {21, {1, -1}}, + {60, {2, -1}}, /* duplicated mesh assignment to object 21 */ + {61, {4, -1}}, /* duplicated mesh assignment to object 21 */ + {22, {7, -1}}, + {62, {3, -1}} /* duplicated mesh assignment to object 15 */ + })), TestSuite::Compare::Container); + + CORRADE_COMPARE_AS(scene.skinsAsArray(), (Containers::arrayView>({ + {22, 5}, + {21, 13}, + {60, 13}, /* duplicated from object 21 */ + {61, 13}, /* duplicated from object 21 */ + })), TestSuite::Compare::Container); + + CORRADE_COMPARE_AS(scene.mapping(Trade::sceneFieldCustom(15)), Containers::arrayView({ + 15, 23, 15, 21, + 60, 61, /* duplicated from object 21 (two duplicates of one object) */ + 62, 62, /* duplicated from object 15 (two entries for one object) */ + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS((scene.field(Trade::sceneFieldCustom(15)).transposed<0, 1>()[0]), Containers::arrayView({ + 0, 2, 4, 6, + 6, 6, /* duplicated from object 21 (two duplicates of one object) */ + 0, 4, /* duplicated from object 15 (two entries for one object) */ + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS((scene.field(Trade::sceneFieldCustom(15)).transposed<0, 1>()[1]), Containers::arrayView({ + 1, 3, 5, 7, + 7, 7, /* duplicated from object 21 (two duplicates of one object) */ + 1, 5, /* duplicated from object 15 (two entries for one object) */ + }), TestSuite::Compare::Container); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::SceneTools::Test::ConvertToSingleFunctionObjectsTest) diff --git a/src/Magnum/Trade/AbstractImporter.cpp b/src/Magnum/Trade/AbstractImporter.cpp index 530d26745..331ae9dd6 100644 --- a/src/Magnum/Trade/AbstractImporter.cpp +++ b/src/Magnum/Trade/AbstractImporter.cpp @@ -48,7 +48,9 @@ #include #include -#include "Magnum/Trade/Implementation/sceneTools.h" +/* This is a header-only tool, meaning no link-time dependency on SceneTools */ +/** @todo once this compat is dropped, drop the header-only implementation */ +#include "Magnum/SceneTools/Implementation/convertToSingleFunctionObjects.h" #define _MAGNUM_NO_DEPRECATED_MESHDATA /* So it doesn't yell here */ #define _MAGNUM_NO_DEPRECATED_OBJECTDATA /* So it doesn't yell here */ @@ -531,7 +533,7 @@ void AbstractImporter::populateCachedScenes() { compatibility code path anyway, so just skip the processing altogether in that case. */ if(_cachedScenes->scenes[i]->hasField(SceneField::Parent)) - _cachedScenes->scenes[i] = Implementation::sceneConvertToSingleFunctionObjects(*_cachedScenes->scenes[i], Containers::arrayView({SceneField::Mesh, SceneField::Camera, SceneField::Light}), Containers::arrayView({SceneField::Skin}), newObjectOffset); + _cachedScenes->scenes[i] = SceneTools::Implementation::convertToSingleFunctionObjects(*_cachedScenes->scenes[i], Containers::arrayView({SceneField::Mesh, SceneField::Camera, SceneField::Light}), Containers::arrayView({SceneField::Skin}), newObjectOffset); /* Return the 2D/3D object count based on which scenes are 2D and which not. The objectCount() provided by the importer is ignored diff --git a/src/Magnum/Trade/CMakeLists.txt b/src/Magnum/Trade/CMakeLists.txt index e5b68f272..2a6fc229f 100644 --- a/src/Magnum/Trade/CMakeLists.txt +++ b/src/Magnum/Trade/CMakeLists.txt @@ -76,8 +76,7 @@ set(MagnumTrade_HEADERS set(MagnumTrade_PRIVATE_HEADERS Implementation/arrayUtilities.h Implementation/converterUtilities.h - Implementation/materialAttributeProperties.hpp - Implementation/sceneTools.h) + Implementation/materialAttributeProperties.hpp) if(MAGNUM_BUILD_DEPRECATED) list(APPEND MagnumTrade_SRCS diff --git a/src/Magnum/Trade/Test/CMakeLists.txt b/src/Magnum/Trade/Test/CMakeLists.txt index 521be6d74..711436146 100644 --- a/src/Magnum/Trade/Test/CMakeLists.txt +++ b/src/Magnum/Trade/Test/CMakeLists.txt @@ -59,7 +59,6 @@ corrade_add_test(TradePbrMetallicRoughnessMate___Test PbrMetallicRoughnessMateri corrade_add_test(TradePbrSpecularGlossinessMat___Test PbrSpecularGlossinessMaterialDataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradePhongMaterialDataTest PhongMaterialDataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradeSceneDataTest SceneDataTest.cpp LIBRARIES MagnumTradeTestLib) -corrade_add_test(TradeSceneToolsTest SceneToolsTest.cpp LIBRARIES MagnumTrade) corrade_add_test(TradeSkinDataTest SkinDataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradeTextureDataTest TextureDataTest.cpp LIBRARIES MagnumTrade) @@ -85,7 +84,6 @@ set_target_properties( TradePbrSpecularGlossinessMat___Test TradePhongMaterialDataTest TradeSceneDataTest - TradeSceneToolsTest TradeTextureDataTest PROPERTIES FOLDER "Magnum/Trade/Test") diff --git a/src/Magnum/Trade/Test/SceneToolsTest.cpp b/src/Magnum/Trade/Test/SceneToolsTest.cpp deleted file mode 100644 index ccc3d3c76..000000000 --- a/src/Magnum/Trade/Test/SceneToolsTest.cpp +++ /dev/null @@ -1,666 +0,0 @@ -/* - This file is part of Magnum. - - Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, - 2020, 2021 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/Math/Complex.h" -#include "Magnum/Math/Vector2.h" -#include "Magnum/Trade/Implementation/sceneTools.h" - -namespace Magnum { namespace Trade { namespace Test { namespace { - -struct SceneToolsTest: TestSuite::Tester { - explicit SceneToolsTest(); - - void combine(); - void combineAlignment(); - void combineObjectsShared(); - void combineObjectsPlaceholderFieldPlaceholder(); - void combineObjectSharedFieldPlaceholder(); - - void convertToSingleFunctionObjects(); - void convertToSingleFunctionObjectsFieldsToCopy(); -}; - -struct { - const char* name; - SceneMappingType objectType; -} CombineData[]{ - {"UnsignedByte output", SceneMappingType::UnsignedByte}, - {"UnsignedShort output", SceneMappingType::UnsignedShort}, - {"UnsignedInt output", SceneMappingType::UnsignedInt}, - {"UnsignedLong output", SceneMappingType::UnsignedLong}, -}; - -struct { - const char* name; - UnsignedLong originalObjectCount; - UnsignedLong expectedObjectCount; - SceneFieldFlags parentFieldFlagsInput; - SceneFieldFlags parentFieldFlagsExpected; -} ConvertToSingleFunctionObjectsData[]{ - {"original object count smaller than new", 64, 70, {}, {}}, - {"original object count larger than new", 96, 96, {}, {}}, - {"parent field with ordered mapping", 64, 70, - SceneFieldFlag::OrderedMapping, SceneFieldFlag::OrderedMapping}, - {"parent field with implicit mapping", 64, 70, - /* The mapping is *not* implicit but we're not using the flag for - anything so this should work */ - SceneFieldFlag::ImplicitMapping, SceneFieldFlag::OrderedMapping} -}; - -SceneToolsTest::SceneToolsTest() { - addInstancedTests({&SceneToolsTest::combine}, - Containers::arraySize(CombineData)); - - addTests({&SceneToolsTest::combineAlignment, - &SceneToolsTest::combineObjectsShared, - &SceneToolsTest::combineObjectsPlaceholderFieldPlaceholder, - &SceneToolsTest::combineObjectSharedFieldPlaceholder}); - - addInstancedTests({&SceneToolsTest::convertToSingleFunctionObjects}, - Containers::arraySize(ConvertToSingleFunctionObjectsData)); - - addTests({&SceneToolsTest::convertToSingleFunctionObjectsFieldsToCopy}); -} - -using namespace Math::Literals; - -void SceneToolsTest::combine() { - auto&& data = CombineData[testCaseInstanceId()]; - setTestCaseDescription(data.name); - - /* Testing the four possible object types, it should be possible to combine - them */ - - const UnsignedInt meshMappingData[]{45, 78, 23}; - const UnsignedByte meshFieldData[]{3, 5, 17}; - - const UnsignedShort parentMappingData[]{0, 1}; - const Short parentData[]{-1, 0}; - - const UnsignedByte translationMappingData[]{16}; - const Vector2d translationFieldData[]{{1.5, -0.5}}; - - const UnsignedLong fooMappingData[]{15, 23}; - const Int fooFieldData[]{0, 1, 2, 3}; - - SceneData scene = Implementation::sceneCombine(data.objectType, 167, Containers::arrayView({ - SceneFieldData{SceneField::Mesh, Containers::arrayView(meshMappingData), Containers::arrayView(meshFieldData)}, - SceneFieldData{SceneField::Parent, Containers::arrayView(parentMappingData), Containers::arrayView(parentData), SceneFieldFlag::ImplicitMapping}, - SceneFieldData{SceneField::Translation, Containers::arrayView(translationMappingData), Containers::arrayView(translationFieldData)}, - /* Array field */ - SceneFieldData{sceneFieldCustom(15), Containers::arrayView(fooMappingData), Containers::StridedArrayView2D{fooFieldData, {2, 2}}, SceneFieldFlag::OrderedMapping}, - /* Empty field */ - SceneFieldData{SceneField::Camera, Containers::ArrayView{}, Containers::ArrayView{}} - })); - - CORRADE_COMPARE(scene.dataFlags(), DataFlag::Owned|DataFlag::Mutable); - CORRADE_COMPARE(scene.mappingType(), data.objectType); - CORRADE_COMPARE(scene.mappingBound(), 167); - CORRADE_COMPARE(scene.fieldCount(), 5); - - CORRADE_COMPARE(scene.fieldName(0), SceneField::Mesh); - CORRADE_COMPARE(scene.fieldFlags(0), SceneFieldFlags{}); - CORRADE_COMPARE(scene.fieldType(0), SceneFieldType::UnsignedByte); - CORRADE_COMPARE(scene.fieldArraySize(0), 0); - CORRADE_COMPARE_AS(scene.mappingAsArray(0), Containers::arrayView({ - 45, 78, 23 - }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(scene.field(0), - Containers::arrayView(meshFieldData), - TestSuite::Compare::Container); - - CORRADE_COMPARE(scene.fieldName(1), SceneField::Parent); - CORRADE_COMPARE(scene.fieldFlags(1), SceneFieldFlag::ImplicitMapping); - CORRADE_COMPARE(scene.fieldType(1), SceneFieldType::Short); - CORRADE_COMPARE(scene.fieldArraySize(1), 0); - CORRADE_COMPARE_AS(scene.mappingAsArray(1), Containers::arrayView({ - 0, 1 - }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(scene.field(1), - Containers::arrayView(parentData), - TestSuite::Compare::Container); - - CORRADE_COMPARE(scene.fieldName(2), SceneField::Translation); - CORRADE_COMPARE(scene.fieldFlags(2), SceneFieldFlags{}); - CORRADE_COMPARE(scene.fieldType(2), SceneFieldType::Vector2d); - CORRADE_COMPARE(scene.fieldArraySize(2), 0); - CORRADE_COMPARE_AS(scene.mappingAsArray(2), - Containers::arrayView({16}), - TestSuite::Compare::Container); - CORRADE_COMPARE_AS(scene.field(2), - Containers::arrayView(translationFieldData), - TestSuite::Compare::Container); - - CORRADE_COMPARE(scene.fieldName(3), sceneFieldCustom(15)); - CORRADE_COMPARE(scene.fieldFlags(3), SceneFieldFlag::OrderedMapping); - CORRADE_COMPARE(scene.fieldType(3), SceneFieldType::Int); - CORRADE_COMPARE(scene.fieldArraySize(3), 2); - CORRADE_COMPARE_AS(scene.mappingAsArray(3), - Containers::arrayView({15, 23}), - TestSuite::Compare::Container); - /** @todo clean up once it's possible to compare multidimensional - containers */ - CORRADE_COMPARE_AS(scene.field(3)[0], - (Containers::StridedArrayView2D{fooFieldData, {2, 2}})[0], - TestSuite::Compare::Container); - CORRADE_COMPARE_AS(scene.field(3)[1], - (Containers::StridedArrayView2D{fooFieldData, {2, 2}})[1], - TestSuite::Compare::Container); - - CORRADE_COMPARE(scene.fieldName(4), SceneField::Camera); - CORRADE_COMPARE(scene.fieldFlags(4), SceneFieldFlags{}); - CORRADE_COMPARE(scene.fieldType(4), SceneFieldType::UnsignedShort); - CORRADE_COMPARE(scene.fieldSize(4), 0); - CORRADE_COMPARE(scene.fieldArraySize(4), 0); -} - -void SceneToolsTest::combineAlignment() { - const UnsignedShort meshMappingData[]{15, 23, 47}; - const UnsignedByte meshFieldData[]{0, 1, 2}; - const UnsignedShort translationMappingData[]{5}; /* 1 byte padding before */ - const Vector2d translationFieldData[]{{1.5, 3.0}}; /* 4 byte padding before */ - - SceneData scene = Implementation::sceneCombine(SceneMappingType::UnsignedShort, 167, Containers::arrayView({ - SceneFieldData{SceneField::Mesh, Containers::arrayView(meshMappingData), Containers::arrayView(meshFieldData)}, - SceneFieldData{SceneField::Translation, Containers::arrayView(translationMappingData), Containers::arrayView(translationFieldData)} - })); - - CORRADE_COMPARE(scene.dataFlags(), DataFlag::Owned|DataFlag::Mutable); - CORRADE_COMPARE(scene.mappingType(), SceneMappingType::UnsignedShort); - CORRADE_COMPARE(scene.mappingBound(), 167); - CORRADE_COMPARE(scene.fieldCount(), 2); - - CORRADE_COMPARE(scene.fieldName(0), SceneField::Mesh); - CORRADE_COMPARE(scene.fieldType(0), SceneFieldType::UnsignedByte); - CORRADE_COMPARE(scene.fieldArraySize(0), 0); - CORRADE_COMPARE_AS(scene.mapping(0), - Containers::arrayView(meshMappingData), - TestSuite::Compare::Container); - CORRADE_COMPARE_AS(scene.field(0), - Containers::arrayView(meshFieldData), - TestSuite::Compare::Container); - CORRADE_COMPARE_AS(reinterpret_cast(scene.mapping(0).data()), 2, TestSuite::Compare::Divisible); - CORRADE_COMPARE(scene.mapping(0).data(), scene.data()); - CORRADE_COMPARE(scene.mapping(0).stride()[0], 2); - CORRADE_COMPARE_AS(reinterpret_cast(scene.field(0).data()), 1, TestSuite::Compare::Divisible); - CORRADE_COMPARE(scene.field(0).data(), scene.data() + 3*2); - CORRADE_COMPARE(scene.field(0).stride()[0], 1); - - CORRADE_COMPARE(scene.fieldName(1), SceneField::Translation); - CORRADE_COMPARE(scene.fieldType(1), SceneFieldType::Vector2d); - CORRADE_COMPARE(scene.fieldArraySize(1), 0); - CORRADE_COMPARE_AS(scene.mapping(1), - Containers::arrayView(translationMappingData), - TestSuite::Compare::Container); - CORRADE_COMPARE_AS(scene.field(1), - Containers::arrayView(translationFieldData), - TestSuite::Compare::Container); - CORRADE_COMPARE_AS(reinterpret_cast(scene.mapping(1).data()), 2, TestSuite::Compare::Divisible); - CORRADE_COMPARE(scene.mapping(1).data(), scene.data() + 3*2 + 3 + 1); - CORRADE_COMPARE(scene.mapping(1).stride()[0], 2); - CORRADE_COMPARE_AS(reinterpret_cast(scene.field(1).data()), 8, TestSuite::Compare::Divisible); - CORRADE_COMPARE(scene.field(1).data(), scene.data() + 3*2 + 3 + 1 + 2 + 4); - CORRADE_COMPARE(scene.field(1).stride()[0], 16); -} - -void SceneToolsTest::combineObjectsShared() { - const UnsignedShort meshMappingData[]{15, 23, 47}; - const UnsignedByte meshFieldData[]{0, 1, 2}; - const Int meshMaterialFieldData[]{72, -1, 23}; - - const UnsignedShort translationRotationMappingData[]{14, 22}; - const Vector2 translationFieldData[]{{-1.0f, 25.3f}, {2.2f, 2.1f}}; - const Complex rotationFieldData[]{Complex::rotation(35.0_degf), Complex::rotation(22.5_degf)}; - - SceneData scene = Implementation::sceneCombine(SceneMappingType::UnsignedInt, 173, Containers::arrayView({ - /* Deliberately in an arbitrary order to avoid false assumptions like - fields sharing the same object mapping always being after each - other */ - SceneFieldData{SceneField::Mesh, Containers::arrayView(meshMappingData), Containers::arrayView(meshFieldData)}, - SceneFieldData{SceneField::Translation, Containers::arrayView(translationRotationMappingData), Containers::arrayView(translationFieldData)}, - SceneFieldData{SceneField::MeshMaterial, Containers::arrayView(meshMappingData), Containers::arrayView(meshMaterialFieldData)}, - SceneFieldData{SceneField::Rotation, Containers::arrayView(translationRotationMappingData), Containers::arrayView(rotationFieldData)} - })); - - CORRADE_COMPARE(scene.dataFlags(), DataFlag::Owned|DataFlag::Mutable); - CORRADE_COMPARE(scene.mappingType(), SceneMappingType::UnsignedInt); - CORRADE_COMPARE(scene.mappingBound(), 173); - CORRADE_COMPARE(scene.fieldCount(), 4); - - CORRADE_COMPARE(scene.fieldSize(SceneField::Mesh), 3); - CORRADE_COMPARE(scene.fieldSize(SceneField::MeshMaterial), 3); - CORRADE_COMPARE(scene.mapping(SceneField::Mesh).data(), scene.mapping(SceneField::MeshMaterial).data()); - - CORRADE_COMPARE(scene.fieldSize(SceneField::Translation), 2); - CORRADE_COMPARE(scene.fieldSize(SceneField::Rotation), 2); - CORRADE_COMPARE(scene.mapping(SceneField::Translation).data(), scene.mapping(SceneField::Rotation).data()); -} - -void SceneToolsTest::combineObjectsPlaceholderFieldPlaceholder() { - const UnsignedShort meshMappingData[]{15, 23, 47}; - const UnsignedByte meshFieldData[]{0, 1, 2}; - - SceneData scene = Implementation::sceneCombine(SceneMappingType::UnsignedShort, 173, Containers::arrayView({ - SceneFieldData{SceneField::Camera, Containers::ArrayView{nullptr, 1}, Containers::ArrayView{nullptr, 1}}, - SceneFieldData{SceneField::Mesh, Containers::arrayView(meshMappingData), Containers::arrayView(meshFieldData)}, - /* Looks like sharing object mapping with the Camera field, but - actually both are placeholders */ - SceneFieldData{SceneField::Light, Containers::ArrayView{nullptr, 2}, Containers::ArrayView{nullptr, 2}}, - /* Array field */ - SceneFieldData{sceneFieldCustom(15), Containers::ArrayView{nullptr, 2}, Containers::StridedArrayView2D{{nullptr, 16}, {2, 4}}}, - })); - - CORRADE_COMPARE(scene.dataFlags(), DataFlag::Owned|DataFlag::Mutable); - CORRADE_COMPARE(scene.mappingType(), SceneMappingType::UnsignedShort); - CORRADE_COMPARE(scene.mappingBound(), 173); - CORRADE_COMPARE(scene.fieldCount(), 4); - - CORRADE_COMPARE(scene.fieldType(SceneField::Camera), SceneFieldType::UnsignedShort); - CORRADE_COMPARE(scene.fieldSize(SceneField::Camera), 1); - CORRADE_COMPARE(scene.fieldArraySize(SceneField::Camera), 0); - CORRADE_COMPARE(scene.mapping(SceneField::Camera).data(), scene.data()); - CORRADE_COMPARE(scene.mapping(SceneField::Camera).stride()[0], 2); - CORRADE_COMPARE(scene.field(SceneField::Camera).data(), scene.data() + 2); - CORRADE_COMPARE(scene.field(SceneField::Camera).stride()[0], 2); - - CORRADE_COMPARE(scene.fieldType(SceneField::Mesh), SceneFieldType::UnsignedByte); - CORRADE_COMPARE(scene.fieldArraySize(SceneField::Mesh), 0); - CORRADE_COMPARE_AS(scene.mapping(SceneField::Mesh), - Containers::arrayView(meshMappingData), - TestSuite::Compare::Container); - CORRADE_COMPARE_AS(scene.field(SceneField::Mesh), - Containers::arrayView(meshFieldData), - TestSuite::Compare::Container); - - CORRADE_COMPARE(scene.fieldType(SceneField::Light), SceneFieldType::UnsignedInt); - CORRADE_COMPARE(scene.fieldSize(SceneField::Light), 2); - CORRADE_COMPARE(scene.fieldArraySize(SceneField::Light), 0); - CORRADE_COMPARE(scene.mapping(SceneField::Light).data(), scene.data() + 2 + 2 + 3*2 + 3 + 1); - CORRADE_COMPARE(scene.mapping(SceneField::Light).stride()[0], 2); - CORRADE_COMPARE(scene.field(SceneField::Light).data(), scene.data() + 2 + 2 + 3*2 + 3 + 1 + 2*2 + 2); - CORRADE_COMPARE(scene.field(SceneField::Light).stride()[0], 4); - - CORRADE_COMPARE(scene.fieldType(sceneFieldCustom(15)), SceneFieldType::Short); - CORRADE_COMPARE(scene.fieldSize(sceneFieldCustom(15)), 2); - CORRADE_COMPARE(scene.fieldArraySize(sceneFieldCustom(15)), 4); - CORRADE_COMPARE(scene.mapping(sceneFieldCustom(15)).data(), scene.data() + 2 + 2 + 3*2 + 3 + 1 + 2*2 + 2 + 2*4); - CORRADE_COMPARE(scene.mapping(sceneFieldCustom(15)).stride()[0], 2); - CORRADE_COMPARE(scene.field(sceneFieldCustom(15)).data(), scene.data() + 2 + 2 + 3*2 + 3 + 1 + 2*2 + 2 + 2*4 + 2*2); - CORRADE_COMPARE(scene.field(sceneFieldCustom(15)).stride()[0], 4*2); -} - -void SceneToolsTest::combineObjectSharedFieldPlaceholder() { - const UnsignedInt meshMappingData[]{15, 23, 47}; - const UnsignedByte meshFieldData[]{0, 1, 2}; - - SceneData scene = Implementation::sceneCombine(SceneMappingType::UnsignedInt, 173, Containers::arrayView({ - SceneFieldData{SceneField::Mesh, Containers::arrayView(meshMappingData), Containers::arrayView(meshFieldData)}, - SceneFieldData{SceneField::MeshMaterial, Containers::arrayView(meshMappingData), Containers::ArrayView{nullptr, 3}}, - })); - - CORRADE_COMPARE(scene.dataFlags(), DataFlag::Owned|DataFlag::Mutable); - CORRADE_COMPARE(scene.mappingType(), SceneMappingType::UnsignedInt); - CORRADE_COMPARE(scene.mappingBound(), 173); - CORRADE_COMPARE(scene.fieldCount(), 2); - - CORRADE_COMPARE(scene.fieldType(SceneField::Mesh), SceneFieldType::UnsignedByte); - CORRADE_COMPARE(scene.fieldArraySize(SceneField::Mesh), 0); - CORRADE_COMPARE_AS(scene.mapping(0), - Containers::arrayView(meshMappingData), - TestSuite::Compare::Container); - CORRADE_COMPARE_AS(scene.field(0), - Containers::arrayView(meshFieldData), - TestSuite::Compare::Container); - - CORRADE_COMPARE(scene.fieldType(SceneField::MeshMaterial), SceneFieldType::Int); - CORRADE_COMPARE(scene.fieldSize(SceneField::MeshMaterial), 3); - CORRADE_COMPARE(scene.fieldArraySize(SceneField::MeshMaterial), 0); - CORRADE_COMPARE(scene.mapping(SceneField::MeshMaterial).data(), scene.mapping(SceneField::Mesh).data()); - CORRADE_COMPARE_AS(scene.mapping(SceneField::MeshMaterial), - Containers::arrayView(meshMappingData), - TestSuite::Compare::Container); - CORRADE_COMPARE(scene.field(SceneField::MeshMaterial).data(), scene.data() + 3*4 + 3 + 1); - CORRADE_COMPARE(scene.field(SceneField::MeshMaterial).stride()[0], 4); -} - -void SceneToolsTest::convertToSingleFunctionObjects() { - auto&& data = ConvertToSingleFunctionObjectsData[testCaseInstanceId()]; - setTestCaseDescription(data.name); - - /* Haha now I can use sceneCombine() to conveniently prepare the initial - state here, without having to mess with an ArrayTuple */ - - const UnsignedShort parentMappingData[]{2, 15, 21, 22, 23}; - const Byte parentFieldData[]{-1, -1, -1, 21, 22}; - - /* Two objects have two and three mesh assignments respectively, meaning we - need three extra */ - const UnsignedShort meshMappingData[]{15, 23, 23, 23, 2, 15, 21}; - const Containers::Pair meshMaterialFieldData[]{ - {6, 4}, - {1, 0}, - {2, 3}, - {4, 2}, - {7, 2}, - {3, 1}, - {5, -1} - }; - - /* One camera is attached to an object that already has a mesh, meaning we - need a fourth extra object */ - const UnsignedShort cameraMappingData[]{22, 2}; - const UnsignedInt cameraFieldData[]{1, 5}; - - /* Lights don't conflict with anything so they *could* retain the - ImplicitMapping flag */ - const UnsignedShort lightMappingData[]{0, 1}; - const UnsignedByte lightFieldData[]{15, 23}; - - /* Object 0 and 1 has a light, 2 a mesh already, meaning we need a fifth, - sixth and seventh extra object and we lose the ImplicitMapping flag. */ - const UnsignedShort fooMappingData[]{0, 1, 2, 3}; - const Float fooFieldData[]{1.0f, 2.0f, 3.0f, 4.0f}; - - /* This field is not among the fields to convert so it should preserve the - ImplicitMapping flag */ - const UnsignedShort foo2MappingData[]{0, 1}; - const Byte foo2FieldData[]{-5, -7}; - - /* This field shares mapping with foo (and thus has the ImplicitMapping - flag), but it's not among the fields to convert. Since the mapping gets - changed, it should not retain the ImplicitMapping flag. */ - const Byte foo3FieldData[]{-1, -2, 7, 2}; - - SceneData original = Implementation::sceneCombine(SceneMappingType::UnsignedShort, data.originalObjectCount, Containers::arrayView({ - SceneFieldData{SceneField::Parent, Containers::arrayView(parentMappingData), Containers::arrayView(parentFieldData), data.parentFieldFlagsInput}, - SceneFieldData{SceneField::Mesh, Containers::arrayView(meshMappingData), Containers::StridedArrayView1D{meshMaterialFieldData, &meshMaterialFieldData[0].first(), Containers::arraySize(meshMaterialFieldData), sizeof(meshMaterialFieldData[0])}}, - SceneFieldData{SceneField::MeshMaterial, Containers::arrayView(meshMappingData), Containers::StridedArrayView1D{meshMaterialFieldData, &meshMaterialFieldData[0].second(), Containers::arraySize(meshMaterialFieldData), sizeof(meshMaterialFieldData[0])}}, - SceneFieldData{SceneField::Camera, Containers::arrayView(cameraMappingData), Containers::arrayView(cameraFieldData)}, - SceneFieldData{SceneField::Light, Containers::arrayView(lightMappingData), Containers::arrayView(lightFieldData), SceneFieldFlag::ImplicitMapping}, - SceneFieldData{sceneFieldCustom(15), Containers::arrayView(fooMappingData), Containers::arrayView(fooFieldData), SceneFieldFlag::ImplicitMapping}, - SceneFieldData{sceneFieldCustom(16), Containers::arrayView(foo2MappingData), Containers::arrayView(foo2FieldData), SceneFieldFlag::ImplicitMapping}, - SceneFieldData{sceneFieldCustom(17), Containers::arrayView(fooMappingData), Containers::arrayView(foo3FieldData), SceneFieldFlag::ImplicitMapping} - })); - - SceneData scene = Implementation::sceneConvertToSingleFunctionObjects(original, Containers::arrayView({ - SceneField::Mesh, - /* Deliberately not including MeshMaterial in the list -- these should - get automatically updated as they share the same object mapping. - OTOH including them would break the output. */ - SceneField::Camera, - /* A field with implicit mapping that doesn't conflict with anything so - it *could* retain the flag */ - SceneField::Light, - /* A field with implicit mapping, which loses the flag because entries - get reassigned */ - sceneFieldCustom(15), - /* Include also a field that's not present -- it should get skipped */ - SceneField::ImporterState - }), {}, 63); - - /* There should be three more objects, or the original count preserved if - it's large enough */ - CORRADE_COMPARE(scene.mappingBound(), data.expectedObjectCount); - - /* Object 0 should have new children with "foo", as it has a light */ - CORRADE_COMPARE_AS(scene.childrenFor(0), - Containers::arrayView({67}), - TestSuite::Compare::Container); - CORRADE_COMPARE_AS(scene.lightsFor(0), - Containers::arrayView({15}), - TestSuite::Compare::Container); - - /* Object 1 should have a new child with "foo", as it has a light */ - CORRADE_COMPARE_AS(scene.childrenFor(1), - Containers::arrayView({68}), - TestSuite::Compare::Container); - CORRADE_COMPARE_AS(scene.lightsFor(1), - Containers::arrayView({23}), - TestSuite::Compare::Container); - - /* Object 2 should have a new child with the camera and "foo", as it has a - mesh */ - CORRADE_COMPARE_AS(scene.childrenFor(2), - Containers::arrayView({66, 69}), - TestSuite::Compare::Container); - CORRADE_COMPARE_AS(scene.meshesMaterialsFor(2), - (Containers::arrayView>({{7, 2}})), - TestSuite::Compare::Container); - CORRADE_COMPARE_AS(scene.camerasFor(2), - Containers::arrayView({}), - TestSuite::Compare::Container); - CORRADE_COMPARE_AS(scene.camerasFor(66), - Containers::arrayView({5}), - TestSuite::Compare::Container); - - /* Object 15 should have a new child that has the second mesh */ - CORRADE_COMPARE_AS(scene.childrenFor(15), - Containers::arrayView({65}), - TestSuite::Compare::Container); - CORRADE_COMPARE_AS(scene.meshesMaterialsFor(15), - (Containers::arrayView>({{6, 4}})), - TestSuite::Compare::Container); - CORRADE_COMPARE_AS(scene.meshesMaterialsFor(65), - (Containers::arrayView>({{3, 1}})), - TestSuite::Compare::Container); - - /* Object 23 should have two new children that have the second and third - mesh */ - CORRADE_COMPARE_AS(scene.childrenFor(23), - Containers::arrayView({63, 64}), - TestSuite::Compare::Container); - CORRADE_COMPARE_AS(scene.meshesMaterialsFor(23), - (Containers::arrayView>({{1, 0}})), - TestSuite::Compare::Container); - CORRADE_COMPARE_AS(scene.meshesMaterialsFor(63), - (Containers::arrayView>({{2, 3}})), - TestSuite::Compare::Container); - CORRADE_COMPARE_AS(scene.meshesMaterialsFor(64), - (Containers::arrayView>({{4, 2}})), - TestSuite::Compare::Container); - - /* To be extra sure, verify the actual data. Parents have a few objects - added, the rest is the same. Because new objects are added at the end, - the ordered flag is preserved if present. */ - CORRADE_COMPARE_AS(scene.parentsAsArray(), (Containers::arrayView>({ - {2, -1}, - {15, -1}, - {21, -1}, - {22, 21}, - {23, 22}, - {63, 23}, - {64, 23}, - {65, 15}, - {66, 2}, - {67, 0}, - {68, 1}, - {69, 2}, - })), TestSuite::Compare::Container); - CORRADE_COMPARE(scene.fieldFlags(SceneField::Parent), data.parentFieldFlagsExpected); - - /* Meshes / materials have certain objects reassigned, field data stay the - same. There was no flag before so neither is after. */ - CORRADE_COMPARE_AS(scene.meshesMaterialsAsArray(), (Containers::arrayView>>({ - {15, {6, 4}}, - {23, {1, 0}}, - {63, {2, 3}}, - {64, {4, 2}}, - {2, {7, 2}}, - {65, {3, 1}}, - {21, {5, -1}} - })), TestSuite::Compare::Container); - CORRADE_COMPARE(scene.fieldFlags(SceneField::Mesh), SceneFieldFlags{}); - CORRADE_COMPARE(scene.fieldFlags(SceneField::MeshMaterial), SceneFieldFlags{}); - - /* Cameras have certain objects reassigned, field data stay the same. There - was no flag before so neither is after. */ - CORRADE_COMPARE_AS(scene.camerasAsArray(), (Containers::arrayView>({ - {22, 1}, - {66, 5} - })), TestSuite::Compare::Container); - CORRADE_COMPARE(scene.fieldFlags(SceneField::Camera), SceneFieldFlags{}); - - /* Lights stay the same, thus the implicit flag could be preserved. It's - not currently, though. */ - CORRADE_COMPARE_AS(scene.lightsAsArray(), (Containers::arrayView>({ - {0, 15}, - {1, 23} - })), TestSuite::Compare::Container); - { - CORRADE_EXPECT_FAIL("Logic for preserving flags of untouched fields is rather complex and thus not implemented yet."); - CORRADE_COMPARE(scene.fieldFlags(SceneField::Light), SceneFieldFlag::ImplicitMapping); - } { - CORRADE_COMPARE(scene.fieldFlags(SceneField::Light), SceneFieldFlags{}); - } - - /* A custom field gets the last object reassigned, field data stay the - same. The implicit flag gets turned to nothing after that. */ - CORRADE_COMPARE_AS(scene.mappingAsArray(sceneFieldCustom(15)), (Containers::arrayView({ - 67, 68, 69, 3 - })), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(scene.field(sceneFieldCustom(15)), - Containers::arrayView(fooFieldData), - TestSuite::Compare::Container); - CORRADE_COMPARE(scene.fieldFlags(sceneFieldCustom(15)), SceneFieldFlags{}); - - /* A custom field that is not among fields to convert so it preserves the - flag */ - CORRADE_COMPARE_AS(scene.mappingAsArray(sceneFieldCustom(16)), (Containers::arrayView({ - 0, 1 - })), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(scene.field(sceneFieldCustom(16)), - Containers::arrayView(foo2FieldData), - TestSuite::Compare::Container); - { - CORRADE_EXPECT_FAIL("Logic for preserving flags of untouched fields is rather complex and thus not implemented yet."); - CORRADE_COMPARE(scene.fieldFlags(sceneFieldCustom(16)), SceneFieldFlag::ImplicitMapping); - } { - CORRADE_COMPARE(scene.fieldFlags(sceneFieldCustom(16)), SceneFieldFlags{}); - } - - /* A custom field that is not among fields to convert but it shares the - mapping with a field that is and that gets changed. The implicit flag - should thus get removed here as well. */ - CORRADE_COMPARE_AS(scene.mappingAsArray(sceneFieldCustom(17)), (Containers::arrayView({ - 67, 68, 69, 3 - })), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(scene.field(sceneFieldCustom(17)), - Containers::arrayView(foo3FieldData), - TestSuite::Compare::Container); - CORRADE_COMPARE(scene.fieldFlags(sceneFieldCustom(17)), SceneFieldFlags{}); -} - -void SceneToolsTest::convertToSingleFunctionObjectsFieldsToCopy() { - const UnsignedShort parentMappingData[]{2, 15, 21, 22}; - const Byte parentFieldData[]{-1, -1, -1, 21}; - - const UnsignedShort meshMappingData[]{15, 21, 21, 21, 22, 15}; - const UnsignedInt meshFieldData[]{6, 1, 2, 4, 7, 3}; - - const UnsignedShort skinMappingData[]{22, 21}; - const UnsignedInt skinFieldData[]{5, 13}; - - const UnsignedLong fooMappingData[]{15, 23, 15, 21}; - const Int fooFieldData[]{0, 1, 2, 3, 4, 5, 6, 7}; - - SceneData original = Implementation::sceneCombine(SceneMappingType::UnsignedShort, 50, Containers::arrayView({ - SceneFieldData{SceneField::Parent, Containers::arrayView(parentMappingData), Containers::arrayView(parentFieldData)}, - SceneFieldData{SceneField::Mesh, Containers::arrayView(meshMappingData), Containers::arrayView(meshFieldData)}, - SceneFieldData{SceneField::Skin, Containers::arrayView(skinMappingData), Containers::arrayView(skinFieldData)}, - /* Array field */ - SceneFieldData{sceneFieldCustom(15), Containers::arrayView(fooMappingData), Containers::StridedArrayView2D{fooFieldData, {4, 2}}}, - /* Just to disambiguate between 2D and 3D */ - SceneFieldData{SceneField::Transformation, SceneMappingType::UnsignedShort, nullptr, SceneFieldType::Matrix4x4, nullptr} - })); - - SceneData scene = Implementation::sceneConvertToSingleFunctionObjects(original, - Containers::arrayView({ - /* Include also a field that's not present -- it should get skipped */ - SceneField::ImporterState, - /* Three additional mesh assignments that go to new objects */ - SceneField::Mesh - }), - Containers::arrayView({ - /* One assignment is to an object that has just one mesh, it should - not be copied anywhere, the other should be duplicated two - times */ - SceneField::Skin, - /* Array field with multiple assignments per object -- all should - be copied */ - sceneFieldCustom(15), - /* Include also a field that's not present -- it should get skipped */ - SceneField::Camera - }), 60); - - CORRADE_COMPARE_AS(scene.parentsAsArray(), (Containers::arrayView>({ - {2, -1}, - {15, -1}, - {21, -1}, - {22, 21}, - {60, 21}, /* duplicated mesh assignment to object 21 */ - {61, 21}, /* duplicated mesh assignment to object 21 */ - {62, 15} /* duplicated mesh assignment to object 15 */ - })), TestSuite::Compare::Container); - - CORRADE_COMPARE_AS(scene.meshesMaterialsAsArray(), (Containers::arrayView>>({ - {15, {6, -1}}, - {21, {1, -1}}, - {60, {2, -1}}, /* duplicated mesh assignment to object 21 */ - {61, {4, -1}}, /* duplicated mesh assignment to object 21 */ - {22, {7, -1}}, - {62, {3, -1}} /* duplicated mesh assignment to object 15 */ - })), TestSuite::Compare::Container); - - CORRADE_COMPARE_AS(scene.skinsAsArray(), (Containers::arrayView>({ - {22, 5}, - {21, 13}, - {60, 13}, /* duplicated from object 21 */ - {61, 13}, /* duplicated from object 21 */ - })), TestSuite::Compare::Container); - - CORRADE_COMPARE_AS(scene.mapping(sceneFieldCustom(15)), Containers::arrayView({ - 15, 23, 15, 21, - 60, 61, /* duplicated from object 21 (two duplicates of one object) */ - 62, 62, /* duplicated from object 15 (two entries for one object) */ - }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS((scene.field(sceneFieldCustom(15)).transposed<0, 1>()[0]), Containers::arrayView({ - 0, 2, 4, 6, - 6, 6, /* duplicated from object 21 (two duplicates of one object) */ - 0, 4, /* duplicated from object 15 (two entries for one object) */ - }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS((scene.field(sceneFieldCustom(15)).transposed<0, 1>()[1]), Containers::arrayView({ - 1, 3, 5, 7, - 7, 7, /* duplicated from object 21 (two duplicates of one object) */ - 1, 5, /* duplicated from object 15 (two entries for one object) */ - }), TestSuite::Compare::Container); -} - -}}}} - -CORRADE_TEST_MAIN(Magnum::Trade::Test::SceneToolsTest)