diff --git a/src/Magnum/Trade/CMakeLists.txt b/src/Magnum/Trade/CMakeLists.txt index 2a6fc229f..e5b68f272 100644 --- a/src/Magnum/Trade/CMakeLists.txt +++ b/src/Magnum/Trade/CMakeLists.txt @@ -76,7 +76,8 @@ set(MagnumTrade_HEADERS set(MagnumTrade_PRIVATE_HEADERS Implementation/arrayUtilities.h Implementation/converterUtilities.h - Implementation/materialAttributeProperties.hpp) + Implementation/materialAttributeProperties.hpp + Implementation/sceneTools.h) if(MAGNUM_BUILD_DEPRECATED) list(APPEND MagnumTrade_SRCS diff --git a/src/Magnum/Trade/Implementation/sceneTools.h b/src/Magnum/Trade/Implementation/sceneTools.h new file mode 100644 index 000000000..98f3e41b5 --- /dev/null +++ b/src/Magnum/Trade/Implementation/sceneTools.h @@ -0,0 +1,193 @@ +#ifndef Magnum_Trade_Implementation_sceneTools_h +#define Magnum_Trade_Implementation_sceneTools_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 "Magnum/Math/PackingBatch.h" +#include "Magnum/Trade/SceneData.h" + +namespace Magnum { namespace Trade { 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].objectData()) continue; + + const Containers::StridedArrayView1D src = fields[i].objectData(); + const Containers::StridedArrayView1D dst = Containers::arrayCast<1, T>(itemViews[mapping]); + if(fields[i].objectType() == SceneObjectType::UnsignedByte) + copyOrCastInto(Containers::arrayCast(src), dst); + else if(fields[i].objectType() == SceneObjectType::UnsignedShort) + copyOrCastInto(Containers::arrayCast(src), dst); + else if(fields[i].objectType() == SceneObjectType::UnsignedInt) + copyOrCastInto(Containers::arrayCast(src), dst); + else if(fields[i].objectType() == SceneObjectType::UnsignedLong) + copyOrCastInto(Containers::arrayCast(src), dst); + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } +} + +/* Combine fields of varying object type together into a SceneData of a single + given objectType. The fields are expected to point to existing object/field + memory, which will be then copied to the resulting scene. If you supply a + field with null object or field data, the object 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 object data shared among + 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 SceneObjectType objectType, const UnsignedLong objectCount, const Containers::ArrayView fields) { + const std::size_t objectTypeSize = sceneObjectTypeSize(objectType); + const std::size_t objectTypeAlignment = sceneObjectTypeAlignment(objectType); + + /* 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.isOffsetOnly()); + + /* Object 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.objectData().data()) + inserted = objectMappings.emplace(field.objectData().data(), itemViewOffset); + if(field.objectData().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()), objectTypeSize, objectTypeAlignment, 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(objectType == SceneObjectType::UnsignedByte) + sceneCombineCopyObjects(fields, itemViews, itemViewMappings); + else if(objectType == SceneObjectType::UnsignedShort) + sceneCombineCopyObjects(fields, itemViews, itemViewMappings); + else if(objectType == SceneObjectType::UnsignedInt) + sceneCombineCopyObjects(fields, itemViews, itemViewMappings); + else if(objectType == SceneObjectType::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()}; + } + + return SceneData{objectType, objectCount, std::move(outData), std::move(outFields)}; +} + +}}} + +#endif diff --git a/src/Magnum/Trade/Test/CMakeLists.txt b/src/Magnum/Trade/Test/CMakeLists.txt index 711436146..521be6d74 100644 --- a/src/Magnum/Trade/Test/CMakeLists.txt +++ b/src/Magnum/Trade/Test/CMakeLists.txt @@ -59,6 +59,7 @@ 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) @@ -84,6 +85,7 @@ 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 new file mode 100644 index 000000000..f5fda2dfa --- /dev/null +++ b/src/Magnum/Trade/Test/SceneToolsTest.cpp @@ -0,0 +1,324 @@ +/* + 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(); +}; + +struct { + const char* name; + SceneObjectType objectType; +} CombineData[]{ + {"UnsignedByte output", SceneObjectType::UnsignedByte}, + {"UnsignedShort output", SceneObjectType::UnsignedShort}, + {"UnsignedInt output", SceneObjectType::UnsignedInt}, + {"UnsignedLong output", SceneObjectType::UnsignedLong}, +}; + +SceneToolsTest::SceneToolsTest() { + addInstancedTests({&SceneToolsTest::combine}, + Containers::arraySize(CombineData)); + + addTests({&SceneToolsTest::combineAlignment, + &SceneToolsTest::combineObjectsShared, + &SceneToolsTest::combineObjectsPlaceholderFieldPlaceholder, + &SceneToolsTest::combineObjectSharedFieldPlaceholder}); +} + +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 meshObjects[]{45, 78, 23}; + const UnsignedByte meshes[]{3, 5, 17}; + + const UnsignedShort parentObjects[]{33, 25}; + const Short parents[]{-1, 0}; + + const UnsignedByte translationObjects[]{16}; + const Vector2d translations[]{{1.5, -0.5}}; + + const UnsignedLong fooObjects[]{15, 23}; + const Int foos[]{0, 1, 2, 3}; + + SceneData scene = Implementation::sceneCombine(data.objectType, 167, Containers::arrayView({ + SceneFieldData{SceneField::Mesh, Containers::arrayView(meshObjects), Containers::arrayView(meshes)}, + SceneFieldData{SceneField::Parent, Containers::arrayView(parentObjects), Containers::arrayView(parents)}, + SceneFieldData{SceneField::Translation, Containers::arrayView(translationObjects), Containers::arrayView(translations)}, + /* Array field */ + SceneFieldData{sceneFieldCustom(15), Containers::arrayView(fooObjects), Containers::StridedArrayView2D{foos, {2, 2}}}, + /* Empty field */ + SceneFieldData{SceneField::Camera, Containers::ArrayView{}, Containers::ArrayView{}} + })); + + CORRADE_COMPARE(scene.dataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(scene.objectType(), data.objectType); + CORRADE_COMPARE(scene.objectCount(), 167); + CORRADE_COMPARE(scene.fieldCount(), 5); + + CORRADE_COMPARE(scene.fieldName(0), SceneField::Mesh); + CORRADE_COMPARE(scene.fieldType(0), SceneFieldType::UnsignedByte); + CORRADE_COMPARE(scene.fieldArraySize(0), 0); + CORRADE_COMPARE_AS(scene.objectsAsArray(0), Containers::arrayView({ + 45, 78, 23 + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.field(0), + Containers::arrayView(meshes), + TestSuite::Compare::Container); + + CORRADE_COMPARE(scene.fieldName(1), SceneField::Parent); + CORRADE_COMPARE(scene.fieldType(1), SceneFieldType::Short); + CORRADE_COMPARE(scene.fieldArraySize(1), 0); + CORRADE_COMPARE_AS(scene.objectsAsArray(1), Containers::arrayView({ + 33, 25 + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.field(1), + Containers::arrayView(parents), + TestSuite::Compare::Container); + + CORRADE_COMPARE(scene.fieldName(2), SceneField::Translation); + CORRADE_COMPARE(scene.fieldType(2), SceneFieldType::Vector2d); + CORRADE_COMPARE(scene.fieldArraySize(2), 0); + CORRADE_COMPARE_AS(scene.objectsAsArray(2), + Containers::arrayView({16}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.field(2), + Containers::arrayView(translations), + TestSuite::Compare::Container); + + CORRADE_COMPARE(scene.fieldName(3), sceneFieldCustom(15)); + CORRADE_COMPARE(scene.fieldType(3), SceneFieldType::Int); + CORRADE_COMPARE(scene.fieldArraySize(3), 2); + CORRADE_COMPARE_AS(scene.objectsAsArray(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{foos, {2, 2}})[0], + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.field(3)[1], + (Containers::StridedArrayView2D{foos, {2, 2}})[1], + TestSuite::Compare::Container); + + CORRADE_COMPARE(scene.fieldName(4), SceneField::Camera); + CORRADE_COMPARE(scene.fieldType(4), SceneFieldType::UnsignedShort); + CORRADE_COMPARE(scene.fieldSize(4), 0); + CORRADE_COMPARE(scene.fieldArraySize(4), 0); +} + +void SceneToolsTest::combineAlignment() { + const UnsignedShort meshObjects[]{15, 23, 47}; + const UnsignedByte meshes[]{0, 1, 2}; + const UnsignedShort translationObjects[]{5}; /* 1 byte padding before */ + const Vector2d translations[]{{1.5, 3.0}}; /* 4 byte padding before */ + + SceneData scene = Implementation::sceneCombine(SceneObjectType::UnsignedShort, 167, Containers::arrayView({ + SceneFieldData{SceneField::Mesh, Containers::arrayView(meshObjects), Containers::arrayView(meshes)}, + SceneFieldData{SceneField::Translation, Containers::arrayView(translationObjects), Containers::arrayView(translations)} + })); + + CORRADE_COMPARE(scene.dataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(scene.objectType(), SceneObjectType::UnsignedShort); + CORRADE_COMPARE(scene.objectCount(), 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.objects(0), + Containers::arrayView(meshObjects), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.field(0), + Containers::arrayView(meshes), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(reinterpret_cast(scene.objects(0).data()), 2, TestSuite::Compare::Divisible); + CORRADE_COMPARE(scene.objects(0).data(), scene.data()); + CORRADE_COMPARE(scene.objects(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.objects(1), + Containers::arrayView(translationObjects), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.field(1), + Containers::arrayView(translations), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(reinterpret_cast(scene.objects(1).data()), 2, TestSuite::Compare::Divisible); + CORRADE_COMPARE(scene.objects(1).data(), scene.data() + 3*2 + 3 + 1); + CORRADE_COMPARE(scene.objects(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 meshObjects[]{15, 23, 47}; + const UnsignedByte meshes[]{0, 1, 2}; + const Int meshMaterials[]{72, -1, 23}; + + const UnsignedShort translationRotationObjects[]{14, 22}; + const Vector2 translations[]{{-1.0f, 25.3f}, {2.2f, 2.1f}}; + const Complex rotations[]{Complex::rotation(35.0_degf), Complex::rotation(22.5_degf)}; + + SceneData scene = Implementation::sceneCombine(SceneObjectType::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(meshObjects), Containers::arrayView(meshes)}, + SceneFieldData{SceneField::Translation, Containers::arrayView(translationRotationObjects), Containers::arrayView(translations)}, + SceneFieldData{SceneField::MeshMaterial, Containers::arrayView(meshObjects), Containers::arrayView(meshMaterials)}, + SceneFieldData{SceneField::Rotation, Containers::arrayView(translationRotationObjects), Containers::arrayView(rotations)} + })); + + CORRADE_COMPARE(scene.dataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(scene.objectType(), SceneObjectType::UnsignedInt); + CORRADE_COMPARE(scene.objectCount(), 173); + CORRADE_COMPARE(scene.fieldCount(), 4); + + CORRADE_COMPARE(scene.fieldSize(SceneField::Mesh), 3); + CORRADE_COMPARE(scene.fieldSize(SceneField::MeshMaterial), 3); + CORRADE_COMPARE(scene.objects(SceneField::Mesh).data(), scene.objects(SceneField::MeshMaterial).data()); + + CORRADE_COMPARE(scene.fieldSize(SceneField::Translation), 2); + CORRADE_COMPARE(scene.fieldSize(SceneField::Rotation), 2); + CORRADE_COMPARE(scene.objects(SceneField::Translation).data(), scene.objects(SceneField::Rotation).data()); +} + +void SceneToolsTest::combineObjectsPlaceholderFieldPlaceholder() { + const UnsignedShort meshObjects[]{15, 23, 47}; + const UnsignedByte meshes[]{0, 1, 2}; + + SceneData scene = Implementation::sceneCombine(SceneObjectType::UnsignedShort, 173, Containers::arrayView({ + SceneFieldData{SceneField::Camera, Containers::ArrayView{nullptr, 1}, Containers::ArrayView{nullptr, 1}}, + SceneFieldData{SceneField::Mesh, Containers::arrayView(meshObjects), Containers::arrayView(meshes)}, + /* 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.objectType(), SceneObjectType::UnsignedShort); + CORRADE_COMPARE(scene.objectCount(), 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.objects(SceneField::Camera).data(), scene.data()); + CORRADE_COMPARE(scene.objects(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.objects(SceneField::Mesh), + Containers::arrayView(meshObjects), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.field(SceneField::Mesh), + Containers::arrayView(meshes), + 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.objects(SceneField::Light).data(), scene.data() + 2 + 2 + 3*2 + 3 + 1); + CORRADE_COMPARE(scene.objects(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.objects(sceneFieldCustom(15)).data(), scene.data() + 2 + 2 + 3*2 + 3 + 1 + 2*2 + 2 + 2*4); + CORRADE_COMPARE(scene.objects(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 meshObjects[]{15, 23, 47}; + const UnsignedByte meshes[]{0, 1, 2}; + + SceneData scene = Implementation::sceneCombine(SceneObjectType::UnsignedInt, 173, Containers::arrayView({ + SceneFieldData{SceneField::Mesh, Containers::arrayView(meshObjects), Containers::arrayView(meshes)}, + SceneFieldData{SceneField::MeshMaterial, Containers::arrayView(meshObjects), Containers::ArrayView{nullptr, 3}}, + })); + + CORRADE_COMPARE(scene.dataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(scene.objectType(), SceneObjectType::UnsignedInt); + CORRADE_COMPARE(scene.objectCount(), 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.objects(0), + Containers::arrayView(meshObjects), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.field(0), + Containers::arrayView(meshes), + 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.objects(SceneField::MeshMaterial).data(), scene.objects(SceneField::Mesh).data()); + CORRADE_COMPARE_AS(scene.objects(SceneField::MeshMaterial), + Containers::arrayView(meshObjects), + 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); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Trade::Test::SceneToolsTest)